Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions llm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# LLM

Text generation with [Electron](https://docs.smallest.ai/waves/api-reference), Smallest AI's LLM, via an OpenAI-compatible `chat/completions` endpoint.

## Endpoint

```
https://api.smallest.ai/waves/v1/chat/completions
```

Use the stock `openai` package: `OpenAI(api_key=..., base_url="https://api.smallest.ai/waves/v1")` with `model="electron"`. Standard chat completions, streaming, and function calling all work unchanged.

## Examples

- [Tool Calling](./tool-calling/): function calling with the standard OpenAI tools schema. The model requests a tool, you execute locally, the model composes the answer.
3 changes: 3 additions & 0 deletions llm/tool-calling/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Smallest AI API Key
# Get yours at https://smallest.ai/console
SMALLEST_API_KEY=your-smallest-api-key-here
50 changes: 50 additions & 0 deletions llm/tool-calling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Tool Calling (Electron)

Function calling with the Electron LLM through the OpenAI-compatible endpoint. Define tools with the standard OpenAI schema, let the model request a call, execute it locally, and feed the result back for the final answer.

## Try It

```bash
uv run tool_calling.py "What's the weather in Mumbai?"
```

Output:

```
Model called get_weather({'city': 'Mumbai'})

It's currently partly cloudy in Mumbai at about 29°C.
```

## Requirements

> Base dependencies are installed via the root `requirements.txt` (includes `openai`). See the [main README](../../README.md#usage) for setup. Add `SMALLEST_API_KEY` to your `.env` (see `.env.sample`).

## How It Works

The endpoint is OpenAI-compatible, so the stock `openai` package works as-is:

```python
from openai import OpenAI

client = OpenAI(
api_key=os.environ["SMALLEST_API_KEY"],
base_url="https://api.smallest.ai/waves/v1",
)
```

The two-step tool loop:

1. `chat.completions.create(model="electron", messages=..., tools=...)`: the model either answers directly or returns `tool_calls`.
2. If it requested tools: execute each one locally (here, a stubbed `get_weather(city)`), append the assistant message and a `role: "tool"` message with the JSON result, then call `create` again. The model composes the final answer from the tool output.

Non-streaming for clarity. The same endpoint also speaks the plain `chat/completions` protocol, so everything else the `openai` client supports (streaming, system prompts, temperature) works the same way.

## API Reference

- [Waves API Reference](https://docs.smallest.ai/waves/api-reference)

## Next Steps

- [Agent with Tools](../../voice-agents/agent_with_tools/): tool calling inside a full voice agent
- [BYOM](../../voice-agents/byom/): point voice agents at any OpenAI-compatible LLM
98 changes: 98 additions & 0 deletions llm/tool-calling/tool_calling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Smallest AI Electron LLM - Tool Calling

Function calling with the Electron LLM via the OpenAI-compatible
chat/completions endpoint. The model decides when to call get_weather(city),
the script runs it locally, and the model composes the final answer from
the tool result.

Usage: python tool_calling.py "What's the weather in Mumbai?"
"""

import json
import os
import sys

from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

MODEL = "electron"
BASE_URL = "https://api.smallest.ai/waves/v1"

DEFAULT_QUESTION = "What's the weather in Mumbai right now?"

TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g. Mumbai",
}
},
"required": ["city"],
},
},
}
]


def get_weather(city: str) -> dict:
"""Stub implementation. Swap in a real weather API call."""
return {"city": city, "condition": "partly cloudy", "temperature_c": 29}


def main():
api_key = os.environ.get("SMALLEST_API_KEY")
if not api_key:
print("Error: SMALLEST_API_KEY environment variable not set", file=sys.stderr)
sys.exit(1)

question = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_QUESTION
client = OpenAI(api_key=api_key, base_url=BASE_URL)

messages = [{"role": "user", "content": question}]

# Step 1: let the model decide whether it needs the tool.
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS,
)
message = response.choices[0].message

if message.tool_calls:
messages.append(message)

# Step 2: execute each requested tool locally and send back the results.
for call in message.tool_calls:
args = json.loads(call.function.arguments)
print(f"Model called {call.function.name}({args})")

result = get_weather(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
})

response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS,
)
message = response.choices[0].message

print(f"\n{message.content}")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions speech-to-speech/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ with client.waves.speech_to_speech.connect() as socket:

## Examples

- [Python Quickstart](./python-quickstart/): minimal headless client. Streams a WAV file as paced live audio, prints the reply transcript, saves the spoken response to `reply.wav`.
- [Hydra Realtime Demo](./hydra-realtime-demo/) — Next.js browser client with multi-agent presets (companion, restaurant, banking), client-side tool calling, a wire-log of every WebSocket frame, and persona/voice editing. Mirror of [smallest-inc/hydra_agents](https://github.com/smallest-inc/hydra_agents).
6 changes: 3 additions & 3 deletions speech-to-speech/hydra-realtime-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Talk to Hydra in your browser, watch every WebSocket frame as it lands, and swit
- **Wire-log tab** — every `session.*`, `input_audio_buffer.*`, `response.*`, `conversation.item.*`, and `error` event, in & out, with a JSON inspector.
- **Multi-agent presets** — a friendly companion, a burger-restaurant phone agent (`Smallest Kitchen`), and a banking concierge (`NovaBank`). All tools execute locally — Hydra is a pure voice engine.
- **Beautiful, audio-reactive orb** that pulses with the live RMS level of whichever side is currently active.
- **Persona / voice editing** — change the system prompt, pick from six Waves voices, toggle whether the agent speaks first.
- **Persona / voice editing** — change the system prompt, pick from six Hydra voices, toggle whether the agent speaks first.
- **Zero server** — connects browser → Hydra directly. Your API key never leaves your browser.

---
Expand Down Expand Up @@ -61,12 +61,12 @@ ws open
← { "type": "session.created", "session_id": "…" }
→ { "type": "session.configure", "session": {
"instructions": "you are a friendly voice assistant.",
"voice": "wren",
"voice": "aria",
"tools": [],
"generate_initial_response": false
}}
← { "type": "session.configured", "session": {
"voice": "wren",
"voice": "aria",
"input_audio_format": "pcm16",
"input_audio_sample_rate": 16000,
"output_audio_format": "pcm16",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const AGENT_PRESETS: AgentPreset[] = [
tagline: "A friendly, curious voice assistant.",
emoji: "✦",
category: "Featured",
voice: "wren",
voice: "aria",
instructions:
`You are Hydra, a friendly real-time voice companion built on Smallest.ai. ` +
`Be warm, concise, and conversational. Speak naturally — you are a voice, ` +
Expand All @@ -48,7 +48,7 @@ export const AGENT_PRESETS: AgentPreset[] = [
tagline: "A burger-joint phone agent that takes orders.",
emoji: "🍔",
category: "Demos",
voice: "sloane",
voice: "maya",
instructions:
`You are the phone agent for Smallest Kitchen, a popular burger restaurant. ` +
`Take the customer's order conversationally — never recite the whole menu unless they ask. ` +
Expand All @@ -64,7 +64,7 @@ export const AGENT_PRESETS: AgentPreset[] = [
tagline: "A banking concierge that handles balances, transfers, and cards.",
emoji: "🏦",
category: "Demos",
voice: "reed",
voice: "sterling",
instructions:
`You are a NovaBank phone concierge. Help the customer with balances, ` +
`recent transactions, transfers between checking and savings, blocking ` +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import { AGENT_PRESETS, findPreset } from "@/app/agents/presets";

const VOICES = [
{ value: "wren", label: "Wren" },
{ value: "sloane", label: "Sloane" },
{ value: "marlowe", label: "Marlowe" },
{ value: "reed", label: "Reed" },
{ value: "knox", label: "Knox" },
{ value: "tate", label: "Tate" },
{ value: "aria", label: "Aria" },
{ value: "maya", label: "Maya" },
{ value: "marin", label: "Marin" },
{ value: "sterling", label: "Sterling" },
{ value: "kai", label: "Kai" },
{ value: "zoe", label: "Zoe" },
];

export interface ControlPanelProps {
Expand Down
3 changes: 3 additions & 0 deletions speech-to-speech/python-quickstart/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Smallest AI API Key
# Get yours at https://smallest.ai/console
SMALLEST_API_KEY=your-smallest-api-key-here
73 changes: 73 additions & 0 deletions speech-to-speech/python-quickstart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Python Quickstart (Hydra)

Minimal headless Hydra client. Streams a WAV file to the realtime speech-to-speech endpoint as if it were live microphone audio, prints the assistant's transcript as it streams, and saves the spoken reply to `reply.wav`.

## Try It

```bash
uv run quickstart.py path/to/audio.wav
```

Output:

```
Streaming 3.2s of audio...
[speech started]
[speech stopped]
Sure, I can help with that. What would you like to know?
Saved reply to reply.wav
```

## Requirements

> Base dependencies are installed via the root `requirements.txt`. See the [main README](../../README.md#usage) for setup. Add `SMALLEST_API_KEY` to your `.env` (see `.env.sample`).

Input must be a 16-bit PCM WAV. Any sample rate and channel count works; the script downmixes to mono and naively resamples to 16 kHz.

## How It Works

1. Connect to:

```
wss://api.smallest.ai/waves/v1/s2s?model=hydra&api_key=<SMALLEST_API_KEY>
```

2. Configure the session:

```json
{
"type": "session.configure",
"session": {
"instructions": "You are a helpful voice assistant...",
"voice": "wren",
"input_audio_sample_rate": 16000,
"output_audio_sample_rate": 16000
}
}
```

3. Stream the file's PCM16 as `{"type": "input_audio_buffer.append", "audio": "<base64>"}` chunks of 100 ms, paced at real time, followed by about 1.2 s of silence chunks so the server-side VAD detects end of speech and closes the turn.

4. Read events concurrently while sending:

| Event | Meaning |
|-------|---------|
| `input_audio_buffer.speech_started` / `speech_stopped` | Server VAD detected your speech |
| `response.output_audio.delta` | Base64 PCM16 reply audio (collected) |
| `response.output_audio_transcript.delta` | Reply transcript text (printed live) |
| `response.done` | Turn finished, stop reading |

5. The collected reply audio is saved to `reply.wav` (16 kHz, mono, 16-bit).

## Pacing Matters

Hydra expects a live audio stream. The script sends one 100 ms chunk every 100 ms of wall-clock time. Dumping the entire file at once gets the socket closed by the server with a "session ended" message, so keep the pacing if you adapt this script.

## API Reference

- [Hydra overview and event reference](https://docs.smallest.ai/waves/documentation/speech-to-speech-hydra/overview)
- [Waves API Reference](https://docs.smallest.ai/waves/api-reference)

## Next Steps

- [Hydra Realtime Demo](../hydra-realtime-demo/): full browser client with live microphone, agent presets, and tool calling
Loading