minmo 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- minmo-0.1.0/.github/workflows/ci.yml +23 -0
- minmo-0.1.0/.github/workflows/format.yml +28 -0
- minmo-0.1.0/.github/workflows/publish.yml +38 -0
- minmo-0.1.0/.gitignore +10 -0
- minmo-0.1.0/LICENSE +21 -0
- minmo-0.1.0/PKG-INFO +176 -0
- minmo-0.1.0/README.md +153 -0
- minmo-0.1.0/docs/cookbook.html +185 -0
- minmo-0.1.0/docs/index.html +195 -0
- minmo-0.1.0/docs/reference.html +311 -0
- minmo-0.1.0/docs/script.js +15 -0
- minmo-0.1.0/docs/style.css +234 -0
- minmo-0.1.0/minmo/__init__.py +15 -0
- minmo-0.1.0/minmo/agent.py +179 -0
- minmo-0.1.0/minmo/cli.py +99 -0
- minmo-0.1.0/minmo/errors.py +14 -0
- minmo-0.1.0/minmo/logging.py +18 -0
- minmo-0.1.0/minmo/providers/__init__.py +0 -0
- minmo-0.1.0/minmo/providers/assemblyai.py +95 -0
- minmo-0.1.0/minmo/providers/base.py +56 -0
- minmo-0.1.0/minmo/providers/hume.py +231 -0
- minmo-0.1.0/minmo/providers/openai_realtime.py +139 -0
- minmo-0.1.0/minmo/schema.py +81 -0
- minmo-0.1.0/minmo/server.py +28 -0
- minmo-0.1.0/minmo/testing.py +68 -0
- minmo-0.1.0/pyproject.toml +34 -0
- minmo-0.1.0/scripts/smoke_test.py +136 -0
- minmo-0.1.0/tests/test_deploy.py +207 -0
- minmo-0.1.0/tests/test_providers_assemblyai.py +239 -0
- minmo-0.1.0/tests/test_providers_hume.py +569 -0
- minmo-0.1.0/tests/test_providers_openai_realtime.py +279 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
|
|
21
|
+
- run: pip install -e ".[dev]"
|
|
22
|
+
|
|
23
|
+
- run: pytest -q
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Format
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
black:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
if: github.actor != 'github-actions[bot]'
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
ref: ${{ github.head_ref || github.ref_name }}
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.11"
|
|
21
|
+
|
|
22
|
+
- run: pip install black
|
|
23
|
+
|
|
24
|
+
- run: black minmo tests scripts
|
|
25
|
+
|
|
26
|
+
- uses: stefanzweifel/git-auto-commit-action@v5
|
|
27
|
+
with:
|
|
28
|
+
commit_message: "style: black auto-format"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
|
|
17
|
+
- run: pip install build
|
|
18
|
+
|
|
19
|
+
- run: python -m build
|
|
20
|
+
|
|
21
|
+
- uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
|
|
26
|
+
publish:
|
|
27
|
+
needs: build
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment: pypi
|
|
30
|
+
permissions:
|
|
31
|
+
id-token: write
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/download-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: dist
|
|
36
|
+
path: dist/
|
|
37
|
+
|
|
38
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
minmo-0.1.0/.gitignore
ADDED
minmo-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anas Elhaag
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
minmo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: minmo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Boilerplate-free SDK for building voice agents on AssemblyAI's Voice Agent API
|
|
5
|
+
Project-URL: Homepage, https://github.com/a-elhaag/minmo
|
|
6
|
+
Author: Anas Elhaag
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: click>=8.1
|
|
11
|
+
Requires-Dist: fastapi>=0.110
|
|
12
|
+
Requires-Dist: jsonschema>=4.0
|
|
13
|
+
Requires-Dist: openai>=1.30
|
|
14
|
+
Requires-Dist: pyngrok>=7.0
|
|
15
|
+
Requires-Dist: requests>=2.31
|
|
16
|
+
Requires-Dist: uvicorn>=0.29
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: black>=24.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: responses>=0.25; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# minmo
|
|
25
|
+
|
|
26
|
+
[](https://github.com/a-elhaag/minmo/actions/workflows/ci.yml)
|
|
27
|
+
[](LICENSE)
|
|
28
|
+
[](pyproject.toml)
|
|
29
|
+
|
|
30
|
+
**Boilerplate-free SDK for building voice agents** on [AssemblyAI's Voice Agent API](https://assemblyai.com/docs/voice-agents/voice-agent-api), with support for Hume and OpenAI Realtime too.
|
|
31
|
+
|
|
32
|
+
Define a prompt, register Python functions as tools, deploy. minmo handles
|
|
33
|
+
JSON-Schema generation, local tool hosting + tunneling, and talking to each
|
|
34
|
+
provider's REST API — so you write the assistant, not the plumbing.
|
|
35
|
+
|
|
36
|
+
📖 **[Full documentation](https://a-elhaag.github.io/minmo/)**
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install -e .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
(Set `NGROK_AUTHTOKEN` in your environment if you plan to use `local=True` —
|
|
45
|
+
see [pyngrok's docs](https://pyngrok.readthedocs.io/) for how to get one.)
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from minmo import VoiceAgent
|
|
51
|
+
|
|
52
|
+
agent = VoiceAgent(
|
|
53
|
+
prompt="You are a friendly voice assistant. Keep answers short.",
|
|
54
|
+
api_key="YOUR_ASSEMBLYAI_API_KEY",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@agent.tool
|
|
59
|
+
def get_weather(city: str) -> str:
|
|
60
|
+
"""Get the current weather for a city."""
|
|
61
|
+
return f"It's sunny in {city}."
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
result = agent.deploy(local=True)
|
|
65
|
+
print(result) # the created AssemblyAI agent record, including its id
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
That's it — `deploy(local=True)` starts a local tool server, tunnels it
|
|
69
|
+
publicly, and registers `get_weather` as a real tool your live voice agent
|
|
70
|
+
can call.
|
|
71
|
+
|
|
72
|
+
`result` always includes an `"info"` field — a plain-language paragraph
|
|
73
|
+
telling you whether the agent is hosted on the provider's servers or only
|
|
74
|
+
exists locally, and how to actually connect to it (usually via
|
|
75
|
+
`mint_token()`). Worth printing after every deploy, especially since this
|
|
76
|
+
differs per provider — see below.
|
|
77
|
+
|
|
78
|
+
## Testing tool logic without burning session minutes
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from minmo import VoiceAgent
|
|
82
|
+
from minmo.testing import simulate
|
|
83
|
+
|
|
84
|
+
agent = VoiceAgent(
|
|
85
|
+
prompt="You are a friendly voice assistant.",
|
|
86
|
+
api_key="YOUR_ASSEMBLYAI_API_KEY",
|
|
87
|
+
llm={"base_url": "https://api.openai.com/v1", "model": "gpt-4o-mini", "api_key": "YOUR_OPENAI_KEY"},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@agent.tool
|
|
92
|
+
def get_weather(city: str) -> str:
|
|
93
|
+
"""Get the current weather for a city."""
|
|
94
|
+
return f"It's sunny in {city}."
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
result = simulate(agent, transcript=["What's the weather in Paris?"])
|
|
98
|
+
print(result["tool_calls"])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`simulate()` calls the `llm` you configured directly (any OpenAI-compatible
|
|
102
|
+
endpoint) and runs tool calls against your local Python functions — no
|
|
103
|
+
AssemblyAI session, no phone call.
|
|
104
|
+
|
|
105
|
+
## Client-side tools (no server, no tunnel)
|
|
106
|
+
|
|
107
|
+
`@agent.tool` defaults to `transport="http"` — minmo hosts the function
|
|
108
|
+
behind a URL AssemblyAI calls. Pass `transport="client"` instead to declare
|
|
109
|
+
a [client-side/function tool](https://assemblyai.com/docs/voice-agents/voice-agent-api/tools/client-side-tools):
|
|
110
|
+
no server, no tunnel, no `host_url` needed for that tool.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
@agent.tool(transport="client")
|
|
114
|
+
def get_account_balance(account_id: str) -> str:
|
|
115
|
+
"""Look up an account's balance."""
|
|
116
|
+
return "$42.00"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
minmo only builds the correct agent config for this — declaring the tool
|
|
120
|
+
as client-side. Running the actual session (the WebSocket connection that
|
|
121
|
+
receives `tool.call` and sends back `tool.result`) is your own code's job:
|
|
122
|
+
a browser app, a Twilio bridge, whatever already holds that live connection.
|
|
123
|
+
If every registered tool is `transport="client"`, `deploy()` skips the local
|
|
124
|
+
server and tunnel (or `host_url` requirement) entirely — nothing to host.
|
|
125
|
+
|
|
126
|
+
## Providers
|
|
127
|
+
|
|
128
|
+
minmo supports AssemblyAI (default), Hume, and OpenAI Realtime. They don't
|
|
129
|
+
all host your agent the same way:
|
|
130
|
+
|
|
131
|
+
| Provider | Hosted on their servers? | How you use it |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| AssemblyAI | Yes — `deploy()` creates a persistent agent record | `mint_token()` for a client token, connect a voice session with it |
|
|
134
|
+
| Hume | Yes — `deploy()` creates/versions a config resource | `mint_token()` for an access token, start an EVI session with it |
|
|
135
|
+
| OpenAI Realtime | No — `deploy()` only builds a config in your process's memory | Call `mint_token()` right after, in the same process, to actually send it to OpenAI and get a session token |
|
|
136
|
+
|
|
137
|
+
Pass a different provider via `VoiceAgent(provider=..., provider_options=...)`;
|
|
138
|
+
see each provider's docstring in `minmo/providers/` for required options.
|
|
139
|
+
|
|
140
|
+
## CLI
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
minmo init # scaffold main.py, .env.example, requirements.txt
|
|
144
|
+
minmo deploy # import main.py, find the VoiceAgent, deploy it
|
|
145
|
+
minmo logs # print the most recent session log
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Deploying to a real server (not `local=True`)
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
agent.deploy(local=False, host_url="https://your-deployed-tool-server.example.com")
|
|
152
|
+
# or set MINMO_HOST_URL in the environment instead of passing host_url
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Your host must already be running the tool server — `local=True` is for
|
|
156
|
+
development; production tool hosting is up to you (minmo's local server
|
|
157
|
+
factory, `minmo.server.create_tool_server`, is reusable if you want to
|
|
158
|
+
deploy it yourself behind a real domain).
|
|
159
|
+
|
|
160
|
+
## Development
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pip install -e ".[dev]"
|
|
164
|
+
pytest -q
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Pushes are auto-formatted with [Black](https://black.readthedocs.io/) via
|
|
168
|
+
GitHub Actions, and every push/PR runs the test suite across Python 3.10–3.12.
|
|
169
|
+
|
|
170
|
+
## Contributing
|
|
171
|
+
|
|
172
|
+
Issues and PRs welcome. Keep changes small and covered by a test.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
[MIT](LICENSE) © [Anas Elhaag](https://github.com/a-elhaag)
|
minmo-0.1.0/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# minmo
|
|
2
|
+
|
|
3
|
+
[](https://github.com/a-elhaag/minmo/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](pyproject.toml)
|
|
6
|
+
|
|
7
|
+
**Boilerplate-free SDK for building voice agents** on [AssemblyAI's Voice Agent API](https://assemblyai.com/docs/voice-agents/voice-agent-api), with support for Hume and OpenAI Realtime too.
|
|
8
|
+
|
|
9
|
+
Define a prompt, register Python functions as tools, deploy. minmo handles
|
|
10
|
+
JSON-Schema generation, local tool hosting + tunneling, and talking to each
|
|
11
|
+
provider's REST API — so you write the assistant, not the plumbing.
|
|
12
|
+
|
|
13
|
+
📖 **[Full documentation](https://a-elhaag.github.io/minmo/)**
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install -e .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
(Set `NGROK_AUTHTOKEN` in your environment if you plan to use `local=True` —
|
|
22
|
+
see [pyngrok's docs](https://pyngrok.readthedocs.io/) for how to get one.)
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from minmo import VoiceAgent
|
|
28
|
+
|
|
29
|
+
agent = VoiceAgent(
|
|
30
|
+
prompt="You are a friendly voice assistant. Keep answers short.",
|
|
31
|
+
api_key="YOUR_ASSEMBLYAI_API_KEY",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@agent.tool
|
|
36
|
+
def get_weather(city: str) -> str:
|
|
37
|
+
"""Get the current weather for a city."""
|
|
38
|
+
return f"It's sunny in {city}."
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
result = agent.deploy(local=True)
|
|
42
|
+
print(result) # the created AssemblyAI agent record, including its id
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
That's it — `deploy(local=True)` starts a local tool server, tunnels it
|
|
46
|
+
publicly, and registers `get_weather` as a real tool your live voice agent
|
|
47
|
+
can call.
|
|
48
|
+
|
|
49
|
+
`result` always includes an `"info"` field — a plain-language paragraph
|
|
50
|
+
telling you whether the agent is hosted on the provider's servers or only
|
|
51
|
+
exists locally, and how to actually connect to it (usually via
|
|
52
|
+
`mint_token()`). Worth printing after every deploy, especially since this
|
|
53
|
+
differs per provider — see below.
|
|
54
|
+
|
|
55
|
+
## Testing tool logic without burning session minutes
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from minmo import VoiceAgent
|
|
59
|
+
from minmo.testing import simulate
|
|
60
|
+
|
|
61
|
+
agent = VoiceAgent(
|
|
62
|
+
prompt="You are a friendly voice assistant.",
|
|
63
|
+
api_key="YOUR_ASSEMBLYAI_API_KEY",
|
|
64
|
+
llm={"base_url": "https://api.openai.com/v1", "model": "gpt-4o-mini", "api_key": "YOUR_OPENAI_KEY"},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@agent.tool
|
|
69
|
+
def get_weather(city: str) -> str:
|
|
70
|
+
"""Get the current weather for a city."""
|
|
71
|
+
return f"It's sunny in {city}."
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
result = simulate(agent, transcript=["What's the weather in Paris?"])
|
|
75
|
+
print(result["tool_calls"])
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`simulate()` calls the `llm` you configured directly (any OpenAI-compatible
|
|
79
|
+
endpoint) and runs tool calls against your local Python functions — no
|
|
80
|
+
AssemblyAI session, no phone call.
|
|
81
|
+
|
|
82
|
+
## Client-side tools (no server, no tunnel)
|
|
83
|
+
|
|
84
|
+
`@agent.tool` defaults to `transport="http"` — minmo hosts the function
|
|
85
|
+
behind a URL AssemblyAI calls. Pass `transport="client"` instead to declare
|
|
86
|
+
a [client-side/function tool](https://assemblyai.com/docs/voice-agents/voice-agent-api/tools/client-side-tools):
|
|
87
|
+
no server, no tunnel, no `host_url` needed for that tool.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
@agent.tool(transport="client")
|
|
91
|
+
def get_account_balance(account_id: str) -> str:
|
|
92
|
+
"""Look up an account's balance."""
|
|
93
|
+
return "$42.00"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
minmo only builds the correct agent config for this — declaring the tool
|
|
97
|
+
as client-side. Running the actual session (the WebSocket connection that
|
|
98
|
+
receives `tool.call` and sends back `tool.result`) is your own code's job:
|
|
99
|
+
a browser app, a Twilio bridge, whatever already holds that live connection.
|
|
100
|
+
If every registered tool is `transport="client"`, `deploy()` skips the local
|
|
101
|
+
server and tunnel (or `host_url` requirement) entirely — nothing to host.
|
|
102
|
+
|
|
103
|
+
## Providers
|
|
104
|
+
|
|
105
|
+
minmo supports AssemblyAI (default), Hume, and OpenAI Realtime. They don't
|
|
106
|
+
all host your agent the same way:
|
|
107
|
+
|
|
108
|
+
| Provider | Hosted on their servers? | How you use it |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| AssemblyAI | Yes — `deploy()` creates a persistent agent record | `mint_token()` for a client token, connect a voice session with it |
|
|
111
|
+
| Hume | Yes — `deploy()` creates/versions a config resource | `mint_token()` for an access token, start an EVI session with it |
|
|
112
|
+
| OpenAI Realtime | No — `deploy()` only builds a config in your process's memory | Call `mint_token()` right after, in the same process, to actually send it to OpenAI and get a session token |
|
|
113
|
+
|
|
114
|
+
Pass a different provider via `VoiceAgent(provider=..., provider_options=...)`;
|
|
115
|
+
see each provider's docstring in `minmo/providers/` for required options.
|
|
116
|
+
|
|
117
|
+
## CLI
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
minmo init # scaffold main.py, .env.example, requirements.txt
|
|
121
|
+
minmo deploy # import main.py, find the VoiceAgent, deploy it
|
|
122
|
+
minmo logs # print the most recent session log
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Deploying to a real server (not `local=True`)
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
agent.deploy(local=False, host_url="https://your-deployed-tool-server.example.com")
|
|
129
|
+
# or set MINMO_HOST_URL in the environment instead of passing host_url
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Your host must already be running the tool server — `local=True` is for
|
|
133
|
+
development; production tool hosting is up to you (minmo's local server
|
|
134
|
+
factory, `minmo.server.create_tool_server`, is reusable if you want to
|
|
135
|
+
deploy it yourself behind a real domain).
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pip install -e ".[dev]"
|
|
141
|
+
pytest -q
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Pushes are auto-formatted with [Black](https://black.readthedocs.io/) via
|
|
145
|
+
GitHub Actions, and every push/PR runs the test suite across Python 3.10–3.12.
|
|
146
|
+
|
|
147
|
+
## Contributing
|
|
148
|
+
|
|
149
|
+
Issues and PRs welcome. Keep changes small and covered by a test.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
[MIT](LICENSE) © [Anas Elhaag](https://github.com/a-elhaag)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Cookbook — minmo</title>
|
|
7
|
+
<meta name="description" content="Copy-paste recipes for common minmo voice agent patterns." />
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎙️</text></svg>" />
|
|
9
|
+
<link rel="stylesheet" href="style.css" />
|
|
10
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
|
|
14
|
+
<header class="nav">
|
|
15
|
+
<div class="wrap nav-inner">
|
|
16
|
+
<a class="brand" href="index.html">minmo</a>
|
|
17
|
+
<nav class="links">
|
|
18
|
+
<a href="index.html#quickstart">Quickstart</a>
|
|
19
|
+
<a href="index.html#providers">Providers</a>
|
|
20
|
+
<a href="index.html#cli">CLI</a>
|
|
21
|
+
<a href="cookbook.html">Cookbook</a>
|
|
22
|
+
<a href="reference.html">Reference</a>
|
|
23
|
+
<a href="https://github.com/a-elhaag/minmo">GitHub</a>
|
|
24
|
+
</nav>
|
|
25
|
+
</div>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
<main>
|
|
29
|
+
|
|
30
|
+
<section class="hero" id="top">
|
|
31
|
+
<div class="wrap">
|
|
32
|
+
<p class="eyebrow">recipes</p>
|
|
33
|
+
<h1>Cookbook</h1>
|
|
34
|
+
<p class="lede">
|
|
35
|
+
Small, copy-paste-ready patterns for things people build with minmo.
|
|
36
|
+
Each one assumes a <code>VoiceAgent</code> is already created — see the
|
|
37
|
+
<a href="index.html#quickstart">quickstart</a> if you need that part.
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<section class="section" id="multi-tool">
|
|
43
|
+
<div class="wrap">
|
|
44
|
+
<h2>Multiple tools on one agent</h2>
|
|
45
|
+
<p>Register as many functions as you need — each becomes its own tool with its own JSON schema, inferred from the signature and docstring.</p>
|
|
46
|
+
<pre class="code-block" data-copy><button class="copy-btn" type="button">Copy</button><code class="language-python">@agent.tool
|
|
47
|
+
def get_weather(city: str) -> str:
|
|
48
|
+
"""Get the current weather for a city."""
|
|
49
|
+
return f"It's sunny in {city}."
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@agent.tool
|
|
53
|
+
def get_account_balance(account_id: str) -> str:
|
|
54
|
+
"""Look up a customer's account balance by id."""
|
|
55
|
+
return "$42.00"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@agent.tool
|
|
59
|
+
def book_appointment(date: str, time: str, reason: str) -> str:
|
|
60
|
+
"""Book an appointment on a given date and time."""
|
|
61
|
+
return f"Booked for {date} at {time}: {reason}"</code></pre>
|
|
62
|
+
</div>
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<section class="section alt" id="mixed-transport">
|
|
66
|
+
<div class="wrap">
|
|
67
|
+
<h2>Mixing server-hosted and client-side tools</h2>
|
|
68
|
+
<p>A tool that only needs data your frontend already has (auth, session state) doesn't need a round trip to your server — declare it <code>transport="client"</code> and answer it yourself when the provider sends <code>tool.call</code> over the live session.</p>
|
|
69
|
+
<pre class="code-block" data-copy><button class="copy-btn" type="button">Copy</button><code class="language-python">@agent.tool # hosted by minmo, transport="http" is the default
|
|
70
|
+
def get_weather(city: str) -> str:
|
|
71
|
+
"""Get the current weather for a city."""
|
|
72
|
+
return f"It's sunny in {city}."
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@agent.tool(transport="client")
|
|
76
|
+
def get_account_balance(account_id: str) -> str:
|
|
77
|
+
"""Look up the current user's balance — answered client-side."""
|
|
78
|
+
return "$42.00"</code></pre>
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
|
|
82
|
+
<section class="section" id="ci-testing">
|
|
83
|
+
<div class="wrap">
|
|
84
|
+
<h2>Asserting tool-call behavior in CI</h2>
|
|
85
|
+
<p><code>simulate()</code> drives your <code>llm</code> directly and checks which tools it picks — no live session, so it's cheap enough to run on every commit.</p>
|
|
86
|
+
<pre class="code-block" data-copy><button class="copy-btn" type="button">Copy</button><code class="language-python">from minmo.testing import simulate
|
|
87
|
+
|
|
88
|
+
result = simulate(agent, transcript=["What's the weather in Paris?"])
|
|
89
|
+
assert result["tool_calls"][0]["name"] == "get_weather"
|
|
90
|
+
assert result["tool_calls"][0]["arguments"]["city"] == "Paris"</code></pre>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
|
|
94
|
+
<section class="section alt" id="hume-recipe">
|
|
95
|
+
<div class="wrap">
|
|
96
|
+
<h2>Switching to Hume</h2>
|
|
97
|
+
<p>Hume needs an extra <code>secret_key</code> (its OAuth client secret) alongside the usual <code>api_key</code>, and optionally a specific voice.</p>
|
|
98
|
+
<pre class="code-block" data-copy><button class="copy-btn" type="button">Copy</button><code class="language-python">from minmo import VoiceAgent
|
|
99
|
+
from minmo.providers.hume import HumeProvider
|
|
100
|
+
|
|
101
|
+
agent = VoiceAgent(
|
|
102
|
+
prompt="You are a friendly voice assistant.",
|
|
103
|
+
api_key="YOUR_HUME_API_KEY",
|
|
104
|
+
provider=HumeProvider(),
|
|
105
|
+
provider_options={
|
|
106
|
+
"secret_key": "YOUR_HUME_SECRET_KEY",
|
|
107
|
+
"voice": {"name": "ITO", "provider": "HUME_AI"},
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
result = agent.deploy()
|
|
112
|
+
print(result["info"])
|
|
113
|
+
|
|
114
|
+
token = agent.mint_token() # OAuth access token, start an EVI session with it</code></pre>
|
|
115
|
+
</div>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
<section class="section" id="openai-realtime-recipe">
|
|
119
|
+
<div class="wrap">
|
|
120
|
+
<h2>Switching to OpenAI Realtime</h2>
|
|
121
|
+
<p>OpenAI Realtime doesn't host anything on their servers — <code>deploy()</code> just builds the config in memory, so call <code>mint_token()</code> right after, in the same process, to actually get a usable session token.</p>
|
|
122
|
+
<pre class="code-block" data-copy><button class="copy-btn" type="button">Copy</button><code class="language-python">from minmo import VoiceAgent
|
|
123
|
+
from minmo.providers.openai_realtime import OpenAIRealtimeProvider
|
|
124
|
+
|
|
125
|
+
agent = VoiceAgent(
|
|
126
|
+
prompt="You are a friendly voice assistant.",
|
|
127
|
+
api_key="YOUR_OPENAI_API_KEY",
|
|
128
|
+
provider=OpenAIRealtimeProvider(),
|
|
129
|
+
provider_options={"model": "gpt-4o-realtime-preview", "voice": "alloy"},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
agent.deploy()
|
|
133
|
+
token = agent.mint_token() # send this session token to your client</code></pre>
|
|
134
|
+
</div>
|
|
135
|
+
</section>
|
|
136
|
+
|
|
137
|
+
<section class="section alt" id="session-logging">
|
|
138
|
+
<div class="wrap">
|
|
139
|
+
<h2>Logging sessions to disk</h2>
|
|
140
|
+
<p>Pass <code>log_path</code> when creating the agent, then call <code>log_session()</code> after a call ends (e.g. from your provider's webhook or end-of-call handler) to persist transcript + tool calls, and optionally react to it.</p>
|
|
141
|
+
<pre class="code-block" data-copy><button class="copy-btn" type="button">Copy</button><code class="language-python">agent = VoiceAgent(
|
|
142
|
+
prompt="You are a friendly voice assistant.",
|
|
143
|
+
api_key="YOUR_ASSEMBLYAI_API_KEY",
|
|
144
|
+
log_path="./sessions.jsonl",
|
|
145
|
+
on_session_end=lambda record: print("saved:", record["session_id"]),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# after a call ends:
|
|
149
|
+
agent.log_session(
|
|
150
|
+
session_id="sess_123",
|
|
151
|
+
transcript=[{"role": "user", "content": "What's the weather in Paris?"}],
|
|
152
|
+
tool_calls=[{"name": "get_weather", "arguments": {"city": "Paris"}}],
|
|
153
|
+
)</code></pre>
|
|
154
|
+
<p>Inspect the latest one anytime with <code>minmo logs</code> on the command line.</p>
|
|
155
|
+
</div>
|
|
156
|
+
</section>
|
|
157
|
+
|
|
158
|
+
<section class="section" id="prod-deploy">
|
|
159
|
+
<div class="wrap">
|
|
160
|
+
<h2>Deploying tool hosting yourself</h2>
|
|
161
|
+
<p><code>local=True</code> is for development — it spins up a local server and an ngrok tunnel. In production, run minmo's own server factory behind your real domain and point <code>deploy()</code> at it.</p>
|
|
162
|
+
<pre class="code-block" data-copy><button class="copy-btn" type="button">Copy</button><code class="language-python">from minmo.server import create_tool_server
|
|
163
|
+
|
|
164
|
+
app = create_tool_server(agent) # a FastAPI app — serve it with uvicorn/gunicorn
|
|
165
|
+
|
|
166
|
+
# elsewhere, once that server is live at your domain:
|
|
167
|
+
agent.deploy(local=False, host_url="https://tools.yourdomain.com")
|
|
168
|
+
# or export MINMO_HOST_URL=https://tools.yourdomain.com and drop host_url</code></pre>
|
|
169
|
+
</div>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
</main>
|
|
173
|
+
|
|
174
|
+
<footer class="footer">
|
|
175
|
+
<div class="wrap footer-inner">
|
|
176
|
+
<p>MIT licensed. Created by <a href="https://github.com/a-elhaag">Anas Elhaag</a> (<a href="https://github.com/a-elhaag">@a-elhaag</a>).</p>
|
|
177
|
+
<p><a href="https://github.com/a-elhaag/minmo">github.com/a-elhaag/minmo</a></p>
|
|
178
|
+
</div>
|
|
179
|
+
</footer>
|
|
180
|
+
|
|
181
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
182
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
|
183
|
+
<script src="script.js"></script>
|
|
184
|
+
</body>
|
|
185
|
+
</html>
|