diff --git a/libs/langchain/pyproject.toml b/libs/langchain/pyproject.toml
index 6aefa38a9c3..d87d7f37219 100644
--- a/libs/langchain/pyproject.toml
+++ b/libs/langchain/pyproject.toml
@@ -180,13 +180,13 @@ select = [
"YTT", # flake8-2020
]
ignore = [
- "D100",
- "D101",
- "D102",
- "D103",
- "D104",
- "D105",
- "D107",
+ "D100", # Missing docstring in public module
+ "D101", # Missing docstring in public class
+ "D102", # Missing docstring in public method
+ "D103", # Missing docstring in public function
+ "D104", # Missing docstring in public package
+ "D105", # Missing docstring in magic method
+ "D107", # Missing docstring in __init__
"COM812", # Messes with the formatter
"ISC001", # Messes with the formatter
"PERF203", # Rarely useful
diff --git a/libs/partners/anthropic/langchain_anthropic/__init__.py b/libs/partners/anthropic/langchain_anthropic/__init__.py
index 4dcf4eb1878..399fece0c2d 100644
--- a/libs/partners/anthropic/langchain_anthropic/__init__.py
+++ b/libs/partners/anthropic/langchain_anthropic/__init__.py
@@ -6,9 +6,9 @@ from langchain_anthropic.chat_models import (
from langchain_anthropic.llms import Anthropic, AnthropicLLM
__all__ = [
- "ChatAnthropicMessages",
- "ChatAnthropic",
- "convert_to_anthropic_tool",
"Anthropic",
"AnthropicLLM",
+ "ChatAnthropic",
+ "ChatAnthropicMessages",
+ "convert_to_anthropic_tool",
]
diff --git a/libs/partners/anthropic/langchain_anthropic/_client_utils.py b/libs/partners/anthropic/langchain_anthropic/_client_utils.py
index a82e5655a9c..40591107fb6 100644
--- a/libs/partners/anthropic/langchain_anthropic/_client_utils.py
+++ b/libs/partners/anthropic/langchain_anthropic/_client_utils.py
@@ -6,6 +6,8 @@ for each instance of ChatAnthropic.
Logic is largely replicated from anthropic._base_client.
"""
+from __future__ import annotations
+
import asyncio
import os
from functools import lru_cache
@@ -17,7 +19,7 @@ _NOT_GIVEN: Any = object()
class _SyncHttpxClientWrapper(anthropic.DefaultHttpxClient):
- """Borrowed from anthropic._base_client"""
+ """Borrowed from anthropic._base_client."""
def __del__(self) -> None:
if self.is_closed:
@@ -30,7 +32,7 @@ class _SyncHttpxClientWrapper(anthropic.DefaultHttpxClient):
class _AsyncHttpxClientWrapper(anthropic.DefaultAsyncHttpxClient):
- """Borrowed from anthropic._base_client"""
+ """Borrowed from anthropic._base_client."""
def __del__(self) -> None:
if self.is_closed:
diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py
index 67fe22c4824..cf6017f99b3 100644
--- a/libs/partners/anthropic/langchain_anthropic/chat_models.py
+++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import copy
import json
import re
@@ -111,18 +113,19 @@ def _is_builtin_tool(tool: Any) -> bool:
def _format_image(url: str) -> dict:
- """
- Converts part["image_url"]["url"] strings (OpenAI format)
- to the correct Anthropic format:
+ """Convert part["image_url"]["url"] strings (OpenAI format) to Anthropic format.
+
{
- "type": "base64",
- "media_type": "image/jpeg",
- "data": "/9j/4AAQSkZJRg...",
+ "type": "base64",
+ "media_type": "image/jpeg",
+ "data": "/9j/4AAQSkZJRg...",
}
+
Or
+
{
- "type": "url",
- "url": "https://example.com/image.jpg",
+ "type": "url",
+ "url": "https://example.com/image.jpg",
}
"""
# Base64 encoded image
@@ -146,11 +149,14 @@ def _format_image(url: str) -> dict:
"url": url,
}
- raise ValueError(
+ msg = (
"Malformed url parameter."
" Must be either an image URL (https://example.com/image.jpg)"
" or base64 encoded string (data:image/png;base64,'/9j/4AAQSk'...)"
)
+ raise ValueError(
+ msg,
+ )
def _merge_messages(
@@ -177,8 +183,8 @@ def _merge_messages(
"content": curr.content,
"tool_use_id": curr.tool_call_id,
"is_error": curr.status == "error",
- }
- ]
+ },
+ ],
)
last = merged[-1] if merged else None
if any(
@@ -187,7 +193,7 @@ def _merge_messages(
):
if isinstance(cast(BaseMessage, last).content, str):
new_content: list = [
- {"type": "text", "text": cast(BaseMessage, last).content}
+ {"type": "text", "text": cast(BaseMessage, last).content},
]
else:
new_content = copy.copy(cast(list, cast(BaseMessage, last).content))
@@ -234,10 +240,13 @@ def _format_data_content_block(block: dict) -> dict:
},
}
else:
- raise ValueError(
+ msg = (
"Anthropic only supports 'url' and 'base64' source_type for image "
"content blocks."
)
+ raise ValueError(
+ msg,
+ )
elif block["type"] == "file":
if block["source_type"] == "url":
@@ -276,7 +285,8 @@ def _format_data_content_block(block: dict) -> dict:
}
else:
- raise ValueError(f"Block of type {block['type']} is not supported.")
+ msg = f"Block of type {block['type']} is not supported."
+ raise ValueError(msg)
if formatted_block:
for key in ["cache_control", "citations", "title", "context"]:
@@ -292,7 +302,6 @@ def _format_messages(
messages: Sequence[BaseMessage],
) -> tuple[Union[str, list[dict], None], list[dict]]:
"""Format messages for anthropic."""
-
"""
[
{
@@ -308,8 +317,9 @@ def _format_messages(
for i, message in enumerate(merged_messages):
if message.type == "system":
if system is not None:
- raise ValueError("Received multiple non-consecutive system messages.")
- elif isinstance(message.content, list):
+ msg = "Received multiple non-consecutive system messages."
+ raise ValueError(msg)
+ if isinstance(message.content, list):
system = [
(
block
@@ -328,8 +338,9 @@ def _format_messages(
if not isinstance(message.content, str):
# parse as dict
if not isinstance(message.content, list):
+ msg = "Anthropic message content must be str or list of dicts"
raise ValueError(
- "Anthropic message content must be str or list of dicts"
+ msg,
)
# populate content
@@ -339,8 +350,9 @@ def _format_messages(
content.append({"type": "text", "text": block})
elif isinstance(block, dict):
if "type" not in block:
- raise ValueError("Dict content block must have a type key")
- elif block["type"] == "image_url":
+ msg = "Dict content block must have a type key"
+ raise ValueError(msg)
+ if block["type"] == "image_url":
# convert format
source = _format_image(block["image_url"]["url"])
content.append({"type": "image", "source": source})
@@ -358,7 +370,9 @@ def _format_messages(
if tc["id"] == block["id"]
]
content.extend(
- _lc_tool_calls_to_anthropic_tool_use_blocks(overlapping)
+ _lc_tool_calls_to_anthropic_tool_use_blocks(
+ overlapping,
+ ),
)
else:
block.pop("text", None)
@@ -398,7 +412,7 @@ def _format_messages(
for k, v in block.items()
if k
in ("type", "text", "cache_control", "citations")
- }
+ },
)
elif block["type"] == "thinking":
content.append(
@@ -407,7 +421,7 @@ def _format_messages(
for k, v in block.items()
if k
in ("type", "thinking", "cache_control", "signature")
- }
+ },
)
elif block["type"] == "redacted_thinking":
content.append(
@@ -415,13 +429,13 @@ def _format_messages(
k: v
for k, v in block.items()
if k in ("type", "cache_control", "data")
- }
+ },
)
elif block["type"] == "tool_result":
tool_content = _format_messages(
- [HumanMessage(block["content"])]
+ [HumanMessage(block["content"])],
)[1][0]["content"]
- content.append({**block, **{"content": tool_content}})
+ content.append({**block, "content": tool_content})
elif block["type"] in (
"code_execution_tool_result",
"mcp_tool_result",
@@ -439,15 +453,18 @@ def _format_messages(
"is_error", # for mcp_tool_result
"cache_control",
)
- }
+ },
)
else:
content.append(block)
else:
- raise ValueError(
+ msg = (
f"Content blocks must be str or dict, instead was: "
f"{type(block)}"
)
+ raise ValueError(
+ msg,
+ )
else:
content = message.content
@@ -468,7 +485,7 @@ def _format_messages(
tc for tc in message.tool_calls if tc["id"] not in tool_use_ids
]
cast(list, content).extend(
- _lc_tool_calls_to_anthropic_tool_use_blocks(missing_tool_calls)
+ _lc_tool_calls_to_anthropic_tool_use_blocks(missing_tool_calls),
)
formatted_messages.append({"role": role, "content": content})
@@ -481,8 +498,7 @@ def _handle_anthropic_bad_request(e: anthropic.BadRequestError) -> None:
message = "Received only system message(s). "
warnings.warn(message)
raise e
- else:
- raise
+ raise
class ChatAnthropic(BaseChatModel):
@@ -635,17 +651,17 @@ class ChatAnthropic(BaseChatModel):
.. code-block:: python
[{'name': 'GetWeather',
- 'args': {'location': 'Los Angeles, CA'},
- 'id': 'toolu_01KzpPEAgzura7hpBqwHbWdo'},
+ 'args': {'location': 'Los Angeles, CA'},
+ 'id': 'toolu_01KzpPEAgzura7hpBqwHbWdo'},
{'name': 'GetWeather',
- 'args': {'location': 'New York, NY'},
- 'id': 'toolu_01JtgbVGVJbiSwtZk3Uycezx'},
+ 'args': {'location': 'New York, NY'},
+ 'id': 'toolu_01JtgbVGVJbiSwtZk3Uycezx'},
{'name': 'GetPopulation',
- 'args': {'location': 'Los Angeles, CA'},
- 'id': 'toolu_01429aygngesudV9nTbCKGuw'},
+ 'args': {'location': 'Los Angeles, CA'},
+ 'id': 'toolu_01429aygngesudV9nTbCKGuw'},
{'name': 'GetPopulation',
- 'args': {'location': 'New York, NY'},
- 'id': 'toolu_01JPktyd44tVMeBcPPnFSEJG'}]
+ 'args': {'location': 'New York, NY'},
+ 'id': 'toolu_01JPktyd44tVMeBcPPnFSEJG'}]
See ``ChatAnthropic.bind_tools()`` method for more.
@@ -673,7 +689,7 @@ class ChatAnthropic(BaseChatModel):
See ``ChatAnthropic.with_structured_output()`` for more.
Image input:
- See `multimodal guides `_
+ See `multimodal guides `__
for more detail.
.. code-block:: python
@@ -717,7 +733,7 @@ class ChatAnthropic(BaseChatModel):
.. dropdown:: Files API
You can also pass in files that are managed through Anthropic's
- `Files API `_:
+ `Files API `__:
.. code-block:: python
@@ -744,7 +760,7 @@ class ChatAnthropic(BaseChatModel):
llm.invoke([input_message])
PDF input:
- See `multimodal guides `_
+ See `multimodal guides `__
for more detail.
.. code-block:: python
@@ -782,7 +798,7 @@ class ChatAnthropic(BaseChatModel):
.. dropdown:: Files API
You can also pass in files that are managed through Anthropic's
- `Files API `_:
+ `Files API `__:
.. code-block:: python
@@ -810,7 +826,7 @@ class ChatAnthropic(BaseChatModel):
Extended thinking:
Claude 3.7 Sonnet supports an
- `extended thinking `_
+ `extended thinking `__
feature, which will output the step-by-step reasoning process that led to its
final answer.
@@ -838,10 +854,10 @@ class ChatAnthropic(BaseChatModel):
Citations:
Anthropic supports a
- `citations `_
+ `citations `__
feature that lets Claude attach context to its answers based on source
documents supplied by the user. When
- `document content blocks `_
+ `document content blocks `__
with ``"citations": {"enabled": True}`` are included in a query, Claude may
generate citations in its response.
@@ -924,7 +940,7 @@ class ChatAnthropic(BaseChatModel):
or by setting ``stream_usage=False`` when initializing ChatAnthropic.
Prompt caching:
- See LangChain `docs `_
+ See LangChain `docs `__
for more detail.
.. code-block:: python
@@ -1000,11 +1016,11 @@ class ChatAnthropic(BaseChatModel):
}
}
- See `Claude documentation `_
+ See `Claude documentation `__
for detail.
Token-efficient tool use (beta):
- See LangChain `docs `_
+ See LangChain `docs `__
for more detail.
.. code-block:: python
@@ -1041,7 +1057,7 @@ class ChatAnthropic(BaseChatModel):
Total tokens: 408
Built-in tools:
- See LangChain `docs `_
+ See LangChain `docs `__
for more detail.
.. dropdown:: Web search
@@ -1266,7 +1282,9 @@ class ChatAnthropic(BaseChatModel):
}
def _get_ls_params(
- self, stop: Optional[list[str]] = None, **kwargs: Any
+ self,
+ stop: Optional[list[str]] = None,
+ **kwargs: Any,
) -> LangSmithParams:
"""Get standard params for tracing."""
params = self._get_invocation_params(stop=stop, **kwargs)
@@ -1286,8 +1304,7 @@ class ChatAnthropic(BaseChatModel):
@classmethod
def build_extra(cls, values: dict) -> Any:
all_required_field_names = get_pydantic_field_names(cls)
- values = _build_model_kwargs(values, all_required_field_names)
- return values
+ return _build_model_kwargs(values, all_required_field_names)
@cached_property
def _client_params(self) -> dict[str, Any]:
@@ -1361,14 +1378,12 @@ class ChatAnthropic(BaseChatModel):
def _create(self, payload: dict) -> Any:
if "betas" in payload:
return self._client.beta.messages.create(**payload)
- else:
- return self._client.messages.create(**payload)
+ return self._client.messages.create(**payload)
async def _acreate(self, payload: dict) -> Any:
if "betas" in payload:
return await self._async_client.beta.messages.create(**payload)
- else:
- return await self._async_client.messages.create(**payload)
+ return await self._async_client.messages.create(**payload)
def _stream(
self,
@@ -1496,7 +1511,10 @@ class ChatAnthropic(BaseChatModel):
) -> ChatResult:
if self.streaming:
stream_iter = self._stream(
- messages, stop=stop, run_manager=run_manager, **kwargs
+ messages,
+ stop=stop,
+ run_manager=run_manager,
+ **kwargs,
)
return generate_from_stream(stream_iter)
payload = self._get_request_payload(messages, stop=stop, **kwargs)
@@ -1515,7 +1533,10 @@ class ChatAnthropic(BaseChatModel):
) -> ChatResult:
if self.streaming:
stream_iter = self._astream(
- messages, stop=stop, run_manager=run_manager, **kwargs
+ messages,
+ stop=stop,
+ run_manager=run_manager,
+ **kwargs,
)
return await agenerate_from_stream(stream_iter)
payload = self._get_request_payload(messages, stop=stop, **kwargs)
@@ -1558,7 +1579,7 @@ class ChatAnthropic(BaseChatModel):
tools: Sequence[Union[dict[str, Any], type, Callable, BaseTool]],
*,
tool_choice: Optional[
- Union[dict[str, str], Literal["any", "auto"], str]
+ Union[dict[str, str], Literal["any", "auto"], str] # noqa: PYI051
] = None,
parallel_tool_calls: Optional[bool] = None,
**kwargs: Any,
@@ -1716,10 +1737,13 @@ class ChatAnthropic(BaseChatModel):
elif isinstance(tool_choice, str):
kwargs["tool_choice"] = {"type": "tool", "name": tool_choice}
else:
- raise ValueError(
+ msg = (
f"Unrecognized 'tool_choice' type {tool_choice=}. Expected dict, "
f"str, or None."
)
+ raise ValueError(
+ msg,
+ )
if parallel_tool_calls is not None:
disable_parallel_tool_use = not parallel_tool_calls
@@ -1861,7 +1885,8 @@ class ChatAnthropic(BaseChatModel):
tool_name = formatted_tool["name"]
if self.thinking is not None and self.thinking.get("type") == "enabled":
llm = self._get_llm_for_structured_output_when_thinking_is_enabled(
- schema, formatted_tool
+ schema,
+ formatted_tool,
)
else:
llm = self.bind_tools(
@@ -1875,24 +1900,27 @@ class ChatAnthropic(BaseChatModel):
if isinstance(schema, type) and is_basemodel_subclass(schema):
output_parser: OutputParserLike = PydanticToolsParser(
- tools=[schema], first_tool_only=True
+ tools=[schema],
+ first_tool_only=True,
)
else:
output_parser = JsonOutputKeyToolsParser(
- key_name=tool_name, first_tool_only=True
+ key_name=tool_name,
+ first_tool_only=True,
)
if include_raw:
parser_assign = RunnablePassthrough.assign(
- parsed=itemgetter("raw") | output_parser, parsing_error=lambda _: None
+ parsed=itemgetter("raw") | output_parser,
+ parsing_error=lambda _: None,
)
parser_none = RunnablePassthrough.assign(parsed=lambda _: None)
parser_with_fallback = parser_assign.with_fallbacks(
- [parser_none], exception_key="parsing_error"
+ [parser_none],
+ exception_key="parsing_error",
)
return RunnableMap(raw=llm) | parser_with_fallback
- else:
- return llm | output_parser
+ return llm | output_parser
@beta()
def get_num_tokens_from_messages(
@@ -1909,6 +1937,8 @@ class ChatAnthropic(BaseChatModel):
messages: The message inputs to tokenize.
tools: If provided, sequence of dict, BaseModel, function, or BaseTools
to be converted to tool schemas.
+ kwargs: Additional keyword arguments are passed to the
+ :meth:`~langchain_anthropic.chat_models.ChatAnthropic.bind` method.
Basic usage:
@@ -1985,7 +2015,7 @@ def convert_to_anthropic_tool(
if isinstance(tool, dict) and all(
k in tool for k in ("name", "description", "input_schema")
):
- anthropic_formatted = AnthropicTool(tool) # type: ignore
+ anthropic_formatted = AnthropicTool(tool) # type: ignore[misc]
else:
oai_formatted = convert_to_openai_tool(tool)["function"]
anthropic_formatted = AnthropicTool(
@@ -2032,17 +2062,15 @@ class _AnthropicToolUse(TypedDict):
def _lc_tool_calls_to_anthropic_tool_use_blocks(
tool_calls: list[ToolCall],
) -> list[_AnthropicToolUse]:
- blocks = []
- for tool_call in tool_calls:
- blocks.append(
- _AnthropicToolUse(
- type="tool_use",
- name=tool_call["name"],
- input=tool_call["args"],
- id=cast(str, tool_call["id"]),
- )
+ return [
+ _AnthropicToolUse(
+ type="tool_use",
+ name=tool_call["name"],
+ input=tool_call["args"],
+ id=cast(str, tool_call["id"]),
)
- return blocks
+ for tool_call in tool_calls
+ ]
def _make_message_chunk_from_anthropic_event(
@@ -2107,7 +2135,7 @@ def _make_message_chunk_from_anthropic_event(
tool_call_chunks = []
message_chunk = AIMessageChunk(
content=[content_block],
- tool_call_chunks=tool_call_chunks, # type: ignore
+ tool_call_chunks=tool_call_chunks,
)
block_start_event = event
elif event.type == "content_block_delta":
@@ -2122,14 +2150,10 @@ def _make_message_chunk_from_anthropic_event(
if "citation" in content_block:
content_block["citations"] = [content_block.pop("citation")]
message_chunk = AIMessageChunk(content=[content_block])
- elif event.delta.type == "thinking_delta":
- content_block = event.delta.model_dump()
- if "text" in content_block and content_block["text"] is None:
- content_block.pop("text")
- content_block["index"] = event.index
- content_block["type"] = "thinking"
- message_chunk = AIMessageChunk(content=[content_block])
- elif event.delta.type == "signature_delta":
+ elif (
+ event.delta.type == "thinking_delta"
+ or event.delta.type == "signature_delta"
+ ):
content_block = event.delta.model_dump()
if "text" in content_block and content_block["text"] is None:
content_block.pop("text")
@@ -2155,7 +2179,7 @@ def _make_message_chunk_from_anthropic_event(
tool_call_chunks = []
message_chunk = AIMessageChunk(
content=[content_block],
- tool_call_chunks=tool_call_chunks, # type: ignore
+ tool_call_chunks=tool_call_chunks,
)
elif event.type == "message_delta" and stream_usage:
usage_metadata = UsageMetadata(
diff --git a/libs/partners/anthropic/langchain_anthropic/experimental.py b/libs/partners/anthropic/langchain_anthropic/experimental.py
index 5f4c8a4d23c..ec30ddcf687 100644
--- a/libs/partners/anthropic/langchain_anthropic/experimental.py
+++ b/libs/partners/anthropic/langchain_anthropic/experimental.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
from typing import (
Any,
@@ -66,7 +68,7 @@ def get_system_message(tools: list[dict]) -> str:
parameter_description=parameter.get("description"),
)
for name, parameter in tool["parameters"]["properties"].items()
- ]
+ ],
),
}
for tool in tools
@@ -79,7 +81,7 @@ def get_system_message(tools: list[dict]) -> str:
formatted_parameters=tool["formatted_parameters"],
)
for tool in tools_data
- ]
+ ],
)
return SYSTEM_PROMPT_FORMAT.format(formatted_tools=tools_formatted)
@@ -111,18 +113,20 @@ def _xml_to_function_call(invoke: Any, tools: list[dict]) -> dict[str, Any]:
if len(filtered_tools) > 0 and not isinstance(arguments, str):
tool = filtered_tools[0]
for key, value in arguments.items():
- if key in tool["parameters"]["properties"]:
- if "type" in tool["parameters"]["properties"][key]:
- if tool["parameters"]["properties"][key][
- "type"
- ] == "array" and not isinstance(value, list):
- arguments[key] = [value]
- if (
- tool["parameters"]["properties"][key]["type"] != "object"
- and isinstance(value, dict)
- and len(value.keys()) == 1
- ):
- arguments[key] = list(value.values())[0]
+ if (
+ key in tool["parameters"]["properties"]
+ and "type" in tool["parameters"]["properties"][key]
+ ):
+ if tool["parameters"]["properties"][key][
+ "type"
+ ] == "array" and not isinstance(value, list):
+ arguments[key] = [value]
+ if (
+ tool["parameters"]["properties"][key]["type"] != "object"
+ and isinstance(value, dict)
+ and len(value.keys()) == 1
+ ):
+ arguments[key] = next(iter(value.values()))
return {
"function": {
@@ -134,9 +138,7 @@ def _xml_to_function_call(invoke: Any, tools: list[dict]) -> dict[str, Any]:
def _xml_to_tool_calls(elem: Any, tools: list[dict]) -> list[dict[str, Any]]:
- """
- Convert an XML element and its children into a dictionary of dictionaries.
- """
+ """Convert an XML element and its children into a dictionary of dictionaries."""
invokes = elem.findall("invoke")
return [_xml_to_function_call(invoke, tools) for invoke in invokes]
diff --git a/libs/partners/anthropic/langchain_anthropic/llms.py b/libs/partners/anthropic/langchain_anthropic/llms.py
index cc2c0777b1f..772a1ea1a8c 100644
--- a/libs/partners/anthropic/langchain_anthropic/llms.py
+++ b/libs/partners/anthropic/langchain_anthropic/llms.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import re
import warnings
from collections.abc import AsyncIterator, Iterator, Mapping
@@ -85,8 +87,7 @@ class _AnthropicCommon(BaseLanguageModel):
@classmethod
def build_extra(cls, values: dict) -> Any:
all_required_field_names = get_pydantic_field_names(cls)
- values = _build_model_kwargs(values, all_required_field_names)
- return values
+ return _build_model_kwargs(values, all_required_field_names)
@model_validator(mode="after")
def validate_environment(self) -> Self:
@@ -125,11 +126,12 @@ class _AnthropicCommon(BaseLanguageModel):
@property
def _identifying_params(self) -> Mapping[str, Any]:
"""Get the identifying parameters."""
- return {**{}, **self._default_params}
+ return {**self._default_params}
def _get_anthropic_stop(self, stop: Optional[list[str]] = None) -> list[str]:
if not self.HUMAN_PROMPT or not self.AI_PROMPT:
- raise NameError("Please ensure the anthropic package is loaded")
+ msg = "Please ensure the anthropic package is loaded"
+ raise NameError(msg)
if stop is None:
stop = []
@@ -152,6 +154,7 @@ class AnthropicLLM(LLM, _AnthropicCommon):
from langchain_anthropic import AnthropicLLM
model = AnthropicLLM()
+
"""
model_config = ConfigDict(
@@ -166,7 +169,7 @@ class AnthropicLLM(LLM, _AnthropicCommon):
warnings.warn(
"This Anthropic LLM is deprecated. "
"Please use `from langchain_anthropic import ChatAnthropic` "
- "instead"
+ "instead",
)
return values
@@ -199,7 +202,9 @@ class AnthropicLLM(LLM, _AnthropicCommon):
}
def _get_ls_params(
- self, stop: Optional[list[str]] = None, **kwargs: Any
+ self,
+ stop: Optional[list[str]] = None,
+ **kwargs: Any,
) -> LangSmithParams:
"""Get standard params for tracing."""
params = super()._get_ls_params(stop=stop, **kwargs)
@@ -213,7 +218,8 @@ class AnthropicLLM(LLM, _AnthropicCommon):
def _wrap_prompt(self, prompt: str) -> str:
if not self.HUMAN_PROMPT or not self.AI_PROMPT:
- raise NameError("Please ensure the anthropic package is loaded")
+ msg = "Please ensure the anthropic package is loaded"
+ raise NameError(msg)
if prompt.startswith(self.HUMAN_PROMPT):
return prompt # Already wrapped.
@@ -238,6 +244,8 @@ class AnthropicLLM(LLM, _AnthropicCommon):
Args:
prompt: The prompt to pass into the model.
stop: Optional list of stop words to use when generating.
+ run_manager: Optional callback manager for LLM run.
+ kwargs: Additional keyword arguments to pass to the model.
Returns:
The string generated by the model.
@@ -253,7 +261,10 @@ class AnthropicLLM(LLM, _AnthropicCommon):
if self.streaming:
completion = ""
for chunk in self._stream(
- prompt=prompt, stop=stop, run_manager=run_manager, **kwargs
+ prompt=prompt,
+ stop=stop,
+ run_manager=run_manager,
+ **kwargs,
):
completion += chunk.text
return completion
@@ -281,7 +292,10 @@ class AnthropicLLM(LLM, _AnthropicCommon):
if self.streaming:
completion = ""
async for chunk in self._astream(
- prompt=prompt, stop=stop, run_manager=run_manager, **kwargs
+ prompt=prompt,
+ stop=stop,
+ run_manager=run_manager,
+ **kwargs,
):
completion += chunk.text
return completion
@@ -308,8 +322,12 @@ class AnthropicLLM(LLM, _AnthropicCommon):
Args:
prompt: The prompt to pass into the model.
stop: Optional list of stop words to use when generating.
+ run_manager: Optional callback manager for LLM run.
+ kwargs: Additional keyword arguments to pass to the model.
+
Returns:
A generator representing the stream of tokens from Anthropic.
+
Example:
.. code-block:: python
@@ -319,12 +337,16 @@ class AnthropicLLM(LLM, _AnthropicCommon):
generator = anthropic.stream(prompt)
for token in generator:
yield token
+
"""
stop = self._get_anthropic_stop(stop)
params = {**self._default_params, **kwargs}
for token in self.client.completions.create(
- prompt=self._wrap_prompt(prompt), stop_sequences=stop, stream=True, **params
+ prompt=self._wrap_prompt(prompt),
+ stop_sequences=stop,
+ stream=True,
+ **params,
):
chunk = GenerationChunk(text=token.completion)
@@ -344,8 +366,12 @@ class AnthropicLLM(LLM, _AnthropicCommon):
Args:
prompt: The prompt to pass into the model.
stop: Optional list of stop words to use when generating.
+ run_manager: Optional callback manager for LLM run.
+ kwargs: Additional keyword arguments to pass to the model.
+
Returns:
A generator representing the stream of tokens from Anthropic.
+
Example:
.. code-block:: python
@@ -354,6 +380,7 @@ class AnthropicLLM(LLM, _AnthropicCommon):
generator = anthropic.stream(prompt)
for token in generator:
yield token
+
"""
stop = self._get_anthropic_stop(stop)
params = {**self._default_params, **kwargs}
@@ -372,15 +399,16 @@ class AnthropicLLM(LLM, _AnthropicCommon):
def get_num_tokens(self, text: str) -> int:
"""Calculate number of tokens."""
- raise NotImplementedError(
+ msg = (
"Anthropic's legacy count_tokens method was removed in anthropic 0.39.0 "
"and langchain-anthropic 0.3.0. Please use "
"ChatAnthropic.get_num_tokens_from_messages instead."
)
+ raise NotImplementedError(
+ msg,
+ )
@deprecated(since="0.1.0", removal="1.0.0", alternative="AnthropicLLM")
class Anthropic(AnthropicLLM):
"""Anthropic large language model."""
-
- pass
diff --git a/libs/partners/anthropic/langchain_anthropic/output_parsers.py b/libs/partners/anthropic/langchain_anthropic/output_parsers.py
index 58c052cd175..83b8b0e8779 100644
--- a/libs/partners/anthropic/langchain_anthropic/output_parsers.py
+++ b/libs/partners/anthropic/langchain_anthropic/output_parsers.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from typing import Any, Optional, Union, cast
from langchain_core.messages import AIMessage, ToolCall
@@ -27,8 +29,12 @@ class ToolsOutputParser(BaseGenerationOutputParser):
Args:
result: A list of Generations to be parsed. The Generations are assumed
to be different candidate outputs for a single model input.
+ partial: (Not used) Whether the result is a partial result. If True, the
+ parser may return a partial result, which may not be complete or valid.
+
Returns:
Structured output.
+
"""
if not result or not isinstance(result[0], ChatGeneration):
return None if self.first_tool_only else []
@@ -53,8 +59,7 @@ class ToolsOutputParser(BaseGenerationOutputParser):
if self.first_tool_only:
return tool_calls[0] if tool_calls else None
- else:
- return [tool_call for tool_call in tool_calls]
+ return list(tool_calls)
def _pydantic_parse(self, tool_call: dict) -> BaseModel:
cls_ = {schema.__name__: schema for schema in self.pydantic_schemas or []}[
@@ -80,8 +85,7 @@ def extract_tool_calls(content: Union[str, list[Union[str, dict]]]) -> list[Tool
if block["type"] != "tool_use":
continue
tool_calls.append(
- tool_call(name=block["name"], args=block["input"], id=block["id"])
+ tool_call(name=block["name"], args=block["input"], id=block["id"]),
)
return tool_calls
- else:
- return []
+ return []
diff --git a/libs/partners/anthropic/pyproject.toml b/libs/partners/anthropic/pyproject.toml
index 42566e0bfd0..1c97d9d0220 100644
--- a/libs/partners/anthropic/pyproject.toml
+++ b/libs/partners/anthropic/pyproject.toml
@@ -60,8 +60,58 @@ plugins = ['pydantic.mypy']
target-version = "py39"
[tool.ruff.lint]
-select = ["E", "F", "I", "T201", "UP", "S"]
-ignore = [ "UP007", ]
+select = [
+ "A", # flake8-builtins
+ "ASYNC", # flake8-async
+ "C4", # flake8-comprehensions
+ "COM", # flake8-commas
+ "D", # pydocstyle
+ "DOC", # pydoclint
+ "E", # pycodestyle error
+ "EM", # flake8-errmsg
+ "F", # pyflakes
+ "FA", # flake8-future-annotations
+ "FBT", # flake8-boolean-trap
+ "FLY", # flake8-flynt
+ "I", # isort
+ "ICN", # flake8-import-conventions
+ "INT", # flake8-gettext
+ "ISC", # isort-comprehensions
+ "PGH", # pygrep-hooks
+ "PIE", # flake8-pie
+ "PERF", # flake8-perf
+ "PYI", # flake8-pyi
+ "Q", # flake8-quotes
+ "RET", # flake8-return
+ "RSE", # flake8-rst-docstrings
+ "RUF", # ruff
+ "S", # flake8-bandit
+ "SLF", # flake8-self
+ "SLOT", # flake8-slots
+ "SIM", # flake8-simplify
+ "T10", # flake8-debugger
+ "T20", # flake8-print
+ "TID", # flake8-tidy-imports
+ "UP", # pyupgrade
+ "W", # pycodestyle warning
+ "YTT", # flake8-2020
+]
+ignore = [
+ "D100", # Missing docstring in public module
+ "D101", # Missing docstring in public class
+ "D102", # Missing docstring in public method
+ "D103", # Missing docstring in public function
+ "D104", # Missing docstring in public package
+ "D105", # Missing docstring in magic method
+ "D107", # Missing docstring in __init__
+ "D214", # Section over-indented, doesn't play well with reStructuredText
+ "COM812", # Messes with the formatter
+ "ISC001", # Messes with the formatter
+ "PERF203", # Rarely useful
+ "UP007", # non-pep604-annotation-union
+ "UP045", # non-pep604-annotation-optional
+ "SIM105", # Rarely useful
+]
[tool.coverage.run]
omit = ["tests/*"]
@@ -78,4 +128,5 @@ asyncio_mode = "auto"
"tests/**/*.py" = [
"S101", # Tests need assertions
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
+ "SLF001", # Private member access in tests
]
diff --git a/libs/partners/anthropic/tests/conftest.py b/libs/partners/anthropic/tests/conftest.py
index 8a72f4ba99b..d008846ec3c 100644
--- a/libs/partners/anthropic/tests/conftest.py
+++ b/libs/partners/anthropic/tests/conftest.py
@@ -20,9 +20,7 @@ def remove_response_headers(response: dict) -> dict:
@pytest.fixture(scope="session")
def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811
- """
- Extend the default configuration coming from langchain_tests.
- """
+ """Extend the default configuration coming from langchain_tests."""
config = _base_vcr_config.copy()
config["before_record_request"] = remove_request_headers
config["before_record_response"] = remove_response_headers
diff --git a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py
index a9e6a954232..d5ea341cb5a 100644
--- a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py
+++ b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py
@@ -1,5 +1,7 @@
"""Test ChatAnthropic chat model."""
+from __future__ import annotations
+
import asyncio
import json
import os
@@ -43,10 +45,7 @@ def test_stream() -> None:
chunks_with_model_name = 0
for token in llm.stream("I'm Pickle Rick"):
assert isinstance(token.content, str)
- if full is None:
- full = cast(BaseMessageChunk, token)
- else:
- full = full + token
+ full = cast(BaseMessageChunk, token) if full is None else full + token
assert isinstance(token, AIMessageChunk)
if token.usage_metadata is not None:
if token.usage_metadata.get("input_tokens"):
@@ -55,11 +54,14 @@ def test_stream() -> None:
chunks_with_output_token_counts += 1
chunks_with_model_name += int("model_name" in token.response_metadata)
if chunks_with_input_token_counts != 1 or chunks_with_output_token_counts != 1:
- raise AssertionError(
+ msg = (
"Expected exactly one chunk with input or output token counts. "
"AIMessageChunk aggregation adds counts. Check that "
"this is behaving properly."
)
+ raise AssertionError(
+ msg,
+ )
assert chunks_with_model_name == 1
# check token usage is populated
assert isinstance(full, AIMessageChunk)
@@ -85,10 +87,7 @@ async def test_astream() -> None:
chunks_with_output_token_counts = 0
async for token in llm.astream("I'm Pickle Rick"):
assert isinstance(token.content, str)
- if full is None:
- full = cast(BaseMessageChunk, token)
- else:
- full = full + token
+ full = cast(BaseMessageChunk, token) if full is None else full + token
assert isinstance(token, AIMessageChunk)
if token.usage_metadata is not None:
if token.usage_metadata.get("input_tokens"):
@@ -96,11 +95,14 @@ async def test_astream() -> None:
if token.usage_metadata.get("output_tokens"):
chunks_with_output_token_counts += 1
if chunks_with_input_token_counts != 1 or chunks_with_output_token_counts != 1:
- raise AssertionError(
+ msg = (
"Expected exactly one chunk with input or output token counts. "
"AIMessageChunk aggregation adds counts. Check that "
"this is behaving properly."
)
+ raise AssertionError(
+ msg,
+ )
# check token usage is populated
assert isinstance(full, AIMessageChunk)
assert full.usage_metadata is not None
@@ -167,7 +169,8 @@ async def test_abatch_tags() -> None:
llm = ChatAnthropicMessages(model_name=MODEL_NAME) # type: ignore[call-arg, call-arg]
result = await llm.abatch(
- ["I'm Pickle Rick", "I'm not Pickle Rick"], config={"tags": ["foo"]}
+ ["I'm Pickle Rick", "I'm not Pickle Rick"],
+ config={"tags": ["foo"]},
)
for token in result:
assert isinstance(token.content, str)
@@ -187,8 +190,8 @@ async def test_async_tool_use() -> None:
"type": "object",
"properties": {"location": {"type": "string"}},
},
- }
- ]
+ },
+ ],
)
response = await llm_with_tools.ainvoke("what's the weather in san francisco, ca")
assert isinstance(response, AIMessage)
@@ -202,16 +205,16 @@ async def test_async_tool_use() -> None:
# Test streaming
first = True
- chunks = [] # type: ignore
+ chunks: list[BaseMessage | BaseMessageChunk] = []
async for chunk in llm_with_tools.astream(
- "what's the weather in san francisco, ca"
+ "what's the weather in san francisco, ca",
):
- chunks = chunks + [chunk]
+ chunks = [*chunks, chunk]
if first:
gathered = chunk
first = False
else:
- gathered = gathered + chunk # type: ignore
+ gathered = gathered + chunk # type: ignore[assignment]
assert len(chunks) > 1
assert isinstance(gathered, AIMessageChunk)
assert isinstance(gathered.tool_call_chunks, list)
@@ -244,12 +247,12 @@ def test_invoke() -> None:
"""Test invoke tokens from ChatAnthropicMessages."""
llm = ChatAnthropicMessages(model_name=MODEL_NAME) # type: ignore[call-arg, call-arg]
- result = llm.invoke("I'm Pickle Rick", config=dict(tags=["foo"]))
+ result = llm.invoke("I'm Pickle Rick", config={"tags": ["foo"]})
assert isinstance(result.content, str)
def test_system_invoke() -> None:
- """Test invoke tokens with a system message"""
+ """Test invoke tokens with a system message."""
llm = ChatAnthropicMessages(model_name=MODEL_NAME) # type: ignore[call-arg, call-arg]
prompt = ChatPromptTemplate.from_messages(
@@ -260,7 +263,7 @@ def test_system_invoke() -> None:
"STAY IN CHARACTER",
),
("human", "Are you a mathematician?"),
- ]
+ ],
)
chain = prompt | llm
@@ -282,7 +285,7 @@ def test_anthropic_generate() -> None:
"""Test generate method of anthropic."""
chat = ChatAnthropic(model=MODEL_NAME)
chat_messages: list[list[BaseMessage]] = [
- [HumanMessage(content="How many toes do dogs have?")]
+ [HumanMessage(content="How many toes do dogs have?")],
]
messages_copy = [messages.copy() for messages in chat_messages]
result: LLMResult = chat.generate(chat_messages)
@@ -330,7 +333,7 @@ async def test_anthropic_async_streaming_callback() -> None:
verbose=True,
)
chat_messages: list[BaseMessage] = [
- HumanMessage(content="How many toes do dogs have?")
+ HumanMessage(content="How many toes do dogs have?"),
]
async for token in chat.astream(chat_messages):
assert isinstance(token, AIMessageChunk)
@@ -352,8 +355,8 @@ def test_anthropic_multimodal() -> None:
},
},
{"type": "text", "text": "What is this a logo for?"},
- ]
- )
+ ],
+ ),
]
response = chat.invoke(messages)
assert isinstance(response, AIMessage)
@@ -368,7 +371,9 @@ def test_streaming() -> None:
callback_manager = CallbackManager([callback_handler])
llm = ChatAnthropicMessages( # type: ignore[call-arg, call-arg]
- model_name=MODEL_NAME, streaming=True, callback_manager=callback_manager
+ model_name=MODEL_NAME,
+ streaming=True,
+ callback_manager=callback_manager,
)
response = llm.generate([[HumanMessage(content="I'm Pickle Rick")]])
@@ -382,7 +387,9 @@ async def test_astreaming() -> None:
callback_manager = CallbackManager([callback_handler])
llm = ChatAnthropicMessages( # type: ignore[call-arg, call-arg]
- model_name=MODEL_NAME, streaming=True, callback_manager=callback_manager
+ model_name=MODEL_NAME,
+ streaming=True,
+ callback_manager=callback_manager,
)
response = await llm.agenerate([[HumanMessage(content="I'm Pickle Rick")]])
@@ -421,19 +428,19 @@ def test_tool_use() -> None:
temperature=0,
# Add extra headers to also test token-efficient tools
model_kwargs={
- "extra_headers": {"anthropic-beta": "token-efficient-tools-2025-02-19"}
+ "extra_headers": {"anthropic-beta": "token-efficient-tools-2025-02-19"},
},
)
llm_with_tools = llm.bind_tools([tool_definition])
first = True
- chunks = [] # type: ignore
+ chunks: list[BaseMessage | BaseMessageChunk] = []
for chunk in llm_with_tools.stream(query):
- chunks = chunks + [chunk]
+ chunks = [*chunks, chunk]
if first:
gathered = chunk
first = False
else:
- gathered = gathered + chunk # type: ignore
+ gathered = gathered + chunk # type: ignore[assignment]
assert len(chunks) > 1
assert isinstance(gathered.content, list)
assert len(gathered.content) == 2
@@ -470,17 +477,17 @@ def test_tool_use() -> None:
query,
gathered,
ToolMessage(content="sunny and warm", tool_call_id=tool_call["id"]),
- ]
+ ],
)
- chunks = [] # type: ignore
+ chunks = []
first = True
for chunk in stream:
- chunks = chunks + [chunk]
+ chunks = [*chunks, chunk]
if first:
gathered = chunk
first = False
else:
- gathered = gathered + chunk # type: ignore
+ gathered = gathered + chunk # type: ignore[assignment]
assert len(chunks) > 1
@@ -489,14 +496,14 @@ def test_builtin_tools() -> None:
tool = {"type": "text_editor_20250124", "name": "str_replace_editor"}
llm_with_tools = llm.bind_tools([tool])
response = llm_with_tools.invoke(
- "There's a syntax error in my primes.py file. Can you help me fix it?"
+ "There's a syntax error in my primes.py file. Can you help me fix it?",
)
assert isinstance(response, AIMessage)
assert response.tool_calls
class GenerateUsername(BaseModel):
- "Get a username based on someone's name and hair color."
+ """Get a username based on someone's name and hair color."""
name: str
hair_color: str
@@ -508,7 +515,7 @@ def test_disable_parallel_tool_calling() -> None:
result = llm_with_tools.invoke(
"Use the GenerateUsername tool to generate user names for:\n\n"
"Sally with green hair\n"
- "Bob with blue hair"
+ "Bob with blue hair",
)
assert isinstance(result, AIMessage)
assert len(result.tool_calls) == 1
@@ -523,7 +530,7 @@ def test_anthropic_with_empty_text_block() -> None:
return "OK"
model = ChatAnthropic(model="claude-3-opus-20240229", temperature=0).bind_tools(
- [type_letter]
+ [type_letter],
)
messages = [
@@ -531,7 +538,7 @@ def test_anthropic_with_empty_text_block() -> None:
content="Repeat the given string using the provided tools. Do not write "
"anything else or provide any explanations. For example, "
"if the string is 'abc', you must print the "
- "letters 'a', 'b', and 'c' one at a time and in that order. "
+ "letters 'a', 'b', and 'c' one at a time and in that order. ",
),
HumanMessage(content="dog"),
AIMessage(
@@ -572,7 +579,7 @@ def test_with_structured_output() -> None:
"type": "object",
"properties": {"location": {"type": "string"}},
},
- }
+ },
)
response = structured_llm.invoke("what's the weather in san francisco, ca")
assert isinstance(response, dict)
@@ -593,10 +600,11 @@ def test_get_num_tokens_from_messages() -> None:
# Test tool use
@tool(parse_docstring=True)
def get_weather(location: str) -> str:
- """Get the current weather in a given location
+ """Get the current weather in a given location.
Args:
location: The city and state, e.g. San Francisco, CA
+
"""
return "Sunny"
@@ -634,7 +642,7 @@ def test_get_num_tokens_from_messages() -> None:
class GetWeather(BaseModel):
- """Get the current weather in a given location"""
+ """Get the current weather in a given location."""
location: str = Field(..., description="The city and state, e.g. San Francisco, CA")
@@ -666,9 +674,9 @@ def test_pdf_document_input() -> None:
"media_type": "application/pdf",
},
},
- ]
- )
- ]
+ ],
+ ),
+ ],
)
assert isinstance(result, AIMessage)
assert isinstance(result.content, str)
@@ -694,7 +702,7 @@ def test_citations() -> None:
},
{"type": "text", "text": "What color is the grass and sky?"},
],
- }
+ },
]
response = llm.invoke(messages)
assert isinstance(response, AIMessage)
@@ -704,10 +712,7 @@ def test_citations() -> None:
# Test streaming
full: Optional[BaseMessageChunk] = None
for chunk in llm.stream(messages):
- if full is None:
- full = cast(BaseMessageChunk, chunk)
- else:
- full = full + chunk
+ full = cast(BaseMessageChunk, chunk) if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert isinstance(full.content, list)
assert any("citations" in block for block in full.content)
@@ -718,7 +723,7 @@ def test_citations() -> None:
"role": "user",
"content": "Can you comment on the citations you just made?",
}
- _ = llm.invoke(messages + [full, next_message])
+ _ = llm.invoke([*messages, full, next_message])
@pytest.mark.vcr
@@ -742,10 +747,7 @@ def test_thinking() -> None:
# Test streaming
full: Optional[BaseMessageChunk] = None
for chunk in llm.stream([input_message]):
- if full is None:
- full = cast(BaseMessageChunk, chunk)
- else:
- full = full + chunk
+ full = cast(BaseMessageChunk, chunk) if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert isinstance(full.content, list)
assert any("thinking" in block for block in full.content)
@@ -784,10 +786,7 @@ def test_redacted_thinking() -> None:
# Test streaming
full: Optional[BaseMessageChunk] = None
for chunk in llm.stream([input_message]):
- if full is None:
- full = cast(BaseMessageChunk, chunk)
- else:
- full = full + chunk
+ full = cast(BaseMessageChunk, chunk) if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert isinstance(full.content, list)
stream_has_reasoning = False
@@ -864,7 +863,7 @@ def test_image_tool_calling() -> None:
"media_type": "image/jpeg",
"data": image_data,
},
- }
+ },
)
messages = [
SystemMessage("you're a good assistant"),
@@ -878,7 +877,7 @@ def test_image_tool_calling() -> None:
"id": "foo",
"name": "color_picker",
},
- ]
+ ],
),
HumanMessage(
[
@@ -889,12 +888,12 @@ def test_image_tool_calling() -> None:
{
"type": "text",
"text": "green is a great pick! that's my sister's favorite color", # noqa: E501
- }
+ },
],
"is_error": False,
},
{"type": "text", "text": "what's my sister's favorite color"},
- ]
+ ],
),
]
llm = ChatAnthropic(model="claude-3-5-sonnet-latest")
@@ -914,7 +913,7 @@ def test_web_search() -> None:
{
"type": "text",
"text": "How do I update a web app to TypeScript 5.5?",
- }
+ },
],
}
response = llm_with_tools.invoke([input_message])
@@ -962,7 +961,7 @@ def test_code_execution() -> None:
"Calculate the mean and standard deviation of "
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
),
- }
+ },
],
}
response = llm_with_tools.invoke([input_message])
@@ -999,7 +998,7 @@ def test_remote_mcp() -> None:
"name": "deepwiki",
"tool_configuration": {"enabled": True, "allowed_tools": ["ask_question"]},
"authorization_token": "PLACEHOLDER",
- }
+ },
]
llm = ChatAnthropic(
@@ -1018,7 +1017,7 @@ def test_remote_mcp() -> None:
"What transport protocols does the 2025-03-26 version of the MCP "
"spec (modelcontextprotocol/modelcontextprotocol) support?"
),
- }
+ },
],
}
response = llm.invoke([input_message])
@@ -1132,9 +1131,9 @@ def test_search_result_tool_message() -> None:
"To request vacation days, submit a leave request form "
"through the HR portal. Approval will be sent by email."
),
- }
+ },
],
- }
+ },
]
tool_call = {
@@ -1182,7 +1181,7 @@ def test_search_result_top_level() -> None:
"To request vacation days, submit a leave request form "
"through the HR portal. Approval will be sent by email."
),
- }
+ },
],
},
{
@@ -1194,14 +1193,14 @@ def test_search_result_top_level() -> None:
{
"type": "text",
"text": "Managers have 3 days to approve a request.",
- }
+ },
],
},
{
"type": "text",
"text": "How do I request vacation days?",
},
- ]
+ ],
)
result = llm.invoke([input_message])
assert isinstance(result, AIMessage)
diff --git a/libs/partners/anthropic/tests/integration_tests/test_compile.py b/libs/partners/anthropic/tests/integration_tests/test_compile.py
index 33ecccdfa0f..f315e45f521 100644
--- a/libs/partners/anthropic/tests/integration_tests/test_compile.py
+++ b/libs/partners/anthropic/tests/integration_tests/test_compile.py
@@ -4,4 +4,3 @@ import pytest
@pytest.mark.compile
def test_placeholder() -> None:
"""Used for compiling integration tests without running any real tests."""
- pass
diff --git a/libs/partners/anthropic/tests/integration_tests/test_experimental.py b/libs/partners/anthropic/tests/integration_tests/test_experimental.py
index 67f5cd23ae8..beca1150eb4 100644
--- a/libs/partners/anthropic/tests/integration_tests/test_experimental.py
+++ b/libs/partners/anthropic/tests/integration_tests/test_experimental.py
@@ -1,5 +1,7 @@
"""Test ChatAnthropic chat model."""
+from __future__ import annotations
+
from enum import Enum
from typing import Optional
@@ -46,7 +48,8 @@ async def test_abatch_tags() -> None:
llm = ChatAnthropicTools(model_name=MODEL_NAME) # type: ignore[call-arg, call-arg]
result = await llm.abatch(
- ["I'm Pickle Rick", "I'm not Pickle Rick"], config={"tags": ["foo"]}
+ ["I'm Pickle Rick", "I'm not Pickle Rick"],
+ config={"tags": ["foo"]},
)
for token in result:
assert isinstance(token.content, str)
@@ -73,12 +76,12 @@ def test_invoke() -> None:
"""Test invoke tokens from ChatAnthropicTools."""
llm = ChatAnthropicTools(model_name=MODEL_NAME) # type: ignore[call-arg, call-arg]
- result = llm.invoke("I'm Pickle Rick", config=dict(tags=["foo"]))
+ result = llm.invoke("I'm Pickle Rick", config={"tags": ["foo"]})
assert isinstance(result.content, str)
def test_system_invoke() -> None:
- """Test invoke tokens with a system message"""
+ """Test invoke tokens with a system message."""
llm = ChatAnthropicTools(model_name=MODEL_NAME) # type: ignore[call-arg, call-arg]
prompt = ChatPromptTemplate.from_messages(
@@ -89,7 +92,7 @@ def test_system_invoke() -> None:
"STAY IN CHARACTER",
),
("human", "Are you a mathematician?"),
- ]
+ ],
)
chain = prompt | llm
@@ -128,19 +131,24 @@ def test_anthropic_complex_structured_output() -> None:
"""Relevant information about an email."""
sender: Optional[str] = Field(
- None, description="The sender's name, if available"
+ None,
+ description="The sender's name, if available",
)
sender_phone_number: Optional[str] = Field(
- None, description="The sender's phone number, if available"
+ None,
+ description="The sender's phone number, if available",
)
sender_address: Optional[str] = Field(
- None, description="The sender's address, if available"
+ None,
+ description="The sender's address, if available",
)
action_items: list[str] = Field(
- ..., description="A list of action items requested by the email"
+ ...,
+ description="A list of action items requested by the email",
)
topic: str = Field(
- ..., description="High level description of what the email is about"
+ ...,
+ description="High level description of what the email is about",
)
tone: ToneEnum = Field(..., description="The tone of the email.")
@@ -150,7 +158,7 @@ def test_anthropic_complex_structured_output() -> None:
"human",
"What can you tell me about the following email? Make sure to answer in the correct format: {email}", # noqa: E501
),
- ]
+ ],
)
llm = ChatAnthropicTools( # type: ignore[call-arg, call-arg]
@@ -163,7 +171,7 @@ def test_anthropic_complex_structured_output() -> None:
response = extraction_chain.invoke(
{
- "email": "From: Erick. The email is about the new project. The tone is positive. The action items are to send the report and to schedule a meeting." # noqa: E501
- }
+ "email": "From: Erick. The email is about the new project. The tone is positive. The action items are to send the report and to schedule a meeting.", # noqa: E501
+ },
)
assert isinstance(response, Email)
diff --git a/libs/partners/anthropic/tests/integration_tests/test_standard.py b/libs/partners/anthropic/tests/integration_tests/test_standard.py
index 6f4e3f4f986..686a97c1bd4 100644
--- a/libs/partners/anthropic/tests/integration_tests/test_standard.py
+++ b/libs/partners/anthropic/tests/integration_tests/test_standard.py
@@ -1,4 +1,4 @@
-"""Standard LangChain interface tests"""
+"""Standard LangChain interface tests."""
from pathlib import Path
from typing import Literal, cast
@@ -87,9 +87,9 @@ class TestAnthropicStandard(ChatModelIntegrationTests):
"type": "text",
"text": input_,
"cache_control": {"type": "ephemeral"},
- }
+ },
],
- }
+ },
],
stream,
)
@@ -118,9 +118,9 @@ class TestAnthropicStandard(ChatModelIntegrationTests):
"type": "text",
"text": input_,
"cache_control": {"type": "ephemeral"},
- }
+ },
],
- }
+ },
],
stream,
)
@@ -134,22 +134,18 @@ class TestAnthropicStandard(ChatModelIntegrationTests):
"type": "text",
"text": input_,
"cache_control": {"type": "ephemeral"},
- }
+ },
],
- }
+ },
],
stream,
)
-def _invoke(llm: ChatAnthropic, input_: list, stream: bool) -> AIMessage:
+def _invoke(llm: ChatAnthropic, input_: list, stream: bool) -> AIMessage: # noqa: FBT001
if stream:
full = None
for chunk in llm.stream(input_):
- if full is None:
- full = cast(BaseMessageChunk, chunk)
- else:
- full = full + chunk
+ full = cast(BaseMessageChunk, chunk) if full is None else full + chunk
return cast(AIMessage, full)
- else:
- return cast(AIMessage, llm.invoke(input_))
+ return cast(AIMessage, llm.invoke(input_))
diff --git a/libs/partners/anthropic/tests/unit_tests/_utils.py b/libs/partners/anthropic/tests/unit_tests/_utils.py
index 2d10ef80f51..5f0db1f9f41 100644
--- a/libs/partners/anthropic/tests/unit_tests/_utils.py
+++ b/libs/partners/anthropic/tests/unit_tests/_utils.py
@@ -1,5 +1,7 @@
"""A fake callback handler for testing purposes."""
+from __future__ import annotations
+
from typing import Any, Union
from langchain_core.callbacks import BaseCallbackHandler
@@ -252,5 +254,5 @@ class FakeCallbackHandler(BaseCallbackHandler, BaseFakeCallbackHandlerMixin):
self.on_retriever_error_common()
# Overriding since BaseModel has __deepcopy__ method as well
- def __deepcopy__(self, memo: dict) -> "FakeCallbackHandler": # type: ignore
+ def __deepcopy__(self, memo: dict) -> FakeCallbackHandler: # type: ignore[override]
return self
diff --git a/libs/partners/anthropic/tests/unit_tests/test_chat_models.py b/libs/partners/anthropic/tests/unit_tests/test_chat_models.py
index 2ca6b6cdf1f..7663c65083b 100644
--- a/libs/partners/anthropic/tests/unit_tests/test_chat_models.py
+++ b/libs/partners/anthropic/tests/unit_tests/test_chat_models.py
@@ -1,5 +1,7 @@
"""Test chat model integration."""
+from __future__ import annotations
+
import os
from typing import Any, Callable, Literal, Optional, cast
from unittest.mock import MagicMock, patch
@@ -187,7 +189,7 @@ def test__merge_messages() -> None:
"text": None,
"name": "blah",
},
- ]
+ ],
),
ToolMessage("buz output", tool_call_id="1", status="error"), # type: ignore[misc]
ToolMessage(
@@ -234,7 +236,7 @@ def test__merge_messages() -> None:
"text": None,
"name": "blah",
},
- ]
+ ],
),
HumanMessage( # type: ignore[misc]
[
@@ -266,7 +268,7 @@ def test__merge_messages() -> None:
"is_error": False,
},
{"type": "text", "text": "next thing"},
- ]
+ ],
),
]
actual = _merge_messages(messages)
@@ -277,7 +279,7 @@ def test__merge_messages() -> None:
ToolMessage("buz output", tool_call_id="1"), # type: ignore[misc]
ToolMessage( # type: ignore[misc]
content=[
- {"type": "tool_result", "content": "blah output", "tool_use_id": "2"}
+ {"type": "tool_result", "content": "blah output", "tool_use_id": "2"},
],
tool_call_id="2",
),
@@ -292,8 +294,8 @@ def test__merge_messages() -> None:
"is_error": False,
},
{"type": "tool_result", "content": "blah output", "tool_use_id": "2"},
- ]
- )
+ ],
+ ),
]
actual = _merge_messages(messages)
assert expected == actual
@@ -310,7 +312,7 @@ def test__merge_messages_mutation() -> None:
]
expected = [
HumanMessage( # type: ignore[misc]
- [{"type": "text", "text": "bar"}, {"type": "text", "text": "next thing"}]
+ [{"type": "text", "text": "bar"}, {"type": "text", "text": "next thing"}],
),
]
actual = _merge_messages(messages)
@@ -327,7 +329,7 @@ def test__format_image() -> None:
@pytest.fixture()
def pydantic() -> type[BaseModel]:
class dummy_function(BaseModel):
- """dummy function"""
+ """Dummy function."""
arg1: int = Field(..., description="foo")
arg2: Literal["bar", "baz"] = Field(..., description="one of 'bar', 'baz'")
@@ -338,13 +340,14 @@ def pydantic() -> type[BaseModel]:
@pytest.fixture()
def function() -> Callable:
def dummy_function(arg1: int, arg2: Literal["bar", "baz"]) -> None:
- """dummy function
+ """Dummy function.
Args:
+ ----
arg1: foo
arg2: one of 'bar', 'baz'
- """
- pass
+
+ """ # noqa: D401
return dummy_function
@@ -358,7 +361,7 @@ def dummy_tool() -> BaseTool:
class DummyFunction(BaseTool): # type: ignore[override]
args_schema: type[BaseModel] = Schema
name: str = "dummy_function"
- description: str = "dummy function"
+ description: str = "Dummy function."
def _run(self, *args: Any, **kwargs: Any) -> Any:
pass
@@ -370,7 +373,7 @@ def dummy_tool() -> BaseTool:
def json_schema() -> dict:
return {
"title": "dummy_function",
- "description": "dummy function",
+ "description": "Dummy function.",
"type": "object",
"properties": {
"arg1": {"description": "foo", "type": "integer"},
@@ -388,7 +391,7 @@ def json_schema() -> dict:
def openai_function() -> dict:
return {
"name": "dummy_function",
- "description": "dummy function",
+ "description": "Dummy function.",
"parameters": {
"type": "object",
"properties": {
@@ -413,7 +416,7 @@ def test_convert_to_anthropic_tool(
) -> None:
expected = {
"name": "dummy_function",
- "description": "dummy function",
+ "description": "Dummy function.",
"input_schema": {
"type": "object",
"properties": {
@@ -429,7 +432,7 @@ def test_convert_to_anthropic_tool(
}
for fn in (pydantic, function, dummy_tool, json_schema, expected, openai_function):
- actual = convert_to_anthropic_tool(fn) # type: ignore
+ actual = convert_to_anthropic_tool(fn)
assert actual == expected
@@ -461,7 +464,7 @@ def test__format_messages_with_tool_calls() -> None:
"type": "base64",
"media_type": "image/jpeg",
},
- }
+ },
],
tool_call_id="3",
)
@@ -478,7 +481,7 @@ def test__format_messages_with_tool_calls() -> None:
"name": "bar",
"id": "1",
"input": {"baz": "buzz"},
- }
+ },
],
},
{
@@ -489,7 +492,7 @@ def test__format_messages_with_tool_calls() -> None:
"content": "blurb",
"tool_use_id": "1",
"is_error": False,
- }
+ },
],
},
{
@@ -500,7 +503,7 @@ def test__format_messages_with_tool_calls() -> None:
"name": "bar",
"id": "2",
"input": {"baz": "buzz"},
- }
+ },
],
},
{
@@ -516,7 +519,7 @@ def test__format_messages_with_tool_calls() -> None:
"type": "base64",
"media_type": "image/jpeg",
},
- }
+ },
],
"tool_use_id": "2",
"is_error": False,
@@ -531,7 +534,7 @@ def test__format_messages_with_tool_calls() -> None:
"type": "base64",
"media_type": "image/jpeg",
},
- }
+ },
],
"tool_use_id": "3",
"is_error": False,
@@ -579,7 +582,7 @@ def test__format_messages_with_str_content_and_tool_calls() -> None:
"content": "blurb",
"tool_use_id": "1",
"is_error": False,
- }
+ },
],
},
],
@@ -624,7 +627,7 @@ def test__format_messages_with_list_content_and_tool_calls() -> None:
"content": "blurb",
"tool_use_id": "1",
"is_error": False,
- }
+ },
],
},
],
@@ -676,7 +679,7 @@ def test__format_messages_with_tool_use_blocks_and_tool_calls() -> None:
"content": "blurb",
"tool_use_id": "1",
"is_error": False,
- }
+ },
],
},
],
@@ -690,7 +693,7 @@ def test__format_messages_with_cache_control() -> None:
SystemMessage(
[
{"type": "text", "text": "foo", "cache_control": {"type": "ephemeral"}},
- ]
+ ],
),
HumanMessage(
[
@@ -699,11 +702,11 @@ def test__format_messages_with_cache_control() -> None:
"type": "text",
"text": "foo",
},
- ]
+ ],
),
]
expected_system = [
- {"type": "text", "text": "foo", "cache_control": {"type": "ephemeral"}}
+ {"type": "text", "text": "foo", "cache_control": {"type": "ephemeral"}},
]
expected_messages = [
{
@@ -712,7 +715,7 @@ def test__format_messages_with_cache_control() -> None:
{"type": "text", "text": "foo", "cache_control": {"type": "ephemeral"}},
{"type": "text", "text": "foo"},
],
- }
+ },
]
actual_system, actual_messages = _format_messages(messages)
assert expected_system == actual_system
@@ -733,8 +736,8 @@ def test__format_messages_with_cache_control() -> None:
"data": "",
"cache_control": {"type": "ephemeral"},
},
- ]
- )
+ ],
+ ),
]
actual_system, actual_messages = _format_messages(messages)
assert actual_system is None
@@ -756,7 +759,7 @@ def test__format_messages_with_cache_control() -> None:
"cache_control": {"type": "ephemeral"},
},
],
- }
+ },
]
assert actual_messages == expected_messages
@@ -773,8 +776,8 @@ def test__format_messages_with_citations() -> None:
"citations": {"enabled": True},
},
{"type": "text", "text": "What color is the grass and sky?"},
- ]
- )
+ ],
+ ),
]
expected_messages = [
{
@@ -791,7 +794,7 @@ def test__format_messages_with_citations() -> None:
},
{"type": "text", "text": "What color is the grass and sky?"},
],
- }
+ },
]
actual_system, actual_messages = _format_messages(input_messages)
assert actual_system is None
@@ -843,7 +846,7 @@ def test__format_messages_openai_image_format() -> None:
},
},
],
- }
+ },
]
assert actual_messages == expected_messages
@@ -856,7 +859,7 @@ def test__format_messages_with_multiple_system() -> None:
SystemMessage(
[
{"type": "text", "text": "foo", "cache_control": {"type": "ephemeral"}},
- ]
+ ],
),
]
expected_system = [
@@ -880,7 +883,8 @@ def test_anthropic_api_key_is_secret_string() -> None:
def test_anthropic_api_key_masked_when_passed_from_env(
- monkeypatch: MonkeyPatch, capsys: CaptureFixture
+ monkeypatch: MonkeyPatch,
+ capsys: CaptureFixture,
) -> None:
"""Test that the API key is masked when passed from an environment variable."""
monkeypatch.setenv("ANTHROPIC_API_KEY ", "secret-api-key")
@@ -920,7 +924,7 @@ def test_anthropic_uses_actual_secret_value_from_secretstr() -> None:
class GetWeather(BaseModel):
- """Get the current weather in a given location"""
+ """Get the current weather in a given location."""
location: str = Field(..., description="The city and state, e.g. San Francisco, CA")
@@ -931,14 +935,16 @@ def test_anthropic_bind_tools_tool_choice() -> None:
anthropic_api_key="secret-api-key",
)
chat_model_with_tools = chat_model.bind_tools(
- [GetWeather], tool_choice={"type": "tool", "name": "GetWeather"}
+ [GetWeather],
+ tool_choice={"type": "tool", "name": "GetWeather"},
)
assert cast(RunnableBinding, chat_model_with_tools).kwargs["tool_choice"] == {
"type": "tool",
"name": "GetWeather",
}
chat_model_with_tools = chat_model.bind_tools(
- [GetWeather], tool_choice="GetWeather"
+ [GetWeather],
+ tool_choice="GetWeather",
)
assert cast(RunnableBinding, chat_model_with_tools).kwargs["tool_choice"] == {
"type": "tool",
@@ -946,11 +952,11 @@ def test_anthropic_bind_tools_tool_choice() -> None:
}
chat_model_with_tools = chat_model.bind_tools([GetWeather], tool_choice="auto")
assert cast(RunnableBinding, chat_model_with_tools).kwargs["tool_choice"] == {
- "type": "auto"
+ "type": "auto",
}
chat_model_with_tools = chat_model.bind_tools([GetWeather], tool_choice="any")
assert cast(RunnableBinding, chat_model_with_tools).kwargs["tool_choice"] == {
- "type": "any"
+ "type": "any",
}
@@ -1021,7 +1027,6 @@ class FakeTracer(BaseTracer):
def _persist_run(self, run: Run) -> None:
"""Persist a run."""
- pass
def on_chat_model_start(self, *args: Any, **kwargs: Any) -> Run:
self.chat_model_start_inputs.append({"args": args, "kwargs": kwargs})
@@ -1036,7 +1041,7 @@ def test_mcp_tracing() -> None:
"url": "https://mcp.deepwiki.com/mcp",
"name": "deepwiki",
"authorization_token": "PLACEHOLDER",
- }
+ },
]
llm = ChatAnthropic(
diff --git a/libs/partners/anthropic/tests/unit_tests/test_output_parsers.py b/libs/partners/anthropic/tests/unit_tests/test_output_parsers.py
index 7bbb517c00c..988b6ff2634 100644
--- a/libs/partners/anthropic/tests/unit_tests/test_output_parsers.py
+++ b/libs/partners/anthropic/tests/unit_tests/test_output_parsers.py
@@ -95,7 +95,8 @@ def test_tools_output_parser_empty_content() -> None:
chart_type: Literal["pie", "line", "bar"]
output_parser = ToolsOutputParser(
- first_tool_only=True, pydantic_schemas=[ChartType]
+ first_tool_only=True,
+ pydantic_schemas=[ChartType],
)
message = AIMessage(
"",
@@ -105,7 +106,7 @@ def test_tools_output_parser_empty_content() -> None:
"args": {"chart_type": "pie"},
"id": "foo",
"type": "tool_call",
- }
+ },
],
)
actual = output_parser.invoke(message)
diff --git a/libs/partners/anthropic/tests/unit_tests/test_standard.py b/libs/partners/anthropic/tests/unit_tests/test_standard.py
index 78546afef9c..832dff42bf2 100644
--- a/libs/partners/anthropic/tests/unit_tests/test_standard.py
+++ b/libs/partners/anthropic/tests/unit_tests/test_standard.py
@@ -1,4 +1,4 @@
-"""Standard LangChain interface tests"""
+"""Standard LangChain interface tests."""
import pytest
from langchain_core.language_models import BaseChatModel
diff --git a/libs/partners/anthropic/uv.lock b/libs/partners/anthropic/uv.lock
index c34594318ee..445e1f60047 100644
--- a/libs/partners/anthropic/uv.lock
+++ b/libs/partners/anthropic/uv.lock
@@ -477,7 +477,7 @@ requires-dist = [
[package.metadata.requires-dev]
codespell = [{ name = "codespell", specifier = ">=2.2.0,<3.0.0" }]
dev = [{ name = "langchain-core", editable = "../../core" }]
-lint = [{ name = "ruff", specifier = ">=0.5,<1.0" }]
+lint = [{ name = "ruff", specifier = ">=0.12.2,<0.13" }]
test = [
{ name = "defusedxml", specifier = ">=0.7.1,<1.0.0" },
{ name = "freezegun", specifier = ">=1.2.2,<2.0.0" },
@@ -534,7 +534,7 @@ dev = [
{ name = "jupyter", specifier = ">=1.0.0,<2.0.0" },
{ name = "setuptools", specifier = ">=67.6.1,<68.0.0" },
]
-lint = [{ name = "ruff", specifier = ">=0.11.2,<0.12.0" }]
+lint = [{ name = "ruff", specifier = ">=0.12.2,<0.13" }]
test = [
{ name = "blockbuster", specifier = "~=1.5.18" },
{ name = "freezegun", specifier = ">=1.2.2,<2.0.0" },
@@ -598,7 +598,7 @@ requires-dist = [
[package.metadata.requires-dev]
codespell = [{ name = "codespell", specifier = ">=2.2.0,<3.0.0" }]
-lint = [{ name = "ruff", specifier = ">=0.9.2,<1.0.0" }]
+lint = [{ name = "ruff", specifier = ">=0.12.2,<0.13" }]
test = [{ name = "langchain-core", editable = "../../core" }]
test-integration = []
typing = [
@@ -1513,27 +1513,27 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.5.7"
+version = "0.12.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/bf/2b/69e5e412f9d390adbdbcbf4f64d6914fa61b44b08839a6584655014fc524/ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5", size = 2449817, upload-time = "2024-08-08T15:43:07.467Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/6c/3d/d9a195676f25d00dbfcf3cf95fdd4c685c497fcfa7e862a44ac5e4e96480/ruff-0.12.2.tar.gz", hash = "sha256:d7b4f55cd6f325cb7621244f19c873c565a08aff5a4ba9c69aa7355f3f7afd3e", size = 4432239, upload-time = "2025-07-03T16:40:19.566Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6b/eb/06e06aaf96af30a68e83b357b037008c54a2ddcbad4f989535007c700394/ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a", size = 9570571, upload-time = "2024-08-08T15:41:56.537Z" },
- { url = "https://files.pythonhosted.org/packages/a4/10/1be32aeaab8728f78f673e7a47dd813222364479b2d6573dbcf0085e83ea/ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be", size = 8685138, upload-time = "2024-08-08T15:42:02.833Z" },
- { url = "https://files.pythonhosted.org/packages/3d/1d/c218ce83beb4394ba04d05e9aa2ae6ce9fba8405688fe878b0fdb40ce855/ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e", size = 8266785, upload-time = "2024-08-08T15:42:08.321Z" },
- { url = "https://files.pythonhosted.org/packages/26/79/7f49509bd844476235b40425756def366b227a9714191c91f02fb2178635/ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8", size = 9983964, upload-time = "2024-08-08T15:42:12.419Z" },
- { url = "https://files.pythonhosted.org/packages/bf/b1/939836b70bf9fcd5e5cd3ea67fdb8abb9eac7631351d32f26544034a35e4/ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea", size = 9359490, upload-time = "2024-08-08T15:42:16.713Z" },
- { url = "https://files.pythonhosted.org/packages/32/7d/b3db19207de105daad0c8b704b2c6f2a011f9c07017bd58d8d6e7b8eba19/ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc", size = 10170833, upload-time = "2024-08-08T15:42:20.54Z" },
- { url = "https://files.pythonhosted.org/packages/a2/45/eae9da55f3357a1ac04220230b8b07800bf516e6dd7e1ad20a2ff3b03b1b/ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692", size = 10896360, upload-time = "2024-08-08T15:42:25.2Z" },
- { url = "https://files.pythonhosted.org/packages/99/67/4388b36d145675f4c51ebec561fcd4298a0e2550c81e629116f83ce45a39/ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf", size = 10477094, upload-time = "2024-08-08T15:42:29.553Z" },
- { url = "https://files.pythonhosted.org/packages/e1/9c/f5e6ed1751dc187a4ecf19a4970dd30a521c0ee66b7941c16e292a4043fb/ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb", size = 11480896, upload-time = "2024-08-08T15:42:33.772Z" },
- { url = "https://files.pythonhosted.org/packages/c8/3b/2b683be597bbd02046678fc3fc1c199c641512b20212073b58f173822bb3/ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e", size = 10179702, upload-time = "2024-08-08T15:42:38.038Z" },
- { url = "https://files.pythonhosted.org/packages/f1/38/c2d94054dc4b3d1ea4c2ba3439b2a7095f08d1c8184bc41e6abe2a688be7/ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499", size = 9982855, upload-time = "2024-08-08T15:42:42.031Z" },
- { url = "https://files.pythonhosted.org/packages/7d/e7/1433db2da505ffa8912dcf5b28a8743012ee780cbc20ad0bf114787385d9/ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e", size = 9433156, upload-time = "2024-08-08T15:42:45.339Z" },
- { url = "https://files.pythonhosted.org/packages/e0/36/4fa43250e67741edeea3d366f59a1dc993d4d89ad493a36cbaa9889895f2/ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5", size = 9782971, upload-time = "2024-08-08T15:42:49.354Z" },
- { url = "https://files.pythonhosted.org/packages/80/0e/8c276103d518e5cf9202f70630aaa494abf6fc71c04d87c08b6d3cd07a4b/ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e", size = 10247775, upload-time = "2024-08-08T15:42:53.294Z" },
- { url = "https://files.pythonhosted.org/packages/cb/b9/673096d61276f39291b729dddde23c831a5833d98048349835782688a0ec/ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a", size = 7841772, upload-time = "2024-08-08T15:42:57.488Z" },
- { url = "https://files.pythonhosted.org/packages/67/1c/4520c98bfc06b9c73cd1457686d4d3935d40046b1ddea08403e5a6deff51/ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3", size = 8699779, upload-time = "2024-08-08T15:43:00.429Z" },
- { url = "https://files.pythonhosted.org/packages/38/23/b3763a237d2523d40a31fe2d1a301191fe392dd48d3014977d079cf8c0bd/ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4", size = 8091891, upload-time = "2024-08-08T15:43:04.162Z" },
+ { url = "https://files.pythonhosted.org/packages/74/b6/2098d0126d2d3318fd5bec3ad40d06c25d377d95749f7a0c5af17129b3b1/ruff-0.12.2-py3-none-linux_armv6l.whl", hash = "sha256:093ea2b221df1d2b8e7ad92fc6ffdca40a2cb10d8564477a987b44fd4008a7be", size = 10369761, upload-time = "2025-07-03T16:39:38.847Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/4b/5da0142033dbe155dc598cfb99262d8ee2449d76920ea92c4eeb9547c208/ruff-0.12.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:09e4cf27cc10f96b1708100fa851e0daf21767e9709e1649175355280e0d950e", size = 11155659, upload-time = "2025-07-03T16:39:42.294Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/21/967b82550a503d7c5c5c127d11c935344b35e8c521f52915fc858fb3e473/ruff-0.12.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ae64755b22f4ff85e9c52d1f82644abd0b6b6b6deedceb74bd71f35c24044cc", size = 10537769, upload-time = "2025-07-03T16:39:44.75Z" },
+ { url = "https://files.pythonhosted.org/packages/33/91/00cff7102e2ec71a4890fb7ba1803f2cdb122d82787c7d7cf8041fe8cbc1/ruff-0.12.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eb3a6b2db4d6e2c77e682f0b988d4d61aff06860158fdb413118ca133d57922", size = 10717602, upload-time = "2025-07-03T16:39:47.652Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/eb/928814daec4e1ba9115858adcda44a637fb9010618721937491e4e2283b8/ruff-0.12.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73448de992d05517170fc37169cbca857dfeaeaa8c2b9be494d7bcb0d36c8f4b", size = 10198772, upload-time = "2025-07-03T16:39:49.641Z" },
+ { url = "https://files.pythonhosted.org/packages/50/fa/f15089bc20c40f4f72334f9145dde55ab2b680e51afb3b55422effbf2fb6/ruff-0.12.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b94317cbc2ae4a2771af641739f933934b03555e51515e6e021c64441532d", size = 11845173, upload-time = "2025-07-03T16:39:52.069Z" },
+ { url = "https://files.pythonhosted.org/packages/43/9f/1f6f98f39f2b9302acc161a4a2187b1e3a97634fe918a8e731e591841cf4/ruff-0.12.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45fc42c3bf1d30d2008023a0a9a0cfb06bf9835b147f11fe0679f21ae86d34b1", size = 12553002, upload-time = "2025-07-03T16:39:54.551Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/70/08991ac46e38ddd231c8f4fd05ef189b1b94be8883e8c0c146a025c20a19/ruff-0.12.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce48f675c394c37e958bf229fb5c1e843e20945a6d962cf3ea20b7a107dcd9f4", size = 12171330, upload-time = "2025-07-03T16:39:57.55Z" },
+ { url = "https://files.pythonhosted.org/packages/88/a9/5a55266fec474acfd0a1c73285f19dd22461d95a538f29bba02edd07a5d9/ruff-0.12.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793d8859445ea47591272021a81391350205a4af65a9392401f418a95dfb75c9", size = 11774717, upload-time = "2025-07-03T16:39:59.78Z" },
+ { url = "https://files.pythonhosted.org/packages/87/e5/0c270e458fc73c46c0d0f7cf970bb14786e5fdb88c87b5e423a4bd65232b/ruff-0.12.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6932323db80484dda89153da3d8e58164d01d6da86857c79f1961934354992da", size = 11646659, upload-time = "2025-07-03T16:40:01.934Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/b6/45ab96070c9752af37f0be364d849ed70e9ccede07675b0ec4e3ef76b63b/ruff-0.12.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6aa7e623a3a11538108f61e859ebf016c4f14a7e6e4eba1980190cacb57714ce", size = 10604012, upload-time = "2025-07-03T16:40:04.363Z" },
+ { url = "https://files.pythonhosted.org/packages/86/91/26a6e6a424eb147cc7627eebae095cfa0b4b337a7c1c413c447c9ebb72fd/ruff-0.12.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2a4a20aeed74671b2def096bdf2eac610c7d8ffcbf4fb0e627c06947a1d7078d", size = 10176799, upload-time = "2025-07-03T16:40:06.514Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/0c/9f344583465a61c8918a7cda604226e77b2c548daf8ef7c2bfccf2b37200/ruff-0.12.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:71a4c550195612f486c9d1f2b045a600aeba851b298c667807ae933478fcef04", size = 11241507, upload-time = "2025-07-03T16:40:08.708Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/b7/99c34ded8fb5f86c0280278fa89a0066c3760edc326e935ce0b1550d315d/ruff-0.12.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4987b8f4ceadf597c927beee65a5eaf994c6e2b631df963f86d8ad1bdea99342", size = 11717609, upload-time = "2025-07-03T16:40:10.836Z" },
+ { url = "https://files.pythonhosted.org/packages/51/de/8589fa724590faa057e5a6d171e7f2f6cffe3287406ef40e49c682c07d89/ruff-0.12.2-py3-none-win32.whl", hash = "sha256:369ffb69b70cd55b6c3fc453b9492d98aed98062db9fec828cdfd069555f5f1a", size = 10523823, upload-time = "2025-07-03T16:40:13.203Z" },
+ { url = "https://files.pythonhosted.org/packages/94/47/8abf129102ae4c90cba0c2199a1a9b0fa896f6f806238d6f8c14448cc748/ruff-0.12.2-py3-none-win_amd64.whl", hash = "sha256:dca8a3b6d6dc9810ed8f328d406516bf4d660c00caeaef36eb831cf4871b0639", size = 11629831, upload-time = "2025-07-03T16:40:15.478Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/1f/72d2946e3cc7456bb837e88000eb3437e55f80db339c840c04015a11115d/ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12", size = 10735334, upload-time = "2025-07-03T16:40:17.677Z" },
]
[[package]]