mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-01 19:12:42 +00:00
openai[minor]: release 0.3 (#29100)
## Goal Solve the following problems with `langchain-openai`: - Structured output with `o1` [breaks out of the box](https://langchain.slack.com/archives/C050X0VTN56/p1735232400232099). - `with_structured_output` by default does not use OpenAI’s [structured output feature](https://platform.openai.com/docs/guides/structured-outputs). - We override API defaults for temperature and other parameters. ## Breaking changes: - Default method for structured output is changing to OpenAI’s dedicated [structured output feature](https://platform.openai.com/docs/guides/structured-outputs). For schemas specified via TypedDict or JSON schema, strict schema validation is disabled by default but can be enabled by specifying `strict=True`. - To recover previous default, pass `method="function_calling"` into `with_structured_output`. - Models that don’t support `method="json_schema"` (e.g., `gpt-4` and `gpt-3.5-turbo`, currently the default model for ChatOpenAI) will raise an error unless `method` is explicitly specified. - To recover previous default, pass `method="function_calling"` into `with_structured_output`. - Schemas specified via Pydantic `BaseModel` that have fields with non-null defaults or metadata (like min/max constraints) will raise an error. - To recover previous default, pass `method="function_calling"` into `with_structured_output`. - `strict` now defaults to False for `method="json_schema"` when schemas are specified via TypedDict or JSON schema. - To recover previous behavior, use `with_structured_output(schema, strict=True)` - Schemas specified via Pydantic V1 will raise a warning (and use `method="function_calling"`) unless `method` is explicitly specified. - To remove the warning, pass `method="function_calling"` into `with_structured_output`. - Streaming with default structured output method / Pydantic schema no longer generates intermediate streamed chunks. - To recover previous behavior, pass `method="function_calling"` into `with_structured_output`. - We no longer override default temperature (was 0.7 in LangChain, now will follow OpenAI, currently 1.0). - To recover previous behavior, initialize `ChatOpenAI` or `AzureChatOpenAI` with `temperature=0.7`. - Note: conceptually there is a difference between forcing a tool call and forcing a response format. Tool calls may have more concise arguments vs. generating content adhering to a schema. Prompts may need to be adjusted to recover desired behavior. --------- Co-authored-by: Jacob Lee <jacoblee93@gmail.com> Co-authored-by: Bagatur <baskaryan@gmail.com>
This commit is contained in:
@@ -21,6 +21,7 @@ from langchain_core.utils.function_calling import tool_example_to_messages
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
from pydantic.v1 import Field as FieldV1
|
||||
from typing_extensions import Annotated, TypedDict
|
||||
|
||||
from langchain_tests.unit_tests.chat_models import (
|
||||
ChatModelTests,
|
||||
@@ -191,6 +192,19 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
def has_structured_output(self) -> bool:
|
||||
return True
|
||||
|
||||
.. dropdown:: structured_output_kwargs
|
||||
|
||||
Dict property that can be used to specify additional kwargs for
|
||||
``with_structured_output``. Useful for testing different models.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@property
|
||||
def structured_output_kwargs(self) -> dict:
|
||||
return {"method": "function_calling"}
|
||||
|
||||
.. dropdown:: supports_json_mode
|
||||
|
||||
Boolean property indicating whether the chat model supports JSON mode in
|
||||
@@ -1128,10 +1142,7 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
|
||||
Joke = _get_joke_class()
|
||||
# Pydantic class
|
||||
# Type ignoring since the interface only officially supports pydantic 1
|
||||
# or pydantic.v1.BaseModel but not pydantic.BaseModel from pydantic 2.
|
||||
# We'll need to do a pass updating the type signatures.
|
||||
chat = model.with_structured_output(Joke) # type: ignore[arg-type]
|
||||
chat = model.with_structured_output(Joke, **self.structured_output_kwargs)
|
||||
result = chat.invoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, Joke)
|
||||
|
||||
@@ -1139,7 +1150,9 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
assert isinstance(chunk, Joke)
|
||||
|
||||
# Schema
|
||||
chat = model.with_structured_output(Joke.model_json_schema())
|
||||
chat = model.with_structured_output(
|
||||
Joke.model_json_schema(), **self.structured_output_kwargs
|
||||
)
|
||||
result = chat.invoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, dict)
|
||||
assert set(result.keys()) == {"setup", "punchline"}
|
||||
@@ -1182,10 +1195,7 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
Joke = _get_joke_class()
|
||||
|
||||
# Pydantic class
|
||||
# Type ignoring since the interface only officially supports pydantic 1
|
||||
# or pydantic.v1.BaseModel but not pydantic.BaseModel from pydantic 2.
|
||||
# We'll need to do a pass updating the type signatures.
|
||||
chat = model.with_structured_output(Joke) # type: ignore[arg-type]
|
||||
chat = model.with_structured_output(Joke, **self.structured_output_kwargs)
|
||||
result = await chat.ainvoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, Joke)
|
||||
|
||||
@@ -1193,7 +1203,9 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
assert isinstance(chunk, Joke)
|
||||
|
||||
# Schema
|
||||
chat = model.with_structured_output(Joke.model_json_schema())
|
||||
chat = model.with_structured_output(
|
||||
Joke.model_json_schema(), **self.structured_output_kwargs
|
||||
)
|
||||
result = await chat.ainvoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, dict)
|
||||
assert set(result.keys()) == {"setup", "punchline"}
|
||||
@@ -1244,7 +1256,7 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
punchline: str = FieldV1(description="answer to resolve the joke")
|
||||
|
||||
# Pydantic class
|
||||
chat = model.with_structured_output(Joke)
|
||||
chat = model.with_structured_output(Joke, **self.structured_output_kwargs)
|
||||
result = chat.invoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, Joke)
|
||||
|
||||
@@ -1252,7 +1264,9 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
assert isinstance(chunk, Joke)
|
||||
|
||||
# Schema
|
||||
chat = model.with_structured_output(Joke.schema())
|
||||
chat = model.with_structured_output(
|
||||
Joke.schema(), **self.structured_output_kwargs
|
||||
)
|
||||
result = chat.invoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, dict)
|
||||
assert set(result.keys()) == {"setup", "punchline"}
|
||||
@@ -1293,6 +1307,7 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
if not self.has_tool_calling:
|
||||
pytest.skip("Test requires tool calling.")
|
||||
|
||||
# Pydantic
|
||||
class Joke(BaseModel):
|
||||
"""Joke to tell user."""
|
||||
|
||||
@@ -1301,7 +1316,7 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
default=None, description="answer to resolve the joke"
|
||||
)
|
||||
|
||||
chat = model.with_structured_output(Joke) # type: ignore[arg-type]
|
||||
chat = model.with_structured_output(Joke, **self.structured_output_kwargs)
|
||||
setup_result = chat.invoke(
|
||||
"Give me the setup to a joke about cats, no punchline."
|
||||
)
|
||||
@@ -1310,6 +1325,24 @@ class ChatModelIntegrationTests(ChatModelTests):
|
||||
joke_result = chat.invoke("Give me a joke about cats, include the punchline.")
|
||||
assert isinstance(joke_result, Joke)
|
||||
|
||||
# Schema
|
||||
chat = model.with_structured_output(
|
||||
Joke.model_json_schema(), **self.structured_output_kwargs
|
||||
)
|
||||
result = chat.invoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, dict)
|
||||
|
||||
# TypedDict
|
||||
class JokeDict(TypedDict):
|
||||
"""Joke to tell user."""
|
||||
|
||||
setup: Annotated[str, ..., "question to set up a joke"]
|
||||
punchline: Annotated[Optional[str], None, "answer to resolve the joke"]
|
||||
|
||||
chat = model.with_structured_output(JokeDict, **self.structured_output_kwargs)
|
||||
result = chat.invoke("Tell me a joke about cats.")
|
||||
assert isinstance(result, dict)
|
||||
|
||||
def test_json_mode(self, model: BaseChatModel) -> None:
|
||||
"""Test structured output via `JSON mode. <https://python.langchain.com/docs/concepts/structured_outputs/#json-mode>`_
|
||||
|
||||
|
@@ -132,6 +132,11 @@ class ChatModelTests(BaseStandardTests):
|
||||
is not BaseChatModel.with_structured_output
|
||||
)
|
||||
|
||||
@property
|
||||
def structured_output_kwargs(self) -> dict:
|
||||
"""If specified, additional kwargs for with_structured_output."""
|
||||
return {}
|
||||
|
||||
@property
|
||||
def supports_json_mode(self) -> bool:
|
||||
"""(bool) whether the chat model supports JSON mode."""
|
||||
@@ -299,6 +304,19 @@ class ChatModelUnitTests(ChatModelTests):
|
||||
def has_structured_output(self) -> bool:
|
||||
return True
|
||||
|
||||
.. dropdown:: structured_output_kwargs
|
||||
|
||||
Dict property that can be used to specify additional kwargs for
|
||||
``with_structured_output``. Useful for testing different models.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@property
|
||||
def structured_output_kwargs(self) -> dict:
|
||||
return {"method": "function_calling"}
|
||||
|
||||
.. dropdown:: supports_json_mode
|
||||
|
||||
Boolean property indicating whether the chat model supports JSON mode in
|
||||
|
Reference in New Issue
Block a user