formatting/nits/docs

This commit is contained in:
Mason Daugherty 2025-08-05 20:25:36 -04:00
parent d5b26bc358
commit cd8d6ae7cd
No known key found for this signature in database
5 changed files with 90 additions and 66 deletions

View File

@ -629,15 +629,16 @@ def tool_example_to_messages(
The list of messages per example by default corresponds to:
1) HumanMessage: contains the content from which content should be extracted.
2) AIMessage: contains the extracted information from the model
3) ToolMessage: contains confirmation to the model that the model requested a tool
correctly.
1. ``HumanMessage``: contains the content from which content should be extracted.
2. ``AIMessage``: contains the extracted information from the model
3. ``ToolMessage``: contains confirmation to the model that the model requested a
tool correctly.
If `ai_response` is specified, there will be a final AIMessage with that response.
If ``ai_response`` is specified, there will be a final ``AIMessage`` with that
response.
The ToolMessage is required because some chat models are hyper-optimized for agents
rather than for an extraction use case.
The ``ToolMessage`` is required because some chat models are hyper-optimized for
agents rather than for an extraction use case.
Arguments:
input: string, the user input
@ -646,7 +647,7 @@ def tool_example_to_messages(
tool_outputs: Optional[list[str]], a list of tool call outputs.
Does not need to be provided. If not provided, a placeholder value
will be inserted. Defaults to None.
ai_response: Optional[str], if provided, content for a final AIMessage.
ai_response: Optional[str], if provided, content for a final ``AIMessage``.
Returns:
A list of messages
@ -728,6 +729,7 @@ def _parse_google_docstring(
"""Parse the function and argument descriptions from the docstring of a function.
Assumes the function docstring follows Google Python style guide.
"""
if docstring:
docstring_blocks = docstring.split("\n\n")

View File

@ -190,7 +190,7 @@ def _format_ls_structured_output(ls_structured_output_format: Optional[dict]) ->
class BaseChatModel(RunnableSerializable[LanguageModelInput, AIMessageV1], ABC):
"""Base class for chat models.
"""Base class for v1 chat models.
Key imperative methods:
Methods that actually call the underlying model.

View File

@ -61,7 +61,7 @@ class ResponseMetadata(TypedDict, total=False):
@dataclass
class AIMessage:
"""A message generated by an AI assistant.
"""A v1 message generated by an AI assistant.
Represents a response from an AI model, including text content, tool calls,
and metadata about the generation process.
@ -133,7 +133,7 @@ class AIMessage:
invalid_tool_calls: Optional[list[types.InvalidToolCall]] = None,
parsed: Optional[Union[dict[str, Any], BaseModel]] = None,
):
"""Initialize an AI message.
"""Initialize a v1 AI message.
Args:
content: Message content as string or list of content blocks.
@ -263,7 +263,7 @@ class AIMessageChunk(AIMessage):
parsed: Optional[Union[dict[str, Any], BaseModel]] = None,
chunk_position: Optional[Literal["last"]] = None,
):
"""Initialize an AI message.
"""Initialize a v1 AI message.
Args:
content: Message content as string or list of content blocks.
@ -541,7 +541,7 @@ class HumanMessage:
id: Optional[str] = None,
name: Optional[str] = None,
):
"""Initialize a human message.
"""Initialize a v1 human message.
Args:
content: Message content as string or list of content blocks.
@ -623,7 +623,7 @@ class SystemMessage:
custom_role: Optional[str] = None,
name: Optional[str] = None,
):
"""Initialize a human message.
"""Initialize a v1 system message.
Args:
content: Message content as string or list of content blocks.
@ -711,7 +711,7 @@ class ToolMessage(ToolOutputMixin):
artifact: Optional[Any] = None,
status: Literal["success", "error"] = "success",
):
"""Initialize a human message.
"""Initialize a v1 tool message.
Args:
content: Message content as string or list of content blocks.

View File

