@@ -1477,6 +1477,7 @@ def with_structured_output(
1477
1477
] = "function_calling" ,
1478
1478
include_raw : bool = False ,
1479
1479
strict : Optional [bool ] = None ,
1480
+ tools : Optional [list ] = None ,
1480
1481
** kwargs : Any ,
1481
1482
) -> Runnable [LanguageModelInput , _DictOrPydantic ]:
1482
1483
"""Model wrapper that returns outputs formatted to match the given schema.
@@ -1537,6 +1538,51 @@ def with_structured_output(
1537
1538
- None:
1538
1539
``strict`` argument will not be passed to the model.
1539
1540
1541
+ tools:
1542
+ A list of tool-like objects to bind to the chat model. Requires that:
1543
+
1544
+ - ``method`` is ``"json_schema"`` (default).
1545
+ - ``strict=True``
1546
+ - ``include_raw=True``
1547
+
1548
+ If a model elects to call a
1549
+ tool, the resulting ``AIMessage`` in ``"raw"`` will include tool calls.
1550
+
1551
+ .. dropdown:: Example
1552
+
1553
+ .. code-block:: python
1554
+
1555
+ from langchain.chat_models import init_chat_model
1556
+ from pydantic import BaseModel
1557
+
1558
+
1559
+ class ResponseSchema(BaseModel):
1560
+ response: str
1561
+
1562
+
1563
+ def get_weather(location: str) -> str:
1564
+ \" \" \" Get weather at a location.\" \" \"
1565
+ pass
1566
+
1567
+ llm = init_chat_model("openai:gpt-4o-mini")
1568
+
1569
+ structured_llm = llm.with_structured_output(
1570
+ ResponseSchema,
1571
+ tools=[get_weather],
1572
+ strict=True,
1573
+ include_raw=True,
1574
+ )
1575
+
1576
+ structured_llm.invoke("What's the weather in Boston?")
1577
+
1578
+ .. code-block:: python
1579
+
1580
+ {
1581
+ "raw": AIMessage(content="", tool_calls=[...], ...),
1582
+ "parsing_error": None,
1583
+ "parsed": None,
1584
+ }
1585
+
1540
1586
kwargs: Additional keyword args aren't supported.
1541
1587
1542
1588
Returns:
@@ -1558,6 +1604,9 @@ def with_structured_output(
1558
1604
1559
1605
Support for ``strict`` argument added.
1560
1606
Support for ``method`` = "json_schema" added.
1607
+
1608
+ .. versionchanged:: 0.3.12
1609
+ Support for ``tools`` added.
1561
1610
""" # noqa: E501
1562
1611
if kwargs :
1563
1612
raise ValueError (f"Received unsupported arguments { kwargs } " )
@@ -1642,13 +1691,18 @@ def with_structured_output(
1642
1691
"Received None."
1643
1692
)
1644
1693
response_format = _convert_to_openai_response_format (schema , strict = strict )
1645
- llm = self . bind (
1694
+ bind_kwargs = dict (
1646
1695
response_format = response_format ,
1647
1696
ls_structured_output_format = {
1648
1697
"kwargs" : {"method" : method , "strict" : strict },
1649
1698
"schema" : convert_to_openai_tool (schema ),
1650
1699
},
1651
1700
)
1701
+ if tools :
1702
+ bind_kwargs ["tools" ] = [
1703
+ convert_to_openai_tool (t , strict = strict ) for t in tools
1704
+ ]
1705
+ llm = self .bind (** bind_kwargs )
1652
1706
if is_pydantic_schema :
1653
1707
output_parser = RunnableLambda (
1654
1708
partial (_oai_structured_outputs_parser , schema = cast (type , schema ))
@@ -2776,14 +2830,16 @@ def _convert_to_openai_response_format(
2776
2830
2777
2831
def _oai_structured_outputs_parser (
2778
2832
ai_msg : AIMessage , schema : Type [_BM ]
2779
- ) -> PydanticBaseModel :
2833
+ ) -> Optional [ PydanticBaseModel ] :
2780
2834
if parsed := ai_msg .additional_kwargs .get ("parsed" ):
2781
2835
if isinstance (parsed , dict ):
2782
2836
return schema (** parsed )
2783
2837
else :
2784
2838
return parsed
2785
2839
elif ai_msg .additional_kwargs .get ("refusal" ):
2786
2840
raise OpenAIRefusalError (ai_msg .additional_kwargs ["refusal" ])
2841
+ elif ai_msg .tool_calls :
2842
+ return None
2787
2843
else :
2788
2844
raise ValueError (
2789
2845
"Structured Output response does not have a 'parsed' field nor a 'refusal' "
0 commit comments