multiple: structured output tracing standard metadata (#29421)

Co-authored-by: Chester Curme <chester.curme@gmail.com>
This commit is contained in:
Erick Friis
2025-01-29 14:00:26 -08:00
committed by GitHub
parent 284c935b08
commit 8f95da4eb1
9 changed files with 288 additions and 28 deletions

View File

@@ -1111,9 +1111,13 @@ class ChatAnthropic(BaseChatModel):
Added support for TypedDict class as `schema`.
""" # noqa: E501
tool_name = convert_to_anthropic_tool(schema)["name"]
llm = self.bind_tools([schema], tool_choice=tool_name)
formatted_tool = convert_to_anthropic_tool(schema)
tool_name = formatted_tool["name"]
llm = self.bind_tools(
[schema],
tool_choice=tool_name,
structured_output_format={"kwargs": {}, "schema": formatted_tool},
)
if isinstance(schema, type) and is_basemodel_subclass(schema):
output_parser: OutputParserLike = PydanticToolsParser(
tools=[schema], first_tool_only=True

View File

@@ -965,8 +965,16 @@ class ChatFireworks(BaseChatModel):
"schema must be specified when method is 'function_calling'. "
"Received None."
)
tool_name = convert_to_openai_tool(schema)["function"]["name"]
llm = self.bind_tools([schema], tool_choice=tool_name)
formatted_tool = convert_to_openai_tool(schema)
tool_name = formatted_tool["function"]["name"]
llm = self.bind_tools(
[schema],
tool_choice=tool_name,
structured_output_format={
"kwargs": {"method": "function_calling"},
"schema": formatted_tool,
},
)
if is_pydantic_schema:
output_parser: OutputParserLike = PydanticToolsParser(
tools=[schema], # type: ignore[list-item]
@@ -977,7 +985,13 @@ class ChatFireworks(BaseChatModel):
key_name=tool_name, first_tool_only=True
)
elif method == "json_mode":
llm = self.bind(response_format={"type": "json_object"})
llm = self.bind(
response_format={"type": "json_object"},
structured_output_format={
"kwargs": {"method": "json_mode"},
"schema": schema,
},
)
output_parser = (
PydanticOutputParser(pydantic_object=schema) # type: ignore[type-var, arg-type]
if is_pydantic_schema

View File

@@ -996,8 +996,16 @@ class ChatGroq(BaseChatModel):
"schema must be specified when method is 'function_calling'. "
"Received None."
)
tool_name = convert_to_openai_tool(schema)["function"]["name"]
llm = self.bind_tools([schema], tool_choice=tool_name)
formatted_tool = convert_to_openai_tool(schema)
tool_name = formatted_tool["function"]["name"]
llm = self.bind_tools(
[schema],
tool_choice=tool_name,
structured_output_format={
"kwargs": {"method": "function_calling"},
"schema": formatted_tool,
},
)
if is_pydantic_schema:
output_parser: OutputParserLike = PydanticToolsParser(
tools=[schema], # type: ignore[list-item]
@@ -1008,7 +1016,13 @@ class ChatGroq(BaseChatModel):
key_name=tool_name, first_tool_only=True
)
elif method == "json_mode":
llm = self.bind(response_format={"type": "json_object"})
llm = self.bind(
response_format={"type": "json_object"},
structured_output_format={
"kwargs": {"method": "json_mode"},
"schema": schema,
},
)
output_parser = (
PydanticOutputParser(pydantic_object=schema) # type: ignore[type-var, arg-type]
if is_pydantic_schema

View File

@@ -931,7 +931,14 @@ class ChatMistralAI(BaseChatModel):
)
# TODO: Update to pass in tool name as tool_choice if/when Mistral supports
# specifying a tool.
llm = self.bind_tools([schema], tool_choice="any")
llm = self.bind_tools(
[schema],
tool_choice="any",
structured_output_format={
"kwargs": {"method": "function_calling"},
"schema": schema,
},
)
if is_pydantic_schema:
output_parser: OutputParserLike = PydanticToolsParser(
tools=[schema], # type: ignore[list-item]
@@ -943,7 +950,16 @@ class ChatMistralAI(BaseChatModel):
key_name=key_name, first_tool_only=True
)
elif method == "json_mode":
llm = self.bind(response_format={"type": "json_object"})
llm = self.bind(
response_format={"type": "json_object"},
structured_output_format={
"kwargs": {
# this is correct - name difference with mistral api
"method": "json_mode"
},
"schema": schema,
},
)
output_parser = (
PydanticOutputParser(pydantic_object=schema) # type: ignore[type-var, arg-type]
if is_pydantic_schema
@@ -956,7 +972,13 @@ class ChatMistralAI(BaseChatModel):
"Received None."
)
response_format = _convert_to_openai_response_format(schema, strict=True)
llm = self.bind(response_format=response_format)
llm = self.bind(
response_format=response_format,
structured_output_format={
"kwargs": {"method": "json_schema"},
"schema": schema,
},
)
output_parser = (
PydanticOutputParser(pydantic_object=schema) # type: ignore[arg-type]

View File

@@ -1085,8 +1085,16 @@ class ChatOllama(BaseChatModel):
"schema must be specified when method is not 'json_mode'. "
"Received None."
)
tool_name = convert_to_openai_tool(schema)["function"]["name"]
llm = self.bind_tools([schema], tool_choice=tool_name)
formatted_tool = convert_to_openai_tool(schema)
tool_name = formatted_tool["function"]["name"]
llm = self.bind_tools(
[schema],
tool_choice=tool_name,
structured_output_format={
"kwargs": {"method": method},
"schema": formatted_tool,
},
)
if is_pydantic_schema:
output_parser: Runnable = PydanticToolsParser(
tools=[schema], # type: ignore[list-item]
@@ -1097,7 +1105,13 @@ class ChatOllama(BaseChatModel):
key_name=tool_name, first_tool_only=True
)
elif method == "json_mode":
llm = self.bind(format="json")
llm = self.bind(
format="json",
structured_output_format={
"kwargs": {"method": method},
"schema": schema,
},
)
output_parser = (
PydanticOutputParser(pydantic_object=schema) # type: ignore[arg-type]
if is_pydantic_schema
@@ -1111,7 +1125,13 @@ class ChatOllama(BaseChatModel):
)
if is_pydantic_schema:
schema = cast(TypeBaseModel, schema)
llm = self.bind(format=schema.model_json_schema())
llm = self.bind(
format=schema.model_json_schema(),
structured_output_format={
"kwargs": {"method": method},
"schema": schema,
},
)
output_parser = PydanticOutputParser(pydantic_object=schema)
else:
if is_typeddict(schema):
@@ -1126,7 +1146,13 @@ class ChatOllama(BaseChatModel):
else:
# is JSON schema
response_format = schema
llm = self.bind(format=response_format)
llm = self.bind(
format=response_format,
structured_output_format={
"kwargs": {"method": method},
"schema": response_format,
},
)
output_parser = JsonOutputParser()
else:
raise ValueError(

View File

@@ -31,8 +31,8 @@ class TestChatOllama(ChatModelIntegrationTests):
"Fails with 'AssertionError'. Ollama does not support 'tool_choice' yet."
)
)
def test_structured_output(self, model: BaseChatModel) -> None:
super().test_structured_output(model)
def test_structured_output(self, model: BaseChatModel, schema_type: str) -> None:
super().test_structured_output(model, schema_type)
@pytest.mark.xfail(
reason=(

View File

@@ -1390,7 +1390,13 @@ class BaseChatOpenAI(BaseChatModel):
)
tool_name = convert_to_openai_tool(schema)["function"]["name"]
bind_kwargs = self._filter_disabled_params(
tool_choice=tool_name, parallel_tool_calls=False, strict=strict
tool_choice=tool_name,
parallel_tool_calls=False,
strict=strict,
structured_output_format={
"kwargs": {"method": method},
"schema": schema,
},
)
llm = self.bind_tools([schema], **bind_kwargs)
@@ -1404,7 +1410,13 @@ class BaseChatOpenAI(BaseChatModel):
key_name=tool_name, first_tool_only=True
)
elif method == "json_mode":
llm = self.bind(response_format={"type": "json_object"})
llm = self.bind(
response_format={"type": "json_object"},
structured_output_format={
"kwargs": {"method": method},
"schema": schema,
},
)
output_parser = (
PydanticOutputParser(pydantic_object=schema) # type: ignore[arg-type]
if is_pydantic_schema
@@ -1417,7 +1429,13 @@ class BaseChatOpenAI(BaseChatModel):
"Received None."
)
response_format = _convert_to_openai_response_format(schema, strict=strict)
llm = self.bind(response_format=response_format)
llm = self.bind(
response_format=response_format,
structured_output_format={
"kwargs": {"method": method},
"schema": convert_to_openai_tool(schema),
},
)
if is_pydantic_schema:
output_parser = _oai_structured_outputs_parser.with_types(
output_type=cast(type, schema)