From 2747342f6c8035bd53da7af6e09de5f37155f501 Mon Sep 17 00:00:00 2001 From: Abhishek Mishra Date: Thu, 30 Jul 2026 23:34:34 +0530 Subject: [PATCH 1/2] feat(cli): poll build status on deploy; hand-written README (5.3.3) agent-crew deploy previously returned at 'queued', leaving users to guess when the build was ready. It now polls to a terminal state, reports the outcome, and on success points to Make Live with a warm-up note. Replaces the generated README boilerplate with a hand-written front door (agents, Waves TTS/STT, agent crew with a custom LLM, CLI) and adds it to .fernignore so regeneration does not overwrite it. Bumps version to 5.3.3 and adds the changelog entry. --- .fernignore | 3 + README.md | 269 +++++++++++-------------------- changelog.md | 12 ++ pyproject.toml | 2 +- src/smallestai/cli/agent_crew.py | 47 +++++- 5 files changed, 159 insertions(+), 174 deletions(-) diff --git a/.fernignore b/.fernignore index 9b54b840..09b1d6b4 100644 --- a/.fernignore +++ b/.fernignore @@ -48,3 +48,6 @@ RELEASE.md scripts/** Makefile src/smallestai/waves/helpers/** + +# Hand-written README (do not regenerate) +README.md diff --git a/README.md b/README.md index 26c8b8f1..02e0e241 100644 --- a/README.md +++ b/README.md @@ -1,238 +1,165 @@ -# SmallestAi Python Library +# SmallestAI Python SDK -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=https%3A%2F%2Fgithub.com%2Fsmallest-inc%2Fsmallest-python-sdk) [![pypi](https://img.shields.io/pypi/v/smallestai)](https://pypi.python.org/pypi/smallestai) -The SmallestAi Python library provides convenient access to the SmallestAi APIs from Python. +`pip install smallestai` gives you one package with two surfaces, plus a CLI: -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Environments](#environments) -- [Async Client](#async-client) -- [Exception Handling](#exception-handling) -- [Streaming](#streaming) -- [Websockets](#websockets) -- [Advanced](#advanced) - - [Access Raw Response Data](#access-raw-response-data) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Custom Client](#custom-client) -- [Contributing](#contributing) - -## Installation +- **Atoms** — build, configure, deploy, and phone-call voice AI agents (`client.atoms`). +- **Waves** — low-latency text-to-speech and speech-to-text, sync/async and streaming (`client.waves`). +- **CLI** — `smallestai` for managing agents and deploying agent-crew code. ```sh pip install smallestai ``` -## Reference - -A full reference for this library is available [here](https://github.com/smallest-inc/smallest-python-sdk/blob/HEAD/./reference.md). +## Table of Contents -## Usage +- [Quickstart](#quickstart-create-an-agent-and-call-it) +- [Text-to-speech and speech-to-text](#text-to-speech-and-speech-to-text-waves) +- [Agent crew: your own LLM](#agent-crew-your-own-llm-in-the-middle) +- [CLI](#cli) +- [Async client](#async-client) +- [Environments](#environments) +- [Exception handling](#exception-handling) +- [Reference and docs](#reference-and-docs) +- [Contributing](#contributing) -Instantiate and use the client with the following: +## Quickstart: create an agent and call it ```python from smallestai import SmallestAI -client = SmallestAI( - api_key="", -) +client = SmallestAI(api_key="") + +# create an agent (the response .data is the new agent id) +agent_id = client.atoms.agents.create_agent(name="my-first-agent").data -client.atoms.agent_templates.create_agent_from_template( - agent_name="agentName", - template_id="templateId", +# place an outbound call (from_product_id is a rented number's product id) +client.atoms.calls.start_outbound_call( + agent_id=agent_id, + phone_number="+1XXXXXXXXXX", + from_product_id="", ) ``` -## Environments +## Text-to-speech and speech-to-text (Waves) -This SDK allows you to configure different environments for API requests. +`synthesize_tts` streams audio bytes. List available voices with `client.waves.get_voices()`. ```python from smallestai import SmallestAI -from smallestai.environment import SmallestAIEnvironment -client = SmallestAI( - environment=SmallestAIEnvironment.PRODUCTION, -) -``` +client = SmallestAI(api_key="") -## Async Client +with open("out.wav", "wb") as f: + for chunk in client.waves.synthesize_tts(text="Hello from Smallest.", voice_id=""): + f.write(chunk) +``` -The SDK also exports an `async` client so that you can make non-blocking calls to our API. Note that if you are constructing an Async httpx client class to pass into this client, use `httpx.AsyncClient()` instead of `httpx.Client()` (e.g. for the `httpx_client` parameter of this client). +Streaming speech-to-text helper: ```python -import asyncio +from smallestai.waves.helpers import stream_speech_to_text -from smallestai import AsyncSmallestAI - -client = AsyncSmallestAI( - api_key="", -) - - -async def main() -> None: - await client.atoms.agent_templates.create_agent_from_template( - agent_name="agentName", - template_id="templateId", - ) - - -asyncio.run(main()) +for event in stream_speech_to_text(client, language="en"): + print(event) ``` -## Exception Handling +## Agent crew: your own LLM in the middle -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. +An agent crew runs the LLM turn on a model **you** choose while Smallest handles +STT and TTS. Point the crew node's `OpenAIClient` at any OpenAI-compatible +endpoint (a hosted API, or a local model via Ollama): ```python -from smallestai.core.api_error import ApiError - -try: - client.atoms.agent_templates.create_agent_from_template(...) -except ApiError as e: - print(e.status_code) - print(e.body) +from smallestai.atoms.crew.nodes import OutputCrewNode +from smallestai.atoms.crew.clients.openai import OpenAIClient + +class Assistant(OutputCrewNode): + def __init__(self): + super().__init__(name="assistant") + self.llm = OpenAIClient( + model="claude-haiku-4-5", + api_key="", + base_url="https://api.anthropic.com/v1/", # or http://localhost:11434/v1 for Ollama + ) + + async def generate_response(self): + async for chunk in await self.llm.chat(self.context.messages, stream=True): + if chunk.content: + yield chunk.content ``` -## Streaming - -The SDK supports streaming responses, as well, the response will be a generator that you can loop over. +Conversation history is handled for you: every turn is appended to `self.context`, +and you send `self.context.messages` to the model each turn. -```python -from smallestai import SmallestAI - -client = SmallestAI( - api_key="", -) +Deploy it with the CLI: -client.atoms.live_transcripts.subscribe_to_live_events( - call_id="CALL-1758124225863-80752e", -) +```sh +smallestai auth login +smallestai agent-crew init --agent-id +smallestai agent-crew deploy --entry-point server.py +smallestai agent-crew builds # pick the build -> Make Live ``` -## Websockets - -The SDK supports both sync and async websocket connections for real-time, low-latency communication. Sockets can be created using the `connect` method, which returns a context manager. -You can either iterate through the returned `SocketClient` to process messages as they arrive, or attach handlers to respond to specific events. +A flat directory (`server.py` + `requirements.txt` at the root) is the simplest +layout; a `src/` layout with a `pyproject.toml` also works (declare all runtime +deps in the pyproject). -```python -from smallestai import SmallestAI +## CLI -client = SmallestAI(...) +```sh +smallestai auth login # store your API key +smallestai agents list # list, get, call, and manage agents +smallestai agent-crew deploy ... # package and deploy crew code +smallestai agent-crew chat # talk to a running crew locally +``` -# Connect to the websocket (Sync) -with client.speech_to_text.stream() as socket: - # Iterate over the messages as they arrive - for message in socket: - print(message) +## Async client - # Or, attach handlers to specific events - socket.on(EventType.MESSAGE, lambda message: print("received message", message)) +The SDK exports an `async` client with the same surface: +```python import asyncio from smallestai import AsyncSmallestAI -client = AsyncSmallestAI(...) +async def main(): + client = AsyncSmallestAI(api_key="") + agents = await client.atoms.agents.list_agents() + print(agents.data) -# Connect to the websocket (Async) -async with client.speech_to_text.stream() as socket: - async for message in socket: - print(message) +asyncio.run(main()) ``` -## Advanced - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.with_raw_response` property. -The `.with_raw_response` property returns a "raw" client that can be used to access the `.headers` and `.data` attributes. +## Environments ```python from smallestai import SmallestAI +from smallestai.environment import SmallestAIEnvironment -client = SmallestAI(...) -response = client.atoms.agent_templates.with_raw_response.create_agent_from_template(...) -print(response.headers) # access the response headers -print(response.status_code) # access the response status code -print(response.data) # access the underlying object -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -Which status codes are retried depends on the `retryStatusCodes` generator configuration: - -**`legacy`** (current default): retries on -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) (Conflict) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses) (All server errors, including 500) - -**`recommended`**: retries on -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) (Conflict) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [502](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) (Bad Gateway) -- [503](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) (Service Unavailable) -- [504](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504) (Gateway Timeout) - -Use the `max_retries` request option to configure this behavior. - -```python -client.atoms.agent_templates.create_agent_from_template(..., request_options={ - "max_retries": 1 -}) +client = SmallestAI(environment=SmallestAIEnvironment.PRODUCTION) ``` -### Timeouts - -The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. +## Exception handling ```python -from smallestai import SmallestAI - -client = SmallestAI(..., timeout=20.0) +from smallestai.core.api_error import ApiError -# Override timeout for a specific method -client.atoms.agent_templates.create_agent_from_template(..., request_options={ - "timeout_in_seconds": 1 -}) +try: + client.atoms.agents.get_agent(id="does-not-exist") +except ApiError as e: + print(e.status_code, e.body) ``` -### Custom Client +## Reference and docs -You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies -and transports. - -```python -import httpx -from smallestai import SmallestAI +- Full API reference: [reference.md](./reference.md) +- Product docs: https://smallest.ai/docs -client = SmallestAI( - ..., - httpx_client=httpx.Client( - proxy="http://my.test.proxy.example.com", - transport=httpx.HTTPTransport(local_address="0.0.0.0"), - ), -) -``` +The client also supports response streaming, automatic retries, per-request +timeouts, and access to raw response data. See the reference for details. ## Contributing -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! +Most of `src/` is generated from an API spec and gets overwritten on regeneration, +so hand edits there will not stick. If you spot a bug or a gap, open an issue. diff --git a/changelog.md b/changelog.md index 7a722331..99814d30 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,15 @@ +## 5.3.3 - 2026-07-30 + +CLI deploy now reports real build status, and a hand-written README. No API changes. + +* **CLI** (`smallestai agent-crew deploy`): after upload, the command polls the build + to a terminal state (SUCCEEDED / failed) instead of returning at "queued", and on + success points to `agent-crew builds` -> Make Live with a note that the first call + after Make Live can need a few seconds while the pod warms up. +* **README**: replaced the generated boilerplate with a hand-written front door + (agents, Waves TTS/STT, agent crew with a custom LLM, CLI). Added to `.fernignore` + so regeneration no longer overwrites it. + ## 5.3.2 - 2026-07-30 Corrects the agent-crew deploy layout warning. No API or behaviour changes. diff --git a/pyproject.toml b/pyproject.toml index 70fb51b1..74f26e4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ dynamic = ["version"] [tool.poetry] name = "smallestai" -version = "5.3.2" +version = "5.3.3" description = "" readme = "README.md" authors = [] diff --git a/src/smallestai/cli/agent_crew.py b/src/smallestai/cli/agent_crew.py index 23129772..86f1bd18 100644 --- a/src/smallestai/cli/agent_crew.py +++ b/src/smallestai/cli/agent_crew.py @@ -236,9 +236,52 @@ async def async_deploy(directory: str, entry_point: str): api_key=access_token, ) - console.print("[bold green]✓ Deployment successful![/bold green]") + console.print("[bold green]✓ Deployment uploaded![/bold green]") console.print(f"[dim]Build ID: {result.build_id}[/dim]") - console.print(f"[dim]Status: {result.message}[/dim]") + + # Poll the build to a terminal state instead of returning at "queued". + # The build has to reach SUCCEEDED before you can Make Live, and a + # freshly-live crew pod can need a moment to warm up before it serves + # the first call. Surfacing the real status here saves guessing. + console.print("[yellow]Building (this usually takes 1-2 min)...[/yellow]") + terminal = {"SUCCEEDED", "BUILD_FAILED", "DEPLOY_FAILED", "FAILED"} + last = None + status = None + for _ in range(60): # ~5 min cap at 5s intervals + try: + build = await atoms_client.get_agent_build( + agent_id=agent_id, + build_id=result.build_id, + api_key=access_token, + ) + status = str(getattr(build.status, "value", build.status)) + except Exception: + status = None + if status and status != last: + console.print(f"[dim] {status}[/dim]") + last = status + if status in terminal: + break + await asyncio.sleep(5) + + if status == "SUCCEEDED": + console.print("[bold green]✓ Build succeeded.[/bold green]") + console.print( + "[dim]Next: `smallestai agent-crew builds` -> Make Live. " + "The first call right after Make Live can take a few seconds " + "while the pod warms up; if it's silent, give it a moment and " + "try again.[/dim]" + ) + elif status in terminal: + console.print( + f"[red]Build ended with status: {status}. " + "Check `smallestai agent-crew builds` for details.[/red]" + ) + else: + console.print( + "[yellow]Still building. Check `smallestai agent-crew builds` " + "for the final status.[/yellow]" + ) except Exception as e: console.print(f"[red]Error deploying agent {e}[/red]") From d1d62f88722dd0acaebeb6d22c2eaf744a54dbdf Mon Sep 17 00:00:00 2001 From: Abhishek Mishra Date: Fri, 31 Jul 2026 11:18:58 +0530 Subject: [PATCH 2/2] docs(readme): restore websockets + Advanced sections dropped in the rewrite The README rewrite replaced the boilerplate intro (good) but also dropped the Websockets and Advanced sections (raw response access, retries, timeouts, custom httpx client) and the streaming note. Those are genuinely useful reference bits. Restore them with verified method names, and fix the old websocket example path (client.speech_to_text.stream -> client.waves.speech_to_text.stream). --- README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 02e0e241..ece71c61 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ pip install smallestai - [Async client](#async-client) - [Environments](#environments) - [Exception handling](#exception-handling) +- [Streaming and websockets](#streaming-and-websockets) +- [Advanced](#advanced) + - [Access raw response data](#access-raw-response-data) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom client](#custom-client) - [Reference and docs](#reference-and-docs) - [Contributing](#contributing) @@ -151,14 +157,80 @@ except ApiError as e: print(e.status_code, e.body) ``` +## Streaming and websockets + +Waves supports real-time, low-latency streaming over websockets. `stream()` returns a context manager; iterate it to process messages as they arrive. + +```python +from smallestai import SmallestAI + +client = SmallestAI(api_key="") + +# real-time speech-to-text +with client.waves.speech_to_text.stream() as socket: + for message in socket: + print(message) +``` + +The async client mirrors this with `async with` / `async for`. Text-to-speech streams too: `synthesize_tts(...)` yields audio bytes as they are generated (see above). + +## Advanced + +### Access raw response data + +Use `.with_raw_response` to get the response headers and status alongside the parsed data. + +```python +response = client.atoms.agents.with_raw_response.get_agent(id="") +print(response.headers) # response headers +print(response.status_code) # status code +print(response.data) # parsed object +``` + +### Retries + +The SDK retries retryable requests with exponential backoff (default 2). Configure it at the client or per request. + +```python +client = SmallestAI(api_key="", max_retries=3) + +# or per request +client.atoms.agents.get_agent(id="", request_options={"max_retries": 1}) +``` + +### Timeouts + +Defaults to 60 seconds. Configure at the client or per request. + +```python +client = SmallestAI(api_key="", timeout=20.0) + +# or per request +client.atoms.agents.get_agent(id="", request_options={"timeout_in_seconds": 5}) +``` + +### Custom client + +Override the `httpx` client for proxies, custom transports, and similar. + +```python +import httpx +from smallestai import SmallestAI + +client = SmallestAI( + api_key="", + httpx_client=httpx.Client( + proxy="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + ## Reference and docs - Full API reference: [reference.md](./reference.md) - Product docs: https://smallest.ai/docs -The client also supports response streaming, automatic retries, per-request -timeouts, and access to raw response data. See the reference for details. - ## Contributing Most of `src/` is generated from an API spec and gets overwritten on regeneration,