Skip to content

Commit 3b36fd9

Browse files
authored
Handle LiteLLM's json_tool_call addition for structured outputs (#1602)
1 parent 711a2e7 commit 3b36fd9

File tree

2 files changed

+51
-7
lines changed

2 files changed

+51
-7
lines changed

examples/model_providers/litellm_auto.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,28 @@
22

33
import asyncio
44

5-
from agents import Agent, Runner, function_tool, set_tracing_disabled
5+
from pydantic import BaseModel
6+
7+
from agents import Agent, ModelSettings, Runner, function_tool, set_tracing_disabled
68

79
"""This example uses the built-in support for LiteLLM. To use this, ensure you have the
810
ANTHROPIC_API_KEY environment variable set.
911
"""
1012

1113
set_tracing_disabled(disabled=True)
1214

15+
# import logging
16+
# logging.basicConfig(level=logging.DEBUG)
1317

1418
@function_tool
1519
def get_weather(city: str):
1620
print(f"[debug] getting weather for {city}")
1721
return f"The weather in {city} is sunny."
1822

23+
class Result(BaseModel):
24+
output_text: str
25+
tool_results: list[str]
26+
1927

2028
async def main():
2129
agent = Agent(
@@ -24,6 +32,8 @@ async def main():
2432
# We prefix with litellm/ to tell the Runner to use the LitellmModel
2533
model="litellm/anthropic/claude-3-5-sonnet-20240620",
2634
tools=[get_weather],
35+
model_settings=ModelSettings(tool_choice="required"),
36+
output_type=Result,
2737
)
2838

2939
result = await Runner.run(agent, "What's the weather in Tokyo?")

src/agents/_run_impl.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,29 @@ def process_model_response(
509509
# Regular function tool call
510510
else:
511511
if output.name not in function_map:
512-
_error_tracing.attach_error_to_current_span(
513-
SpanError(
514-
message="Tool not found",
515-
data={"tool_name": output.name},
512+
if output_schema is not None and output.name == "json_tool_call":
513+
# LiteLLM could generate non-existent tool calls for structured outputs
514+
items.append(ToolCallItem(raw_item=output, agent=agent))
515+
functions.append(
516+
ToolRunFunction(
517+
tool_call=output,
518+
# this tool does not exist in function_map, so generate ad-hoc one,
519+
# which just parses the input if it's a string, and returns the
520+
# value otherwise
521+
function_tool=_build_litellm_json_tool_call(output),
522+
)
516523
)
517-
)
518-
raise ModelBehaviorError(f"Tool {output.name} not found in agent {agent.name}")
524+
continue
525+
else:
526+
_error_tracing.attach_error_to_current_span(
527+
SpanError(
528+
message="Tool not found",
529+
data={"tool_name": output.name},
530+
)
531+
)
532+
error = f"Tool {output.name} not found in agent {agent.name}"
533+
raise ModelBehaviorError(error)
534+
519535
items.append(ToolCallItem(raw_item=output, agent=agent))
520536
functions.append(
521537
ToolRunFunction(
@@ -1193,3 +1209,21 @@ async def execute(
11931209
# "id": "out" + call.tool_call.id, # TODO remove this, it should be optional
11941210
},
11951211
)
1212+
1213+
1214+
def _build_litellm_json_tool_call(output: ResponseFunctionToolCall) -> FunctionTool:
1215+
async def on_invoke_tool(_ctx: ToolContext[Any], value: Any) -> Any:
1216+
if isinstance(value, str):
1217+
import json
1218+
1219+
return json.loads(value)
1220+
return value
1221+
1222+
return FunctionTool(
1223+
name=output.name,
1224+
description=output.name,
1225+
params_json_schema={},
1226+
on_invoke_tool=on_invoke_tool,
1227+
strict_json_schema=True,
1228+
is_enabled=True,
1229+
)

0 commit comments

Comments
 (0)