Skip to content

release: 0.5.1 — first-class llm= selector + 5 LLM providers#69

Merged
nicolotognoni merged 6 commits into
mainfrom
phase-2/llm-selector
Apr 22, 2026
Merged

release: 0.5.1 — first-class llm= selector + 5 LLM providers#69
nicolotognoni merged 6 commits into
mainfrom
phase-2/llm-selector

Conversation

@nicolotognoni
Copy link
Copy Markdown
Collaborator

Summary

Adds llm= as a first-class selector on phone.agent(), mirroring the stt=/tts= pattern shipped in 0.5.0. Five LLM provider classes with env-var fallback ship in both SDKs:

from getpatter import Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
    stt=DeepgramSTT(),                    # DEEPGRAM_API_KEY
    llm=AnthropicLLM(),                   # ANTHROPIC_API_KEY (new in 0.5.1)
    tts=ElevenLabsTTS(voice_id="rachel"), # ELEVENLABS_API_KEY
    system_prompt="You are helpful.",
)
await phone.serve(agent)

TypeScript mirror uses the same names with new AnthropicLLM() etc.

What's new

  • getpatter.llm.{openai,anthropic,groq,cerebras,google}.LLM namespaced classes (Python + TS)
  • Flat re-exports: OpenAILLM, AnthropicLLM, GroqLLM, CerebrasLLM, GoogleLLM
  • Env-var fallback per provider (OPENAI_API_KEY, ANTHROPIC_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, GEMINI_API_KEYGOOGLE_API_KEY)
  • Tool calling works across all 5 providers — each adapter normalizes vendor-specific formats to Patter's unified chunk protocol
  • Docs refreshed: docs/{python,typescript}-sdk/llm.mdx full rewrites, concepts.mdx pipeline example updated, reference tables + agent pages updated, CHANGELOG entry added

Semantics

  • llm= + on_message are mutually exclusive — conflict raises a clear error at serve() time
  • engine= + llm= → one-time log warning, llm= is ignored (the engine handles its own LLM)
  • Default OpenAI LLMLoop still auto-constructs when llm= is absent and openai_key is present → no break from 0.5.0

Test plan

  • Python: 1327 → 1350 passed (+23 new in tests/unit/test_llm_api.py), 8 skipped, 0 failures
  • TypeScript: 1013 → 1042 passed across 61 files (+29 new in tests/unit/llm-api.test.ts); tsc --noEmit clean
  • 4-line quickstart with llm=AnthropicLLM() smoke tested under -W error::DeprecationWarning (Python) and via tsx (TypeScript)
  • Each of the 5 LLM wrappers: explicit apiKey, env fallback, missing-key error
  • engine + llm → agent builds, warning logged once
  • llm + on_message → raises at serve/handler init

Version

0.5.00.5.1 — patch bump because the feature is additive and backward-compatible. Existing code using on_message keeps working.

🤖 Generated with Claude Code

Add ``llm=`` as a first-class selector on ``phone.agent()`` mirroring the
STT/TTS pattern. Five LLM provider classes ship with env-var fallback:

```python
from getpatter import Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
    stt=DeepgramSTT(),
    llm=AnthropicLLM(),                      # ANTHROPIC_API_KEY from env
    tts=ElevenLabsTTS(voice_id="rachel"),
    system_prompt="You are helpful.",
)
await phone.serve(agent)
```

TypeScript mirror identical (`new AnthropicLLM()`, etc.).

## What's new

- ``getpatter.llm.{openai,anthropic,groq,cerebras,google}.LLM`` namespaced
  classes (Python and TypeScript).
- Flat re-exports: ``OpenAILLM``, ``AnthropicLLM``, ``GroqLLM``,
  ``CerebrasLLM``, ``GoogleLLM``.
- Env-var fallback on each: ``OPENAI_API_KEY``, ``ANTHROPIC_API_KEY``,
  ``GROQ_API_KEY``, ``CEREBRAS_API_KEY``, ``GEMINI_API_KEY`` →
  ``GOOGLE_API_KEY``.
- Tool-calling works across all five — each adapter normalizes
  vendor-specific formats to Patter's unified chunk protocol.
- Docs: ``docs/python-sdk/llm.mdx`` + ``typescript-sdk/llm.mdx`` refreshed
  with real provider catalog; ``concepts.mdx`` pipeline example now uses
  ``llm=AnthropicLLM()``; reference tables and agent pages updated.

## Semantics

- ``llm=`` + ``on_message`` are mutually exclusive — conflict raises at
  ``serve()`` time with a clear error.
- ``engine=`` + ``llm=`` → one-time warning logged, ``llm=`` ignored
  (the engine handles the LLM internally).
- Default OpenAI LLMLoop still auto-constructs when ``llm=`` is absent
  and ``openai_key`` is present — no break from 0.5.0.

## Test counts

