json mode standard test (#25497)

Co-authored-by: Chester Curme <chester.curme@gmail.com>
This commit is contained in:
Bagatur 2024-12-17 10:47:34 -08:00 committed by GitHub
parent 24bf24270d
commit e4d3ccf62f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 127 additions and 2 deletions

View File

@ -29,3 +29,7 @@ class TestFireworksStandard(ChatModelIntegrationTests):
self, model: BaseChatModel, my_adder_tool: BaseTool self, model: BaseChatModel, my_adder_tool: BaseTool
) -> None: ) -> None:
super().test_tool_message_histories_list_content(model, my_adder_tool) super().test_tool_message_histories_list_content(model, my_adder_tool)
@property
def supports_json_mode(self) -> bool:
return True

View File

@ -26,6 +26,10 @@ class BaseTestGroq(ChatModelIntegrationTests):
) -> None: ) -> None:
super().test_tool_message_histories_list_content(model, my_adder_tool) super().test_tool_message_histories_list_content(model, my_adder_tool)
@property
def supports_json_mode(self) -> bool:
return True
class TestGroqLlama(BaseTestGroq): class TestGroqLlama(BaseTestGroq):
@property @property
@ -41,6 +45,10 @@ class TestGroqLlama(BaseTestGroq):
"""Value to use for tool choice when used in tests.""" """Value to use for tool choice when used in tests."""
return "any" return "any"
@property
def supports_json_mode(self) -> bool:
return False # Not supported in streaming mode
@pytest.mark.xfail( @pytest.mark.xfail(
reason=("Fails with 'Failed to call a function. Please adjust your prompt.'") reason=("Fails with 'Failed to call a function. Please adjust your prompt.'")
) )

View File

@ -19,6 +19,10 @@ class TestMistralStandard(ChatModelIntegrationTests):
def chat_model_params(self) -> dict: def chat_model_params(self) -> dict:
return {"model": "mistral-large-latest", "temperature": 0} return {"model": "mistral-large-latest", "temperature": 0}
@property
def supports_json_mode(self) -> bool:
return True
@property @property
def tool_choice_value(self) -> Optional[str]: def tool_choice_value(self) -> Optional[str]:
"""Value to use for tool choice when used in tests.""" """Value to use for tool choice when used in tests."""

View File

