Skip to content
Merged
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
14 changes: 14 additions & 0 deletions livekit-plugins/livekit-plugins-phonic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ cd examples
uv run voice_agents/phonic_realtime_agent.py dev
```

### Reusing tools with Phonic Responses

Convert an existing LiveKit `ToolContext` into the schema-only definitions
accepted by Phonic's Responses API:

```python
from livekit.plugins.phonic.realtime import to_phonic_tool_definitions

tool_definitions = to_phonic_tool_definitions(tool_context)
```

The executable functions remain in the `ToolContext`; only their names,
descriptions, and parameter schemas are returned.

## Configuration

Set the `PHONIC_API_KEY` environment variable, or pass `api_key` directly to `RealtimeModel`. All other options are optional.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
PronunciationEntry,
RealtimeModel,
RealtimeSession,
to_phonic_tool_definitions,
)

__all__ = [
Expand All @@ -16,4 +17,5 @@
"PronunciationEntry",
"RealtimeModel",
"RealtimeSession",
"to_phonic_tool_definitions",
]
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
AsyncConversationsSocketClient,
)
from phonic.core import RequestOptions
from phonic.requests import ResponsesToolDefinitionParams
from phonic.types import (
AddSystemMessagePayload,
AudioChunkPayload,
Expand Down Expand Up @@ -99,6 +100,32 @@ class ConfigurationEndpoint(TypedDict, total=False):
timeout_ms: int


def _to_phonic_tool_definition(
tool_schema: dict[str, typing.Any],
) -> ResponsesToolDefinitionParams:
function = tool_schema["function"]
return {
"name": function["name"],
"description": function.get("description") or "",
"parameters": function["parameters"],
}


def to_phonic_tool_definitions(
tool_context: llm.ToolContext,
) -> list[ResponsesToolDefinitionParams]:
"""Convert LiveKit function tools to Phonic Responses API definitions.

The returned values contain schemas only; the executable callables remain in
``tool_context`` for the caller to invoke when Phonic returns a tool call.
"""

return [
_to_phonic_tool_definition(tool_schema)
for tool_schema in tool_context.parse_function_tools("openai", strict=True)
]
Comment on lines +123 to +126

@devin-ai-integration devin-ai-integration Bot Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Unsupported tool schemas are exported

For reserved parameter names or schemas nested beyond five levels, to_phonic_tool_definitions exports definitions Phonic rejects. Those existing LiveKit tools cannot be reused through Responses.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine, we should let Phonic do the validation instead of doing validation in this helper



@dataclass
class _RealtimeOptions:
api_key: str
Expand Down
2 changes: 1 addition & 1 deletion livekit-plugins/livekit-plugins-phonic/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ dependencies = [
"livekit-agents>=1.7.1",
"websockets>=11.0",
"aiohttp>=3.8.0",
"phonic>=0.32.22"
"phonic>=0.32.27"
]

[project.urls]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest

from livekit.agents import llm
from livekit.plugins.phonic.realtime import to_phonic_tool_definitions

pytestmark = pytest.mark.unit


def test_to_phonic_tool_definitions() -> None:
@llm.function_tool(
name="search_pizza_shop_recs",
description="Search for pizza shop recommendations in a location.",
)
async def search_pizza_shop_recs(location: str) -> str:
return location

definitions = to_phonic_tool_definitions(llm.ToolContext([search_pizza_shop_recs]))

assert definitions == [
{
"name": "search_pizza_shop_recs",
"description": "Search for pizza shop recommendations in a location.",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
"additionalProperties": False,
},
}
]


def test_to_phonic_tool_definitions_allows_no_description() -> None:
@llm.function_tool(name="search_pizza_shop_recs")
async def search_pizza_shop_recs() -> None:
pass

assert (
to_phonic_tool_definitions(llm.ToolContext([search_pizza_shop_recs]))[0]["description"]
== ""
)
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading