Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion livekit-agents/livekit/agents/llm/_provider_format/openai.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import base64
import copy
from typing import Any, Literal

from livekit.agents import llm
Expand Down Expand Up @@ -224,7 +225,7 @@ def to_responses_fnc_ctx(tool_ctx: llm.ToolContext, *, strict: bool = True) -> l
schemas: list[dict[str, Any]] = []
for tool in tool_ctx.flatten():
if isinstance(tool, llm.RawFunctionTool):
schema = tool.info.raw_schema
schema = copy.deepcopy(tool.info.raw_schema)
schema["type"] = "function"
schemas.append(schema)
elif isinstance(tool, llm.FunctionTool):
Expand Down
20 changes: 20 additions & 0 deletions tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,26 @@ def test_strict_schema_no_parameters_has_no_required(self):
assert "required" not in params


class TestProviderSchemaFormatting:
def test_openai_responses_raw_schema_does_not_mutate_tool(self):
tool_ctx = ToolContext([raw_tool_1])
original_schema = dict(raw_tool_1.info.raw_schema)

schema = tool_ctx.parse_function_tools("openai.responses")[0]

assert schema["type"] == "function"
assert raw_tool_1.info.raw_schema == original_schema
assert "type" not in raw_tool_1.info.raw_schema

def test_openai_responses_raw_schema_isolated_from_caller_mutation(self):
tool_ctx = ToolContext([raw_tool_1])
schema = tool_ctx.parse_function_tools("openai.responses")[0]

schema["parameters"]["properties"]["extra"] = {"type": "string"}

assert "extra" not in raw_tool_1.info.raw_schema["parameters"]["properties"]


class _NullableEnumModel(BaseModel):
status: Literal["active", "inactive"] | None = Field(None)

Expand Down
Loading