- Python: 1327 → **1350 passed** (+23 new in ``tests/unit/test_llm_api.py``),
  8 skipped, 0 failures.
- TypeScript: 1013 → **1042 passed** across 61 files (+29 new in
  ``tests/unit/llm-api.test.ts``); ``tsc --noEmit`` clean.

## Version

``0.5.0`` → ``0.5.1`` (feature add, backward-compatible — old
``on_message`` pipeline keeps working).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify
Copy link
Copy Markdown

mintlify Bot commented Apr 22, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
patter-06b046ce 🟢 Ready View Preview Apr 22, 2026, 6:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

nicolotognoni and others added 5 commits April 22, 2026 20:41
The base Python CI matrix installs ``getpatter[local,dev]`` only — no
``anthropic`` or ``google-genai``. Five tests in ``test_llm_api.py``
instantiate ``AnthropicLLM()`` / ``GoogleLLM()`` which triggers the
underlying provider's lazy vendor import and raises ``RuntimeError``.

Add ``pytest.mark.skipif(importlib.util.find_spec(...) is None)`` guards
so the base matrix skips Anthropic + Google instantiation cases and the
``python-all-extras`` job (which installs everything) keeps exercising
them end-to-end.

Local run with extras installed: 23/23 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`importlib.util.find_spec('google.genai')` raises ModuleNotFoundError
when the parent `google` namespace doesn't exist at all (instead of
returning None). The base CI matrix has no `google` installed, so
test collection blew up before any test ran. Wrap in try/except.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0.5.1 shipped a broken ESM dist because tsup was bundling
``cloudflared`` into ``dist/index.mjs``. ``cloudflared`` is CJS and
calls ``require("path")`` at runtime — inside an ESM bundle that
resolves to "Dynamic require of 'path' is not supported" and the
tunnel startup crashes on every ``serve({ tunnel: true })``.

Fix: add ``sdk-ts/tsup.config.ts`` marking ``cloudflared`` and
``@ngrok/ngrok`` as external. Now the dist contains a literal
``await import("cloudflared")`` that resolves from the consumer's
``node_modules/`` at runtime, not a bundled copy.

Verified in ``dist/chunk-*.mjs`` (ESM) and ``dist/index.js`` (CJS):
``await import("cloudflared")`` appears as a plain reference; the
``tunnel-*.mjs`` chunk shrank from kilobytes to 142 bytes.

``cloudflared`` is already in ``optionalDependencies`` so users who
don't use ``tunnel: true`` aren't forced to install it; users who do
get it auto-installed by ``npm install getpatter``.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before: INFO emitted on every WebSocket open, DTMF, STT final, duplicate
transcript drop, hallucination filter, barge-in, guardrail trigger, AI
adapter connect, pipeline-mode setup, LLM-loop active, Deepgram/Twilio
cost query, transfer, hangup — dozens of lines per call.

After: exactly **two INFO lines per call**. Everything else is
``logger.debug(...)`` (Python) / ``getLogger().debug(...)`` (TypeScript)
and invisible unless the user wires a debug-level logger.

### Call started

```
Call started: CAxxx... (Twilio, engine=openai_realtime, +15550001 → +15550002)
```

### Call ended

```
Call ended: CAxxx... (42.3s, 8 turns, cost=$0.0127, p95=612ms)
```

Cost and latency come from the already-finalized ``CallMetrics`` /
``CallMetricsAccumulator.endCall()`` — no extra computation.

Logger ``.debug`` is a no-op in the default logger implementation, so
existing integrations that replace ``getLogger()`` still receive every
event for debugging/analytics. ``on_call_start`` / ``on_call_end`` /
``on_metrics`` / ``on_transcript`` callbacks continue to fire — this
patch only touches the stdout/stderr noise.

### Files touched

- ``sdk-py/getpatter/handlers/{twilio_handler,telnyx_handler,stream_handler}.py``
- ``sdk-ts/src/stream-handler.ts``

### Validation

- Python: 1350 passed / 8 skipped (unchanged vs before)
- TypeScript: 1042 passed / 61 files (unchanged); ``tsc --noEmit`` clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Parity gap with sdk-py: when ``phone.agent({ llm: new AnthropicLLM() })``
is called without an ``engine`` (or without explicit ``provider``), the
TypeScript client left ``agent.provider`` undefined so downstream metrics
labelled the call as ``openai_realtime``. Python normalises to
``"pipeline"`` in the same case.

Fix: mirror the Python ``else if (stt || tts || llm): provider='pipeline'``
branch in ``client.ts`` ``agent()``. Pure label-level fix — the LLMLoop
construction that actually picks the provider was already correct.

1042 vitest + tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nicolotognoni nicolotognoni merged commit 85e6294 into main Apr 22, 2026
15 checks passed
@github-actions github-actions Bot deleted the phase-2/llm-selector branch April 23, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant