Skip to content

Commit f791549

Browse files
review: use asyncio.wait_for (py3.9-safe) in integration test; document Tools/Secrets helpers in README
- asyncio.timeout is 3.11+, but the package targets python ^3.9; switch the frame collector to asyncio.wait_for so the live test runs on 3.9/3.10 too. - README had no Tools/Secrets section (shipped in 5.12.0/5.12.1); add one with the registry create/list/duplicate/delete flow and the toolRefs note.
1 parent bd3ad91 commit f791549

2 files changed

Lines changed: 55 additions & 11 deletions

File tree

‎README.md‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pip install smallestai
1717
- [Quickstart](#quickstart-create-an-agent-and-call-it)
1818
- [Text-to-speech and speech-to-text](#text-to-speech-and-speech-to-text-models)
1919
- [Agent crew: your own LLM](#agent-crew-your-own-llm-in-the-middle)
20+
- [Reusable tools and secrets](#reusable-tools-and-secrets)
2021
- [CLI](#cli)
2122
- [Async client](#async-client)
2223
- [Environments](#environments)
@@ -113,6 +114,44 @@ A flat directory (`server.py` + `requirements.txt` at the root) is the simplest
113114
layout; a `src/` layout with a `pyproject.toml` also works (declare all runtime
114115
deps in the pyproject).
115116

117+
## Reusable tools and secrets
118+
119+
Build a tool once in your org's registry and reference it from any agent, and store
120+
API keys in a write-only secrets vault. `Tools` and `Secrets` wrap the `/tool` and
121+
`/secret` endpoints; both default to the `SMALLEST_API_KEY` environment variable.
122+
123+
```python
124+
from smallestai.atoms.helpers import Tools, Secrets
125+
126+
tools = Tools() # or Tools(api_key="sk_...")
127+
created = tools.create(
128+
{
129+
"name": "get_order_status",
130+
"description": "Look up an order status by id",
131+
"type": "api_call",
132+
"method": "GET",
133+
"url": "https://api.example.com/orders",
134+
"parameters": [
135+
{"name": "order_id", "type": "text", "description": "order id", "required": True}
136+
],
137+
}
138+
)
139+
tool_id = created["data"]["toolId"]
140+
tools.list()
141+
tools.duplicate(tool_id)
142+
tools.delete(tool_id)
143+
144+
# secrets are write-only: the value is encrypted at rest and never returned
145+
secrets = Secrets()
146+
secrets.create(name="example_api_key", value="...")
147+
secrets.list() # names and metadata only
148+
```
149+
150+
Reference a saved tool from an agent with `toolRefs: ["<toolId>"]` on its single-prompt
151+
config. The tool must exist in the registry first, or the reference is dropped when the
152+
agent runs. Referencing a tool makes it available; the agent only calls it when its
153+
prompt says to.
154+
116155
## CLI
117156

118157
```sh

‎tests/custom/test_realtime_integration.py‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,23 @@ async def run():
6060
got_audio = False
6161
async with websockets.connect(url, ssl=ssl.create_default_context(), open_timeout=20) as ws:
6262
# Collect frames for up to 15s: the session, then the agent's first spoken turn.
63+
# asyncio.wait_for (not asyncio.timeout) so this runs on Python 3.9+.
64+
loop = asyncio.get_event_loop()
65+
deadline = loop.time() + 15
6366
try:
64-
async with asyncio.timeout(15):
65-
while True:
66-
raw = await ws.recv()
67-
msg = json.loads(raw) if isinstance(raw, (str, bytes)) else {}
68-
t = msg.get("type", "")
69-
types.append(t)
70-
# audio arrives as base64 payloads on audio/response events
71-
if "audio" in t or msg.get("audio") or "audio" in msg.get("data", {}):
72-
got_audio = True
73-
if got_audio and len(types) >= 3:
74-
break
67+
while True:
68+
remaining = deadline - loop.time()
69+
if remaining <= 0:
70+
break
71+
raw = await asyncio.wait_for(ws.recv(), timeout=remaining)
72+
msg = json.loads(raw) if isinstance(raw, (str, bytes)) else {}
73+
t = msg.get("type", "")
74+
types.append(t)
75+
# audio arrives as base64 payloads on audio/response events
76+
if "audio" in t or msg.get("audio") or "audio" in msg.get("data", {}):
77+
got_audio = True
78+
if got_audio and len(types) >= 3:
79+
break
7580
except (asyncio.TimeoutError, websockets.ConnectionClosed):
7681
pass
7782
return types, got_audio

0 commit comments

Comments
 (0)