@ -374,11 +374,6 @@ class TestFakeChatModelV1Integration(ChatModelV1IntegrationTests):
"""Enable non-standard blocks support."""
return True
@property
def requires_api_key(self) -> bool:
"""This fake model doesn't require an API key."""
return False
# Example of a more realistic integration test configuration
# that would require API keys and external services

View File

@ -108,6 +108,7 @@ def magic_function_no_args() -> int:
def _validate_tool_call_message(message: BaseMessage) -> None:
assert isinstance(message, AIMessage)
assert len(message.tool_calls) == 1
tool_call = message.tool_calls[0]
assert tool_call["name"] == "magic_function"
assert tool_call["args"] == {"input": 3}
@ -118,6 +119,7 @@ def _validate_tool_call_message(message: BaseMessage) -> None:
def _validate_tool_call_message_no_args(message: BaseMessage) -> None:
assert isinstance(message, AIMessage)
assert len(message.tool_calls) == 1
tool_call = message.tool_calls[0]
assert tool_call["name"] == "magic_function_no_args"
assert tool_call["args"] == {}
@ -750,10 +752,10 @@ class ChatModelIntegrationTests(ChatModelTests):
First, debug
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_invoke`.
because `stream` has a default implementation that calls `invoke` and yields
the result as a single chunk.
because ``stream`` has a default implementation that calls ``invoke`` and
yields the result as a single chunk.
If that test passes but not this one, you should make sure your _stream
If that test passes but not this one, you should make sure your ``_stream``
method does not raise any exceptions, and that it yields valid
:class:`~langchain_core.outputs.chat_generation.ChatGenerationChunk`
objects like so:
@ -769,6 +771,7 @@ class ChatModelIntegrationTests(ChatModelTests):
for chunk in model.stream("Hello"):
assert chunk is not None
assert isinstance(chunk, AIMessageChunk)
assert isinstance(chunk.content, (str, list))
num_chunks += 1
assert num_chunks > 0
@ -785,11 +788,11 @@ class ChatModelIntegrationTests(ChatModelTests):
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_stream`.
and
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_ainvoke`.
because `astream` has a default implementation that calls `_stream` in an
async context if it is implemented, or `ainvoke` and yields the result as a
single chunk if not.
because ``astream`` has a default implementation that calls ``_stream`` in
an async context if it is implemented, or ``ainvoke`` and yields the result
as a single chunk if not.
If those tests pass but not this one, you should make sure your _astream
If those tests pass but not this one, you should make sure your ``_astream``
method does not raise any exceptions, and that it yields valid
:class:`~langchain_core.outputs.chat_generation.ChatGenerationChunk`
objects like so:
@ -819,12 +822,13 @@ class ChatModelIntegrationTests(ChatModelTests):
First, debug
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_invoke`
because `batch` has a default implementation that calls `invoke` for each
message in the batch.
because ``batch`` has a default implementation that calls ``invoke`` for
each message in the batch.
If that test passes but not this one, you should make sure your `batch`
If that test passes but not this one, you should make sure your ``batch``
method does not raise any exceptions, and that it returns a list of valid
:class:`~langchain_core.messages.AIMessage` objects.
"""
batch_results = model.batch(["Hello", "Hey"])
assert batch_results is not None
@ -848,10 +852,10 @@ class ChatModelIntegrationTests(ChatModelTests):
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_batch`
and
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_ainvoke`
because `abatch` has a default implementation that calls `ainvoke` for each
message in the batch.
because ``abatch`` has a default implementation that calls ``ainvoke`` for
each message in the batch.
If those tests pass but not this one, you should make sure your `abatch`
If those tests pass but not this one, you should make sure your ``abatch``
method does not raise any exceptions, and that it returns a list of valid
:class:`~langchain_core.messages.AIMessage` objects.
@ -877,7 +881,7 @@ class ChatModelIntegrationTests(ChatModelTests):
First, debug
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_invoke`
because this test also uses `model.invoke()`.
because this test also uses ``model.invoke()``.
If that test passes but not this one, you should verify that:
1. Your model correctly processes the message history
@ -890,6 +894,7 @@ class ChatModelIntegrationTests(ChatModelTests):
AIMessage("hello"),
HumanMessage("how are you"),
]
result = model.invoke(messages)
assert result is not None
assert isinstance(result, AIMessage)
@ -907,18 +912,17 @@ class ChatModelIntegrationTests(ChatModelTests):
First, debug
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_invoke`
because this test also uses `model.invoke()`.
because this test also uses ``model.invoke()``.
Second, debug
:meth:`~langchain_tests.integration_tests.chat_models.ChatModelIntegrationTests.test_conversation`
because this test is the "basic case" without double messages.
If that test passes those but not this one, you should verify that:
1. Your model API can handle double messages, or the integration should
merge messages before sending them to the API.
1. Your model API can handle double messages, or the integration should merge messages before sending them to the API.
2. The response is a valid :class:`~langchain_core.messages.AIMessage`
"""
""" # noqa: E501
messages = [
SystemMessage("hello"),
SystemMessage("hello"),
@ -928,6 +932,7 @@ class ChatModelIntegrationTests(ChatModelTests):
AIMessage("hello"),
HumanMessage("how are you"),
]
result = model.invoke(messages)
assert result is not None
assert isinstance(result, AIMessage)
@ -1023,9 +1028,11 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.returns_usage_metadata:
pytest.skip("Not implemented.")
result = model.invoke("Hello")
assert result is not None
assert isinstance(result, AIMessage)
assert result.usage_metadata is not None
assert isinstance(result.usage_metadata["input_tokens"], int)
assert isinstance(result.usage_metadata["output_tokens"], int)
@ -1201,6 +1208,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.returns_usage_metadata:
pytest.skip("Not implemented.")
full: Optional[AIMessageChunk] = None
for chunk in model.stream("Write me 2 haikus. Only include the haikus."):
assert isinstance(chunk, AIMessageChunk)
@ -1339,6 +1347,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.has_tool_calling:
pytest.skip("Test requires tool calling.")
tool_choice_value = None if not self.has_tool_choice else "any"
# Emit warning if tool_choice_value property is overridden
if inspect.getattr_static(
@ -1413,6 +1422,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.has_tool_calling:
pytest.skip("Test requires tool calling.")
tool_choice_value = None if not self.has_tool_choice else "any"
model_with_tools = model.bind_tools(
[magic_function], tool_choice=tool_choice_value
@ -1522,10 +1532,10 @@ class ChatModelIntegrationTests(ChatModelTests):
If this test fails, check that:
1. The model can correctly handle message histories that include AIMessage objects with ``""`` content.
2. The ``tool_calls`` attribute on AIMessage objects is correctly handled and passed to the model in an appropriate format.
3. The model can correctly handle ToolMessage objects with string content and arbitrary string values for ``tool_call_id``.
assert tool_call.get("type") == "tool_call"
1. The model can correctly handle message histories that include ``AIMessage`` objects with ``""`` content.
2. The ``tool_calls`` attribute on ``AIMessage`` objects is correctly handled and passed to the model in an appropriate format.
3. The model can correctly handle ``ToolMessage`` objects with string content and arbitrary string values for ``tool_call_id``.
You can ``xfail`` the test if tool calling is implemented but this format
is not supported.
@ -1538,6 +1548,7 @@ class ChatModelIntegrationTests(ChatModelTests):
""" # noqa: E501
if not self.has_tool_calling:
pytest.skip("Test requires tool calling.")
model_with_tools = model.bind_tools([my_adder_tool])
function_name = "my_adder_tool"
function_args = {"a": "1", "b": "2"}
@ -1623,6 +1634,7 @@ class ChatModelIntegrationTests(ChatModelTests):
""" # noqa: E501
if not self.has_tool_calling:
pytest.skip("Test requires tool calling.")
model_with_tools = model.bind_tools([my_adder_tool])
function_name = "my_adder_tool"
function_args = {"a": 1, "b": 2}
@ -1695,7 +1707,7 @@ class ChatModelIntegrationTests(ChatModelTests):
pytest.skip("Test requires tool choice.")
@tool
def get_weather(location: str) -> str: # pylint: disable=unused-argument
def get_weather(location: str) -> str:
"""Get weather at a location."""
return "It's sunny."
@ -1753,6 +1765,7 @@ class ChatModelIntegrationTests(ChatModelTests):
""" # noqa: E501
if not self.has_tool_calling:
pytest.skip("Test requires tool calling.")
tool_choice_value = None if not self.has_tool_choice else "any"
model_with_tools = model.bind_tools(
[magic_function_no_args], tool_choice=tool_choice_value
@ -1770,7 +1783,7 @@ class ChatModelIntegrationTests(ChatModelTests):
def test_tool_message_error_status(
self, model: BaseChatModel, my_adder_tool: BaseTool
) -> None:
"""Test that ToolMessage with ``status="error"`` can be handled.
"""Test that ``ToolMessage`` with ``status="error"`` can be handled.
These messages may take the form:
@ -1809,6 +1822,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.has_tool_calling:
pytest.skip("Test requires tool calling.")
model_with_tools = model.bind_tools([my_adder_tool])
messages = [
HumanMessage("What is 1 + 2"),
@ -1863,8 +1877,9 @@ class ChatModelIntegrationTests(ChatModelTests):
.. dropdown:: Troubleshooting
This test uses a utility function in ``langchain_core`` to generate a
sequence of messages representing "few-shot" examples: https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.tool_example_to_messages.html
This test uses `a utility function <https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.tool_example_to_messages.html>`__
in ``langchain_core`` to generate a sequence of messages representing
"few-shot" examples.
If this test fails, check that the model can correctly handle this
sequence of messages.
@ -1881,6 +1896,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.has_tool_calling:
pytest.skip("Test requires tool calling.")
model_with_tools = model.bind_tools([my_adder_tool], tool_choice="any")
function_result = json.dumps({"result": 3})
@ -1924,10 +1940,12 @@ class ChatModelIntegrationTests(ChatModelTests):
If this test fails, ensure that the model's ``bind_tools`` method
properly handles both JSON Schema and Pydantic V2 models.
``langchain_core`` implements a utility function that will accommodate
most formats: https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html
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
``langchain_core`` implements `a utility function <https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html>`__
that will accommodate most formats.
See `example implementation <https://python.langchain.com/api_reference/_modules/langchain_openai/chat_models/base.html#BaseChatOpenAI.with_structured_output>`__
of ``with_structured_output``.
"""
if not self.has_structured_output:
@ -2003,10 +2021,12 @@ class ChatModelIntegrationTests(ChatModelTests):
If this test fails, ensure that the model's ``bind_tools`` method
properly handles both JSON Schema and Pydantic V2 models.
``langchain_core`` implements a utility function that will accommodate
most formats: https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html
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
``langchain_core`` implements `a utility function <https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html>`__
that will accommodate most formats.
See `example implementation <https://python.langchain.com/api_reference/_modules/langchain_openai/chat_models/base.html#BaseChatOpenAI.with_structured_output>`__
of ``with_structured_output``.
"""
if not self.has_structured_output:
@ -2055,10 +2075,9 @@ class ChatModelIntegrationTests(ChatModelTests):
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Test requires pydantic 2.")
def test_structured_output_pydantic_2_v1(self, model: BaseChatModel) -> None:
"""Test to verify we can generate structured output using
``pydantic.v1.BaseModel``.
"""Test to verify we can generate structured output using ``pydantic.v1.BaseModel``.
``pydantic.v1.BaseModel`` is available in the pydantic 2 package.
``pydantic.v1.BaseModel`` is available in the Pydantic 2 package.
This test is optional and should be skipped if the model does not support
structured output (see Configuration below).
@ -2082,12 +2101,14 @@ class ChatModelIntegrationTests(ChatModelTests):
If this test fails, ensure that the model's ``bind_tools`` method
properly handles both JSON Schema and Pydantic V1 models.
``langchain_core`` implements a utility function that will accommodate
most formats: https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html
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
``langchain_core`` implements `a utility function <https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html>`__
that will accommodate most formats.
"""
See `example implementation <https://python.langchain.com/api_reference/_modules/langchain_openai/chat_models/base.html#BaseChatOpenAI.with_structured_output>`__
of ``with_structured_output``.
""" # noqa: E501
if not self.has_structured_output:
pytest.skip("Test requires structured output.")
@ -2144,10 +2165,12 @@ class ChatModelIntegrationTests(ChatModelTests):
If this test fails, ensure that the model's ``bind_tools`` method
properly handles Pydantic V2 models with optional parameters.
``langchain_core`` implements a utility function that will accommodate
most formats: https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html
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
``langchain_core`` implements `a utility function <https://python.langchain.com/api_reference/core/utils/langchain_core.utils.function_calling.convert_to_openai_tool.html>`__
that will accommodate most formats.
See `example implementation <https://python.langchain.com/api_reference/_modules/langchain_openai/chat_models/base.html#BaseChatOpenAI.with_structured_output>`__
of ``with_structured_output``.
"""
if not self.has_structured_output:
@ -2228,7 +2251,7 @@ class ChatModelIntegrationTests(ChatModelTests):
# 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]
chat = model.with_structured_output(Joke, method="json_mode")
msg = (
"Tell me a joke about cats. Return the result as a JSON with 'setup' and "
"'punchline' keys. Return nothing other than JSON."
@ -2291,6 +2314,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.supports_pdf_inputs:
pytest.skip("Model does not support PDF inputs.")
url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
pdf_data = base64.b64encode(httpx.get(url).content).decode("utf-8")
@ -2367,6 +2391,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.supports_audio_inputs:
pytest.skip("Model does not support audio inputs.")
url = "https://upload.wikimedia.org/wikipedia/commons/3/3d/Alcal%C3%A1_de_Henares_%28RPS_13-04-2024%29_canto_de_ruise%C3%B1or_%28Luscinia_megarhynchos%29_en_el_Soto_del_Henares.wav"
audio_data = base64.b64encode(httpx.get(url).content).decode("utf-8")
@ -2468,6 +2493,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.supports_image_inputs:
pytest.skip("Model does not support image message.")
image_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8")
@ -2575,6 +2601,7 @@ class ChatModelIntegrationTests(ChatModelTests):
"""
if not self.supports_image_tool_message:
pytest.skip("Model does not support image tool message.")
image_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8")
@ -2845,7 +2872,7 @@ class ChatModelIntegrationTests(ChatModelTests):
chat model.
Check also that all required information (e.g., tool calling identifiers)
from AIMessage objects is propagated correctly to model payloads.
from ``AIMessage`` objects is propagated correctly to model payloads.
This test may fail if the chat model does not consistently generate tool
calls in response to an appropriate query. In these cases you can ``xfail``
@ -2862,7 +2889,7 @@ class ChatModelIntegrationTests(ChatModelTests):
pytest.skip("Test requires tool calling.")
@tool
def get_weather(location: str) -> str: # pylint: disable=unused-argument
def get_weather(location: str) -> str:
"""Call to surf the web."""
return "It's sunny."
@ -2956,7 +2983,7 @@ class ChatModelIntegrationTests(ChatModelTests):
Args:
model: The chat model to test
tool_choice: Tool choice parameter to pass to bind_tools (provider-specific)
tool_choice: Tool choice parameter to pass to ``bind_tools()`` (provider-specific)
force_tool_call: Whether to force a tool call (use ``tool_choice=True`` if None)
Tests that Unicode characters in tool call arguments are preserved correctly,