revert changes to v0 BaseChatModel

This commit is contained in:
Chester Curme 2025-07-28 18:15:23 -04:00
parent 61e329637b
commit abaf0c5828

View File

@ -108,12 +108,7 @@ from langchain_openai.chat_models._client_utils import (
) )
from langchain_openai.chat_models._compat import ( from langchain_openai.chat_models._compat import (
_convert_from_v03_ai_message, _convert_from_v03_ai_message,
_convert_from_v1_to_chat_completions,
_convert_from_v1_to_responses,
_convert_to_v03_ai_message, _convert_to_v03_ai_message,
_convert_to_v1_from_chat_completions,
_convert_to_v1_from_chat_completions_chunk,
_convert_to_v1_from_responses,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -466,7 +461,7 @@ class BaseChatOpenAI(BaseChatModel):
"""Base URL path for API requests, leave blank if not using a proxy or service """Base URL path for API requests, leave blank if not using a proxy or service
emulator.""" emulator."""
openai_organization: Optional[str] = Field(default=None, alias="organization") openai_organization: Optional[str] = Field(default=None, alias="organization")
"""Automatically inferred from env var `OPENAI_ORG_ID` if not provided.""" """Automatically inferred from env var ``OPENAI_ORG_ID`` if not provided."""
# to support explicit proxy for OpenAI # to support explicit proxy for OpenAI
openai_proxy: Optional[str] = Field( openai_proxy: Optional[str] = Field(
default_factory=from_env("OPENAI_PROXY", default=None) default_factory=from_env("OPENAI_PROXY", default=None)
@ -474,7 +469,7 @@ class BaseChatOpenAI(BaseChatModel):
request_timeout: Union[float, tuple[float, float], Any, None] = Field( request_timeout: Union[float, tuple[float, float], Any, None] = Field(
default=None, alias="timeout" default=None, alias="timeout"
) )
"""Timeout for requests to OpenAI completion API. Can be float, httpx.Timeout or """Timeout for requests to OpenAI completion API. Can be float, ``httpx.Timeout`` or
None.""" None."""
stream_usage: bool = False stream_usage: bool = False
"""Whether to include usage metadata in streaming output. If True, an additional """Whether to include usage metadata in streaming output. If True, an additional
@ -531,6 +526,7 @@ class BaseChatOpenAI(BaseChatModel):
} }
.. versionadded:: 0.3.24 .. versionadded:: 0.3.24
""" """
tiktoken_model_name: Optional[str] = None tiktoken_model_name: Optional[str] = None
"""The model name to pass to tiktoken when using this class. """The model name to pass to tiktoken when using this class.
@ -552,15 +548,32 @@ class BaseChatOpenAI(BaseChatModel):
invocations. invocations.
""" """
http_async_client: Union[Any, None] = Field(default=None, exclude=True) http_async_client: Union[Any, None] = Field(default=None, exclude=True)
"""Optional httpx.AsyncClient. Only used for async invocations. Must specify """Optional ``httpx.AsyncClient``. Only used for async invocations. Must specify
``http_client`` as well if you'd like a custom client for sync invocations.""" ``http_client`` as well if you'd like a custom client for sync invocations."""
stop: Optional[Union[list[str], str]] = Field(default=None, alias="stop_sequences") stop: Optional[Union[list[str], str]] = Field(default=None, alias="stop_sequences")
"""Default stop sequences.""" """Default stop sequences."""
extra_body: Optional[Mapping[str, Any]] = None extra_body: Optional[Mapping[str, Any]] = None
"""Optional additional JSON properties to include in the request parameters when """Optional additional JSON properties to include in the request parameters when
making requests to OpenAI compatible APIs, such as vLLM.""" making requests to OpenAI compatible APIs, such as vLLM, LM Studio, or other
providers.
This is the recommended way to pass custom parameters that are specific to your
OpenAI-compatible API provider but not part of the standard OpenAI API.
Examples:
- LM Studio TTL parameter: ``extra_body={"ttl": 300}``
- vLLM custom parameters: ``extra_body={"use_beam_search": True}``
- Any other provider-specific parameters
.. note::
Do NOT use ``model_kwargs`` for custom parameters that are not part of the
standard OpenAI API, as this will cause errors when making API calls. Use
``extra_body`` instead.
"""
include_response_headers: bool = False include_response_headers: bool = False
"""Whether to include response headers in the output message response_metadata.""" """Whether to include response headers in the output message ``response_metadata``.""" # noqa: E501
disabled_params: Optional[dict[str, Any]] = Field(default=None) disabled_params: Optional[dict[str, Any]] = Field(default=None)
"""Parameters of the OpenAI client or chat.completions endpoint that should be """Parameters of the OpenAI client or chat.completions endpoint that should be
disabled for the given model. disabled for the given model.
@ -569,7 +582,7 @@ class BaseChatOpenAI(BaseChatModel):
parameter and the value is either None, meaning that parameter should never be parameter and the value is either None, meaning that parameter should never be
used, or it's a list of disabled values for the parameter. used, or it's a list of disabled values for the parameter.
For example, older models may not support the 'parallel_tool_calls' parameter at For example, older models may not support the ``'parallel_tool_calls'`` parameter at
all, in which case ``disabled_params={"parallel_tool_calls": None}`` can be passed all, in which case ``disabled_params={"parallel_tool_calls": None}`` can be passed
in. in.
@ -584,11 +597,11 @@ class BaseChatOpenAI(BaseChatModel):
Supported values: Supported values:
- ``"file_search_call.results"`` - ``'file_search_call.results'``
- ``"message.input_image.image_url"`` - ``'message.input_image.image_url'``
- ``"computer_call_output.output.image_url"`` - ``'computer_call_output.output.image_url'``
- ``"reasoning.encrypted_content"`` - ``'reasoning.encrypted_content'``
- ``"code_interpreter_call.outputs"`` - ``'code_interpreter_call.outputs'``
.. versionadded:: 0.3.24 .. versionadded:: 0.3.24
""" """
@ -645,6 +658,7 @@ class BaseChatOpenAI(BaseChatModel):
llm.invoke([HumanMessage("How are you?")], previous_response_id="resp_123") llm.invoke([HumanMessage("How are you?")], previous_response_id="resp_123")
.. versionadded:: 0.3.26 .. versionadded:: 0.3.26
""" """
use_responses_api: Optional[bool] = None use_responses_api: Optional[bool] = None
@ -655,7 +669,7 @@ class BaseChatOpenAI(BaseChatModel):
.. versionadded:: 0.3.9 .. versionadded:: 0.3.9
""" """
output_version: str = "v0" output_version: Literal["v0", "responses/v1"] = "v0"
"""Version of AIMessage output format to use. """Version of AIMessage output format to use.
This field is used to roll-out new output formats for chat model AIMessages This field is used to roll-out new output formats for chat model AIMessages
@ -663,12 +677,12 @@ class BaseChatOpenAI(BaseChatModel):
Supported values: Supported values:
- ``"v0"``: AIMessage format as of langchain-openai 0.3.x. - ``'v0'``: AIMessage format as of langchain-openai 0.3.x.
- ``"responses/v1"``: Formats Responses API output - ``'responses/v1'``: Formats Responses API output
items into AIMessage content blocks. items into AIMessage content blocks.
- ``"v1"``: v1 of LangChain cross-provider standard.
``output_version="v1"`` is recommended. Currently only impacts the Responses API. ``output_version='responses/v1'`` is
recommended.
.. versionadded:: 0.3.25 .. versionadded:: 0.3.25
@ -855,10 +869,6 @@ class BaseChatOpenAI(BaseChatModel):
message=default_chunk_class(content="", usage_metadata=usage_metadata), message=default_chunk_class(content="", usage_metadata=usage_metadata),
generation_info=base_generation_info, generation_info=base_generation_info,
) )
if self.output_version == "v1":
generation_chunk.message = _convert_to_v1_from_chat_completions_chunk(
cast(AIMessageChunk, generation_chunk.message)
)
return generation_chunk return generation_chunk
choice = choices[0] choice = choices[0]
@ -886,20 +896,6 @@ class BaseChatOpenAI(BaseChatModel):
if usage_metadata and isinstance(message_chunk, AIMessageChunk): if usage_metadata and isinstance(message_chunk, AIMessageChunk):
message_chunk.usage_metadata = usage_metadata message_chunk.usage_metadata = usage_metadata
if self.output_version == "v1":
message_chunk = cast(AIMessageChunk, message_chunk)
# Convert to v1 format
if isinstance(message_chunk.content, str):
message_chunk = _convert_to_v1_from_chat_completions_chunk(
message_chunk
)
if message_chunk.content:
message_chunk.content[0]["index"] = 0 # type: ignore[index]
else:
message_chunk = _convert_to_v1_from_chat_completions_chunk(
message_chunk
)
generation_chunk = ChatGenerationChunk( generation_chunk = ChatGenerationChunk(
message=message_chunk, generation_info=generation_info or None message=message_chunk, generation_info=generation_info or None
) )
@ -1192,12 +1188,7 @@ class BaseChatOpenAI(BaseChatModel):
else: else:
payload = _construct_responses_api_payload(messages, payload) payload = _construct_responses_api_payload(messages, payload)
else: else:
payload["messages"] = [ payload["messages"] = [_convert_message_to_dict(m) for m in messages]
_convert_message_to_dict(_convert_from_v1_to_chat_completions(m))
if isinstance(m, AIMessage)
else _convert_message_to_dict(m)
for m in messages
]
return payload return payload
def _create_chat_result( def _create_chat_result(
@ -1263,11 +1254,6 @@ class BaseChatOpenAI(BaseChatModel):
if hasattr(message, "refusal"): if hasattr(message, "refusal"):
generations[0].message.additional_kwargs["refusal"] = message.refusal generations[0].message.additional_kwargs["refusal"] = message.refusal
if self.output_version == "v1":
_ = llm_output.pop("token_usage", None)
generations[0].message = _convert_to_v1_from_chat_completions(
cast(AIMessage, generations[0].message)
)
return ChatResult(generations=generations, llm_output=llm_output) return ChatResult(generations=generations, llm_output=llm_output)
async def _astream( async def _astream(
@ -1593,8 +1579,9 @@ class BaseChatOpenAI(BaseChatModel):
Assumes model is compatible with OpenAI function-calling API. Assumes model is compatible with OpenAI function-calling API.
NOTE: Using bind_tools is recommended instead, as the `functions` and .. note::
`function_call` request parameters are officially marked as deprecated by Using ``bind_tools()`` is recommended instead, as the ``functions`` and
``function_call`` request parameters are officially marked as deprecated by
OpenAI. OpenAI.
Args: Args:
@ -1604,7 +1591,7 @@ class BaseChatOpenAI(BaseChatModel):
their schema dictionary representation. their schema dictionary representation.
function_call: Which function to require the model to call. function_call: Which function to require the model to call.
Must be the name of the single provided function or Must be the name of the single provided function or
"auto" to automatically determine which function to call ``'auto'`` to automatically determine which function to call
(if any). (if any).
**kwargs: Any additional parameters to pass to the **kwargs: Any additional parameters to pass to the
:class:`~langchain.runnable.Runnable` constructor. :class:`~langchain.runnable.Runnable` constructor.
@ -1655,16 +1642,15 @@ class BaseChatOpenAI(BaseChatModel):
:meth:`langchain_core.utils.function_calling.convert_to_openai_tool`. :meth:`langchain_core.utils.function_calling.convert_to_openai_tool`.
tool_choice: Which tool to require the model to call. Options are: tool_choice: Which tool to require the model to call. Options are:
- str of the form ``"<<tool_name>>"``: calls <<tool_name>> tool. - str of the form ``'<<tool_name>>'``: calls <<tool_name>> tool.
- ``"auto"``: automatically selects a tool (including no tool). - ``'auto'``: automatically selects a tool (including no tool).
- ``"none"``: does not call a tool. - ``'none'``: does not call a tool.
- ``"any"`` or ``"required"`` or ``True``: force at least one tool to be called. - ``'any'`` or ``'required'`` or ``True``: force at least one tool to be called.
- dict of the form ``{"type": "function", "function": {"name": <<tool_name>>}}``: calls <<tool_name>> tool. - dict of the form ``{"type": "function", "function": {"name": <<tool_name>>}}``: calls <<tool_name>> tool.
- ``False`` or ``None``: no effect, default OpenAI behavior. - ``False`` or ``None``: no effect, default OpenAI behavior.
strict: If True, model output is guaranteed to exactly match the JSON Schema strict: If True, model output is guaranteed to exactly match the JSON Schema
provided in the tool definition. If True, the input schema will be provided in the tool definition. The input schema will also be validated according to the
validated according to `supported schemas <https://platform.openai.com/docs/guides/structured-outputs/supported-schemas?api-mode=responses#supported-schemas>`__.
https://platform.openai.com/docs/guides/structured-outputs/supported-schemas.
If False, input schema will not be validated and model output will not If False, input schema will not be validated and model output will not
be validated. be validated.
If None, ``strict`` argument will not be passed to the model. If None, ``strict`` argument will not be passed to the model.
@ -1735,8 +1721,7 @@ class BaseChatOpenAI(BaseChatModel):
"""Model wrapper that returns outputs formatted to match the given schema. """Model wrapper that returns outputs formatted to match the given schema.
Args: Args:
schema: schema: The output schema. Can be passed in as:
The output schema. Can be passed in as:
- an OpenAI function/tool schema, - an OpenAI function/tool schema,
- a JSON Schema, - a JSON Schema,
@ -1752,24 +1737,20 @@ class BaseChatOpenAI(BaseChatModel):
method: The method for steering model generation, one of: method: The method for steering model generation, one of:
- "function_calling": - ``'function_calling'``:
Uses OpenAI's tool-calling (formerly called function calling) Uses OpenAI's tool-calling (formerly called function calling)
API: https://platform.openai.com/docs/guides/function-calling `API <https://platform.openai.com/docs/guides/function-calling>`__
- "json_schema": - ``'json_schema'``:
Uses OpenAI's Structured Output API: https://platform.openai.com/docs/guides/structured-outputs Uses OpenAI's Structured Output `API <https://platform.openai.com/docs/guides/structured-outputs>`__
Supported for "gpt-4o-mini", "gpt-4o-2024-08-06", "o1", and later Supported for ``'gpt-4o-mini'``, ``'gpt-4o-2024-08-06'``, ``'o1'``, and later
models. models.
- "json_mode": - ``'json_mode'``:
Uses OpenAI's JSON mode. Note that if using JSON mode then you Uses OpenAI's `JSON mode <https://platform.openai.com/docs/guides/structured-outputs/json-mode>`__.
must include instructions for formatting the output into the Note that if using JSON mode then you must include instructions for
desired schema into the model call: formatting the output into the desired schema into the model call
https://platform.openai.com/docs/guides/structured-outputs/json-mode
Learn more about the differences between the methods and which models Learn more about the differences between the methods and which models
support which methods here: support which methods `here <https://platform.openai.com/docs/guides/structured-outputs/function-calling-vs-response-format>`__.
- https://platform.openai.com/docs/guides/structured-outputs/structured-outputs-vs-json-mode
- https://platform.openai.com/docs/guides/structured-outputs/function-calling-vs-response-format
include_raw: include_raw:
If False then only the parsed structured output is returned. If If False then only the parsed structured output is returned. If
@ -1777,13 +1758,12 @@ class BaseChatOpenAI(BaseChatModel):
then both the raw model response (a BaseMessage) and the parsed model then both the raw model response (a BaseMessage) and the parsed model
response will be returned. If an error occurs during output parsing it response will be returned. If an error occurs during output parsing it
will be caught and returned as well. The final output is always a dict will be caught and returned as well. The final output is always a dict
with keys "raw", "parsed", and "parsing_error". with keys ``'raw'``, ``'parsed'``, and ``'parsing_error'``.
strict: strict:
- True: - True:
Model output is guaranteed to exactly match the schema. Model output is guaranteed to exactly match the schema.
The input schema will also be validated according to The input schema will also be validated according to the `supported schemas <https://platform.openai.com/docs/guides/structured-outputs/supported-schemas?api-mode=responses#supported-schemas>`__.
https://platform.openai.com/docs/guides/structured-outputs/supported-schemas
- False: - False:
Input schema will not be validated and model output will not be Input schema will not be validated and model output will not be
validated. validated.
@ -1793,12 +1773,12 @@ class BaseChatOpenAI(BaseChatModel):
tools: tools:
A list of tool-like objects to bind to the chat model. Requires that: A list of tool-like objects to bind to the chat model. Requires that:
- ``method`` is ``"json_schema"`` (default). - ``method`` is ``'json_schema'`` (default).
- ``strict=True`` - ``strict=True``
- ``include_raw=True`` - ``include_raw=True``
If a model elects to call a If a model elects to call a
tool, the resulting ``AIMessage`` in ``"raw"`` will include tool calls. tool, the resulting ``AIMessage`` in ``'raw'`` will include tool calls.
.. dropdown:: Example .. dropdown:: Example
@ -1840,13 +1820,14 @@ class BaseChatOpenAI(BaseChatModel):
Returns: Returns:
A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`. A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`.
| If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs an instance of ``schema`` (i.e., a Pydantic object). Otherwise, if ``include_raw`` is False then Runnable outputs a dict. If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs
an instance of ``schema`` (i.e., a Pydantic object). Otherwise, if ``include_raw`` is False then Runnable outputs a dict.
| If ``include_raw`` is True, then Runnable outputs a dict with keys: If ``include_raw`` is True, then Runnable outputs a dict with keys:
- "raw": BaseMessage - ``'raw'``: BaseMessage
- "parsed": None if there was a parsing error, otherwise the type depends on the ``schema`` as described above. - ``'parsed'``: None if there was a parsing error, otherwise the type depends on the ``schema`` as described above.
- "parsing_error": Optional[BaseException] - ``'parsing_error'``: Optional[BaseException]
.. versionchanged:: 0.1.20 .. versionchanged:: 0.1.20
@ -1855,13 +1836,14 @@ class BaseChatOpenAI(BaseChatModel):
.. versionchanged:: 0.1.21 .. versionchanged:: 0.1.21
Support for ``strict`` argument added. Support for ``strict`` argument added.
Support for ``method`` = "json_schema" added. Support for ``method="json_schema"`` added.
.. versionchanged:: 0.3.12 .. versionchanged:: 0.3.12
Support for ``tools`` added. Support for ``tools`` added.
.. versionchanged:: 0.3.21 .. versionchanged:: 0.3.21
Pass ``kwargs`` through to the model. Pass ``kwargs`` through to the model.
""" # noqa: E501 """ # noqa: E501
if strict is not None and method == "json_mode": if strict is not None and method == "json_mode":
raise ValueError( raise ValueError(
@ -2097,24 +2079,25 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
# other params... # other params...
) )
**NOTE**: Any param which is not explicitly supported will be passed directly to the .. note::
``openai.OpenAI.chat.completions.create(...)`` API every time to the model is Any param which is not explicitly supported will be passed directly to the
invoked. For example: ``openai.OpenAI.chat.completions.create(...)`` API every time to the model is
invoked. For example:
.. code-block:: python .. code-block:: python
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
import openai import openai
ChatOpenAI(..., frequency_penalty=0.2).invoke(...) ChatOpenAI(..., frequency_penalty=0.2).invoke(...)
# results in underlying API call of: # results in underlying API call of:
openai.OpenAI(..).chat.completions.create(..., frequency_penalty=0.2) openai.OpenAI(..).chat.completions.create(..., frequency_penalty=0.2)
# which is also equivalent to: # which is also equivalent to:
ChatOpenAI(...).invoke(..., frequency_penalty=0.2) ChatOpenAI(...).invoke(..., frequency_penalty=0.2)
.. dropdown:: Invoke .. dropdown:: Invoke
@ -2281,26 +2264,27 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
}, },
] ]
Note that ``openai >= 1.32`` supports a ``parallel_tool_calls`` parameter .. note::
that defaults to ``True``. This parameter can be set to ``False`` to ``openai >= 1.32`` supports a ``parallel_tool_calls`` parameter
disable parallel tool calls: that defaults to ``True``. This parameter can be set to ``False`` to
disable parallel tool calls:
.. code-block:: python .. code-block:: python
ai_msg = llm_with_tools.invoke( ai_msg = llm_with_tools.invoke(
"What is the weather in LA and NY?", parallel_tool_calls=False "What is the weather in LA and NY?", parallel_tool_calls=False
) )
ai_msg.tool_calls ai_msg.tool_calls
.. code-block:: python .. code-block:: python
[ [
{ {
"name": "GetWeather", "name": "GetWeather",
"args": {"location": "Los Angeles, CA"}, "args": {"location": "Los Angeles, CA"},
"id": "call_4OoY0ZR99iEvC7fevsH8Uhtz", "id": "call_4OoY0ZR99iEvC7fevsH8Uhtz",
} }
] ]
Like other runtime parameters, ``parallel_tool_calls`` can be bound to a model Like other runtime parameters, ``parallel_tool_calls`` can be bound to a model
using ``llm.bind(parallel_tool_calls=False)`` or during instantiation by using ``llm.bind(parallel_tool_calls=False)`` or during instantiation by
@ -2314,7 +2298,7 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
You can access `built-in tools <https://platform.openai.com/docs/guides/tools?api-mode=responses>`_ You can access `built-in tools <https://platform.openai.com/docs/guides/tools?api-mode=responses>`_
supported by the OpenAI Responses API. See LangChain supported by the OpenAI Responses API. See LangChain
`docs <https://python.langchain.com/docs/integrations/chat/openai/>`_ for more `docs <https://python.langchain.com/docs/integrations/chat/openai/>`__ for more
detail. detail.
.. note:: .. note::
@ -2369,7 +2353,7 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
`conversation state <https://platform.openai.com/docs/guides/conversation-state?api-mode=responses>`_. `conversation state <https://platform.openai.com/docs/guides/conversation-state?api-mode=responses>`_.
Passing in response IDs from previous messages will continue a conversational Passing in response IDs from previous messages will continue a conversational
thread. See LangChain thread. See LangChain
`docs <https://python.langchain.com/docs/integrations/chat/openai/>`_ for more `conversation docs <https://python.langchain.com/docs/integrations/chat/openai/>`__ for more
detail. detail.
.. code-block:: python .. code-block:: python
@ -2658,9 +2642,95 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
llm = ChatOpenAI(model="o4-mini", service_tier="flex") llm = ChatOpenAI(model="o4-mini", service_tier="flex")
Note that this is a beta feature that is only available for a subset of models. Note that this is a beta feature that is only available for a subset of models.
See OpenAI `docs <https://platform.openai.com/docs/guides/flex-processing>`_ See OpenAI `flex processing docs <https://platform.openai.com/docs/guides/flex-processing>`__
for more detail. for more detail.
.. dropdown:: OpenAI-compatible APIs
``ChatOpenAI`` can be used with OpenAI-compatible APIs like `LM Studio <https://lmstudio.ai/>`__,
`vLLM <https://github.com/vllm-project/vllm>`__,
`Ollama <https://ollama.com/>`__, and others.
To use custom parameters specific to these providers, use the ``extra_body`` parameter.
**LM Studio example** with TTL (auto-eviction):
.. code-block:: python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="http://localhost:1234/v1",
api_key="lm-studio", # Can be any string
model="mlx-community/QwQ-32B-4bit",
temperature=0,
extra_body={
"ttl": 300
}, # Auto-evict model after 5 minutes of inactivity
)
**vLLM example** with custom parameters:
.. code-block:: python
llm = ChatOpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY",
model="meta-llama/Llama-2-7b-chat-hf",
extra_body={"use_beam_search": True, "best_of": 4},
)
.. dropdown:: model_kwargs vs extra_body
Use the correct parameter for different types of API arguments:
**Use ``model_kwargs`` for:**
- Standard OpenAI API parameters not explicitly defined as class parameters
- Parameters that should be flattened into the top-level request payload
- Examples: ``max_completion_tokens``, ``stream_options``, ``modalities``, ``audio``
.. code-block:: python
# Standard OpenAI parameters
llm = ChatOpenAI(
model="gpt-4o",
model_kwargs={
"stream_options": {"include_usage": True},
"max_completion_tokens": 300,
"modalities": ["text", "audio"],
"audio": {"voice": "alloy", "format": "wav"},
},
)
**Use ``extra_body`` for:**
- Custom parameters specific to OpenAI-compatible providers (vLLM, LM Studio, etc.)
- Parameters that need to be nested under ``extra_body`` in the request
- Any non-standard OpenAI API parameters
.. code-block:: python
# Custom provider parameters
llm = ChatOpenAI(
base_url="http://localhost:8000/v1",
model="custom-model",
extra_body={
"use_beam_search": True, # vLLM parameter
"best_of": 4, # vLLM parameter
"ttl": 300, # LM Studio parameter
},
)
**Key Differences:**
- ``model_kwargs``: Parameters are **merged into top-level** request payload
- ``extra_body``: Parameters are **nested under ``extra_body``** key in request
.. important::
Always use ``extra_body`` for custom parameters, **not** ``model_kwargs``.
Using ``model_kwargs`` for non-OpenAI parameters will cause API errors.
""" # noqa: E501 """ # noqa: E501
max_tokens: Optional[int] = Field(default=None, alias="max_completion_tokens") max_tokens: Optional[int] = Field(default=None, alias="max_completion_tokens")
@ -2692,7 +2762,7 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
@classmethod @classmethod
def is_lc_serializable(cls) -> bool: def is_lc_serializable(cls) -> bool:
"""Return whether this model can be serialized by Langchain.""" """Return whether this model can be serialized by LangChain."""
return True return True
@property @property
@ -2754,8 +2824,7 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
"""Model wrapper that returns outputs formatted to match the given schema. """Model wrapper that returns outputs formatted to match the given schema.
Args: Args:
schema: schema: The output schema. Can be passed in as:
The output schema. Can be passed in as:
- a JSON Schema, - a JSON Schema,
- a TypedDict class, - a TypedDict class,
@ -2771,25 +2840,20 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
method: The method for steering model generation, one of: method: The method for steering model generation, one of:
- "json_schema": - ``'json_schema'``:
Uses OpenAI's Structured Output API: Uses OpenAI's `Structured Output API <https://platform.openai.com/docs/guides/structured-outputs>`__.
https://platform.openai.com/docs/guides/structured-outputs Supported for ``'gpt-4o-mini'``, ``'gpt-4o-2024-08-06'``, ``'o1'``, and later
Supported for "gpt-4o-mini", "gpt-4o-2024-08-06", "o1", and later
models. models.
- "function_calling": - ``'function_calling'``:
Uses OpenAI's tool-calling (formerly called function calling) Uses OpenAI's tool-calling (formerly called function calling)
API: https://platform.openai.com/docs/guides/function-calling `API <https://platform.openai.com/docs/guides/function-calling>`__
- "json_mode": - ``'json_mode'``:
Uses OpenAI's JSON mode. Note that if using JSON mode then you Uses OpenAI's `JSON mode <https://platform.openai.com/docs/guides/structured-outputs/json-mode>`__.
must include instructions for formatting the output into the Note that if using JSON mode then you must include instructions for
desired schema into the model call: formatting the output into the desired schema into the model call
https://platform.openai.com/docs/guides/structured-outputs/json-mode
Learn more about the differences between the methods and which models Learn more about the differences between the methods and which models
support which methods here: support which methods `here <https://platform.openai.com/docs/guides/structured-outputs/function-calling-vs-response-format>`__.
- https://platform.openai.com/docs/guides/structured-outputs/structured-outputs-vs-json-mode
- https://platform.openai.com/docs/guides/structured-outputs/function-calling-vs-response-format
include_raw: include_raw:
If False then only the parsed structured output is returned. If If False then only the parsed structured output is returned. If
@ -2797,13 +2861,12 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
then both the raw model response (a BaseMessage) and the parsed model then both the raw model response (a BaseMessage) and the parsed model
response will be returned. If an error occurs during output parsing it response will be returned. If an error occurs during output parsing it
will be caught and returned as well. The final output is always a dict will be caught and returned as well. The final output is always a dict
with keys "raw", "parsed", and "parsing_error". with keys ``'raw'``, ``'parsed'``, and ``'parsing_error'``.
strict: strict:
- True: - True:
Model output is guaranteed to exactly match the schema. Model output is guaranteed to exactly match the schema.
The input schema will also be validated according to The input schema will also be validated according to the `supported schemas <https://platform.openai.com/docs/guides/structured-outputs/supported-schemas?api-mode=responses#supported-schemas>`__.
https://platform.openai.com/docs/guides/structured-outputs/supported-schemas
- False: - False:
Input schema will not be validated and model output will not be Input schema will not be validated and model output will not be
validated. validated.
@ -2813,17 +2876,17 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
If schema is specified via TypedDict or JSON schema, ``strict`` is not If schema is specified via TypedDict or JSON schema, ``strict`` is not
enabled by default. Pass ``strict=True`` to enable it. enabled by default. Pass ``strict=True`` to enable it.
Note: ``strict`` can only be non-null if ``method`` is .. note::
``"json_schema"`` or ``"function_calling"``. ``strict`` can only be non-null if ``method`` is ``'json_schema'`` or ``'function_calling'``.
tools: tools:
A list of tool-like objects to bind to the chat model. Requires that: A list of tool-like objects to bind to the chat model. Requires that:
- ``method`` is ``"json_schema"`` (default). - ``method`` is ``'json_schema'`` (default).
- ``strict=True`` - ``strict=True``
- ``include_raw=True`` - ``include_raw=True``
If a model elects to call a If a model elects to call a
tool, the resulting ``AIMessage`` in ``"raw"`` will include tool calls. tool, the resulting ``AIMessage`` in ``'raw'`` will include tool calls.
.. dropdown:: Example .. dropdown:: Example
@ -2865,13 +2928,14 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
Returns: Returns:
A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`. A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`.
| If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs an instance of ``schema`` (i.e., a Pydantic object). Otherwise, if ``include_raw`` is False then Runnable outputs a dict. If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs
an instance of ``schema`` (i.e., a Pydantic object). Otherwise, if ``include_raw`` is False then Runnable outputs a dict.
| If ``include_raw`` is True, then Runnable outputs a dict with keys: If ``include_raw`` is True, then Runnable outputs a dict with keys:
- "raw": BaseMessage - ``'raw'``: BaseMessage
- "parsed": None if there was a parsing error, otherwise the type depends on the ``schema`` as described above. - ``'parsed'``: None if there was a parsing error, otherwise the type depends on the ``schema`` as described above.
- "parsing_error": Optional[BaseException] - ``'parsing_error'``: Optional[BaseException]
.. versionchanged:: 0.1.20 .. versionchanged:: 0.1.20
@ -2899,7 +2963,7 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
specify any Field metadata (like min/max constraints) and fields cannot specify any Field metadata (like min/max constraints) and fields cannot
have default values. have default values.
See all constraints here: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas See all constraints `here <https://platform.openai.com/docs/guides/structured-outputs/supported-schemas>`__.
.. code-block:: python .. code-block:: python
@ -3101,6 +3165,7 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
# }, # },
# 'parsing_error': None # 'parsing_error': None
# } # }
""" # noqa: E501 """ # noqa: E501
return super().with_structured_output( return super().with_structured_output(
schema, method=method, include_raw=include_raw, strict=strict, **kwargs schema, method=method, include_raw=include_raw, strict=strict, **kwargs
@ -3117,7 +3182,7 @@ def _lc_tool_call_to_openai_tool_call(tool_call: ToolCall) -> dict:
"id": tool_call["id"], "id": tool_call["id"],
"function": { "function": {
"name": tool_call["name"], "name": tool_call["name"],
"arguments": json.dumps(tool_call["args"]), "arguments": json.dumps(tool_call["args"], ensure_ascii=False),
}, },
} }
@ -3512,7 +3577,6 @@ def _construct_responses_api_input(messages: Sequence[BaseMessage]) -> list:
for lc_msg in messages: for lc_msg in messages:
if isinstance(lc_msg, AIMessage): if isinstance(lc_msg, AIMessage):
lc_msg = _convert_from_v03_ai_message(lc_msg) lc_msg = _convert_from_v03_ai_message(lc_msg)
lc_msg = _convert_from_v1_to_responses(lc_msg)
msg = _convert_message_to_dict(lc_msg) msg = _convert_message_to_dict(lc_msg)
# "name" parameter unsupported # "name" parameter unsupported
if "name" in msg: if "name" in msg:
@ -3656,7 +3720,7 @@ def _construct_lc_result_from_responses_api(
response: Response, response: Response,
schema: Optional[type[_BM]] = None, schema: Optional[type[_BM]] = None,
metadata: Optional[dict] = None, metadata: Optional[dict] = None,
output_version: str = "v0", output_version: Literal["v0", "responses/v1"] = "v0",
) -> ChatResult: ) -> ChatResult:
"""Construct ChatResponse from OpenAI Response API response.""" """Construct ChatResponse from OpenAI Response API response."""
if response.error: if response.error:
@ -3795,27 +3859,6 @@ def _construct_lc_result_from_responses_api(
) )
if output_version == "v0": if output_version == "v0":
message = _convert_to_v03_ai_message(message) message = _convert_to_v03_ai_message(message)
elif output_version == "v1":
message = _convert_to_v1_from_responses(message)
if response.tools and any(
tool.type == "image_generation" for tool in response.tools
):
# Get mime_time from tool definition and add to image generations
# if missing (primarily for tracing purposes).
image_generation_call = next(
tool for tool in response.tools if tool.type == "image_generation"
)
if image_generation_call.output_format:
mime_type = f"image/{image_generation_call.output_format}"
for content_block in message.content:
# OK to mutate output message
if (
isinstance(content_block, dict)
and content_block.get("type") == "image"
and "base64" in content_block
and "mime_type" not in block
):
block["mime_type"] = mime_type
else: else:
pass pass
return ChatResult(generations=[ChatGeneration(message=message)]) return ChatResult(generations=[ChatGeneration(message=message)])
@ -3829,7 +3872,7 @@ def _convert_responses_chunk_to_generation_chunk(
schema: Optional[type[_BM]] = None, schema: Optional[type[_BM]] = None,
metadata: Optional[dict] = None, metadata: Optional[dict] = None,
has_reasoning: bool = False, has_reasoning: bool = False,
output_version: str = "v0", output_version: Literal["v0", "responses/v1"] = "v0",
) -> tuple[int, int, int, Optional[ChatGenerationChunk]]: ) -> tuple[int, int, int, Optional[ChatGenerationChunk]]:
def _advance(output_idx: int, sub_idx: Optional[int] = None) -> None: def _advance(output_idx: int, sub_idx: Optional[int] = None) -> None:
"""Advance indexes tracked during streaming. """Advance indexes tracked during streaming.
@ -3864,6 +3907,7 @@ def _convert_responses_chunk_to_generation_chunk(
This function just identifies updates in output or sub-indexes and increments This function just identifies updates in output or sub-indexes and increments
the current index accordingly. the current index accordingly.
""" """
nonlocal current_index, current_output_index, current_sub_index nonlocal current_index, current_output_index, current_sub_index
if sub_idx is None: if sub_idx is None:
@ -3894,29 +3938,9 @@ def _convert_responses_chunk_to_generation_chunk(
annotation = chunk.annotation annotation = chunk.annotation
else: else:
annotation = chunk.annotation.model_dump(exclude_none=True, mode="json") annotation = chunk.annotation.model_dump(exclude_none=True, mode="json")
if output_version == "v1": content.append({"annotations": [annotation], "index": current_index})
content.append(
{
"type": "text",
"text": "",
"annotations": [annotation],
"index": current_index,
}
)
else:
content.append({"annotations": [annotation], "index": current_index})
elif chunk.type == "response.output_text.done": elif chunk.type == "response.output_text.done":
if output_version == "v1": content.append({"id": chunk.item_id, "index": current_index})
content.append(
{
"type": "text",
"text": "",
"id": chunk.item_id,
"index": current_index,
}
)
else:
content.append({"id": chunk.item_id, "index": current_index})
elif chunk.type == "response.created": elif chunk.type == "response.created":
id = chunk.response.id id = chunk.response.id
response_metadata["id"] = chunk.response.id # Backwards compatibility response_metadata["id"] = chunk.response.id # Backwards compatibility
@ -3992,34 +4016,21 @@ def _convert_responses_chunk_to_generation_chunk(
content.append({"type": "refusal", "refusal": chunk.refusal}) content.append({"type": "refusal", "refusal": chunk.refusal})
elif chunk.type == "response.output_item.added" and chunk.item.type == "reasoning": elif chunk.type == "response.output_item.added" and chunk.item.type == "reasoning":
_advance(chunk.output_index) _advance(chunk.output_index)
current_sub_index = 0
reasoning = chunk.item.model_dump(exclude_none=True, mode="json") reasoning = chunk.item.model_dump(exclude_none=True, mode="json")
reasoning["index"] = current_index reasoning["index"] = current_index
content.append(reasoning) content.append(reasoning)
elif chunk.type == "response.reasoning_summary_part.added": elif chunk.type == "response.reasoning_summary_part.added":
if output_version in ("v0", "responses/v1"): _advance(chunk.output_index)
_advance(chunk.output_index) content.append(
content.append( {
{ # langchain-core uses the `index` key to aggregate text blocks.
# langchain-core uses the `index` key to aggregate text blocks. "summary": [
"summary": [ {"index": chunk.summary_index, "type": "summary_text", "text": ""}
{ ],
"index": chunk.summary_index, "index": current_index,
"type": "summary_text", "type": "reasoning",
"text": "", }
} )
],
"index": current_index,
"type": "reasoning",
}
)
else:
block: dict = {"type": "reasoning", "reasoning": ""}
if chunk.summary_index > 0:
_advance(chunk.output_index, chunk.summary_index)
block["id"] = chunk.item_id
block["index"] = current_index
content.append(block)
elif chunk.type == "response.image_generation_call.partial_image": elif chunk.type == "response.image_generation_call.partial_image":
# Partial images are not supported yet. # Partial images are not supported yet.
pass pass
@ -4054,15 +4065,6 @@ def _convert_responses_chunk_to_generation_chunk(
AIMessageChunk, AIMessageChunk,
_convert_to_v03_ai_message(message, has_reasoning=has_reasoning), _convert_to_v03_ai_message(message, has_reasoning=has_reasoning),
) )
elif output_version == "v1":
message = cast(AIMessageChunk, _convert_to_v1_from_responses(message))
for content_block in message.content:
if (
isinstance(content_block, dict)
and content_block.get("index", -1) > current_index
):
# blocks were added for v1
current_index = content_block["index"]
else: else:
pass pass
return ( return (