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
59 changes: 59 additions & 0 deletions recipes/python/voice-agents/v1/dynamic-tool-availability/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Dynamic Tool Availability (Voice Agents v1)

Control which tools the LLM can call on each conversation turn, enabling phased workflows where tool access is granted or revoked as the conversation progresses.

## What it does

Starts a voice agent session with a single tool (`verify_identity`). Once the user's identity is verified via a function call, the agent dynamically swaps in a new tool set (`get_balance`, `transfer_funds`) by calling `send_update_think()` on the live WebSocket — no reconnection needed. The system prompt is updated in tandem so the LLM knows which tools are now available.

This pattern is essential for production agents with multi-phase conversations: a banking agent should only expose transfer tools after identity verification, a support agent should restrict escalation tools until troubleshooting is exhausted, and so on.

## Key parameters

| Parameter | Description |
|-----------|-------------|
| `think.functions` | List of tool/function definitions the LLM may call |
| `send_update_think(AgentV1UpdateThink)` | Replace the think config (including functions) mid-session |
| `send_update_prompt(AgentV1UpdatePrompt)` | Update the system prompt to reflect new tool availability |
| `AgentV1FunctionCallRequest` | Event fired when the LLM invokes a tool |
| `send_function_call_response()` | Return the tool result to the agent |

## Conversation phases

| Phase | Available tools | Trigger to advance |
|-------|-----------------|--------------------|
| 1 — Greeting | `verify_identity` | User provides verification code |
| 2 — Authorized | `get_balance`, `transfer_funds` | (end of demo) |

## Example output

```
Connection opened
[Phase 1] Tools: verify_identity
[user] Hi, my code is ABC123
[Phase 1] Tool called: verify_identity
[Phase 2] Tools updated → get_balance, transfer_funds
[assistant] Your identity has been verified. How can I help you today?
[user] What is my balance?
[Phase 2] Tool called: get_balance
[assistant] Your current balance is $4,210.50.
Connection closed
```

## Prerequisites

- Python 3.10+
- Set `DEEPGRAM_API_KEY` environment variable
- Install: `pip install -r recipes/python/requirements.txt`

## Run

```bash
python example.py
```

## Test

```bash
pytest example_test.py -v
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Recipe: Dynamic Tool Availability (Voice Agents v1)
=====================================================
Demonstrates changing which tools the LLM can call per conversation
phase. Phase 1 offers only verify_identity; after verification
succeeds, send_update_think() swaps in get_balance and transfer_funds
— all on the same live WebSocket session.
"""

import threading, time

from deepgram import DeepgramClient
from deepgram.agent.v1.types import (
AgentV1FunctionCallRequest, AgentV1InjectUserMessage,
AgentV1SendFunctionCallResponse, AgentV1Settings, AgentV1SettingsAgent,
AgentV1SettingsAgentListen, AgentV1SettingsAgentListenProvider_V1,
AgentV1SettingsAudio, AgentV1SettingsAudioInput, AgentV1UpdateThink,
)
from deepgram.core.events import EventType
from deepgram.types.speak_settings_v1 import SpeakSettingsV1
from deepgram.types.speak_settings_v1provider import SpeakSettingsV1Provider_Deepgram
from deepgram.types.think_settings_v1 import ThinkSettingsV1
from deepgram.types.think_settings_v1provider import ThinkSettingsV1Provider_OpenAi

def _fn(name, desc, props, req):
return {"name": name, "description": desc, "parameters": {"type": "object", "properties": props, "required": req}}

VERIFY = _fn("verify_identity", "Verify customer", {"code": {"type": "string"}}, ["code"])
BALANCE = _fn("get_balance", "Check balance", {}, [])
TRANSFER = _fn("transfer_funds", "Transfer money", {"amount": {"type": "number"}, "to": {"type": "string"}}, ["amount", "to"])

RESULTS = {"verify_identity": '{"verified":true}', "get_balance": '{"balance":"$4,210.50"}', "transfer_funds": '{"confirmation":"TXN-98201"}'}
P1 = "You are a bank assistant. Verify the user with verify_identity before anything else."
P2 = "Identity verified. You may now use get_balance and transfer_funds."

def main():
client = DeepgramClient()
phase, ready, done = {"n": 1}, threading.Event(), threading.Event()

with client.agent.v1.connect() as agent:
def on_msg(msg):
if isinstance(msg, AgentV1FunctionCallRequest):
print(f"[Phase {phase['n']}] Tool called: {msg.name}")
agent.send_function_call_response(AgentV1SendFunctionCallResponse(
type="FunctionCallResponse", id=msg.id, name=msg.name, content=RESULTS.get(msg.name, "{}")))
if msg.name == "verify_identity" and phase["n"] == 1:
phase["n"] = 2
agent.send_update_think(AgentV1UpdateThink(think=ThinkSettingsV1(
provider=ThinkSettingsV1Provider_OpenAi(type="open_ai", model="gpt-4o-mini"),
prompt=P2, functions=[BALANCE, TRANSFER])))
print("[Phase 2] Tools updated → get_balance, transfer_funds")
elif phase["n"] == 2:
done.set()
elif not isinstance(msg, bytes):
mt = getattr(msg, "type", "")
if mt == "SettingsApplied":
ready.set()
elif mt == "ConversationText":
print(f"[{getattr(msg, 'role', '?')}] {getattr(msg, 'content', '')}")

agent.on(EventType.OPEN, lambda _: print("Connection opened"))
agent.on(EventType.MESSAGE, on_msg)
agent.on(EventType.CLOSE, lambda _: print("Connection closed"))
threading.Thread(target=agent.start_listening, daemon=True).start()

agent.send_settings(AgentV1Settings(
audio=AgentV1SettingsAudio(input=AgentV1SettingsAudioInput(encoding="linear16", sample_rate=24000)),
agent=AgentV1SettingsAgent(
listen=AgentV1SettingsAgentListen(provider=AgentV1SettingsAgentListenProvider_V1(type="deepgram", model="nova-3")),
think=ThinkSettingsV1(provider=ThinkSettingsV1Provider_OpenAi(type="open_ai", model="gpt-4o-mini"), prompt=P1, functions=[VERIFY]),
speak=SpeakSettingsV1(provider=SpeakSettingsV1Provider_Deepgram(type="deepgram", model="aura-2-thalia-en")))))
print("[Phase 1] Tools: verify_identity")
if not ready.wait(10):
raise TimeoutError("Settings not applied")

agent.send_inject_user_message(AgentV1InjectUserMessage(content="Hi, my code is ABC123"))
if not done.wait(30):
agent.send_inject_user_message(AgentV1InjectUserMessage(content="What is my balance?"))
done.wait(15)
time.sleep(2)

if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import subprocess
from pathlib import Path

def test_example_runs():
"""Runs the dynamic-tool-availability example and verifies it produces output."""
example = Path(__file__).parent / "example.py"
result = subprocess.run(
["python", str(example)],
capture_output=True,
text=True,
timeout=60,
)
assert result.returncode == 0, (
f"Example failed\nSTDOUT: {result.stdout}\nSTDERR: {result.stderr}"
)
assert result.stdout.strip(), "Example produced no output"
Loading