@ -22,6 +22,10 @@ class TestChatOllama(ChatModelIntegrationTests):
def supports_image_inputs(self) -> bool: def supports_image_inputs(self) -> bool:
return True return True
@property
def supports_json_mode(self) -> bool:
return True
@pytest.mark.xfail( @pytest.mark.xfail(
reason=( reason=(
"Fails with 'AssertionError'. Ollama does not support 'tool_choice' yet." "Fails with 'AssertionError'. Ollama does not support 'tool_choice' yet."

View File

@ -31,6 +31,10 @@ class TestAzureOpenAIStandard(ChatModelIntegrationTests):
def supports_image_inputs(self) -> bool: def supports_image_inputs(self) -> bool:
return True return True
@property
def supports_json_mode(self) -> bool:
return True
@pytest.mark.xfail(reason="Not yet supported.") @pytest.mark.xfail(reason="Not yet supported.")
def test_usage_metadata_streaming(self, model: BaseChatModel) -> None: def test_usage_metadata_streaming(self, model: BaseChatModel) -> None:
super().test_usage_metadata_streaming(model) super().test_usage_metadata_streaming(model)

View File

@ -25,6 +25,10 @@ class TestOpenAIStandard(ChatModelIntegrationTests):
def supports_image_inputs(self) -> bool: def supports_image_inputs(self) -> bool:
return True return True
@property
def supports_json_mode(self) -> bool:
return True
@property @property
def supported_usage_metadata_details( def supported_usage_metadata_details(
self, self,

View File

@ -178,7 +178,7 @@ class ChatModelIntegrationTests(ChatModelTests):
output. output.
By default, this is determined by whether the chat model's By default, this is determined by whether the chat model's
`with_structured_output` method is overridden. If the base implementation is ``with_structured_output`` method is overridden. If the base implementation is
intended to be used, this method should be overridden. intended to be used, this method should be overridden.
See: https://python.langchain.com/docs/concepts/structured_outputs/ See: https://python.langchain.com/docs/concepts/structured_outputs/
@ -191,6 +191,21 @@ class ChatModelIntegrationTests(ChatModelTests):
def has_structured_output(self) -> bool: def has_structured_output(self) -> bool:
return True return True
.. dropdown:: supports_json_mode
Boolean property indicating whether the chat model supports JSON mode in
``with_structured_output``.
See: https://python.langchain.com/docs/concepts/structured_outputs/#json-mode
Example:
.. code-block:: python
@property
def supports_json_mode(self) -> bool:
return True
.. dropdown:: supports_image_inputs .. dropdown:: supports_image_inputs
Boolean property indicating whether the chat model supports image inputs. Boolean property indicating whether the chat model supports image inputs.
@ -1295,6 +1310,68 @@ class ChatModelIntegrationTests(ChatModelTests):
joke_result = chat.invoke("Give me a joke about cats, include the punchline.") joke_result = chat.invoke("Give me a joke about cats, include the punchline.")
assert isinstance(joke_result, Joke) assert isinstance(joke_result, Joke)
def test_json_mode(self, model: BaseChatModel) -> None:
"""Test structured output via `JSON mode. <https://python.langchain.com/docs/concepts/structured_outputs/#json-mode>`_
This test is optional and should be skipped if the model does not support
the JSON mode feature (see Configuration below).
.. dropdown:: Configuration
To disable this test, set ``supports_json_mode`` to False in your
test class:
.. code-block:: python
class TestMyChatModelIntegration(ChatModelIntegrationTests):
@property
def supports_json_mode(self) -> bool:
return False
.. dropdown:: Troubleshooting
See example implementation of ``with_structured_output`` here: https://python.langchain.com/api_reference/_modules/langchain_openai/chat_models/base.html#BaseChatOpenAI.with_structured_output
""" # noqa: E501
if not self.supports_json_mode:
pytest.skip("Test requires json mode support.")
from pydantic import BaseModel as BaseModelProper
from pydantic import Field as FieldProper
class Joke(BaseModelProper):
"""Joke to tell user."""
setup: str = FieldProper(description="question to set up a joke")
punchline: str = FieldProper(description="answer to resolve the joke")
# 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, method="json_mode") # type: ignore[arg-type]
msg = (
"Tell me a joke about cats. Return the result as a JSON with 'setup' and "
"'punchline' keys. Return nothing other than JSON."
)
result = chat.invoke(msg)
assert isinstance(result, Joke)
for chunk in chat.stream(msg):
assert isinstance(chunk, Joke)
# Schema
chat = model.with_structured_output(
Joke.model_json_schema(), method="json_mode"
)
result = chat.invoke(msg)
assert isinstance(result, dict)
assert set(result.keys()) == {"setup", "punchline"}
for chunk in chat.stream(msg):
assert isinstance(chunk, dict)
assert isinstance(chunk, dict) # for mypy
assert set(chunk.keys()) == {"setup", "punchline"}
def test_tool_message_histories_string_content( def test_tool_message_histories_string_content(
self, model: BaseChatModel, my_adder_tool: BaseTool self, model: BaseChatModel, my_adder_tool: BaseTool
) -> None: ) -> None:

View File

@ -132,6 +132,11 @@ class ChatModelTests(BaseStandardTests):
is not BaseChatModel.with_structured_output is not BaseChatModel.with_structured_output
) )
@property
def supports_json_mode(self) -> bool:
"""(bool) whether the chat model supports JSON mode."""
return False
@property @property
def supports_image_inputs(self) -> bool: def supports_image_inputs(self) -> bool:
"""(bool) whether the chat model supports image inputs, defaults to """(bool) whether the chat model supports image inputs, defaults to
@ -281,7 +286,7 @@ class ChatModelUnitTests(ChatModelTests):
output. output.
By default, this is determined by whether the chat model's By default, this is determined by whether the chat model's
`with_structured_output` method is overridden. If the base implementation is ``with_structured_output`` method is overridden. If the base implementation is
intended to be used, this method should be overridden. intended to be used, this method should be overridden.
See: https://python.langchain.com/docs/concepts/structured_outputs/ See: https://python.langchain.com/docs/concepts/structured_outputs/
@ -294,6 +299,21 @@ class ChatModelUnitTests(ChatModelTests):
def has_structured_output(self) -> bool: def has_structured_output(self) -> bool:
return True return True
.. dropdown:: supports_json_mode
Boolean property indicating whether the chat model supports JSON mode in
``with_structured_output``.
See: https://python.langchain.com/docs/concepts/structured_outputs/#json-mode
Example:
.. code-block:: python
@property
def supports_json_mode(self) -> bool:
return True
.. dropdown:: supports_image_inputs .. dropdown:: supports_image_inputs
Boolean property indicating whether the chat model supports image inputs. Boolean property indicating whether the chat model supports image inputs.