diff --git a/libs/prebuilt/LICENSE b/libs/prebuilt/LICENSE deleted file mode 100644 index fc0602fee..000000000 --- a/libs/prebuilt/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 LangChain, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/prebuilt/Makefile b/libs/prebuilt/Makefile deleted file mode 100644 index 08cd38c83..000000000 --- a/libs/prebuilt/Makefile +++ /dev/null @@ -1,78 +0,0 @@ -.PHONY: all format lint test test_watch integration_tests spell_check spell_fix benchmark profile - -# Default target executed when no arguments are given to make. -all: help - -###################### -# TESTING AND COVERAGE -###################### - -start-postgres: - docker compose -f tests/compose-postgres.yml up -V --force-recreate --wait --remove-orphans - -stop-postgres: - docker compose -f tests/compose-postgres.yml down -v - -TEST ?= . - -test: - make start-postgres && poetry run pytest $(TEST); \ - EXIT_CODE=$$?; \ - make stop-postgres; \ - exit $$EXIT_CODE - -test_watch: - make start-postgres && poetry run ptw $(TEST); \ - EXIT_CODE=$$?; \ - make stop-postgres; \ - exit $$EXIT_CODE - -###################### -# LINTING AND FORMATTING -###################### - -# Define a variable for Python and notebook files. -PYTHON_FILES=. -MYPY_CACHE=.mypy_cache -lint format: PYTHON_FILES=. -lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$') -lint_package: PYTHON_FILES=langgraph -lint_tests: PYTHON_FILES=tests -lint_tests: MYPY_CACHE=.mypy_cache_test - -lint lint_diff lint_package lint_tests: - poetry run ruff check . - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff - [ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES) - [ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) - [ "$(PYTHON_FILES)" = "" ] || poetry run mypy langgraph --cache-dir $(MYPY_CACHE) - -format format_diff: - poetry run ruff format $(PYTHON_FILES) - poetry run ruff check --select I --fix $(PYTHON_FILES) - -spell_check: - poetry run codespell --toml pyproject.toml - -spell_fix: - poetry run codespell --toml pyproject.toml -w - - -###################### -# HELP -###################### - -help: - @echo '====================' - @echo '-- DOCUMENTATION --' - - @echo '-- LINTING --' - @echo 'format - run code formatters' - @echo 'lint - run linters' - @echo 'spell_check - run codespell on the project' - @echo 'spell_fix - run codespell on the project and fix the errors' - @echo '-- TESTS --' - @echo 'coverage - run unit tests and generate coverage report' - @echo 'test - run unit tests' - @echo 'test TEST_FILE= - run all tests in file' - @echo 'test_watch - run unit tests in watch mode' diff --git a/libs/prebuilt/README.md b/libs/prebuilt/README.md deleted file mode 100644 index 6a7d1e1a6..000000000 --- a/libs/prebuilt/README.md +++ /dev/null @@ -1,117 +0,0 @@ -# LangGraph Prebuilt - -This library defines high-level APIs for creating and executing LangGraph agents and tools. - -> [!IMPORTANT] -> This library is meant to be bundled with `langgraph`, don't install it directly - -## Agents - -`langgraph-prebuilt` provides an [implementation](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent) of a tool-calling [ReAct-style](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#react-implementation) agent - `create_react_agent`: - -```bash -pip install langchain-anthropic -``` - -```python -from langchain_anthropic import ChatAnthropic -from langgraph.prebuilt import create_react_agent - -# Define the tools for the agent to use -def search(query: str): - """Call to surf the web.""" - # This is a placeholder, but don't tell the LLM that... - if "sf" in query.lower() or "san francisco" in query.lower(): - return "It's 60 degrees and foggy." - return "It's 90 degrees and sunny." - -tools = [search] -model = ChatAnthropic(model="claude-3-7-sonnet-latest") - -app = create_react_agent(model, tools) -# run the agent -app.invoke( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, -) -``` - -## Tools - -### ToolNode - -`langgraph-prebuilt` provides an [implementation](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.tool_node.ToolNode) of a node that executes tool calls - `ToolNode`: - -```python -from langgraph.prebuilt import ToolNode -from langchain_core.messages import AIMessage - -def search(query: str): - """Call to surf the web.""" - # This is a placeholder, but don't tell the LLM that... - if "sf" in query.lower() or "san francisco" in query.lower(): - return "It's 60 degrees and foggy." - return "It's 90 degrees and sunny." - -tool_node = ToolNode([search]) -tool_calls = [{"name": "search", "args": {"query": "what is the weather in sf"}, "id": "1"}] -ai_message = AIMessage(content="", tool_calls=tool_calls) -# execute tool call -tool_node.invoke({"messages": [ai_message]}) -``` - -### ValidationNode - -`langgraph-prebuilt` provides an [implementation](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.tool_validator.ValidationNode) of a node that validates tool calls against a pydantic schema - `ValidationNode`: - -```python -from pydantic import BaseModel, field_validator -from langgraph.prebuilt import ValidationNode -from langchain_core.messages import AIMessage - - -class SelectNumber(BaseModel): - a: int - - @field_validator("a") - def a_must_be_meaningful(cls, v): - if v != 37: - raise ValueError("Only 37 is allowed") - return v - -validation_node = ValidationNode([SelectNumber]) -validation_node.invoke({ - "messages": [AIMessage("", tool_calls=[{"name": "SelectNumber", "args": {"a": 42}, "id": "1"}])] -}) -``` - -## Agent Inbox - -The library contains schemas for using the [Agent Inbox](https://github.com/langchain-ai/agent-inbox) with LangGraph agents. Learn more about how to use Agent Inbox [here](https://github.com/langchain-ai/agent-inbox#interrupts). - -```python -from langgraph.types import interrupt -from langgraph.prebuilt.interrupt import HumanInterrupt, HumanResponse - -def my_graph_function(): - # Extract the last tool call from the `messages` field in the state - tool_call = state["messages"][-1].tool_calls[0] - # Create an interrupt - request: HumanInterrupt = { - "action_request": { - "action": tool_call['name'], - "args": tool_call['args'] - }, - "config": { - "allow_ignore": True, - "allow_respond": True, - "allow_edit": False, - "allow_accept": False - }, - "description": _generate_email_markdown(state) # Generate a detailed markdown description. - } - # Send the interrupt request inside a list, and extract the first response - response = interrupt([request])[0] - if response['type'] == "response": - # Do something with the response - ... -``` \ No newline at end of file diff --git a/libs/prebuilt/langgraph/prebuilt/__init__.py b/libs/prebuilt/langgraph/prebuilt/__init__.py deleted file mode 100644 index 0b9581053..000000000 --- a/libs/prebuilt/langgraph/prebuilt/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""langgraph.prebuilt exposes a higher-level API for creating and executing agents and tools.""" - -from langgraph.prebuilt.chat_agent_executor import create_react_agent -from langgraph.prebuilt.tool_node import ( - InjectedState, - InjectedStore, - ToolNode, - tools_condition, -) -from langgraph.prebuilt.tool_validator import ValidationNode - -__all__ = [ - "create_react_agent", - "ToolNode", - "tools_condition", - "ValidationNode", - "InjectedState", - "InjectedStore", -] diff --git a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py deleted file mode 100644 index 5c34f1ecc..000000000 --- a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py +++ /dev/null @@ -1,839 +0,0 @@ -import functools -import inspect -from typing import ( - Any, - Callable, - Literal, - Optional, - Sequence, - Type, - TypeVar, - Union, - cast, -) - -from langchain_core.language_models import ( - BaseChatModel, - LanguageModelInput, - LanguageModelLike, -) -from langchain_core.messages import AIMessage, BaseMessage, SystemMessage, ToolMessage -from langchain_core.runnables import ( - Runnable, - RunnableBinding, - RunnableConfig, -) -from langchain_core.tools import BaseTool -from pydantic import BaseModel -from typing_extensions import Annotated, TypedDict - -from langgraph.errors import ErrorCode, create_error_message -from langgraph.graph import END, StateGraph -from langgraph.graph.graph import CompiledGraph -from langgraph.graph.message import add_messages -from langgraph.managed import IsLastStep, RemainingSteps -from langgraph.prebuilt.tool_node import ToolNode -from langgraph.store.base import BaseStore -from langgraph.types import Checkpointer, Send -from langgraph.utils.runnable import RunnableCallable - -StructuredResponse = Union[dict, BaseModel] -StructuredResponseSchema = Union[dict, type[BaseModel]] -F = TypeVar("F", bound=Callable[..., Any]) - - -# We create the AgentState that we will pass around -# This simply involves a list of messages -# We want steps to return messages to append to the list -# So we annotate the messages attribute with `add_messages` reducer -class AgentState(TypedDict): - """The state of the agent.""" - - messages: Annotated[Sequence[BaseMessage], add_messages] - - is_last_step: IsLastStep - - remaining_steps: RemainingSteps - - -class AgentStateWithStructuredResponse(AgentState): - """The state of the agent with a structured response.""" - - structured_response: StructuredResponse - - -StateSchema = TypeVar("StateSchema", bound=AgentState) -StateSchemaType = Type[StateSchema] - -PROMPT_RUNNABLE_NAME = "Prompt" - -Prompt = Union[ - SystemMessage, - str, - Callable[[StateSchema], LanguageModelInput], - Runnable[StateSchema, LanguageModelInput], -] - - -def _get_prompt_runnable(prompt: Optional[Prompt]) -> Runnable: - prompt_runnable: Runnable - if prompt is None: - prompt_runnable = RunnableCallable( - lambda state: state["messages"], name=PROMPT_RUNNABLE_NAME - ) - elif isinstance(prompt, str): - _system_message: BaseMessage = SystemMessage(content=prompt) - prompt_runnable = RunnableCallable( - lambda state: [_system_message] + state["messages"], - name=PROMPT_RUNNABLE_NAME, - ) - elif isinstance(prompt, SystemMessage): - prompt_runnable = RunnableCallable( - lambda state: [prompt] + state["messages"], - name=PROMPT_RUNNABLE_NAME, - ) - elif inspect.iscoroutinefunction(prompt): - prompt_runnable = RunnableCallable( - None, - prompt, - name=PROMPT_RUNNABLE_NAME, - ) - elif callable(prompt): - prompt_runnable = RunnableCallable( - prompt, - name=PROMPT_RUNNABLE_NAME, - ) - elif isinstance(prompt, Runnable): - prompt_runnable = prompt - else: - raise ValueError(f"Got unexpected type for `prompt`: {type(prompt)}") - - return prompt_runnable - - -def _convert_modifier_to_prompt(func: F) -> F: - """Decorator that converts state_modifier kwarg to prompt kwarg.""" - - @functools.wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Any: - prompt = kwargs.get("prompt") - state_modifier = kwargs.pop("state_modifier", None) - if sum(p is not None for p in (prompt, state_modifier)) > 1: - raise ValueError( - "Expected only one of (prompt, state_modifier), got multiple values" - ) - - if state_modifier is not None: - prompt = state_modifier - - kwargs["prompt"] = prompt - return func(*args, **kwargs) - - return cast(F, wrapper) - - -def _should_bind_tools(model: LanguageModelLike, tools: Sequence[BaseTool]) -> bool: - if not isinstance(model, RunnableBinding): - return True - - if "tools" not in model.kwargs: - return True - - bound_tools = model.kwargs["tools"] - if len(tools) != len(bound_tools): - raise ValueError( - "Number of tools in the model.bind_tools() and tools passed to create_react_agent must match" - ) - - tool_names = set(tool.name for tool in tools) - bound_tool_names = set() - for bound_tool in bound_tools: - # OpenAI-style tool - if bound_tool.get("type") == "function": - bound_tool_name = bound_tool["function"]["name"] - # Anthropic-style tool - elif bound_tool.get("name"): - bound_tool_name = bound_tool["name"] - else: - # unknown tool type so we'll ignore it - continue - - bound_tool_names.add(bound_tool_name) - - if missing_tools := tool_names - bound_tool_names: - raise ValueError(f"Missing tools '{missing_tools}' in the model.bind_tools()") - - return False - - -def _get_model(model: LanguageModelLike) -> BaseChatModel: - """Get the underlying model from a RunnableBinding or return the model itself.""" - if isinstance(model, RunnableBinding): - model = model.bound - - if not isinstance(model, BaseChatModel): - raise TypeError( - f"Expected `model` to be a ChatModel or RunnableBinding (e.g. model.bind_tools(...)), got {type(model)}" - ) - - return model - - -def _validate_chat_history( - messages: Sequence[BaseMessage], -) -> None: - """Validate that all tool calls in AIMessages have a corresponding ToolMessage.""" - all_tool_calls = [ - tool_call - for message in messages - if isinstance(message, AIMessage) - for tool_call in message.tool_calls - ] - tool_call_ids_with_results = { - message.tool_call_id for message in messages if isinstance(message, ToolMessage) - } - tool_calls_without_results = [ - tool_call - for tool_call in all_tool_calls - if tool_call["id"] not in tool_call_ids_with_results - ] - if not tool_calls_without_results: - return - - error_message = create_error_message( - message="Found AIMessages with tool_calls that do not have a corresponding ToolMessage. " - f"Here are the first few of those tool calls: {tool_calls_without_results[:3]}.\n\n" - "Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage " - "(result of a tool invocation to return to the LLM) - this is required by most LLM providers.", - error_code=ErrorCode.INVALID_CHAT_HISTORY, - ) - raise ValueError(error_message) - - -@_convert_modifier_to_prompt -def create_react_agent( - model: Union[str, LanguageModelLike], - tools: Union[Sequence[BaseTool], ToolNode], - *, - prompt: Optional[Prompt] = None, - response_format: Optional[ - Union[StructuredResponseSchema, tuple[str, StructuredResponseSchema]] - ] = None, - state_schema: Optional[StateSchemaType] = None, - config_schema: Optional[Type[Any]] = None, - checkpointer: Optional[Checkpointer] = None, - store: Optional[BaseStore] = None, - interrupt_before: Optional[list[str]] = None, - interrupt_after: Optional[list[str]] = None, - debug: bool = False, - version: Literal["v1", "v2"] = "v1", - name: Optional[str] = None, -) -> CompiledGraph: - """Creates a graph that works with a chat model that utilizes tool calling. - - Args: - model: The `LangChain` chat model that supports tool calling. - tools: A list of tools or a ToolNode instance. - If an empty list is provided, the agent will consist of a single LLM node without tool calling. - prompt: An optional prompt for the LLM. Can take a few different forms: - - - str: This is converted to a SystemMessage and added to the beginning of the list of messages in state["messages"]. - - SystemMessage: this is added to the beginning of the list of messages in state["messages"]. - - Callable: This function should take in full graph state and the output is then passed to the language model. - - Runnable: This runnable should take in full graph state and the output is then passed to the language model. - - response_format: An optional schema for the final agent output. - - If provided, output will be formatted to match the given schema and returned in the 'structured_response' state key. - If not provided, `structured_response` will not be present in the output state. - Can be passed in as: - - - an OpenAI function/tool schema, - - a JSON Schema, - - a TypedDict class, - - or a Pydantic class. - - a tuple (prompt, schema), where schema is one of the above. - The prompt will be used together with the model that is being used to generate the structured response. - - !!! Important - `response_format` requires the model to support `.with_structured_output` - - !!! Note - The graph will make a separate call to the LLM to generate the structured response after the agent loop is finished. - This is not the only strategy to get structured responses, see more options in [this guide](https://langchain-ai.github.io/langgraph/how-tos/react-agent-structured-output/). - state_schema: An optional state schema that defines graph state. - Must have `messages` and `is_last_step` keys. - Defaults to `AgentState` that defines those two keys. - config_schema: An optional schema for configuration. - Use this to expose configurable parameters via agent.config_specs. - checkpointer: An optional checkpoint saver object. This is used for persisting - the state of the graph (e.g., as chat memory) for a single thread (e.g., a single conversation). - store: An optional store object. This is used for persisting data - across multiple threads (e.g., multiple conversations / users). - interrupt_before: An optional list of node names to interrupt before. - Should be one of the following: "agent", "tools". - This is useful if you want to add a user confirmation or other interrupt before taking an action. - interrupt_after: An optional list of node names to interrupt after. - Should be one of the following: "agent", "tools". - This is useful if you want to return directly or run additional processing on an output. - debug: A flag indicating whether to enable debug mode. - version: Determines the version of the graph to create. - Can be one of: - - - `"v1"`: The tool node processes a single message. All tool - calls in the message are executed in parallel within the tool node. - - `"v2"`: The tool node processes a tool call. - Tool calls are distributed across multiple instances of the tool - node using the [Send](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) - API. - name: An optional name for the CompiledStateGraph. - This name will be automatically used when adding ReAct agent graph to another graph as a subgraph node - - particularly useful for building multi-agent systems. - - Returns: - A compiled LangChain runnable that can be used for chat interactions. - - The resulting graph looks like this: - - ``` mermaid - stateDiagram-v2 - [*] --> Start - Start --> Agent - Agent --> Tools : continue - Tools --> Agent - Agent --> End : end - End --> [*] - - classDef startClass fill:#ffdfba; - classDef endClass fill:#baffc9; - classDef otherClass fill:#fad7de; - - class Start startClass - class End endClass - class Agent,Tools otherClass - ``` - - The "agent" node calls the language model with the messages list (after applying the messages modifier). - If the resulting AIMessage contains `tool_calls`, the graph will then call the ["tools"][langgraph.prebuilt.tool_node.ToolNode]. - The "tools" node executes the tools (1 tool per `tool_call`) and adds the responses to the messages list - as `ToolMessage` objects. The agent node then calls the language model again. - The process repeats until no more `tool_calls` are present in the response. - The agent then returns the full list of messages as a dictionary containing the key "messages". - - ``` mermaid - sequenceDiagram - participant U as User - participant A as Agent (LLM) - participant T as Tools - U->>A: Initial input - Note over A: Messages modifier + LLM - loop while tool_calls present - A->>T: Execute tools - T-->>A: ToolMessage for each tool_calls - end - A->>U: Return final state - ``` - - Examples: - Use with a simple tool: - - ```pycon - >>> from datetime import datetime - >>> from langchain_openai import ChatOpenAI - >>> from langgraph.prebuilt import create_react_agent - - - ... def check_weather(location: str, at_time: datetime | None = None) -> str: - ... '''Return the weather forecast for the specified location.''' - ... return f"It's always sunny in {location}" - >>> - >>> tools = [check_weather] - >>> model = ChatOpenAI(model="gpt-4o") - >>> graph = create_react_agent(model, tools=tools) - >>> inputs = {"messages": [("user", "what is the weather in sf")]} - >>> for s in graph.stream(inputs, stream_mode="values"): - ... message = s["messages"][-1] - ... if isinstance(message, tuple): - ... print(message) - ... else: - ... message.pretty_print() - ('user', 'what is the weather in sf') - ================================== Ai Message ================================== - Tool Calls: - check_weather (call_LUzFvKJRuaWQPeXvBOzwhQOu) - Call ID: call_LUzFvKJRuaWQPeXvBOzwhQOu - Args: - location: San Francisco - ================================= Tool Message ================================= - Name: check_weather - It's always sunny in San Francisco - ================================== Ai Message ================================== - The weather in San Francisco is sunny. - ``` - Add a system prompt for the LLM: - - ```pycon - >>> system_prompt = "You are a helpful bot named Fred." - >>> graph = create_react_agent(model, tools, prompt=system_prompt) - >>> inputs = {"messages": [("user", "What's your name? And what's the weather in SF?")]} - >>> for s in graph.stream(inputs, stream_mode="values"): - ... message = s["messages"][-1] - ... if isinstance(message, tuple): - ... print(message) - ... else: - ... message.pretty_print() - ('user', "What's your name? And what's the weather in SF?") - ================================== Ai Message ================================== - Hi, my name is Fred. Let me check the weather in San Francisco for you. - Tool Calls: - check_weather (call_lqhj4O0hXYkW9eknB4S41EXk) - Call ID: call_lqhj4O0hXYkW9eknB4S41EXk - Args: - location: San Francisco - ================================= Tool Message ================================= - Name: check_weather - It's always sunny in San Francisco - ================================== Ai Message ================================== - The weather in San Francisco is currently sunny. If you need any more details or have other questions, feel free to ask! - ``` - - Add a more complex prompt for the LLM: - - ```pycon - >>> from langchain_core.prompts import ChatPromptTemplate - >>> prompt = ChatPromptTemplate.from_messages([ - ... ("system", "You are a helpful bot named Fred."), - ... ("placeholder", "{messages}"), - ... ("user", "Remember, always be polite!"), - ... ]) - >>> - >>> graph = create_react_agent(model, tools, prompt=prompt) - >>> inputs = {"messages": [("user", "What's your name? And what's the weather in SF?")]} - >>> for s in graph.stream(inputs, stream_mode="values"): - ... message = s["messages"][-1] - ... if isinstance(message, tuple): - ... print(message) - ... else: - ... message.pretty_print() - ``` - - Add complex prompt with custom graph state: - - ```pycon - >>> from typing_extensions import TypedDict - >>> - >>> from langgraph.managed import IsLastStep - >>> prompt = ChatPromptTemplate.from_messages( - ... [ - ... ("system", "Today is {today}"), - ... ("placeholder", "{messages}"), - ... ] - ... ) - >>> - >>> class CustomState(TypedDict): - ... today: str - ... messages: Annotated[list[BaseMessage], add_messages] - ... is_last_step: IsLastStep - >>> - >>> graph = create_react_agent(model, tools, state_schema=CustomState, prompt=prompt) - >>> inputs = {"messages": [("user", "What's today's date? And what's the weather in SF?")], "today": "July 16, 2004"} - >>> for s in graph.stream(inputs, stream_mode="values"): - ... message = s["messages"][-1] - ... if isinstance(message, tuple): - ... print(message) - ... else: - ... message.pretty_print() - ``` - - Add thread-level "chat memory" to the graph: - - ```pycon - >>> from langgraph.checkpoint.memory import MemorySaver - >>> graph = create_react_agent(model, tools, checkpointer=MemorySaver()) - >>> config = {"configurable": {"thread_id": "thread-1"}} - >>> def print_stream(graph, inputs, config): - ... for s in graph.stream(inputs, config, stream_mode="values"): - ... message = s["messages"][-1] - ... if isinstance(message, tuple): - ... print(message) - ... else: - ... message.pretty_print() - >>> inputs = {"messages": [("user", "What's the weather in SF?")]} - >>> print_stream(graph, inputs, config) - >>> inputs2 = {"messages": [("user", "Cool, so then should i go biking today?")]} - >>> print_stream(graph, inputs2, config) - ('user', "What's the weather in SF?") - ================================== Ai Message ================================== - Tool Calls: - check_weather (call_ChndaktJxpr6EMPEB5JfOFYc) - Call ID: call_ChndaktJxpr6EMPEB5JfOFYc - Args: - location: San Francisco - ================================= Tool Message ================================= - Name: check_weather - It's always sunny in San Francisco - ================================== Ai Message ================================== - The weather in San Francisco is sunny. Enjoy your day! - ================================ Human Message ================================= - Cool, so then should i go biking today? - ================================== Ai Message ================================== - Since the weather in San Francisco is sunny, it sounds like a great day for biking! Enjoy your ride! - ``` - - Add an interrupt to let the user confirm before taking an action: - - ```pycon - >>> graph = create_react_agent( - ... model, tools, interrupt_before=["tools"], checkpointer=MemorySaver() - >>> ) - >>> config = {"configurable": {"thread_id": "thread-1"}} - - >>> inputs = {"messages": [("user", "What's the weather in SF?")]} - >>> print_stream(graph, inputs, config) - >>> snapshot = graph.get_state(config) - >>> print("Next step: ", snapshot.next) - >>> print_stream(graph, None, config) - ``` - - Add cross-thread memory to the graph: - - ```pycon - >>> from langgraph.prebuilt import InjectedStore - >>> from langgraph.store.base import BaseStore - - >>> def save_memory(memory: str, *, config: RunnableConfig, store: Annotated[BaseStore, InjectedStore()]) -> str: - ... '''Save the given memory for the current user.''' - ... # This is a **tool** the model can use to save memories to storage - ... user_id = config.get("configurable", {}).get("user_id") - ... namespace = ("memories", user_id) - ... store.put(namespace, f"memory_{len(store.search(namespace))}", {"data": memory}) - ... return f"Saved memory: {memory}" - - >>> def prepare_model_inputs(state: AgentState, config: RunnableConfig, store: BaseStore): - ... # Retrieve user memories and add them to the system message - ... # This function is called **every time** the model is prompted. It converts the state to a prompt - ... user_id = config.get("configurable", {}).get("user_id") - ... namespace = ("memories", user_id) - ... memories = [m.value["data"] for m in store.search(namespace)] - ... system_msg = f"User memories: {', '.join(memories)}" - ... return [{"role": "system", "content": system_msg)] + state["messages"] - - >>> from langgraph.checkpoint.memory import MemorySaver - >>> from langgraph.store.memory import InMemoryStore - >>> store = InMemoryStore() - >>> graph = create_react_agent(model, [save_memory], prompt=prepare_model_inputs, store=store, checkpointer=MemorySaver()) - >>> config = {"configurable": {"thread_id": "thread-1", "user_id": "1"}} - - >>> inputs = {"messages": [("user", "Hey I'm Will, how's it going?")]} - >>> print_stream(graph, inputs, config) - ('user', "Hey I'm Will, how's it going?") - ================================== Ai Message ================================== - Hello Will! It's nice to meet you. I'm doing well, thank you for asking. How are you doing today? - - >>> inputs2 = {"messages": [("user", "I like to bike")]} - >>> print_stream(graph, inputs2, config) - ================================ Human Message ================================= - I like to bike - ================================== Ai Message ================================== - That's great to hear, Will! Biking is an excellent hobby and form of exercise. It's a fun way to stay active and explore your surroundings. Do you have any favorite biking routes or trails you enjoy? Or perhaps you're into a specific type of biking, like mountain biking or road cycling? - - >>> config = {"configurable": {"thread_id": "thread-2", "user_id": "1"}} - >>> inputs3 = {"messages": [("user", "Hi there! Remember me?")]} - >>> print_stream(graph, inputs3, config) - ================================ Human Message ================================= - Hi there! Remember me? - ================================== Ai Message ================================== - User memories: - Hello! Of course, I remember you, Will! You mentioned earlier that you like to bike. It's great to hear from you again. How have you been? Have you been on any interesting bike rides lately? - ``` - - Add a timeout for a given step: - - ```pycon - >>> import time - ... def check_weather(location: str, at_time: datetime | None = None) -> float: - ... '''Return the weather forecast for the specified location.''' - ... time.sleep(2) - ... return f"It's always sunny in {location}" - >>> - >>> tools = [check_weather] - >>> graph = create_react_agent(model, tools) - >>> graph.step_timeout = 1 # Seconds - >>> for s in graph.stream({"messages": [("user", "what is the weather in sf")]}): - ... print(s) - TimeoutError: Timed out at step 2 - ``` - """ - if version not in ("v1", "v2"): - raise ValueError( - f"Invalid version {version}. Supported versions are 'v1' and 'v2'." - ) - - if state_schema is not None: - required_keys = {"messages", "remaining_steps"} - if response_format is not None: - required_keys.add("structured_response") - - if missing_keys := required_keys - set(state_schema.__annotations__): - raise ValueError(f"Missing required key(s) {missing_keys} in state_schema") - - if state_schema is None: - state_schema = ( - AgentStateWithStructuredResponse - if response_format is not None - else AgentState - ) - - if isinstance(tools, ToolNode): - tool_classes = list(tools.tools_by_name.values()) - tool_node = tools - else: - tool_node = ToolNode(tools) - # get the tool functions wrapped in a tool class from the ToolNode - tool_classes = list(tool_node.tools_by_name.values()) - - if isinstance(model, str): - try: - from langchain.chat_models import ( # type: ignore[import-not-found] - init_chat_model, - ) - except ImportError: - raise ImportError( - "Please install langchain (`pip install langchain`) to use ':' string syntax for `model` parameter." - ) - - model = cast(BaseChatModel, init_chat_model(model)) - - tool_calling_enabled = len(tool_classes) > 0 - - if _should_bind_tools(model, tool_classes) and tool_calling_enabled: - model = cast(BaseChatModel, model).bind_tools(tool_classes) - - model_runnable = _get_prompt_runnable(prompt) | model - - # If any of the tools are configured to return_directly after running, - # our graph needs to check if these were called - should_return_direct = {t.name for t in tool_classes if t.return_direct} - - # Define the function that calls the model - def call_model(state: AgentState, config: RunnableConfig) -> AgentState: - _validate_chat_history(state["messages"]) - response = cast(AIMessage, model_runnable.invoke(state, config)) - # add agent name to the AIMessage - response.name = name - has_tool_calls = isinstance(response, AIMessage) and response.tool_calls - all_tools_return_direct = ( - all(call["name"] in should_return_direct for call in response.tool_calls) - if isinstance(response, AIMessage) - else False - ) - if ( - ( - "remaining_steps" not in state - and state.get("is_last_step", False) - and has_tool_calls - ) - or ( - "remaining_steps" in state - and state["remaining_steps"] < 1 - and all_tools_return_direct - ) - or ( - "remaining_steps" in state - and state["remaining_steps"] < 2 - and has_tool_calls - ) - ): - return { - "messages": [ - AIMessage( - id=response.id, - content="Sorry, need more steps to process this request.", - ) - ] - } - # We return a list, because this will get added to the existing list - return {"messages": [response]} - - async def acall_model(state: AgentState, config: RunnableConfig) -> AgentState: - _validate_chat_history(state["messages"]) - response = cast(AIMessage, await model_runnable.ainvoke(state, config)) - # add agent name to the AIMessage - response.name = name - has_tool_calls = isinstance(response, AIMessage) and response.tool_calls - all_tools_return_direct = ( - all(call["name"] in should_return_direct for call in response.tool_calls) - if isinstance(response, AIMessage) - else False - ) - if ( - ( - "remaining_steps" not in state - and state.get("is_last_step", False) - and has_tool_calls - ) - or ( - "remaining_steps" in state - and state["remaining_steps"] < 1 - and all_tools_return_direct - ) - or ( - "remaining_steps" in state - and state["remaining_steps"] < 2 - and has_tool_calls - ) - ): - return { - "messages": [ - AIMessage( - id=response.id, - content="Sorry, need more steps to process this request.", - ) - ] - } - # We return a list, because this will get added to the existing list - return {"messages": [response]} - - def generate_structured_response( - state: AgentState, config: RunnableConfig - ) -> AgentState: - # NOTE: we exclude the last message because there is enough information - # for the LLM to generate the structured response - messages = state["messages"][:-1] - structured_response_schema = response_format - if isinstance(response_format, tuple): - system_prompt, structured_response_schema = response_format - messages = [SystemMessage(content=system_prompt)] + list(messages) - - model_with_structured_output = _get_model(model).with_structured_output( - cast(StructuredResponseSchema, structured_response_schema) - ) - response = model_with_structured_output.invoke(messages, config) - return {"structured_response": response} - - async def agenerate_structured_response( - state: AgentState, config: RunnableConfig - ) -> AgentState: - # NOTE: we exclude the last message because there is enough information - # for the LLM to generate the structured response - messages = state["messages"][:-1] - structured_response_schema = response_format - if isinstance(response_format, tuple): - system_prompt, structured_response_schema = response_format - messages = [SystemMessage(content=system_prompt)] + list(messages) - - model_with_structured_output = _get_model(model).with_structured_output( - cast(StructuredResponseSchema, structured_response_schema) - ) - response = await model_with_structured_output.ainvoke(messages, config) - return {"structured_response": response} - - if not tool_calling_enabled: - # Define a new graph - workflow = StateGraph(state_schema, config_schema=config_schema) - workflow.add_node("agent", RunnableCallable(call_model, acall_model)) - workflow.set_entry_point("agent") - if response_format is not None: - workflow.add_node( - "generate_structured_response", - RunnableCallable( - generate_structured_response, agenerate_structured_response - ), - ) - workflow.add_edge("agent", "generate_structured_response") - - return workflow.compile( - checkpointer=checkpointer, - store=store, - interrupt_before=interrupt_before, - interrupt_after=interrupt_after, - debug=debug, - name=name, - ) - - # Define the function that determines whether to continue or not - def should_continue(state: AgentState) -> Union[str, list]: - messages = state["messages"] - last_message = messages[-1] - # If there is no function call, then we finish - if not isinstance(last_message, AIMessage) or not last_message.tool_calls: - return END if response_format is None else "generate_structured_response" - # Otherwise if there is, we continue - else: - if version == "v1": - return "tools" - elif version == "v2": - tool_calls = [ - tool_node.inject_tool_args(call, state, store) # type: ignore[arg-type] - for call in last_message.tool_calls - ] - return [Send("tools", [tool_call]) for tool_call in tool_calls] - - # Define a new graph - workflow = StateGraph(state_schema or AgentState, config_schema=config_schema) - - # Define the two nodes we will cycle between - workflow.add_node("agent", RunnableCallable(call_model, acall_model)) - workflow.add_node("tools", tool_node) - - # Set the entrypoint as `agent` - # This means that this node is the first one called - workflow.set_entry_point("agent") - - # Add a structured output node if response_format is provided - if response_format is not None: - workflow.add_node( - "generate_structured_response", - RunnableCallable( - generate_structured_response, agenerate_structured_response - ), - ) - workflow.add_edge("generate_structured_response", END) - should_continue_destinations = ["tools", "generate_structured_response"] - else: - should_continue_destinations = ["tools", END] - - # We now add a conditional edge - workflow.add_conditional_edges( - # First, we define the start node. We use `agent`. - # This means these are the edges taken after the `agent` node is called. - "agent", - # Next, we pass in the function that will determine which node is called next. - should_continue, - path_map=should_continue_destinations, - ) - - def route_tool_responses(state: AgentState) -> Literal["agent", "__end__"]: - for m in reversed(state["messages"]): - if not isinstance(m, ToolMessage): - break - if m.name in should_return_direct: - return END - return "agent" - - if should_return_direct: - workflow.add_conditional_edges("tools", route_tool_responses) - else: - workflow.add_edge("tools", "agent") - - # Finally, we compile it! - # This compiles it into a LangChain Runnable, - # meaning you can use it as you would any other runnable - return workflow.compile( - checkpointer=checkpointer, - store=store, - interrupt_before=interrupt_before, - interrupt_after=interrupt_after, - debug=debug, - name=name, - ) - - -# Keep for backwards compatibility -create_tool_calling_executor = create_react_agent - -__all__ = [ - "create_react_agent", - "create_tool_calling_executor", - "AgentState", -] diff --git a/libs/prebuilt/langgraph/prebuilt/interrupt.py b/libs/prebuilt/langgraph/prebuilt/interrupt.py deleted file mode 100644 index 2ab3ee0d1..000000000 --- a/libs/prebuilt/langgraph/prebuilt/interrupt.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import ( - Literal, - Optional, - Union, -) - -from typing_extensions import TypedDict - - -class HumanInterruptConfig(TypedDict): - """Configuration that defines what actions are allowed for a human interrupt. - - This controls the available interaction options when the graph is paused for human input. - - Attributes: - allow_ignore: Whether the human can choose to ignore/skip the current step - allow_respond: Whether the human can provide a text response/feedback - allow_edit: Whether the human can edit the provided content/state - allow_accept: Whether the human can accept/approve the current state - """ - - allow_ignore: bool - allow_respond: bool - allow_edit: bool - allow_accept: bool - - -class ActionRequest(TypedDict): - """Represents a request for human action within the graph execution. - - Contains the action type and any associated arguments needed for the action. - - Attributes: - action: The type or name of action being requested (e.g., "Approve XYZ action") - args: Key-value pairs of arguments needed for the action - """ - - action: str - args: dict - - -class HumanInterrupt(TypedDict): - """Represents an interrupt triggered by the graph that requires human intervention. - - This is passed to the `interrupt` function when execution is paused for human input. - - Attributes: - action_request: The specific action being requested from the human - config: Configuration defining what actions are allowed - description: Optional detailed description of what input is needed - - Example: - ```python - # Extract a tool call from the state and create an interrupt request - request = HumanInterrupt( - action_request=ActionRequest( - action="run_command", # The action being requested - args={"command": "ls", "args": ["-l"]} # Arguments for the action - ), - config=HumanInterruptConfig( - allow_ignore=True, # Allow skipping this step - allow_respond=True, # Allow text feedback - allow_edit=False, # Don't allow editing - allow_accept=True # Allow direct acceptance - ), - description="Please review the command before execution" - ) - # Send the interrupt request and get the response - response = interrupt([request])[0] - ``` - """ - - action_request: ActionRequest - config: HumanInterruptConfig - description: Optional[str] - - -class HumanResponse(TypedDict): - """The response provided by a human to an interrupt, which is returned when graph execution resumes. - - Attributes: - type: The type of response: - - "accept": Approves the current state without changes - - "ignore": Skips/ignores the current step - - "response": Provides text feedback or instructions - - "edit": Modifies the current state/content - arg: The response payload: - - None: For ignore/accept actions - - str: For text responses - - ActionRequest: For edit actions with updated content - """ - - type: Literal["accept", "ignore", "response", "edit"] - args: Union[None, str, ActionRequest] diff --git a/libs/prebuilt/langgraph/prebuilt/py.typed b/libs/prebuilt/langgraph/prebuilt/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/prebuilt/langgraph/prebuilt/tool_node.py b/libs/prebuilt/langgraph/prebuilt/tool_node.py deleted file mode 100644 index 8175c9cda..000000000 --- a/libs/prebuilt/langgraph/prebuilt/tool_node.py +++ /dev/null @@ -1,848 +0,0 @@ -import asyncio -import inspect -import json -from copy import copy, deepcopy -from typing import ( - Any, - Callable, - Literal, - Optional, - Sequence, - Tuple, - Type, - Union, - cast, - get_type_hints, -) - -from langchain_core.messages import ( - AIMessage, - AnyMessage, - ToolCall, - ToolMessage, - convert_to_messages, -) -from langchain_core.runnables import RunnableConfig -from langchain_core.runnables.config import ( - get_config_list, - get_executor_for_config, -) -from langchain_core.tools import BaseTool, InjectedToolArg -from langchain_core.tools import tool as create_tool -from langchain_core.tools.base import get_all_basemodel_annotations -from pydantic import BaseModel -from typing_extensions import Annotated, get_args, get_origin - -from langgraph.errors import GraphBubbleUp -from langgraph.store.base import BaseStore -from langgraph.types import Command -from langgraph.utils.runnable import RunnableCallable - -INVALID_TOOL_NAME_ERROR_TEMPLATE = ( - "Error: {requested_tool} is not a valid tool, try one of [{available_tools}]." -) -TOOL_CALL_ERROR_TEMPLATE = "Error: {error}\n Please fix your mistakes." - - -def msg_content_output(output: Any) -> Union[str, list[dict]]: - recognized_content_block_types = ("image", "image_url", "text", "json") - if isinstance(output, str): - return output - elif isinstance(output, list) and all( - [ - isinstance(x, dict) and x.get("type") in recognized_content_block_types - for x in output - ] - ): - return output - # Technically a list of strings is also valid message content but it's not currently - # well tested that all chat models support this. And for backwards compatibility - # we want to make sure we don't break any existing ToolNode usage. - else: - try: - return json.dumps(output, ensure_ascii=False) - except Exception: - return str(output) - - -def _handle_tool_error( - e: Exception, - *, - flag: Union[ - bool, - str, - Callable[..., str], - tuple[type[Exception], ...], - ], -) -> str: - if isinstance(flag, (bool, tuple)): - content = TOOL_CALL_ERROR_TEMPLATE.format(error=repr(e)) - elif isinstance(flag, str): - content = flag - elif callable(flag): - content = flag(e) - else: - raise ValueError( - f"Got unexpected type of `handle_tool_error`. Expected bool, str " - f"or callable. Received: {flag}" - ) - return content - - -def _infer_handled_types(handler: Callable[..., str]) -> tuple[type[Exception], ...]: - sig = inspect.signature(handler) - params = list(sig.parameters.values()) - if params: - # If it's a method, the first argument is typically 'self' or 'cls' - if params[0].name in ["self", "cls"] and len(params) == 2: - first_param = params[1] - else: - first_param = params[0] - - type_hints = get_type_hints(handler) - if first_param.name in type_hints: - origin = get_origin(first_param.annotation) - if origin is Union: - args = get_args(first_param.annotation) - if all(issubclass(arg, Exception) for arg in args): - return tuple(args) - else: - raise ValueError( - "All types in the error handler error annotation must be Exception types. " - "For example, `def custom_handler(e: Union[ValueError, TypeError])`. " - f"Got '{first_param.annotation}' instead." - ) - - exception_type = type_hints[first_param.name] - if Exception in exception_type.__mro__: - return (exception_type,) - else: - raise ValueError( - f"Arbitrary types are not supported in the error handler signature. " - "Please annotate the error with either a specific Exception type or a union of Exception types. " - "For example, `def custom_handler(e: ValueError)` or `def custom_handler(e: Union[ValueError, TypeError])`. " - f"Got '{exception_type}' instead." - ) - - # If no type information is available, return (Exception,) for backwards compatibility. - return (Exception,) - - -class ToolNode(RunnableCallable): - """A node that runs the tools called in the last AIMessage. - - It can be used either in StateGraph with a "messages" state key (or a custom key passed via ToolNode's 'messages_key'). - If multiple tool calls are requested, they will be run in parallel. The output will be - a list of ToolMessages, one for each tool call. - - Tool calls can also be passed directly as a list of `ToolCall` dicts. - - Args: - tools: A sequence of tools that can be invoked by the ToolNode. - name: The name of the ToolNode in the graph. Defaults to "tools". - tags: Optional tags to associate with the node. Defaults to None. - handle_tool_errors: How to handle tool errors raised by tools inside the node. Defaults to True. - Must be one of the following: - - - True: all errors will be caught and - a ToolMessage with a default error message (TOOL_CALL_ERROR_TEMPLATE) will be returned. - - str: all errors will be caught and - a ToolMessage with the string value of 'handle_tool_errors' will be returned. - - tuple[type[Exception], ...]: exceptions in the tuple will be caught and - a ToolMessage with a default error message (TOOL_CALL_ERROR_TEMPLATE) will be returned. - - Callable[..., str]: exceptions from the signature of the callable will be caught and - a ToolMessage with the string value of the result of the 'handle_tool_errors' callable will be returned. - - False: none of the errors raised by the tools will be caught - messages_key: The state key in the input that contains the list of messages. - The same key will be used for the output from the ToolNode. - Defaults to "messages". - - The `ToolNode` is roughly analogous to: - - ```python - tools_by_name = {tool.name: tool for tool in tools} - def tool_node(state: dict): - result = [] - for tool_call in state["messages"][-1].tool_calls: - tool = tools_by_name[tool_call["name"]] - observation = tool.invoke(tool_call["args"]) - result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"])) - return {"messages": result} - ``` - - Tool calls can also be passed directly to a ToolNode. This can be useful when using - the Send API, e.g., in a conditional edge: - - ```python - def example_conditional_edge(state: dict) -> List[Send]: - tool_calls = state["messages"][-1].tool_calls - # If tools rely on state or store variables (whose values are not generated - # directly by a model), you can inject them into the tool calls. - tool_calls = [ - tool_node.inject_tool_args(call, state, store) - for call in last_message.tool_calls - ] - return [Send("tools", [tool_call]) for tool_call in tool_calls] - ``` - - Important: - - The input state can be one of the following: - - A dict with a messages key containing a list of messages. - - A list of messages. - - A list of tool calls. - - If operating on a message list, the last message must be an `AIMessage` with - `tool_calls` populated. - """ - - name: str = "ToolNode" - - def __init__( - self, - tools: Sequence[Union[BaseTool, Callable]], - *, - name: str = "tools", - tags: Optional[list[str]] = None, - handle_tool_errors: Union[ - bool, str, Callable[..., str], tuple[type[Exception], ...] - ] = True, - messages_key: str = "messages", - ) -> None: - super().__init__(self._func, self._afunc, name=name, tags=tags, trace=False) - self.tools_by_name: dict[str, BaseTool] = {} - self.tool_to_state_args: dict[str, dict[str, Optional[str]]] = {} - self.tool_to_store_arg: dict[str, Optional[str]] = {} - self.handle_tool_errors = handle_tool_errors - self.messages_key = messages_key - for tool_ in tools: - if not isinstance(tool_, BaseTool): - tool_ = create_tool(tool_) - self.tools_by_name[tool_.name] = tool_ - self.tool_to_state_args[tool_.name] = _get_state_args(tool_) - self.tool_to_store_arg[tool_.name] = _get_store_arg(tool_) - - def _func( - self, - input: Union[ - list[AnyMessage], - dict[str, Any], - BaseModel, - ], - config: RunnableConfig, - *, - store: Optional[BaseStore], - ) -> Any: - tool_calls, input_type = self._parse_input(input, store) - config_list = get_config_list(config, len(tool_calls)) - input_types = [input_type] * len(tool_calls) - with get_executor_for_config(config) as executor: - outputs = [ - *executor.map(self._run_one, tool_calls, input_types, config_list) - ] - - # preserve existing behavior for non-command tool outputs for backwards - # compatibility - if not any(isinstance(output, Command) for output in outputs): - # TypedDict, pydantic, dataclass, etc. should all be able to load from dict - return outputs if input_type == "list" else {self.messages_key: outputs} - - # LangGraph will automatically handle list of Command and non-command node - # updates - combined_outputs: list[ - Command | list[ToolMessage] | dict[str, list[ToolMessage]] - ] = [] - for output in outputs: - if isinstance(output, Command): - combined_outputs.append(output) - else: - combined_outputs.append( - [output] if input_type == "list" else {self.messages_key: [output]} - ) - return combined_outputs - - async def _afunc( - self, - input: Union[ - list[AnyMessage], - dict[str, Any], - BaseModel, - ], - config: RunnableConfig, - *, - store: Optional[BaseStore], - ) -> Any: - tool_calls, input_type = self._parse_input(input, store) - outputs = await asyncio.gather( - *(self._arun_one(call, input_type, config) for call in tool_calls) - ) - - # preserve existing behavior for non-command tool outputs for backwards compatibility - if not any(isinstance(output, Command) for output in outputs): - # TypedDict, pydantic, dataclass, etc. should all be able to load from dict - return outputs if input_type == "list" else {self.messages_key: outputs} - - # LangGraph will automatically handle list of Command and non-command node updates - combined_outputs: list[ - Command | list[ToolMessage] | dict[str, list[ToolMessage]] - ] = [] - for output in outputs: - if isinstance(output, Command): - combined_outputs.append(output) - else: - combined_outputs.append( - [output] if input_type == "list" else {self.messages_key: [output]} - ) - return combined_outputs - - def _run_one( - self, - call: ToolCall, - input_type: Literal["list", "dict", "tool_calls"], - config: RunnableConfig, - ) -> ToolMessage: - if invalid_tool_message := self._validate_tool_call(call): - return invalid_tool_message - - try: - input = {**call, **{"type": "tool_call"}} - response = self.tools_by_name[call["name"]].invoke(input, config) - - # GraphInterrupt is a special exception that will always be raised. - # It can be triggered in the following scenarios: - # (1) a NodeInterrupt is raised inside a tool - # (2) a NodeInterrupt is raised inside a graph node for a graph called as a tool - # (3) a GraphInterrupt is raised when a subgraph is interrupted inside a graph called as a tool - # (2 and 3 can happen in a "supervisor w/ tools" multi-agent architecture) - except GraphBubbleUp as e: - raise e - except Exception as e: - if isinstance(self.handle_tool_errors, tuple): - handled_types: tuple = self.handle_tool_errors - elif callable(self.handle_tool_errors): - handled_types = _infer_handled_types(self.handle_tool_errors) - else: - # default behavior is catching all exceptions - handled_types = (Exception,) - - # Unhandled - if not self.handle_tool_errors or not isinstance(e, handled_types): - raise e - # Handled - else: - content = _handle_tool_error(e, flag=self.handle_tool_errors) - return ToolMessage( - content=content, - name=call["name"], - tool_call_id=call["id"], - status="error", - ) - - if isinstance(response, Command): - return self._validate_tool_command(response, call, input_type) - elif isinstance(response, ToolMessage): - response.content = cast( - Union[str, list], msg_content_output(response.content) - ) - return response - else: - raise TypeError( - f"Tool {call['name']} returned unexpected type: {type(response)}" - ) - - async def _arun_one( - self, - call: ToolCall, - input_type: Literal["list", "dict", "tool_calls"], - config: RunnableConfig, - ) -> ToolMessage: - if invalid_tool_message := self._validate_tool_call(call): - return invalid_tool_message - - try: - input = {**call, **{"type": "tool_call"}} - response = await self.tools_by_name[call["name"]].ainvoke(input, config) - - # GraphInterrupt is a special exception that will always be raised. - # It can be triggered in the following scenarios: - # (1) a NodeInterrupt is raised inside a tool - # (2) a NodeInterrupt is raised inside a graph node for a graph called as a tool - # (3) a GraphInterrupt is raised when a subgraph is interrupted inside a graph called as a tool - # (2 and 3 can happen in a "supervisor w/ tools" multi-agent architecture) - except GraphBubbleUp as e: - raise e - except Exception as e: - if isinstance(self.handle_tool_errors, tuple): - handled_types: tuple = self.handle_tool_errors - elif callable(self.handle_tool_errors): - handled_types = _infer_handled_types(self.handle_tool_errors) - else: - # default behavior is catching all exceptions - handled_types = (Exception,) - - # Unhandled - if not self.handle_tool_errors or not isinstance(e, handled_types): - raise e - # Handled - else: - content = _handle_tool_error(e, flag=self.handle_tool_errors) - - return ToolMessage( - content=content, - name=call["name"], - tool_call_id=call["id"], - status="error", - ) - - if isinstance(response, Command): - return self._validate_tool_command(response, call, input_type) - elif isinstance(response, ToolMessage): - response.content = cast( - Union[str, list], msg_content_output(response.content) - ) - return response - else: - raise TypeError( - f"Tool {call['name']} returned unexpected type: {type(response)}" - ) - - def _parse_input( - self, - input: Union[ - list[AnyMessage], - dict[str, Any], - BaseModel, - ], - store: Optional[BaseStore], - ) -> Tuple[list[ToolCall], Literal["list", "dict", "tool_calls"]]: - if isinstance(input, list): - if isinstance(input[-1], dict) and input[-1].get("type") == "tool_call": - input_type = "tool_calls" - tool_calls = input - return tool_calls, input_type - else: - input_type = "list" - message: AnyMessage = input[-1] - elif isinstance(input, dict) and (messages := input.get(self.messages_key, [])): - input_type = "dict" - message = messages[-1] - elif messages := getattr(input, self.messages_key, None): - # Assume dataclass-like state that can coerce from dict - input_type = "dict" - message = messages[-1] - else: - raise ValueError("No message found in input") - - if not isinstance(message, AIMessage): - raise ValueError("Last message is not an AIMessage") - - tool_calls = [ - self.inject_tool_args(call, input, store) for call in message.tool_calls - ] - return tool_calls, input_type - - def _validate_tool_call(self, call: ToolCall) -> Optional[ToolMessage]: - if (requested_tool := call["name"]) not in self.tools_by_name: - content = INVALID_TOOL_NAME_ERROR_TEMPLATE.format( - requested_tool=requested_tool, - available_tools=", ".join(self.tools_by_name.keys()), - ) - return ToolMessage( - content, name=requested_tool, tool_call_id=call["id"], status="error" - ) - else: - return None - - def _inject_state( - self, - tool_call: ToolCall, - input: Union[ - list[AnyMessage], - dict[str, Any], - BaseModel, - ], - ) -> ToolCall: - state_args = self.tool_to_state_args[tool_call["name"]] - if state_args and isinstance(input, list): - required_fields = list(state_args.values()) - if ( - len(required_fields) == 1 - and required_fields[0] == self.messages_key - or required_fields[0] is None - ): - input = {self.messages_key: input} - else: - err_msg = ( - f"Invalid input to ToolNode. Tool {tool_call['name']} requires " - f"graph state dict as input." - ) - if any(state_field for state_field in state_args.values()): - required_fields_str = ", ".join(f for f in required_fields if f) - err_msg += f" State should contain fields {required_fields_str}." - raise ValueError(err_msg) - if isinstance(input, dict): - tool_state_args = { - tool_arg: input[state_field] if state_field else input - for tool_arg, state_field in state_args.items() - } - - else: - tool_state_args = { - tool_arg: getattr(input, state_field) if state_field else input - for tool_arg, state_field in state_args.items() - } - - tool_call["args"] = { - **tool_call["args"], - **tool_state_args, - } - return tool_call - - def _inject_store( - self, tool_call: ToolCall, store: Optional[BaseStore] - ) -> ToolCall: - store_arg = self.tool_to_store_arg[tool_call["name"]] - if not store_arg: - return tool_call - - if store is None: - raise ValueError( - "Cannot inject store into tools with InjectedStore annotations - " - "please compile your graph with a store." - ) - - tool_call["args"] = { - **tool_call["args"], - store_arg: store, - } - return tool_call - - def inject_tool_args( - self, - tool_call: ToolCall, - input: Union[ - list[AnyMessage], - dict[str, Any], - BaseModel, - ], - store: Optional[BaseStore], - ) -> ToolCall: - """Injects the state and store into the tool call. - - Tool arguments with types annotated as `InjectedState` and `InjectedStore` are - ignored in tool schemas for generation purposes. This method injects them into - tool calls for tool invocation. - - Args: - tool_call (ToolCall): The tool call to inject state and store into. - input (Union[list[AnyMessage], dict[str, Any], BaseModel]): The input state - to inject. - store (Optional[BaseStore]): The store to inject. - - Returns: - ToolCall: The tool call with injected state and store. - """ - if tool_call["name"] not in self.tools_by_name: - return tool_call - - tool_call_copy: ToolCall = copy(tool_call) - tool_call_with_state = self._inject_state(tool_call_copy, input) - tool_call_with_store = self._inject_store(tool_call_with_state, store) - return tool_call_with_store - - def _validate_tool_command( - self, - command: Command, - call: ToolCall, - input_type: Literal["list", "dict", "tool_calls"], - ) -> Command: - if isinstance(command.update, dict): - # input type is dict when ToolNode is invoked with a dict input (e.g. {"messages": [AIMessage(..., tool_calls=[...])]}) - if input_type not in ("dict", "tool_calls"): - raise ValueError( - f"Tools can provide a dict in Command.update only when using dict with '{self.messages_key}' key as ToolNode input, " - f"got: {command.update} for tool '{call['name']}'" - ) - - updated_command = deepcopy(command) - state_update = cast(dict[str, Any], updated_command.update) or {} - messages_update = state_update.get(self.messages_key, []) - elif isinstance(command.update, list): - # input type is list when ToolNode is invoked with a list input (e.g. [AIMessage(..., tool_calls=[...])]) - if input_type != "list": - raise ValueError( - f"Tools can provide a list of messages in Command.update only when using list of messages as ToolNode input, " - f"got: {command.update} for tool '{call['name']}'" - ) - - updated_command = deepcopy(command) - messages_update = updated_command.update - else: - return command - - # convert to message objects if updates are in a dict format - messages_update = convert_to_messages(messages_update) - has_matching_tool_message = False - for message in messages_update: - if not isinstance(message, ToolMessage): - continue - - if message.tool_call_id == call["id"]: - message.name = call["name"] - has_matching_tool_message = True - - # validate that we always have a ToolMessage matching the tool call in - # Command.update if command is sent to the CURRENT graph - if updated_command.graph is None and not has_matching_tool_message: - example_update = ( - '`Command(update={"messages": [ToolMessage("Success", tool_call_id=tool_call_id), ...]}, ...)`' - if input_type == "dict" - else '`Command(update=[ToolMessage("Success", tool_call_id=tool_call_id), ...], ...)`' - ) - raise ValueError( - f"Expected to have a matching ToolMessage in Command.update for tool '{call['name']}', got: {messages_update}. " - "Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage. " - f"You can fix it by modifying the tool to return {example_update}." - ) - return updated_command - - -def tools_condition( - state: Union[list[AnyMessage], dict[str, Any], BaseModel], - messages_key: str = "messages", -) -> Literal["tools", "__end__"]: - """Use in the conditional_edge to route to the ToolNode if the last message - - has tool calls. Otherwise, route to the end. - - Args: - state (Union[list[AnyMessage], dict[str, Any], BaseModel]): The state to check for - tool calls. Must have a list of messages (MessageGraph) or have the - "messages" key (StateGraph). - - Returns: - The next node to route to. - - - Examples: - Create a custom ReAct-style agent with tools. - - ```pycon - >>> from langchain_anthropic import ChatAnthropic - >>> from langchain_core.tools import tool - ... - >>> from langgraph.graph import StateGraph - >>> from langgraph.prebuilt import ToolNode, tools_condition - >>> from langgraph.graph.message import add_messages - ... - >>> from typing import Annotated - >>> from typing_extensions import TypedDict - ... - >>> @tool - >>> def divide(a: float, b: float) -> int: - ... \"\"\"Return a / b.\"\"\" - ... return a / b - ... - >>> llm = ChatAnthropic(model="claude-3-haiku-20240307") - >>> tools = [divide] - ... - >>> class State(TypedDict): - ... messages: Annotated[list, add_messages] - >>> - >>> graph_builder = StateGraph(State) - >>> graph_builder.add_node("tools", ToolNode(tools)) - >>> graph_builder.add_node("chatbot", lambda state: {"messages":llm.bind_tools(tools).invoke(state['messages'])}) - >>> graph_builder.add_edge("tools", "chatbot") - >>> graph_builder.add_conditional_edges( - ... "chatbot", tools_condition - ... ) - >>> graph_builder.set_entry_point("chatbot") - >>> graph = graph_builder.compile() - >>> graph.invoke({"messages": {"role": "user", "content": "What's 329993 divided by 13662?"}}) - ``` - """ - if isinstance(state, list): - ai_message = state[-1] - elif isinstance(state, dict) and (messages := state.get(messages_key, [])): - ai_message = messages[-1] - elif messages := getattr(state, messages_key, []): - ai_message = messages[-1] - else: - raise ValueError(f"No messages found in input state to tool_edge: {state}") - if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0: - return "tools" - return "__end__" - - -class InjectedState(InjectedToolArg): - """Annotation for a Tool arg that is meant to be populated with the graph state. - - Any Tool argument annotated with InjectedState will be hidden from a tool-calling - model, so that the model doesn't attempt to generate the argument. If using - ToolNode, the appropriate graph state field will be automatically injected into - the model-generated tool args. - - Args: - field: The key from state to insert. If None, the entire state is expected to - be passed in. - - Example: - ```python - from typing import List - from typing_extensions import Annotated, TypedDict - - from langchain_core.messages import BaseMessage, AIMessage - from langchain_core.tools import tool - - from langgraph.prebuilt import InjectedState, ToolNode - - - class AgentState(TypedDict): - messages: List[BaseMessage] - foo: str - - @tool - def state_tool(x: int, state: Annotated[dict, InjectedState]) -> str: - '''Do something with state.''' - if len(state["messages"]) > 2: - return state["foo"] + str(x) - else: - return "not enough messages" - - @tool - def foo_tool(x: int, foo: Annotated[str, InjectedState("foo")]) -> str: - '''Do something else with state.''' - return foo + str(x + 1) - - node = ToolNode([state_tool, foo_tool]) - - tool_call1 = {"name": "state_tool", "args": {"x": 1}, "id": "1", "type": "tool_call"} - tool_call2 = {"name": "foo_tool", "args": {"x": 1}, "id": "2", "type": "tool_call"} - state = { - "messages": [AIMessage("", tool_calls=[tool_call1, tool_call2])], - "foo": "bar", - } - node.invoke(state) - ``` - - ```pycon - [ - ToolMessage(content='not enough messages', name='state_tool', tool_call_id='1'), - ToolMessage(content='bar2', name='foo_tool', tool_call_id='2') - ] - ``` - """ # noqa: E501 - - def __init__(self, field: Optional[str] = None) -> None: - self.field = field - - -class InjectedStore(InjectedToolArg): - """Annotation for a Tool arg that is meant to be populated with LangGraph store. - - Any Tool argument annotated with InjectedStore will be hidden from a tool-calling - model, so that the model doesn't attempt to generate the argument. If using - ToolNode, the appropriate store field will be automatically injected into - the model-generated tool args. Note: if a graph is compiled with a store object, - the store will be automatically propagated to the tools with InjectedStore args - when using ToolNode. - - !!! Warning - `InjectedStore` annotation requires `langchain-core >= 0.3.8` - - Example: - ```python - from typing import Any - from typing_extensions import Annotated - - from langchain_core.messages import AIMessage - from langchain_core.tools import tool - - from langgraph.store.memory import InMemoryStore - from langgraph.prebuilt import InjectedStore, ToolNode - - store = InMemoryStore() - store.put(("values",), "foo", {"bar": 2}) - - @tool - def store_tool(x: int, my_store: Annotated[Any, InjectedStore()]) -> str: - '''Do something with store.''' - stored_value = my_store.get(("values",), "foo").value["bar"] - return stored_value + x - - node = ToolNode([store_tool]) - - tool_call = {"name": "store_tool", "args": {"x": 1}, "id": "1", "type": "tool_call"} - state = { - "messages": [AIMessage("", tool_calls=[tool_call])], - } - - node.invoke(state, store=store) - ``` - - ```pycon - { - "messages": [ - ToolMessage(content='3', name='store_tool', tool_call_id='1'), - ] - } - ``` - """ # noqa: E501 - - -def _is_injection( - type_arg: Any, injection_type: Union[Type[InjectedState], Type[InjectedStore]] -) -> bool: - if isinstance(type_arg, injection_type) or ( - isinstance(type_arg, type) and issubclass(type_arg, injection_type) - ): - return True - origin_ = get_origin(type_arg) - if origin_ is Union or origin_ is Annotated: - return any(_is_injection(ta, injection_type) for ta in get_args(type_arg)) - return False - - -def _get_state_args(tool: BaseTool) -> dict[str, Optional[str]]: - full_schema = tool.get_input_schema() - tool_args_to_state_fields: dict = {} - - for name, type_ in get_all_basemodel_annotations(full_schema).items(): - injections = [ - type_arg - for type_arg in get_args(type_) - if _is_injection(type_arg, InjectedState) - ] - if len(injections) > 1: - raise ValueError( - "A tool argument should not be annotated with InjectedState more than " - f"once. Received arg {name} with annotations {injections}." - ) - elif len(injections) == 1: - injection = injections[0] - if isinstance(injection, InjectedState) and injection.field: - tool_args_to_state_fields[name] = injection.field - else: - tool_args_to_state_fields[name] = None - else: - pass - return tool_args_to_state_fields - - -def _get_store_arg(tool: BaseTool) -> Optional[str]: - full_schema = tool.get_input_schema() - for name, type_ in get_all_basemodel_annotations(full_schema).items(): - injections = [ - type_arg - for type_arg in get_args(type_) - if _is_injection(type_arg, InjectedStore) - ] - if len(injections) > 1: - ValueError( - "A tool argument should not be annotated with InjectedStore more than " - f"once. Received arg {name} with annotations {injections}." - ) - elif len(injections) == 1: - return name - else: - pass - - return None diff --git a/libs/prebuilt/langgraph/prebuilt/tool_validator.py b/libs/prebuilt/langgraph/prebuilt/tool_validator.py deleted file mode 100644 index 70796a8d1..000000000 --- a/libs/prebuilt/langgraph/prebuilt/tool_validator.py +++ /dev/null @@ -1,251 +0,0 @@ -"""This module provides a ValidationNode class that can be used to validate tool calls -in a langchain graph. It applies a pydantic schema to tool_calls in the models' outputs, -and returns a ToolMessage with the validated content. If the schema is not valid, it -returns a ToolMessage with the error message. The ValidationNode can be used in a -StateGraph with a "messages" key or in a MessageGraph. If multiple tool calls are -requested, they will be run in parallel. -""" - -from typing import ( - Any, - Callable, - Dict, - Optional, - Sequence, - Tuple, - Type, - Union, - cast, -) - -from langchain_core.messages import ( - AIMessage, - AnyMessage, - ToolCall, - ToolMessage, -) -from langchain_core.runnables import ( - RunnableConfig, -) -from langchain_core.runnables.config import get_executor_for_config -from langchain_core.tools import BaseTool, create_schema_from_function -from langchain_core.utils.pydantic import is_basemodel_subclass -from pydantic import BaseModel, ValidationError -from pydantic.v1 import BaseModel as BaseModelV1 -from pydantic.v1 import ValidationError as ValidationErrorV1 - -from langgraph.utils.runnable import RunnableCallable - - -def _default_format_error( - error: BaseException, - call: ToolCall, - schema: Union[Type[BaseModel], Type[BaseModelV1]], -) -> str: - """Default error formatting function.""" - return f"{repr(error)}\n\nRespond after fixing all validation errors." - - -class ValidationNode(RunnableCallable): - """A node that validates all tools requests from the last AIMessage. - - It can be used either in StateGraph with a "messages" key or in MessageGraph. - - !!! note - - This node does not actually **run** the tools, it only validates the tool calls, - which is useful for extraction and other use cases where you need to generate - structured output that conforms to a complex schema without losing the original - messages and tool IDs (for use in multi-turn conversations). - - Args: - schemas: A list of schemas to validate the tool calls with. These can be - any of the following: - - A pydantic BaseModel class - - A BaseTool instance (the args_schema will be used) - - A function (a schema will be created from the function signature) - format_error: A function that takes an exception, a ToolCall, and a schema - and returns a formatted error string. By default, it returns the - exception repr and a message to respond after fixing validation errors. - name: The name of the node. - tags: A list of tags to add to the node. - - Returns: - (Union[Dict[str, List[ToolMessage]], Sequence[ToolMessage]]): A list of ToolMessages with the validated content or error messages. - - Examples: - Example usage for re-prompting the model to generate a valid response: - >>> from typing import Literal, Annotated - >>> from typing_extensions import TypedDict - ... - >>> from langchain_anthropic import ChatAnthropic - >>> from pydantic import BaseModel, field_validator - ... - >>> from langgraph.graph import END, START, StateGraph - >>> from langgraph.prebuilt import ValidationNode - >>> from langgraph.graph.message import add_messages - ... - ... - >>> class SelectNumber(BaseModel): - ... a: int - ... - ... @field_validator("a") - ... def a_must_be_meaningful(cls, v): - ... if v != 37: - ... raise ValueError("Only 37 is allowed") - ... return v - ... - ... - >>> builder = StateGraph(Annotated[list, add_messages]) - >>> llm = ChatAnthropic(model="claude-3-5-haiku-latest").bind_tools([SelectNumber]) - >>> builder.add_node("model", llm) - >>> builder.add_node("validation", ValidationNode([SelectNumber])) - >>> builder.add_edge(START, "model") - ... - ... - >>> def should_validate(state: list) -> Literal["validation", "__end__"]: - ... if state[-1].tool_calls: - ... return "validation" - ... return END - ... - ... - >>> builder.add_conditional_edges("model", should_validate) - ... - ... - >>> def should_reprompt(state: list) -> Literal["model", "__end__"]: - ... for msg in state[::-1]: - ... # None of the tool calls were errors - ... if msg.type == "ai": - ... return END - ... if msg.additional_kwargs.get("is_error"): - ... return "model" - ... return END - ... - ... - >>> builder.add_conditional_edges("validation", should_reprompt) - ... - ... - >>> graph = builder.compile() - >>> res = graph.invoke(("user", "Select a number, any number")) - >>> # Show the retry logic - >>> for msg in res: - ... msg.pretty_print() - ================================ Human Message ================================= - Select a number, any number - ================================== Ai Message ================================== - [{'id': 'toolu_01JSjT9Pq8hGmTgmMPc6KnvM', 'input': {'a': 42}, 'name': 'SelectNumber', 'type': 'tool_use'}] - Tool Calls: - SelectNumber (toolu_01JSjT9Pq8hGmTgmMPc6KnvM) - Call ID: toolu_01JSjT9Pq8hGmTgmMPc6KnvM - Args: - a: 42 - ================================= Tool Message ================================= - Name: SelectNumber - ValidationError(model='SelectNumber', errors=[{'loc': ('a',), 'msg': 'Only 37 is allowed', 'type': 'value_error'}]) - Respond after fixing all validation errors. - ================================== Ai Message ================================== - [{'id': 'toolu_01PkxSVxNxc5wqwCPW1FiSmV', 'input': {'a': 37}, 'name': 'SelectNumber', 'type': 'tool_use'}] - Tool Calls: - SelectNumber (toolu_01PkxSVxNxc5wqwCPW1FiSmV) - Call ID: toolu_01PkxSVxNxc5wqwCPW1FiSmV - Args: - a: 37 - ================================= Tool Message ================================= - Name: SelectNumber - {"a": 37} - - """ - - def __init__( - self, - schemas: Sequence[Union[BaseTool, Type[BaseModel], Callable]], - *, - format_error: Optional[ - Callable[[BaseException, ToolCall, Type[BaseModel]], str] - ] = None, - name: str = "validation", - tags: Optional[list[str]] = None, - ) -> None: - super().__init__(self._func, None, name=name, tags=tags, trace=False) - self._format_error = format_error or _default_format_error - self.schemas_by_name: Dict[str, Type[BaseModel]] = {} - for schema in schemas: - if isinstance(schema, BaseTool): - if schema.args_schema is None: - raise ValueError( - f"Tool {schema.name} does not have an args_schema defined." - ) - elif not isinstance( - schema.args_schema, type - ) or not is_basemodel_subclass(schema.args_schema): - raise ValueError( - "Validation node only works with tools that have a pydantic BaseModel args_schema. " - f"Got {schema.name} with args_schema: {schema.args_schema}." - ) - self.schemas_by_name[schema.name] = schema.args_schema - elif isinstance(schema, type) and issubclass( - schema, (BaseModel, BaseModelV1) - ): - self.schemas_by_name[schema.__name__] = cast(Type[BaseModel], schema) - elif callable(schema): - base_model = create_schema_from_function("Validation", schema) - self.schemas_by_name[schema.__name__] = base_model - else: - raise ValueError( - f"Unsupported input to ValidationNode. Expected BaseModel, tool or function. Got: {type(schema)}." - ) - - def _get_message( - self, input: Union[list[AnyMessage], dict[str, Any]] - ) -> Tuple[str, AIMessage]: - """Extract the last AIMessage from the input.""" - if isinstance(input, list): - output_type = "list" - messages: list = input - elif messages := input.get("messages", []): - output_type = "dict" - else: - raise ValueError("No message found in input") - message: AnyMessage = messages[-1] - if not isinstance(message, AIMessage): - raise ValueError("Last message is not an AIMessage") - return output_type, message - - def _func( - self, input: Union[list[AnyMessage], dict[str, Any]], config: RunnableConfig - ) -> Any: - """Validate and run tool calls synchronously.""" - output_type, message = self._get_message(input) - - def run_one(call: ToolCall) -> ToolMessage: - schema = self.schemas_by_name[call["name"]] - try: - if issubclass(schema, BaseModel): - output = schema.model_validate(call["args"]) - content = output.model_dump_json() - elif issubclass(schema, BaseModelV1): - output = schema.validate(call["args"]) - content = output.json() - else: - raise ValueError( - f"Unsupported schema type: {type(schema)}. Expected BaseModel or BaseModelV1." - ) - return ToolMessage( - content=content, - name=call["name"], - tool_call_id=cast(str, call["id"]), - ) - except (ValidationError, ValidationErrorV1) as e: - return ToolMessage( - content=self._format_error(e, call, schema), - name=call["name"], - tool_call_id=cast(str, call["id"]), - additional_kwargs={"is_error": True}, - ) - - with get_executor_for_config(config) as executor: - outputs = [*executor.map(run_one, message.tool_calls)] - if output_type == "list": - return outputs - else: - return {"messages": outputs} diff --git a/libs/prebuilt/poetry.lock b/libs/prebuilt/poetry.lock deleted file mode 100644 index 3000e6ae7..000000000 --- a/libs/prebuilt/poetry.lock +++ /dev/null @@ -1,1476 +0,0 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. - -[[package]] -name = "aiosqlite" -version = "0.20.0" -description = "asyncio bridge to the standard sqlite3 module" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"}, - {file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"}, -] - -[package.dependencies] -typing_extensions = ">=4.0" - -[package.extras] -dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] -docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.8.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "certifi" -version = "2025.1.31" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "platform_python_implementation == \"PyPy\"" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, -] - -[[package]] -name = "codespell" -version = "2.4.1" -description = "Fix common misspellings in text files" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425"}, - {file = "codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5"}, -] - -[package.extras] -dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] -hard-encoding-detection = ["chardet"] -toml = ["tomli"] -types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.7" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "dev"] -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "3.0.0" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, - {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, -] - -[[package]] -name = "langchain-core" -version = "0.3.40" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] -files = [ - {file = "langchain_core-0.3.40-py3-none-any.whl", hash = "sha256:9f31358741f10a13db8531e8288b8a5ae91904018c5c2e6f739d6645a98fca03"}, - {file = "langchain_core-0.3.40.tar.gz", hash = "sha256:893a238b38491967c804662c1ec7c3e6ebaf223d1125331249c3cf3862ff2746"}, -] - -[package.dependencies] -jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.125,<0.4" -packaging = ">=23.2,<25" -pydantic = [ - {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -PyYAML = ">=5.3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" -typing-extensions = ">=4.7" - -[[package]] -name = "langgraph" -version = "0.3.0" -description = "Building stateful, multi-actor applications with LLMs" -optional = false -python-versions = ">=3.9.0,<4.0" -groups = ["dev"] -files = [] -develop = true - -[package.dependencies] -langchain-core = ">=0.1,<0.4" -langgraph-checkpoint = "^2.0.10" -langgraph-sdk = "^0.1.42" - -[package.source] -type = "directory" -url = "../langgraph" - -[[package]] -name = "langgraph-checkpoint" -version = "2.0.16" -description = "Library with base interfaces for LangGraph checkpoint savers." -optional = false -python-versions = "^3.9.0,<4.0" -groups = ["main", "dev"] -files = [] -develop = true - -[package.dependencies] -langchain-core = ">=0.2.38,<0.4" -msgpack = "^1.1.0" - -[package.source] -type = "directory" -url = "../checkpoint" - -[[package]] -name = "langgraph-checkpoint-postgres" -version = "2.0.15" -description = "Library with a Postgres implementation of LangGraph checkpoint saver." -optional = false -python-versions = "^3.9.0,<4.0" -groups = ["dev"] -files = [] -develop = true - -[package.dependencies] -langgraph-checkpoint = "^2.0.15" -orjson = ">=3.10.1" -psycopg = "^3.2.0" -psycopg-pool = "^3.2.0" - -[package.source] -type = "directory" -url = "../checkpoint-postgres" - -[[package]] -name = "langgraph-checkpoint-sqlite" -version = "2.0.5" -description = "Library with a SQLite implementation of LangGraph checkpoint saver." -optional = false -python-versions = "^3.9.0" -groups = ["dev"] -files = [] -develop = true - -[package.dependencies] -aiosqlite = ">=0.20,<0.22" -langgraph-checkpoint = "^2.0.15" - -[package.source] -type = "directory" -url = "../checkpoint-sqlite" - -[[package]] -name = "langgraph-sdk" -version = "0.1.53" -description = "SDK for interacting with LangGraph API" -optional = false -python-versions = "<4.0.0,>=3.9.0" -groups = ["dev"] -files = [ - {file = "langgraph_sdk-0.1.53-py3-none-any.whl", hash = "sha256:4fab62caad73661ffe4c3ababedcd0d7bfaaba986bee4416b9c28948458a3af5"}, - {file = "langgraph_sdk-0.1.53.tar.gz", hash = "sha256:12906ed965905fa27e0c28d9fa07dc6fd89e6895ff321ff049fdf3965d057cc4"}, -] - -[package.dependencies] -httpx = ">=0.25.2" -orjson = ">=3.10.1" - -[[package]] -name = "langsmith" -version = "0.3.11" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] -files = [ - {file = "langsmith-0.3.11-py3-none-any.whl", hash = "sha256:0cca22737ef07d3b038a437c141deda37e00add56022582680188b681bec095e"}, - {file = "langsmith-0.3.11.tar.gz", hash = "sha256:ddf29d24352e99de79c9618aaf95679214324e146c5d3d9475a7ddd2870018b1"}, -] - -[package.dependencies] -httpx = ">=0.23.0,<1" -orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} -packaging = ">=23.2" -pydantic = [ - {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -requests = ">=2,<3" -requests-toolbelt = ">=1.0.0,<2.0.0" -zstandard = ">=0.23.0,<0.24.0" - -[package.extras] -langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] -pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"] - -[[package]] -name = "msgpack" -version = "1.1.0" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, - {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, - {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, - {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, - {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, - {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, - {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, - {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, - {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, - {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, - {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, - {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, - {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, - {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, -] - -[[package]] -name = "mypy" -version = "1.15.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, - {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, - {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, - {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, - {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, - {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, - {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, - {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, - {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, - {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, - {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, - {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, - {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, - {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, -] - -[package.dependencies] -mypy_extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "orjson" -version = "3.10.15" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"}, - {file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"}, - {file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"}, - {file = "orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"}, - {file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"}, - {file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"}, - {file = "orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"}, - {file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"}, - {file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"}, - {file = "orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"}, - {file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"}, - {file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"}, - {file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"}, - {file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"}, - {file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"}, - {file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"}, - {file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"}, - {file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"}, - {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, -] -markers = {main = "platform_python_implementation != \"PyPy\""} - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "psycopg" -version = "3.2.5" -description = "PostgreSQL database adapter for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "psycopg-3.2.5-py3-none-any.whl", hash = "sha256:b782130983e5b3de30b4c529623d3687033b4dafa05bb661fc6bf45837ca5879"}, - {file = "psycopg-3.2.5.tar.gz", hash = "sha256:f5f750611c67cb200e85b408882f29265c66d1de7f813add4f8125978bfd70e8"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} -tzdata = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -binary = ["psycopg-binary (==3.2.5)"] -c = ["psycopg-c (==3.2.5)"] -dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] -docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] -pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] - -[[package]] -name = "psycopg-pool" -version = "3.2.5" -description = "Connection Pool for Psycopg" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "psycopg_pool-3.2.5-py3-none-any.whl", hash = "sha256:522b9befae8631550217fd2ae9904622daaf4b28a0a36ccfcc87ba3d2abc7100"}, - {file = "psycopg_pool-3.2.5.tar.gz", hash = "sha256:507d845bd13da83df449ded7fb581b35dba613421bdb12962e608b16e70d6be9"}, -] - -[package.dependencies] -typing-extensions = ">=4.6" - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "platform_python_implementation == \"PyPy\"" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "2.10.6" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" -typing-extensions = ">=4.12.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.27.2" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.21.2" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, - {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, -] - -[package.dependencies] -pytest = ">=7.0.0" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "pytest-watcher" -version = "0.4.3" -description = "Automatically rerun your tests on file modifications" -optional = false -python-versions = "<4.0.0,>=3.7.0" -groups = ["dev"] -files = [ - {file = "pytest_watcher-0.4.3-py3-none-any.whl", hash = "sha256:d59b1e1396f33a65ea4949b713d6884637755d641646960056a90b267c3460f9"}, - {file = "pytest_watcher-0.4.3.tar.gz", hash = "sha256:0cb0e4661648c8c0ff2b2d25efa5a8e421784b9e4c60fcecbf9b7c30b2d731b3"}, -] - -[package.dependencies] -tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} -watchdog = ">=2.0.0" - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -description = "A utility belt for advanced users of python-requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "dev"] -files = [ - {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, - {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, -] - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - -[[package]] -name = "ruff" -version = "0.6.9" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "tenacity" -version = "9.0.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, - {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - -[[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2025.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, - {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, -] - -[[package]] -name = "urllib3" -version = "2.3.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "watchdog" -version = "6.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, - {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, - {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, - {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, - {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "zstandard" -version = "0.23.0" -description = "Zstandard bindings for Python" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, - {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"}, - {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"}, - {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"}, - {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"}, - {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"}, - {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"}, - {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"}, - {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"}, - {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"}, - {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"}, - {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"}, - {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"}, - {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"}, - {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"}, - {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"}, - {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"}, - {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"}, - {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"}, - {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"}, - {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"}, - {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"}, - {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"}, - {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"}, - {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"}, - {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"}, - {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"}, - {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"}, - {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"}, - {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"}, - {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"}, - {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"}, - {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"}, - {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"}, - {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"}, - {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, - {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, -] - -[package.dependencies] -cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} - -[package.extras] -cffi = ["cffi (>=1.11)"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.9.0,<4.0" -content-hash = "af5dc04219a8b3bce05705a0664ff424b40b426e47575d0a8a36415563553942" diff --git a/libs/prebuilt/pyproject.toml b/libs/prebuilt/pyproject.toml deleted file mode 100644 index 86a8cda42..000000000 --- a/libs/prebuilt/pyproject.toml +++ /dev/null @@ -1,62 +0,0 @@ -[tool.poetry] -name = "langgraph-prebuilt" -version = "0.1.1" -description = "Library with high-level APIs for creating and executing LangGraph agents and tools." -authors = [] -license = "MIT" -readme = "README.md" -repository = "https://www.github.com/langchain-ai/langgraph" -packages = [{ include = "langgraph" }] - -[tool.poetry.dependencies] -python = "^3.9.0,<4.0" -langgraph-checkpoint = "^2.0.10" -langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.20,!=0.3.21,!=0.3.22" - -[tool.poetry.group.dev.dependencies] -ruff = "^0.6.2" -codespell = "^2.2.0" -pytest = "^7.2.1" -pytest-asyncio = "^0.21.1" -pytest-mock = "^3.11.1" -pytest-watcher = "^0.4.1" -mypy = "^1.10.0" -langgraph = {path = "../langgraph", develop = true} -langgraph-checkpoint = {path = "../checkpoint", develop = true} -langgraph-checkpoint-sqlite = {path = "../checkpoint-sqlite", develop = true} -langgraph-checkpoint-postgres = {path = "../checkpoint-postgres", develop = true} - -[tool.pytest.ini_options] -# --strict-markers will raise errors on unknown marks. -# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks -# -# https://docs.pytest.org/en/7.1.x/reference/reference.html -# --strict-config any warnings encountered while parsing the `pytest` -# section of the configuration file raise errors. -addopts = "--strict-markers --strict-config --durations=5 -vv" -asyncio_mode = "auto" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.ruff] -lint.select = [ "E", "F", "I", "TID251" ] -lint.ignore = [ "E501" ] - -[tool.pytest-watcher] -now = true -delay = 0.1 -runner_args = ["--ff", "-v", "--tb", "short"] -patterns = ["*.py"] - -[tool.mypy] -# https://mypy.readthedocs.io/en/stable/config_file.html -disallow_untyped_defs = "True" -explicit_package_bases = "True" -warn_no_return = "False" -warn_unused_ignores = "True" -warn_redundant_casts = "True" -allow_redefinition = "True" -disable_error_code = "typeddict-item, return-value" diff --git a/libs/prebuilt/tests/__init__.py b/libs/prebuilt/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/libs/prebuilt/tests/any_str.py b/libs/prebuilt/tests/any_str.py deleted file mode 100644 index 5643a00fb..000000000 --- a/libs/prebuilt/tests/any_str.py +++ /dev/null @@ -1,86 +0,0 @@ -import re -from typing import Any, Sequence, Union - -from typing_extensions import Self - - -class FloatBetween(float): - def __new__(cls, min_value: float, max_value: float) -> Self: - return super().__new__(cls, min_value) - - def __init__(self, min_value: float, max_value: float) -> None: - super().__init__() - self.min_value = min_value - self.max_value = max_value - - def __eq__(self, other: object) -> bool: - return ( - isinstance(other, float) - and other >= self.min_value - and other <= self.max_value - ) - - def __hash__(self) -> int: - return hash((float(self), self.min_value, self.max_value)) - - -class AnyStr(str): - def __init__(self, prefix: Union[str, re.Pattern] = "") -> None: - super().__init__() - self.prefix = prefix - - def __eq__(self, other: object) -> bool: - return isinstance(other, str) and ( - other.startswith(self.prefix) - if isinstance(self.prefix, str) - else self.prefix.match(other) - ) - - def __hash__(self) -> int: - return hash((str(self), self.prefix)) - - -class AnyDict(dict): - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, dict) or len(self) != len(other): - return False - for k, v in self.items(): - if kk := next((kk for kk in other if kk == k), None): - if v == other[kk]: - continue - else: - return False - else: - return True - - -class AnyVersion: - def __init__(self) -> None: - super().__init__() - - def __eq__(self, other: object) -> bool: - return isinstance(other, (str, int, float)) - - def __hash__(self) -> int: - return hash(str(self)) - - -class UnsortedSequence: - def __init__(self, *values: Any) -> None: - self.seq = values - - def __eq__(self, value: object) -> bool: - return ( - isinstance(value, Sequence) - and len(self.seq) == len(value) - and all(a in value for a in self.seq) - ) - - def __hash__(self) -> int: - return hash(frozenset(self.seq)) - - def __repr__(self) -> str: - return repr(self.seq) diff --git a/libs/prebuilt/tests/compose-postgres.yml b/libs/prebuilt/tests/compose-postgres.yml deleted file mode 100644 index 221b35daf..000000000 --- a/libs/prebuilt/tests/compose-postgres.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: langgraph-tests -services: - postgres-test: - image: postgres:16 - ports: - - "5442:5432" - environment: - POSTGRES_DB: postgres - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - healthcheck: - test: pg_isready -U postgres - start_period: 10s - timeout: 1s - retries: 5 - interval: 60s - start_interval: 1s diff --git a/libs/prebuilt/tests/conftest.py b/libs/prebuilt/tests/conftest.py deleted file mode 100644 index 20f9e15f6..000000000 --- a/libs/prebuilt/tests/conftest.py +++ /dev/null @@ -1,448 +0,0 @@ -import sys -from contextlib import asynccontextmanager -from typing import AsyncIterator, Optional -from uuid import UUID, uuid4 - -import pytest -from langchain_core import __version__ as core_version -from packaging import version -from psycopg import AsyncConnection, Connection -from psycopg_pool import AsyncConnectionPool, ConnectionPool -from pytest_mock import MockerFixture - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver -from langgraph.checkpoint.postgres.aio import ( - AsyncPostgresSaver, - AsyncShallowPostgresSaver, -) -from langgraph.checkpoint.sqlite import SqliteSaver -from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver -from langgraph.store.base import BaseStore -from langgraph.store.memory import InMemoryStore -from langgraph.store.postgres import AsyncPostgresStore, PostgresStore - -pytest.register_assert_rewrite("tests.memory_assert") - -DEFAULT_POSTGRES_URI = "postgres://postgres:postgres@localhost:5442/" -# TODO: fix this once core is released -IS_LANGCHAIN_CORE_030_OR_GREATER = version.parse(core_version) >= version.parse( - "0.3.0.dev0" -) - - -@pytest.fixture -def anyio_backend(): - return "asyncio" - - -@pytest.fixture() -def deterministic_uuids(mocker: MockerFixture) -> MockerFixture: - side_effect = ( - UUID(f"00000000-0000-4000-8000-{i:012}", version=4) for i in range(10000) - ) - return mocker.patch("uuid.uuid4", side_effect=side_effect) - - -# checkpointer fixtures - - -@pytest.fixture(scope="function") -def checkpointer_memory(): - from tests.memory_assert import MemorySaverAssertImmutable - - yield MemorySaverAssertImmutable() - - -@pytest.fixture(scope="function") -def checkpointer_sqlite(): - with SqliteSaver.from_conn_string(":memory:") as checkpointer: - yield checkpointer - - -@asynccontextmanager -async def _checkpointer_sqlite_aio(): - async with AsyncSqliteSaver.from_conn_string(":memory:") as checkpointer: - yield checkpointer - - -@pytest.fixture(scope="function") -def checkpointer_postgres(): - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - with PostgresSaver.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as checkpointer: - checkpointer.setup() - yield checkpointer - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture(scope="function") -def checkpointer_postgres_shallow(): - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - with ShallowPostgresSaver.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as checkpointer: - checkpointer.setup() - yield checkpointer - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture(scope="function") -def checkpointer_postgres_pipe(): - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - with PostgresSaver.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as checkpointer: - checkpointer.setup() - # setup can't run inside pipeline because of implicit transaction - with checkpointer.conn.pipeline() as pipe: - checkpointer.pipe = pipe - yield checkpointer - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture(scope="function") -def checkpointer_postgres_pool(): - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - with ConnectionPool( - DEFAULT_POSTGRES_URI + database, max_size=10, kwargs={"autocommit": True} - ) as pool: - checkpointer = PostgresSaver(pool) - checkpointer.setup() - yield checkpointer - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") - - -@asynccontextmanager -async def _checkpointer_postgres_aio(): - if sys.version_info < (3, 10): - pytest.skip("Async Postgres tests require Python 3.10+") - database = f"test_{uuid4().hex[:16]}" - # create unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - async with AsyncPostgresSaver.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as checkpointer: - await checkpointer.setup() - yield checkpointer - finally: - # drop unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@asynccontextmanager -async def _checkpointer_postgres_aio_shallow(): - if sys.version_info < (3, 10): - pytest.skip("Async Postgres tests require Python 3.10+") - database = f"test_{uuid4().hex[:16]}" - # create unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - async with AsyncShallowPostgresSaver.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as checkpointer: - await checkpointer.setup() - yield checkpointer - finally: - # drop unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@asynccontextmanager -async def _checkpointer_postgres_aio_pipe(): - if sys.version_info < (3, 10): - pytest.skip("Async Postgres tests require Python 3.10+") - database = f"test_{uuid4().hex[:16]}" - # create unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - async with AsyncPostgresSaver.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as checkpointer: - await checkpointer.setup() - # setup can't run inside pipeline because of implicit transaction - async with checkpointer.conn.pipeline() as pipe: - checkpointer.pipe = pipe - yield checkpointer - finally: - # drop unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@asynccontextmanager -async def _checkpointer_postgres_aio_pool(): - if sys.version_info < (3, 10): - pytest.skip("Async Postgres tests require Python 3.10+") - database = f"test_{uuid4().hex[:16]}" - # create unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - # yield checkpointer - async with AsyncConnectionPool( - DEFAULT_POSTGRES_URI + database, max_size=10, kwargs={"autocommit": True} - ) as pool: - checkpointer = AsyncPostgresSaver(pool) - await checkpointer.setup() - yield checkpointer - finally: - # drop unique db - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@asynccontextmanager -async def awith_checkpointer( - checkpointer_name: Optional[str], -) -> AsyncIterator[BaseCheckpointSaver]: - if checkpointer_name is None: - yield None - elif checkpointer_name == "memory": - from tests.memory_assert import MemorySaverAssertImmutable - - yield MemorySaverAssertImmutable() - elif checkpointer_name == "sqlite_aio": - async with _checkpointer_sqlite_aio() as checkpointer: - yield checkpointer - elif checkpointer_name == "postgres_aio": - async with _checkpointer_postgres_aio() as checkpointer: - yield checkpointer - elif checkpointer_name == "postgres_aio_shallow": - async with _checkpointer_postgres_aio_shallow() as checkpointer: - yield checkpointer - elif checkpointer_name == "postgres_aio_pipe": - async with _checkpointer_postgres_aio_pipe() as checkpointer: - yield checkpointer - elif checkpointer_name == "postgres_aio_pool": - async with _checkpointer_postgres_aio_pool() as checkpointer: - yield checkpointer - else: - raise NotImplementedError(f"Unknown checkpointer: {checkpointer_name}") - - -@asynccontextmanager -async def _store_postgres_aio(): - if sys.version_info < (3, 10): - pytest.skip("Async Postgres tests require Python 3.10+") - database = f"test_{uuid4().hex[:16]}" - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - async with AsyncPostgresStore.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as store: - await store.setup() - yield store - finally: - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@asynccontextmanager -async def _store_postgres_aio_pipe(): - if sys.version_info < (3, 10): - pytest.skip("Async Postgres tests require Python 3.10+") - database = f"test_{uuid4().hex[:16]}" - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - async with AsyncPostgresStore.from_conn_string( - DEFAULT_POSTGRES_URI + database - ) as store: - await store.setup() # Run in its own transaction - async with AsyncPostgresStore.from_conn_string( - DEFAULT_POSTGRES_URI + database, pipeline=True - ) as store: - yield store - finally: - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@asynccontextmanager -async def _store_postgres_aio_pool(): - if sys.version_info < (3, 10): - pytest.skip("Async Postgres tests require Python 3.10+") - database = f"test_{uuid4().hex[:16]}" - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"CREATE DATABASE {database}") - try: - async with AsyncPostgresStore.from_conn_string( - DEFAULT_POSTGRES_URI + database, - pool_config={"max_size": 10}, - ) as store: - await store.setup() - yield store - finally: - async with await AsyncConnection.connect( - DEFAULT_POSTGRES_URI, autocommit=True - ) as conn: - await conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture(scope="function") -def store_postgres(): - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield store - with PostgresStore.from_conn_string(DEFAULT_POSTGRES_URI + database) as store: - store.setup() - yield store - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture(scope="function") -def store_postgres_pipe(): - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield store - with PostgresStore.from_conn_string(DEFAULT_POSTGRES_URI + database) as store: - store.setup() # Run in its own transaction - with PostgresStore.from_conn_string( - DEFAULT_POSTGRES_URI + database, pipeline=True - ) as store: - yield store - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture(scope="function") -def store_postgres_pool(): - database = f"test_{uuid4().hex[:16]}" - # create unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"CREATE DATABASE {database}") - try: - # yield store - with PostgresStore.from_conn_string( - DEFAULT_POSTGRES_URI + database, pool_config={"max_size": 10} - ) as store: - store.setup() - yield store - finally: - # drop unique db - with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn: - conn.execute(f"DROP DATABASE {database}") - - -@pytest.fixture(scope="function") -def store_in_memory(): - yield InMemoryStore() - - -@asynccontextmanager -async def awith_store(store_name: Optional[str]) -> AsyncIterator[BaseStore]: - if store_name is None: - yield None - elif store_name == "in_memory": - yield InMemoryStore() - elif store_name == "postgres_aio": - async with _store_postgres_aio() as store: - yield store - elif store_name == "postgres_aio_pipe": - async with _store_postgres_aio_pipe() as store: - yield store - elif store_name == "postgres_aio_pool": - async with _store_postgres_aio_pool() as store: - yield store - else: - raise NotImplementedError(f"Unknown store {store_name}") - - -ALL_CHECKPOINTERS_SYNC = [ - "memory", - "sqlite", - "postgres", - "postgres_pipe", - "postgres_pool", - "postgres_shallow", -] - -ALL_CHECKPOINTERS_ASYNC = [ - "memory", - "sqlite_aio", - "postgres_aio", - "postgres_aio_pipe", - "postgres_aio_pool", - "postgres_aio_shallow", -] diff --git a/libs/prebuilt/tests/memory_assert.py b/libs/prebuilt/tests/memory_assert.py deleted file mode 100644 index f88f0358f..000000000 --- a/libs/prebuilt/tests/memory_assert.py +++ /dev/null @@ -1,134 +0,0 @@ -import asyncio -import os -import tempfile -from collections import defaultdict -from functools import partial -from typing import Any, Optional - -from langchain_core.runnables import RunnableConfig - -from langgraph.checkpoint.base import ( - ChannelVersions, - Checkpoint, - CheckpointMetadata, - CheckpointTuple, - SerializerProtocol, - copy_checkpoint, -) -from langgraph.checkpoint.memory import InMemorySaver, PersistentDict - - -class NoopSerializer(SerializerProtocol): - def loads_typed(self, data: tuple[str, bytes]) -> Any: - return data[1] - - def dumps_typed(self, obj: Any) -> tuple[str, bytes]: - return "type", obj - - -class MemorySaverAssertImmutable(InMemorySaver): - storage_for_copies: defaultdict[str, dict[str, dict[str, Checkpoint]]] - - def __init__( - self, - *, - serde: Optional[SerializerProtocol] = None, - put_sleep: Optional[float] = None, - ) -> None: - _, filename = tempfile.mkstemp() - super().__init__( - serde=serde, factory=partial(PersistentDict, filename=filename) - ) - self.storage_for_copies = defaultdict(lambda: defaultdict(dict)) - self.put_sleep = put_sleep - self.stack.callback(os.remove, filename) - - def put( - self, - config: dict, - checkpoint: Checkpoint, - metadata: CheckpointMetadata, - new_versions: ChannelVersions, - ) -> None: - if self.put_sleep: - import time - - time.sleep(self.put_sleep) - # assert checkpoint hasn't been modified since last written - thread_id = config["configurable"]["thread_id"] - checkpoint_ns = config["configurable"]["checkpoint_ns"] - if saved := super().get(config): - assert ( - self.serde.loads_typed( - self.storage_for_copies[thread_id][checkpoint_ns][saved["id"]] - ) - == saved - ) - self.storage_for_copies[thread_id][checkpoint_ns][checkpoint["id"]] = ( - self.serde.dumps_typed(copy_checkpoint(checkpoint)) - ) - # call super to write checkpoint - return super().put(config, checkpoint, metadata, new_versions) - - -class MemorySaverAssertCheckpointMetadata(InMemorySaver): - """This custom checkpointer is for verifying that a run's configurable - fields are merged with the previous checkpoint config for each step in - the run. This is the desired behavior. Because the checkpointer's (a)put() - method is called for each step, the implementation of this checkpointer - should produce a side effect that can be asserted. - """ - - def put( - self, - config: RunnableConfig, - checkpoint: Checkpoint, - metadata: CheckpointMetadata, - new_versions: ChannelVersions, - ) -> None: - """The implementation of put() merges config["configurable"] (a run's - configurable fields) with the metadata field. The state of the - checkpoint metadata can be asserted to confirm that the run's - configurable fields were merged with the previous checkpoint config. - """ - configurable = config["configurable"].copy() - - # remove checkpoint_id to make testing simpler - checkpoint_id = configurable.pop("checkpoint_id", None) - thread_id = config["configurable"]["thread_id"] - checkpoint_ns = config["configurable"]["checkpoint_ns"] - self.storage[thread_id][checkpoint_ns].update( - { - checkpoint["id"]: ( - self.serde.dumps_typed(checkpoint), - # merge configurable fields and metadata - self.serde.dumps_typed({**configurable, **metadata}), - checkpoint_id, - ) - } - ) - return { - "configurable": { - "thread_id": config["configurable"]["thread_id"], - "checkpoint_id": checkpoint["id"], - } - } - - async def aput( - self, - config: RunnableConfig, - checkpoint: Checkpoint, - metadata: CheckpointMetadata, - new_versions: ChannelVersions, - ) -> RunnableConfig: - return await asyncio.get_running_loop().run_in_executor( - None, self.put, config, checkpoint, metadata, new_versions - ) - - -class MemorySaverNoPending(InMemorySaver): - def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: - result = super().get_tuple(config) - if result: - return CheckpointTuple(result.config, result.checkpoint, result.metadata) - return result diff --git a/libs/prebuilt/tests/messages.py b/libs/prebuilt/tests/messages.py deleted file mode 100644 index ecc657a36..000000000 --- a/libs/prebuilt/tests/messages.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Redefined messages as a work-around for pydantic issue with AnyStr. - -The code below creates version of pydantic models -that will work in unit tests with AnyStr as id field -Please note that the `id` field is assigned AFTER the model is created -to workaround an issue with pydantic ignoring the __eq__ method on -subclassed strings. -""" - -from typing import Any - -from langchain_core.documents import Document -from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage, ToolMessage - -from tests.any_str import AnyStr - - -def _AnyIdDocument(**kwargs: Any) -> Document: - """Create a document with an id field.""" - message = Document(**kwargs) - message.id = AnyStr() - return message - - -def _AnyIdAIMessage(**kwargs: Any) -> AIMessage: - """Create ai message with an any id field.""" - message = AIMessage(**kwargs) - message.id = AnyStr() - return message - - -def _AnyIdAIMessageChunk(**kwargs: Any) -> AIMessageChunk: - """Create ai message with an any id field.""" - message = AIMessageChunk(**kwargs) - message.id = AnyStr() - return message - - -def _AnyIdHumanMessage(**kwargs: Any) -> HumanMessage: - """Create a human message with an any id field.""" - message = HumanMessage(**kwargs) - message.id = AnyStr() - return message - - -def _AnyIdToolMessage(**kwargs: Any) -> ToolMessage: - """Create a tool message with an any id field.""" - message = ToolMessage(**kwargs) - message.id = AnyStr() - return message diff --git a/libs/prebuilt/tests/model.py b/libs/prebuilt/tests/model.py deleted file mode 100644 index 8e9857675..000000000 --- a/libs/prebuilt/tests/model.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import ( - Any, - Callable, - Dict, - List, - Literal, - Optional, - Sequence, - Type, - Union, -) - -from langchain_core.callbacks import CallbackManagerForLLMRun -from langchain_core.language_models import BaseChatModel, LanguageModelInput -from langchain_core.messages import ( - AIMessage, - BaseMessage, - ToolCall, -) -from langchain_core.outputs import ChatGeneration, ChatResult -from langchain_core.runnables import Runnable, RunnableLambda -from langchain_core.tools import BaseTool -from pydantic import BaseModel - -from langgraph.prebuilt.chat_agent_executor import StructuredResponse - - -class FakeToolCallingModel(BaseChatModel): - tool_calls: Optional[list[list[ToolCall]]] = None - structured_response: Optional[StructuredResponse] = None - index: int = 0 - tool_style: Literal["openai", "anthropic"] = "openai" - - def _generate( - self, - messages: List[BaseMessage], - stop: Optional[List[str]] = None, - run_manager: Optional[CallbackManagerForLLMRun] = None, - **kwargs: Any, - ) -> ChatResult: - """Top Level call""" - messages_string = "-".join([m.content for m in messages]) - tool_calls = ( - self.tool_calls[self.index % len(self.tool_calls)] - if self.tool_calls - else [] - ) - message = AIMessage( - content=messages_string, id=str(self.index), tool_calls=tool_calls.copy() - ) - self.index += 1 - return ChatResult(generations=[ChatGeneration(message=message)]) - - @property - def _llm_type(self) -> str: - return "fake-tool-call-model" - - def with_structured_output( - self, schema: Type[BaseModel] - ) -> Runnable[LanguageModelInput, StructuredResponse]: - if self.structured_response is None: - raise ValueError("Structured response is not set") - - return RunnableLambda(lambda x: self.structured_response) - - def bind_tools( - self, - tools: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool]], - **kwargs: Any, - ) -> Runnable[LanguageModelInput, BaseMessage]: - if len(tools) == 0: - raise ValueError("Must provide at least one tool") - - tool_dicts = [] - for tool in tools: - if not isinstance(tool, BaseTool): - raise TypeError( - "Only BaseTool is supported by FakeToolCallingModel.bind_tools" - ) - - # NOTE: this is a simplified tool spec for testing purposes only - if self.tool_style == "openai": - tool_dicts.append( - { - "type": "function", - "function": { - "name": tool.name, - }, - } - ) - elif self.tool_style == "anthropic": - tool_dicts.append( - { - "name": tool.name, - } - ) - - return self.bind(tools=tool_dicts) diff --git a/libs/prebuilt/tests/test_react_agent.py b/libs/prebuilt/tests/test_react_agent.py deleted file mode 100644 index 0b6f10f51..000000000 --- a/libs/prebuilt/tests/test_react_agent.py +++ /dev/null @@ -1,1326 +0,0 @@ -import dataclasses -import inspect -import json -from functools import partial -from typing import ( - Annotated, - List, - Type, - TypeVar, - Union, -) - -import pytest -from langchain_core.messages import ( - AIMessage, - AnyMessage, - HumanMessage, - SystemMessage, - ToolCall, - ToolMessage, -) -from langchain_core.runnables import RunnableLambda -from langchain_core.tools import InjectedToolCallId, ToolException -from langchain_core.tools import tool as dec_tool -from pydantic import BaseModel, Field -from pydantic.v1 import BaseModel as BaseModelV1 -from typing_extensions import TypedDict - -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.graph import START, MessagesState, StateGraph, add_messages -from langgraph.prebuilt import ( - ToolNode, - create_react_agent, - tools_condition, -) -from langgraph.prebuilt.chat_agent_executor import ( - AgentState, - _validate_chat_history, -) -from langgraph.prebuilt.tool_node import ( - InjectedState, - InjectedStore, - _get_state_args, - _infer_handled_types, -) -from langgraph.store.base import BaseStore -from langgraph.store.memory import InMemoryStore -from langgraph.types import Command, Interrupt, interrupt -from langgraph.utils.config import get_stream_writer -from tests.any_str import AnyStr -from tests.conftest import ( - ALL_CHECKPOINTERS_ASYNC, - ALL_CHECKPOINTERS_SYNC, - IS_LANGCHAIN_CORE_030_OR_GREATER, - awith_checkpointer, -) -from tests.messages import _AnyIdHumanMessage, _AnyIdToolMessage -from tests.model import FakeToolCallingModel - -pytestmark = pytest.mark.anyio - -REACT_TOOL_CALL_VERSIONS = ["v1", "v2"] - - -@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_no_prompt( - request: pytest.FixtureRequest, checkpointer_name: str, version: str -) -> None: - checkpointer: BaseCheckpointSaver = request.getfixturevalue( - "checkpointer_" + checkpointer_name - ) - model = FakeToolCallingModel() - - agent = create_react_agent( - model, - [], - checkpointer=checkpointer, - version=version, - ) - inputs = [HumanMessage("hi?")] - thread = {"configurable": {"thread_id": "123"}} - response = agent.invoke({"messages": inputs}, thread, debug=True) - expected_response = {"messages": inputs + [AIMessage(content="hi?", id="0")]} - assert response == expected_response - - if checkpointer: - saved = checkpointer.get_tuple(thread) - assert saved is not None - assert saved.checkpoint["channel_values"] == { - "messages": [ - _AnyIdHumanMessage(content="hi?"), - AIMessage(content="hi?", id="0"), - ], - "agent": "agent", - } - assert saved.metadata == { - "parents": {}, - "source": "loop", - "writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}}, - "step": 1, - "thread_id": "123", - } - assert saved.pending_writes == [] - - -@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) -async def test_no_prompt_async(checkpointer_name: str) -> None: - async with awith_checkpointer(checkpointer_name) as checkpointer: - model = FakeToolCallingModel() - - agent = create_react_agent(model, [], checkpointer=checkpointer) - inputs = [HumanMessage("hi?")] - thread = {"configurable": {"thread_id": "123"}} - response = await agent.ainvoke({"messages": inputs}, thread, debug=True) - expected_response = {"messages": inputs + [AIMessage(content="hi?", id="0")]} - assert response == expected_response - - if checkpointer: - saved = await checkpointer.aget_tuple(thread) - assert saved is not None - assert saved.checkpoint["channel_values"] == { - "messages": [ - _AnyIdHumanMessage(content="hi?"), - AIMessage(content="hi?", id="0"), - ], - "agent": "agent", - } - assert saved.metadata == { - "parents": {}, - "source": "loop", - "writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}}, - "step": 1, - "thread_id": "123", - } - assert saved.pending_writes == [] - - -def test_passing_two_modifiers(): - model = FakeToolCallingModel() - - with pytest.raises(ValueError): - create_react_agent(model, [], state_modifier="Foo", prompt="Bar") - - -def test_system_message_prompt(): - prompt = SystemMessage(content="Foo") - for agent in ( - create_react_agent(FakeToolCallingModel(), [], prompt=prompt), - create_react_agent(FakeToolCallingModel(), [], state_modifier=prompt), - ): - inputs = [HumanMessage("hi?")] - response = agent.invoke({"messages": inputs}) - expected_response = { - "messages": inputs + [AIMessage(content="Foo-hi?", id="0", tool_calls=[])] - } - assert response == expected_response - - -def test_string_prompt(): - prompt = "Foo" - for agent in ( - create_react_agent(FakeToolCallingModel(), [], prompt=prompt), - create_react_agent(FakeToolCallingModel(), [], state_modifier=prompt), - ): - inputs = [HumanMessage("hi?")] - response = agent.invoke({"messages": inputs}) - expected_response = { - "messages": inputs + [AIMessage(content="Foo-hi?", id="0", tool_calls=[])] - } - assert response == expected_response - - -def test_callable_prompt(): - def prompt(state): - modified_message = f"Bar {state['messages'][-1].content}" - return [HumanMessage(content=modified_message)] - - for agent in ( - create_react_agent(FakeToolCallingModel(), [], prompt=prompt), - create_react_agent(FakeToolCallingModel(), [], state_modifier=prompt), - ): - inputs = [HumanMessage("hi?")] - response = agent.invoke({"messages": inputs}) - expected_response = { - "messages": inputs + [AIMessage(content="Bar hi?", id="0")] - } - assert response == expected_response - - -async def test_callable_prompt_async(): - async def prompt(state): - modified_message = f"Bar {state['messages'][-1].content}" - return [HumanMessage(content=modified_message)] - - for agent in ( - create_react_agent(FakeToolCallingModel(), [], prompt=prompt), - create_react_agent(FakeToolCallingModel(), [], state_modifier=prompt), - ): - inputs = [HumanMessage("hi?")] - response = await agent.ainvoke({"messages": inputs}) - expected_response = { - "messages": inputs + [AIMessage(content="Bar hi?", id="0")] - } - assert response == expected_response - - -def test_runnable_prompt(): - prompt = RunnableLambda( - lambda state: [HumanMessage(content=f"Baz {state['messages'][-1].content}")] - ) - - for agent in ( - create_react_agent(FakeToolCallingModel(), [], prompt=prompt), - create_react_agent(FakeToolCallingModel(), [], state_modifier=prompt), - ): - inputs = [HumanMessage("hi?")] - response = agent.invoke({"messages": inputs}) - expected_response = { - "messages": inputs + [AIMessage(content="Baz hi?", id="0")] - } - assert response == expected_response - - -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_prompt_with_store(version: str): - def add(a: int, b: int): - """Adds a and b""" - return a + b - - in_memory_store = InMemoryStore() - in_memory_store.put(("memories", "1"), "user_name", {"data": "User name is Alice"}) - in_memory_store.put(("memories", "2"), "user_name", {"data": "User name is Bob"}) - - def prompt(state, config, *, store): - user_id = config["configurable"]["user_id"] - system_str = store.get(("memories", user_id), "user_name").value["data"] - return [SystemMessage(system_str)] + state["messages"] - - def prompt_no_store(state, config): - return SystemMessage("foo") + state["messages"] - - model = FakeToolCallingModel() - - # test state modifier that uses store works - agent = create_react_agent( - model, - [add], - prompt=prompt, - store=in_memory_store, - version=version, - ) - response = agent.invoke( - {"messages": [("user", "hi")]}, {"configurable": {"user_id": "1"}} - ) - assert response["messages"][-1].content == "User name is Alice-hi" - - # test state modifier that doesn't use store works - agent = create_react_agent( - model, - [add], - prompt=prompt_no_store, - store=in_memory_store, - version=version, - ) - response = agent.invoke( - {"messages": [("user", "hi")]}, {"configurable": {"user_id": "2"}} - ) - assert response["messages"][-1].content == "foo-hi" - - -async def test_prompt_with_store_async(): - async def add(a: int, b: int): - """Adds a and b""" - return a + b - - in_memory_store = InMemoryStore() - await in_memory_store.aput( - ("memories", "1"), "user_name", {"data": "User name is Alice"} - ) - await in_memory_store.aput( - ("memories", "2"), "user_name", {"data": "User name is Bob"} - ) - - async def prompt(state, config, *, store): - user_id = config["configurable"]["user_id"] - system_str = (await store.aget(("memories", user_id), "user_name")).value[ - "data" - ] - return [SystemMessage(system_str)] + state["messages"] - - async def prompt_no_store(state, config): - return SystemMessage("foo") + state["messages"] - - model = FakeToolCallingModel() - - # test state modifier that uses store works - agent = create_react_agent(model, [add], prompt=prompt, store=in_memory_store) - response = await agent.ainvoke( - {"messages": [("user", "hi")]}, {"configurable": {"user_id": "1"}} - ) - assert response["messages"][-1].content == "User name is Alice-hi" - - # test state modifier that doesn't use store works - agent = create_react_agent( - model, [add], prompt=prompt_no_store, store=in_memory_store - ) - response = await agent.ainvoke( - {"messages": [("user", "hi")]}, {"configurable": {"user_id": "2"}} - ) - assert response["messages"][-1].content == "foo-hi" - - -@pytest.mark.parametrize("tool_style", ["openai", "anthropic"]) -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_model_with_tools(tool_style: str, version: str): - model = FakeToolCallingModel(tool_style=tool_style) - - @dec_tool - def tool1(some_val: int) -> str: - """Tool 1 docstring.""" - return f"Tool 1: {some_val}" - - @dec_tool - def tool2(some_val: int) -> str: - """Tool 2 docstring.""" - return f"Tool 2: {some_val}" - - # check valid agent constructor - agent = create_react_agent( - model.bind_tools([tool1, tool2]), - [tool1, tool2], - version=version, - ) - result = agent.nodes["tools"].invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 2}, - "id": "some 1", - }, - { - "name": "tool2", - "args": {"some_val": 2}, - "id": "some 2", - }, - ], - ) - ] - } - ) - tool_messages: ToolMessage = result["messages"][-2:] - for tool_message in tool_messages: - assert tool_message.type == "tool" - assert tool_message.content in {"Tool 1: 2", "Tool 2: 2"} - assert tool_message.tool_call_id in {"some 1", "some 2"} - - # test mismatching tool lengths - with pytest.raises(ValueError): - create_react_agent(model.bind_tools([tool1]), [tool1, tool2]) - - # test missing bound tools - with pytest.raises(ValueError): - create_react_agent(model.bind_tools([tool1]), [tool2]) - - -def test__validate_messages(): - # empty input - _validate_chat_history([]) - - # single human message - _validate_chat_history( - [ - HumanMessage(content="What's the weather?"), - ] - ) - - # human + AI - _validate_chat_history( - [ - HumanMessage(content="What's the weather?"), - AIMessage(content="The weather is sunny and 75°F."), - ] - ) - - # Answered tool calls - _validate_chat_history( - [ - HumanMessage(content="What's the weather?"), - AIMessage( - content="Let me check that for you.", - tool_calls=[{"id": "call1", "name": "get_weather", "args": {}}], - ), - ToolMessage(content="Sunny, 75°F", tool_call_id="call1"), - AIMessage(content="The weather is sunny and 75°F."), - ] - ) - - # Unanswered tool calls - with pytest.raises(ValueError): - _validate_chat_history( - [ - AIMessage( - content="I'll check that for you.", - tool_calls=[ - {"id": "call1", "name": "get_weather", "args": {}}, - {"id": "call2", "name": "get_time", "args": {}}, - ], - ) - ] - ) - - with pytest.raises(ValueError): - _validate_chat_history( - [ - HumanMessage(content="What's the weather and time?"), - AIMessage( - content="I'll check that for you.", - tool_calls=[ - {"id": "call1", "name": "get_weather", "args": {}}, - {"id": "call2", "name": "get_time", "args": {}}, - ], - ), - ToolMessage(content="Sunny, 75°F", tool_call_id="call1"), - AIMessage( - content="The weather is sunny and 75°F. Let me check the time." - ), - ] - ) - - -def test__infer_handled_types() -> None: - def handle(e): # type: ignore - return "" - - def handle2(e: Exception) -> str: - return "" - - def handle3(e: Union[ValueError, ToolException]) -> str: - return "" - - class Handler: - def handle(self, e: ValueError) -> str: - return "" - - handle4 = Handler().handle - - def handle5(e: Union[Union[TypeError, ValueError], ToolException]): - return "" - - expected: tuple = (Exception,) - actual = _infer_handled_types(handle) - assert expected == actual - - expected = (Exception,) - actual = _infer_handled_types(handle2) - assert expected == actual - - expected = (ValueError, ToolException) - actual = _infer_handled_types(handle3) - assert expected == actual - - expected = (ValueError,) - actual = _infer_handled_types(handle4) - assert expected == actual - - expected = (TypeError, ValueError, ToolException) - actual = _infer_handled_types(handle5) - assert expected == actual - - with pytest.raises(ValueError): - - def handler(e: str): - return "" - - _infer_handled_types(handler) - - with pytest.raises(ValueError): - - def handler(e: list[Exception]): - return "" - - _infer_handled_types(handler) - - with pytest.raises(ValueError): - - def handler(e: Union[str, int]): - return "" - - _infer_handled_types(handler) - - -@pytest.mark.skipif( - not IS_LANGCHAIN_CORE_030_OR_GREATER, - reason="Pydantic v1 is required for this test to pass in langchain-core < 0.3", -) -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_react_agent_with_structured_response(version: str) -> None: - class WeatherResponse(BaseModel): - temperature: float = Field(description="The temperature in fahrenheit") - - tool_calls = [[{"args": {}, "id": "1", "name": "get_weather"}], []] - - def get_weather(): - """Get the weather""" - return "The weather is sunny and 75°F." - - expected_structured_response = WeatherResponse(temperature=75) - model = FakeToolCallingModel( - tool_calls=tool_calls, structured_response=expected_structured_response - ) - for response_format in (WeatherResponse, ("Meow", WeatherResponse)): - agent = create_react_agent( - model, - [get_weather], - response_format=response_format, - version=version, - ) - response = agent.invoke({"messages": [HumanMessage("What's the weather?")]}) - assert response["structured_response"] == expected_structured_response - assert len(response["messages"]) == 4 - assert response["messages"][-2].content == "The weather is sunny and 75°F." - - -@pytest.mark.skipif( - not IS_LANGCHAIN_CORE_030_OR_GREATER, - reason="Langchain core 0.3.0 or greater is required", -) -@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_react_agent_update_state( - request: pytest.FixtureRequest, checkpointer_name: str, version: str -) -> None: - checkpointer: BaseCheckpointSaver = request.getfixturevalue( - "checkpointer_" + checkpointer_name - ) - - class State(AgentState): - user_name: str - - @dec_tool - def get_user_name(tool_call_id: Annotated[str, InjectedToolCallId]): - """Retrieve user name""" - user_name = interrupt("Please provider user name:") - return Command( - update={ - "user_name": user_name, - "messages": [ - ToolMessage( - "Successfully retrieved user name", tool_call_id=tool_call_id - ) - ], - } - ) - - def prompt(state: State): - user_name = state.get("user_name") - if user_name is None: - return state["messages"] - - system_msg = f"User name is {user_name}" - return [{"role": "system", "content": system_msg}] + state["messages"] - - tool_calls = [[{"args": {}, "id": "1", "name": "get_user_name"}]] - model = FakeToolCallingModel(tool_calls=tool_calls) - agent = create_react_agent( - model, - [get_user_name], - state_schema=State, - prompt=prompt, - checkpointer=checkpointer, - version=version, - ) - config = {"configurable": {"thread_id": "1"}} - # run until interrpupted - agent.invoke({"messages": [("user", "what's my name")]}, config) - # supply the value for the interrupt - response = agent.invoke(Command(resume="Archibald"), config) - # confirm that the state was updated - assert response["user_name"] == "Archibald" - assert len(response["messages"]) == 4 - tool_message: ToolMessage = response["messages"][-2] - assert tool_message.content == "Successfully retrieved user name" - assert tool_message.tool_call_id == "1" - assert tool_message.name == "get_user_name" - - -@pytest.mark.skipif( - not IS_LANGCHAIN_CORE_030_OR_GREATER, - reason="Langchain core 0.3.0 or greater is required", -) -@pytest.mark.parametrize( - "checkpointer_name", - [ - checkpointer - for checkpointer in ALL_CHECKPOINTERS_SYNC - if "shallow" not in checkpointer - ], -) -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_react_agent_parallel_tool_calls( - request: pytest.FixtureRequest, checkpointer_name: str, version: str -) -> None: - checkpointer: BaseCheckpointSaver = request.getfixturevalue( - "checkpointer_" + checkpointer_name - ) - human_assistance_execution_count = 0 - - @dec_tool - def human_assistance(query: str) -> str: - """Request assistance from a human.""" - nonlocal human_assistance_execution_count - human_response = interrupt({"query": query}) - human_assistance_execution_count += 1 - return human_response["data"] - - get_weather_execution_count = 0 - - @dec_tool - def get_weather(location: str) -> str: - """Use this tool to get the weather.""" - nonlocal get_weather_execution_count - get_weather_execution_count += 1 - return "It's sunny!" - - tool_calls = [ - [ - {"args": {"location": "sf"}, "id": "1", "name": "get_weather"}, - {"args": {"query": "request help"}, "id": "2", "name": "human_assistance"}, - ], - [], - ] - model = FakeToolCallingModel(tool_calls=tool_calls) - agent = create_react_agent( - model, - [human_assistance, get_weather], - checkpointer=checkpointer, - version=version, - ) - config = {"configurable": {"thread_id": "1"}} - query = "Get user assistance and also check the weather" - message_types = [] - for event in agent.stream( - {"messages": [("user", query)]}, config, stream_mode="values" - ): - message_types.append([message.type for message in event["messages"]]) - - if version == "v1": - assert message_types == [ - ["human"], - ["human", "ai"], - ] - elif version == "v2": - assert message_types == [ - ["human"], - ["human", "ai"], - ["human", "ai", "tool"], - ] - - # Resume - message_types = [] - for event in agent.stream( - Command(resume={"data": "Hello"}), config, stream_mode="values" - ): - message_types.append([message.type for message in event["messages"]]) - - assert message_types == [ - ["human", "ai"], - ["human", "ai", "tool", "tool"], - ["human", "ai", "tool", "tool", "ai"], - ] - - if version == "v1": - assert human_assistance_execution_count == 1 - assert get_weather_execution_count == 2 - elif version == "v2": - assert human_assistance_execution_count == 1 - assert get_weather_execution_count == 1 - - -class _InjectStateSchema(TypedDict): - messages: list - foo: str - - -class _InjectedStatePydanticSchema(BaseModelV1): - messages: list - foo: str - - -class _InjectedStatePydanticV2Schema(BaseModel): - messages: list - foo: str - - -@dataclasses.dataclass -class _InjectedStateDataclassSchema: - messages: list - foo: str - - -T = TypeVar("T") - - -@pytest.mark.parametrize( - "schema_", - [ - _InjectStateSchema, - _InjectedStatePydanticSchema, - _InjectedStatePydanticV2Schema, - _InjectedStateDataclassSchema, - ], -) -def test_tool_node_inject_state(schema_: Type[T]) -> None: - def tool1(some_val: int, state: Annotated[T, InjectedState]) -> str: - """Tool 1 docstring.""" - if isinstance(state, dict): - return state["foo"] - else: - return getattr(state, "foo") - - def tool2(some_val: int, state: Annotated[T, InjectedState()]) -> str: - """Tool 2 docstring.""" - if isinstance(state, dict): - return state["foo"] - else: - return getattr(state, "foo") - - def tool3( - some_val: int, - foo: Annotated[str, InjectedState("foo")], - msgs: Annotated[List[AnyMessage], InjectedState("messages")], - ) -> str: - """Tool 1 docstring.""" - return foo - - def tool4( - some_val: int, msgs: Annotated[List[AnyMessage], InjectedState("messages")] - ) -> str: - """Tool 1 docstring.""" - return msgs[0].content - - node = ToolNode([tool1, tool2, tool3, tool4]) - for tool_name in ("tool1", "tool2", "tool3"): - tool_call = { - "name": tool_name, - "args": {"some_val": 1}, - "id": "some 0", - "type": "tool_call", - } - msg = AIMessage("hi?", tool_calls=[tool_call]) - result = node.invoke(schema_(**{"messages": [msg], "foo": "bar"})) - tool_message = result["messages"][-1] - assert tool_message.content == "bar", f"Failed for tool={tool_name}" - - if tool_name == "tool3": - failure_input = None - try: - failure_input = schema_(**{"messages": [msg], "notfoo": "bar"}) - except Exception: - pass - if failure_input is not None: - with pytest.raises(KeyError): - node.invoke(failure_input) - - with pytest.raises(ValueError): - node.invoke([msg]) - else: - failure_input = None - try: - failure_input = schema_(**{"messages": [msg], "notfoo": "bar"}) - except Exception: - # We'd get a validation error from pydantic state and wouldn't make it to the node - # anyway - pass - if failure_input is not None: - messages_ = node.invoke(failure_input) - tool_message = messages_["messages"][-1] - assert "KeyError" in tool_message.content - tool_message = node.invoke([msg])[-1] - assert "KeyError" in tool_message.content - - tool_call = { - "name": "tool4", - "args": {"some_val": 1}, - "id": "some 0", - "type": "tool_call", - } - msg = AIMessage("hi?", tool_calls=[tool_call]) - result = node.invoke(schema_(**{"messages": [msg], "foo": ""})) - tool_message = result["messages"][-1] - assert tool_message.content == "hi?" - - result = node.invoke([msg]) - tool_message = result[-1] - assert tool_message.content == "hi?" - - -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_create_react_agent_inject_vars(version: str) -> None: - class AgentStateExtraKey(AgentState): - foo: int - - store = InMemoryStore() - namespace = ("test",) - store.put(namespace, "test_key", {"bar": 3}) - - def tool1( - some_val: int, - state: Annotated[dict, InjectedState], - store: Annotated[BaseStore, InjectedStore()], - ) -> str: - """Tool 1 docstring.""" - store_val = store.get(namespace, "test_key").value["bar"] - return some_val + state["foo"] + store_val - - tool_call = { - "name": "tool1", - "args": {"some_val": 1}, - "id": "some 0", - "type": "tool_call", - } - model = FakeToolCallingModel(tool_calls=[[tool_call], []]) - agent = create_react_agent( - model, - [tool1], - state_schema=AgentStateExtraKey, - store=store, - version=version, - ) - input_message = HumanMessage("hi") - result = agent.invoke({"messages": [input_message], "foo": 2}) - assert result["messages"] == [ - input_message, - AIMessage(content="hi", tool_calls=[tool_call], id="0"), - _AnyIdToolMessage(content="6", name="tool1", tool_call_id="some 0"), - AIMessage("hi-hi-6", id="1"), - ] - assert result["foo"] == 2 - - -@pytest.mark.skipif( - not IS_LANGCHAIN_CORE_030_OR_GREATER, - reason="Langchain core 0.3.0 or greater is required", -) -def test_tool_node_inject_store() -> None: - store = InMemoryStore() - namespace = ("test",) - - def tool1(some_val: int, store: Annotated[BaseStore, InjectedStore()]) -> str: - """Tool 1 docstring.""" - store_val = store.get(namespace, "test_key").value["foo"] - return f"Some val: {some_val}, store val: {store_val}" - - def tool2(some_val: int, store: Annotated[BaseStore, InjectedStore()]) -> str: - """Tool 2 docstring.""" - store_val = store.get(namespace, "test_key").value["foo"] - return f"Some val: {some_val}, store val: {store_val}" - - def tool3( - some_val: int, - bar: Annotated[str, InjectedState("bar")], - store: Annotated[BaseStore, InjectedStore()], - ) -> str: - """Tool 3 docstring.""" - store_val = store.get(namespace, "test_key").value["foo"] - return f"Some val: {some_val}, store val: {store_val}, state val: {bar}" - - node = ToolNode([tool1, tool2, tool3], handle_tool_errors=True) - store.put(namespace, "test_key", {"foo": "bar"}) - - class State(MessagesState): - bar: str - - builder = StateGraph(State) - builder.add_node("tools", node) - builder.add_edge(START, "tools") - graph = builder.compile(store=store) - - for tool_name in ("tool1", "tool2"): - tool_call = { - "name": tool_name, - "args": {"some_val": 1}, - "id": "some 0", - "type": "tool_call", - } - msg = AIMessage("hi?", tool_calls=[tool_call]) - node_result = node.invoke({"messages": [msg]}, store=store) - graph_result = graph.invoke({"messages": [msg]}) - for result in (node_result, graph_result): - result["messages"][-1] - tool_message = result["messages"][-1] - assert ( - tool_message.content == "Some val: 1, store val: bar" - ), f"Failed for tool={tool_name}" - - tool_call = { - "name": "tool3", - "args": {"some_val": 1}, - "id": "some 0", - "type": "tool_call", - } - msg = AIMessage("hi?", tool_calls=[tool_call]) - node_result = node.invoke({"messages": [msg], "bar": "baz"}, store=store) - graph_result = graph.invoke({"messages": [msg], "bar": "baz"}) - for result in (node_result, graph_result): - result["messages"][-1] - tool_message = result["messages"][-1] - assert ( - tool_message.content == "Some val: 1, store val: bar, state val: baz" - ), f"Failed for tool={tool_name}" - - # test injected store without passing store to compiled graph - failing_graph = builder.compile() - with pytest.raises(ValueError): - failing_graph.invoke({"messages": [msg], "bar": "baz"}) - - -def test_tool_node_ensure_utf8() -> None: - @dec_tool - def get_day_list(days: list[str]) -> list[str]: - """choose days""" - return days - - data = ["星期一", "水曜日", "목요일", "Friday"] - tools = [get_day_list] - tool_calls = [ToolCall(name=get_day_list.name, args={"days": data}, id="test_id")] - outputs: list[ToolMessage] = ToolNode(tools).invoke( - [AIMessage(content="", tool_calls=tool_calls)] - ) - assert outputs[0].content == json.dumps(data, ensure_ascii=False) - - -def test_tool_node_messages_key() -> None: - @dec_tool - def add(a: int, b: int): - """Adds a and b.""" - return a + b - - model = FakeToolCallingModel( - tool_calls=[[ToolCall(name=add.name, args={"a": 1, "b": 2}, id="test_id")]] - ) - - class State(TypedDict): - subgraph_messages: Annotated[list[AnyMessage], add_messages] - - def call_model(state: State): - response = model.invoke(state["subgraph_messages"]) - model.tool_calls = [] - return {"subgraph_messages": response} - - builder = StateGraph(State) - builder.add_node("agent", call_model) - builder.add_node("tools", ToolNode([add], messages_key="subgraph_messages")) - builder.add_conditional_edges( - "agent", partial(tools_condition, messages_key="subgraph_messages") - ) - builder.add_edge(START, "agent") - builder.add_edge("tools", "agent") - - graph = builder.compile() - result = graph.invoke({"subgraph_messages": [HumanMessage(content="hi")]}) - assert result["subgraph_messages"] == [ - _AnyIdHumanMessage(content="hi"), - AIMessage( - content="hi", - id="0", - tool_calls=[ToolCall(name=add.name, args={"a": 1, "b": 2}, id="test_id")], - ), - _AnyIdToolMessage(content="3", name=add.name, tool_call_id="test_id"), - AIMessage(content="hi-hi-3", id="1"), - ] - - -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -async def test_return_direct(version: str) -> None: - @dec_tool(return_direct=True) - def tool_return_direct(input: str) -> str: - """A tool that returns directly.""" - return f"Direct result: {input}" - - @dec_tool - def tool_normal(input: str) -> str: - """A normal tool.""" - return f"Normal result: {input}" - - first_tool_call = [ - ToolCall( - name="tool_return_direct", - args={"input": "Test direct"}, - id="1", - ), - ] - expected_ai = AIMessage( - content="Test direct", - id="0", - tool_calls=first_tool_call, - ) - model = FakeToolCallingModel(tool_calls=[first_tool_call, []]) - agent = create_react_agent( - model, - [tool_return_direct, tool_normal], - version=version, - ) - - # Test direct return for tool_return_direct - result = agent.invoke( - {"messages": [HumanMessage(content="Test direct", id="hum0")]} - ) - assert result["messages"] == [ - HumanMessage(content="Test direct", id="hum0"), - expected_ai, - ToolMessage( - content="Direct result: Test direct", - name="tool_return_direct", - tool_call_id="1", - id=result["messages"][2].id, - ), - ] - second_tool_call = [ - ToolCall( - name="tool_normal", - args={"input": "Test normal"}, - id="2", - ), - ] - model = FakeToolCallingModel(tool_calls=[second_tool_call, []]) - agent = create_react_agent(model, [tool_return_direct, tool_normal]) - result = agent.invoke( - {"messages": [HumanMessage(content="Test normal", id="hum1")]} - ) - assert result["messages"] == [ - HumanMessage(content="Test normal", id="hum1"), - AIMessage(content="Test normal", id="0", tool_calls=second_tool_call), - ToolMessage( - content="Normal result: Test normal", - name="tool_normal", - tool_call_id="2", - id=result["messages"][2].id, - ), - AIMessage(content="Test normal-Test normal-Normal result: Test normal", id="1"), - ] - - both_tool_calls = [ - ToolCall( - name="tool_return_direct", - args={"input": "Test both direct"}, - id="3", - ), - ToolCall( - name="tool_normal", - args={"input": "Test both normal"}, - id="4", - ), - ] - model = FakeToolCallingModel(tool_calls=[both_tool_calls, []]) - agent = create_react_agent(model, [tool_return_direct, tool_normal]) - result = agent.invoke({"messages": [HumanMessage(content="Test both", id="hum2")]}) - assert result["messages"] == [ - HumanMessage(content="Test both", id="hum2"), - AIMessage(content="Test both", id="0", tool_calls=both_tool_calls), - ToolMessage( - content="Direct result: Test both direct", - name="tool_return_direct", - tool_call_id="3", - id=result["messages"][2].id, - ), - ToolMessage( - content="Normal result: Test both normal", - name="tool_normal", - tool_call_id="4", - id=result["messages"][3].id, - ), - ] - - -def test__get_state_args() -> None: - class Schema1(BaseModel): - a: Annotated[str, InjectedState] - - class Schema2(Schema1): - b: Annotated[int, InjectedState("bar")] - - @dec_tool(args_schema=Schema2) - def foo(a: str, b: int) -> float: - """return""" - return 0.0 - - assert _get_state_args(foo) == {"a": None, "b": "bar"} - - -def test_inspect_react() -> None: - model = FakeToolCallingModel(tool_calls=[]) - agent = create_react_agent(model, []) - inspect.getclosurevars(agent.nodes["agent"].bound.func) - - -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) -def test_react_with_subgraph_tools( - request: pytest.FixtureRequest, checkpointer_name: str, version: str -) -> None: - checkpointer: BaseCheckpointSaver = request.getfixturevalue( - "checkpointer_" + checkpointer_name - ) - - class State(TypedDict): - a: int - b: int - - class Output(TypedDict): - result: int - - # Define the subgraphs - def add(state): - return {"result": state["a"] + state["b"]} - - add_subgraph = ( - StateGraph(State, output=Output).add_node(add).add_edge(START, "add").compile() - ) - - def multiply(state): - return {"result": state["a"] * state["b"]} - - multiply_subgraph = ( - StateGraph(State, output=Output) - .add_node(multiply) - .add_edge(START, "multiply") - .compile() - ) - - multiply_subgraph.invoke({"a": 2, "b": 3}) - - # Add subgraphs as tools - - def addition(a: int, b: int): - """Add two numbers""" - return add_subgraph.invoke({"a": a, "b": b})["result"] - - def multiplication(a: int, b: int): - """Multiply two numbers""" - return multiply_subgraph.invoke({"a": a, "b": b})["result"] - - model = FakeToolCallingModel( - tool_calls=[ - [ - {"args": {"a": 2, "b": 3}, "id": "1", "name": "addition"}, - {"args": {"a": 2, "b": 3}, "id": "2", "name": "multiplication"}, - ], - [], - ] - ) - tool_node = ToolNode([addition, multiplication], handle_tool_errors=False) - agent = create_react_agent( - model, - tool_node, - checkpointer=checkpointer, - version=version, - ) - result = agent.invoke( - {"messages": [HumanMessage(content="What's 2 + 3 and 2 * 3?")]}, - config={"configurable": {"thread_id": "1"}}, - ) - assert result["messages"] == [ - _AnyIdHumanMessage(content="What's 2 + 3 and 2 * 3?"), - AIMessage( - content="What's 2 + 3 and 2 * 3?", - id="0", - tool_calls=[ - ToolCall(name="addition", args={"a": 2, "b": 3}, id="1"), - ToolCall(name="multiplication", args={"a": 2, "b": 3}, id="2"), - ], - ), - ToolMessage( - content="5", name="addition", tool_call_id="1", id=result["messages"][2].id - ), - ToolMessage( - content="6", - name="multiplication", - tool_call_id="2", - id=result["messages"][3].id, - ), - AIMessage( - content="What's 2 + 3 and 2 * 3?-What's 2 + 3 and 2 * 3?-5-6", id="1" - ), - ] - - -def test_tool_node_stream_writer() -> None: - @dec_tool - def streaming_tool(x: int) -> str: - """Do something with writer.""" - my_writer = get_stream_writer() - for value in ["foo", "bar", "baz"]: - my_writer({"custom_tool_value": value}) - - return x - - tool_node = ToolNode([streaming_tool]) - graph = ( - StateGraph(MessagesState) - .add_node("tools", tool_node) - .add_edge(START, "tools") - .compile() - ) - - tool_call = { - "name": "streaming_tool", - "args": {"x": 1}, - "id": "1", - "type": "tool_call", - } - inputs = { - "messages": [AIMessage("", tool_calls=[tool_call])], - } - - assert list(graph.stream(inputs, stream_mode="custom")) == [ - {"custom_tool_value": "foo"}, - {"custom_tool_value": "bar"}, - {"custom_tool_value": "baz"}, - ] - assert list(graph.stream(inputs, stream_mode=["custom", "updates"])) == [ - ("custom", {"custom_tool_value": "foo"}), - ("custom", {"custom_tool_value": "bar"}), - ("custom", {"custom_tool_value": "baz"}), - ( - "updates", - { - "tools": { - "messages": [ - _AnyIdToolMessage( - content="1", - name="streaming_tool", - tool_call_id="1", - ), - ], - }, - }, - ), - ] - - -@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) -def test_tool_node_node_interrupt( - request: pytest.FixtureRequest, checkpointer_name: str, version: str -) -> None: - checkpointer: BaseCheckpointSaver = request.getfixturevalue( - "checkpointer_" + checkpointer_name - ) - - def tool_normal(some_val: int) -> str: - """Tool docstring.""" - return "normal" - - def tool_interrupt(some_val: int) -> str: - """Tool docstring.""" - foo = interrupt("provide value for foo") - return foo - - # test inside react agent - model = FakeToolCallingModel( - tool_calls=[ - [ - ToolCall(name="tool_interrupt", args={"some_val": 0}, id="1"), - ToolCall(name="tool_normal", args={"some_val": 1}, id="2"), - ], - [], - ] - ) - config = {"configurable": {"thread_id": "1"}} - agent = create_react_agent( - model, - [tool_interrupt, tool_normal], - checkpointer=checkpointer, - version=version, - ) - result = agent.invoke({"messages": [HumanMessage("hi?")]}, config) - expected_messages = [ - _AnyIdHumanMessage(content="hi?"), - AIMessage( - content="hi?", - id="0", - tool_calls=[ - { - "name": "tool_interrupt", - "args": {"some_val": 0}, - "id": "1", - "type": "tool_call", - }, - { - "name": "tool_normal", - "args": {"some_val": 1}, - "id": "2", - "type": "tool_call", - }, - ], - ), - _AnyIdToolMessage(content="normal", name="tool_normal", tool_call_id="2"), - ] - if version == "v1": - # Interrupt blocks second tool result - assert result["messages"] == expected_messages[:-1] - elif version == "v2": - assert result["messages"] == expected_messages - - # TODO: figure out why this is not working w/ shallow postgres checkpointer - if "shallow" in checkpointer_name: - return - - state = agent.get_state(config) - assert state.next == ("tools",) - task = state.tasks[0] - assert task.name == "tools" - assert task.interrupts == ( - Interrupt( - value="provide value for foo", - when="during", - resumable=True, - ns=[AnyStr("tools:")], - ), - ) diff --git a/libs/prebuilt/tests/test_tool_node.py b/libs/prebuilt/tests/test_tool_node.py deleted file mode 100644 index 2dd5ef635..000000000 --- a/libs/prebuilt/tests/test_tool_node.py +++ /dev/null @@ -1,1053 +0,0 @@ -from typing import ( - Annotated, - Any, - Union, -) - -import pytest -from langchain_core.messages import ( - AIMessage, - ToolMessage, -) -from langchain_core.tools import BaseTool, ToolException -from langchain_core.tools import tool as dec_tool -from pydantic import BaseModel, ValidationError -from pydantic.v1 import ValidationError as ValidationErrorV1 - -from langgraph.errors import NodeInterrupt -from langgraph.prebuilt import ToolNode -from langgraph.prebuilt.tool_node import TOOL_CALL_ERROR_TEMPLATE -from langgraph.types import Command -from tests.conftest import IS_LANGCHAIN_CORE_030_OR_GREATER - -pytestmark = pytest.mark.anyio - - -def tool1(some_val: int, some_other_val: str) -> str: - """Tool 1 docstring.""" - if some_val == 0: - raise ValueError("Test error") - return f"{some_val} - {some_other_val}" - - -async def tool2(some_val: int, some_other_val: str) -> str: - """Tool 2 docstring.""" - if some_val == 0: - raise ToolException("Test error") - return f"tool2: {some_val} - {some_other_val}" - - -async def tool3(some_val: int, some_other_val: str) -> str: - """Tool 3 docstring.""" - return [ - {"key_1": some_val, "key_2": "foo"}, - {"key_1": some_other_val, "key_2": "baz"}, - ] - - -async def tool4(some_val: int, some_other_val: str) -> str: - """Tool 4 docstring.""" - return [ - {"type": "image_url", "image_url": {"url": "abdc"}}, - ] - - -@dec_tool -def tool5(some_val: int): - """Tool 5 docstring.""" - raise ToolException("Test error") - - -tool5.handle_tool_error = "foo" - - -async def test_tool_node(): - result = ToolNode([tool1]).invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 1, "some_other_val": "foo"}, - "id": "some 0", - } - ], - ) - ] - } - ) - - tool_message: ToolMessage = result["messages"][-1] - assert tool_message.type == "tool" - assert tool_message.content == "1 - foo" - assert tool_message.tool_call_id == "some 0" - - result2 = await ToolNode([tool2]).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool2", - "args": {"some_val": 2, "some_other_val": "bar"}, - "id": "some 1", - } - ], - ) - ] - } - ) - - tool_message: ToolMessage = result2["messages"][-1] - assert tool_message.type == "tool" - assert tool_message.content == "tool2: 2 - bar" - - # list of dicts tool content - result3 = await ToolNode([tool3]).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool3", - "args": {"some_val": 2, "some_other_val": "bar"}, - "id": "some 2", - } - ], - ) - ] - } - ) - tool_message: ToolMessage = result3["messages"][-1] - assert tool_message.type == "tool" - assert ( - tool_message.content - == '[{"key_1": 2, "key_2": "foo"}, {"key_1": "bar", "key_2": "baz"}]' - ) - assert tool_message.tool_call_id == "some 2" - - # list of content blocks tool content - result4 = await ToolNode([tool4]).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool4", - "args": {"some_val": 2, "some_other_val": "bar"}, - "id": "some 3", - } - ], - ) - ] - } - ) - tool_message: ToolMessage = result4["messages"][-1] - assert tool_message.type == "tool" - assert tool_message.content == [{"type": "image_url", "image_url": {"url": "abdc"}}] - assert tool_message.tool_call_id == "some 3" - - -async def test_tool_node_tool_call_input(): - # Single tool call - tool_call_1 = { - "name": "tool1", - "args": {"some_val": 1, "some_other_val": "foo"}, - "id": "some 0", - "type": "tool_call", - } - result = ToolNode([tool1]).invoke([tool_call_1]) - assert result["messages"] == [ - ToolMessage(content="1 - foo", tool_call_id="some 0", name="tool1"), - ] - - # Multiple tool calls - tool_call_2 = { - "name": "tool1", - "args": {"some_val": 2, "some_other_val": "bar"}, - "id": "some 1", - "type": "tool_call", - } - result = ToolNode([tool1]).invoke([tool_call_1, tool_call_2]) - assert result["messages"] == [ - ToolMessage(content="1 - foo", tool_call_id="some 0", name="tool1"), - ToolMessage(content="2 - bar", tool_call_id="some 1", name="tool1"), - ] - - # Test with unknown tool - tool_call_3 = tool_call_1.copy() - tool_call_3["name"] = "tool2" - result = ToolNode([tool1]).invoke([tool_call_1, tool_call_3]) - assert result["messages"] == [ - ToolMessage(content="1 - foo", tool_call_id="some 0", name="tool1"), - ToolMessage( - content="Error: tool2 is not a valid tool, try one of [tool1].", - name="tool2", - tool_call_id="some 0", - status="error", - ), - ] - - -async def test_tool_node_error_handling(): - def handle_all(e: Union[ValueError, ToolException, ValidationError]): - return TOOL_CALL_ERROR_TEMPLATE.format(error=repr(e)) - - # test catching all exceptions, via: - # - handle_tool_errors = True - # - passing a tuple of all exceptions - # - passing a callable with all exceptions in the signature - for handle_tool_errors in ( - True, - (ValueError, ToolException, ValidationError), - handle_all, - ): - result_error = await ToolNode( - [tool1, tool2, tool3], handle_tool_errors=handle_tool_errors - ).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 0, "some_other_val": "foo"}, - "id": "some id", - }, - { - "name": "tool2", - "args": {"some_val": 0, "some_other_val": "bar"}, - "id": "some other id", - }, - { - "name": "tool3", - "args": {"some_val": 0}, - "id": "another id", - }, - ], - ) - ] - } - ) - - assert all(m.type == "tool" for m in result_error["messages"]) - assert all(m.status == "error" for m in result_error["messages"]) - assert ( - result_error["messages"][0].content - == f"Error: {repr(ValueError('Test error'))}\n Please fix your mistakes." - ) - assert ( - result_error["messages"][1].content - == f"Error: {repr(ToolException('Test error'))}\n Please fix your mistakes." - ) - assert ( - "ValidationError" in result_error["messages"][2].content - or "validation error" in result_error["messages"][2].content - ) - - assert result_error["messages"][0].tool_call_id == "some id" - assert result_error["messages"][1].tool_call_id == "some other id" - assert result_error["messages"][2].tool_call_id == "another id" - - -async def test_tool_node_error_handling_callable(): - def handle_value_error(e: ValueError): - return "Value error" - - def handle_tool_exception(e: ToolException): - return "Tool exception" - - for handle_tool_errors in ("Value error", handle_value_error): - result_error = await ToolNode( - [tool1], handle_tool_errors=handle_tool_errors - ).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 0, "some_other_val": "foo"}, - "id": "some id", - }, - ], - ) - ] - } - ) - tool_message: ToolMessage = result_error["messages"][-1] - assert tool_message.type == "tool" - assert tool_message.status == "error" - assert tool_message.content == "Value error" - - # test raising for an unhandled exception, via: - # - passing a tuple of all exceptions - # - passing a callable with all exceptions in the signature - for handle_tool_errors in ((ValueError,), handle_value_error): - with pytest.raises(ToolException) as exc_info: - await ToolNode( - [tool1, tool2], handle_tool_errors=handle_tool_errors - ).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 0, "some_other_val": "foo"}, - "id": "some id", - }, - { - "name": "tool2", - "args": {"some_val": 0, "some_other_val": "bar"}, - "id": "some other id", - }, - ], - ) - ] - } - ) - assert str(exc_info.value) == "Test error" - - for handle_tool_errors in ((ToolException,), handle_tool_exception): - with pytest.raises(ValueError) as exc_info: - await ToolNode( - [tool1, tool2], handle_tool_errors=handle_tool_errors - ).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 0, "some_other_val": "foo"}, - "id": "some id", - }, - { - "name": "tool2", - "args": {"some_val": 0, "some_other_val": "bar"}, - "id": "some other id", - }, - ], - ) - ] - } - ) - assert str(exc_info.value) == "Test error" - - -async def test_tool_node_handle_tool_errors_false(): - with pytest.raises(ValueError) as exc_info: - ToolNode([tool1], handle_tool_errors=False).invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 0, "some_other_val": "foo"}, - "id": "some id", - } - ], - ) - ] - } - ) - - assert str(exc_info.value) == "Test error" - - with pytest.raises(ToolException): - await ToolNode([tool2], handle_tool_errors=False).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool2", - "args": {"some_val": 0, "some_other_val": "bar"}, - "id": "some id", - } - ], - ) - ] - } - ) - - assert str(exc_info.value) == "Test error" - - # test validation errors get raised if handle_tool_errors is False - with pytest.raises((ValidationError, ValidationErrorV1)): - ToolNode([tool1], handle_tool_errors=False).invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 0}, - "id": "some id", - } - ], - ) - ] - } - ) - - -def test_tool_node_individual_tool_error_handling(): - # test error handling on individual tools (and that it overrides overall error handling!) - result_individual_tool_error_handler = ToolNode( - [tool5], handle_tool_errors="bar" - ).invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool5", - "args": {"some_val": 0}, - "id": "some 0", - } - ], - ) - ] - } - ) - - tool_message: ToolMessage = result_individual_tool_error_handler["messages"][-1] - assert tool_message.type == "tool" - assert tool_message.status == "error" - assert tool_message.content == "foo" - assert tool_message.tool_call_id == "some 0" - - -def test_tool_node_incorrect_tool_name(): - result_incorrect_name = ToolNode([tool1, tool2]).invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool3", - "args": {"some_val": 1, "some_other_val": "foo"}, - "id": "some 0", - } - ], - ) - ] - } - ) - - tool_message: ToolMessage = result_incorrect_name["messages"][-1] - assert tool_message.type == "tool" - assert tool_message.status == "error" - assert ( - tool_message.content - == "Error: tool3 is not a valid tool, try one of [tool1, tool2]." - ) - assert tool_message.tool_call_id == "some 0" - - -def test_tool_node_node_interrupt(): - def tool_interrupt(some_val: int) -> str: - """Tool docstring.""" - raise NodeInterrupt("foo") - - def handle(e: NodeInterrupt): - return "handled" - - for handle_tool_errors in (True, (NodeInterrupt,), "handled", handle, False): - node = ToolNode([tool_interrupt], handle_tool_errors=handle_tool_errors) - with pytest.raises(NodeInterrupt) as exc_info: - node.invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool_interrupt", - "args": {"some_val": 0}, - "id": "some 0", - } - ], - ) - ] - } - ) - assert exc_info.value == "foo" - - -@pytest.mark.skipif( - not IS_LANGCHAIN_CORE_030_OR_GREATER, - reason="Langchain core 0.3.0 or greater is required", -) -@pytest.mark.parametrize("input_type", ["dict", "tool_calls"]) -async def test_tool_node_command(input_type: str): - from langchain_core.tools.base import InjectedToolCallId - - @dec_tool - def transfer_to_bob(tool_call_id: Annotated[str, InjectedToolCallId]): - """Transfer to Bob""" - return Command( - update={ - "messages": [ - ToolMessage(content="Transferred to Bob", tool_call_id=tool_call_id) - ] - }, - goto="bob", - graph=Command.PARENT, - ) - - @dec_tool - async def async_transfer_to_bob(tool_call_id: Annotated[str, InjectedToolCallId]): - """Transfer to Bob""" - return Command( - update={ - "messages": [ - ToolMessage(content="Transferred to Bob", tool_call_id=tool_call_id) - ] - }, - goto="bob", - graph=Command.PARENT, - ) - - class CustomToolSchema(BaseModel): - tool_call_id: Annotated[str, InjectedToolCallId] - - class MyCustomTool(BaseTool): - def _run(*args: Any, **kwargs: Any): - return Command( - update={ - "messages": [ - ToolMessage( - content="Transferred to Bob", - tool_call_id=kwargs["tool_call_id"], - ) - ] - }, - goto="bob", - graph=Command.PARENT, - ) - - async def _arun(*args: Any, **kwargs: Any): - return Command( - update={ - "messages": [ - ToolMessage( - content="Transferred to Bob", - tool_call_id=kwargs["tool_call_id"], - ) - ] - }, - goto="bob", - graph=Command.PARENT, - ) - - custom_tool = MyCustomTool( - name="custom_transfer_to_bob", - description="Transfer to bob", - args_schema=CustomToolSchema, - ) - async_custom_tool = MyCustomTool( - name="async_custom_transfer_to_bob", - description="Transfer to bob", - args_schema=CustomToolSchema, - ) - - # test mixing regular tools and tools returning commands - def add(a: int, b: int) -> int: - """Add two numbers""" - return a + b - - tool_calls = [ - {"args": {"a": 1, "b": 2}, "id": "1", "name": "add", "type": "tool_call"}, - {"args": {}, "id": "2", "name": "transfer_to_bob", "type": "tool_call"}, - ] - if input_type == "dict": - input_ = {"messages": [AIMessage("", tool_calls=tool_calls)]} - elif input_type == "tool_calls": - input_ = tool_calls - result = ToolNode([add, transfer_to_bob]).invoke(input_) - - assert result == [ - { - "messages": [ - ToolMessage( - content="3", - tool_call_id="1", - name="add", - ) - ] - }, - Command( - update={ - "messages": [ - ToolMessage( - content="Transferred to Bob", - tool_call_id="2", - name="transfer_to_bob", - ) - ] - }, - goto="bob", - graph=Command.PARENT, - ), - ] - - # test tools returning commands - - # test sync tools - for tool in [transfer_to_bob, custom_tool]: - result = ToolNode([tool]).invoke( - { - "messages": [ - AIMessage( - "", tool_calls=[{"args": {}, "id": "1", "name": tool.name}] - ) - ] - } - ) - assert result == [ - Command( - update={ - "messages": [ - ToolMessage( - content="Transferred to Bob", - tool_call_id="1", - name=tool.name, - ) - ] - }, - goto="bob", - graph=Command.PARENT, - ) - ] - - # test async tools - for tool in [async_transfer_to_bob, async_custom_tool]: - result = await ToolNode([tool]).ainvoke( - { - "messages": [ - AIMessage( - "", tool_calls=[{"args": {}, "id": "1", "name": tool.name}] - ) - ] - } - ) - assert result == [ - Command( - update={ - "messages": [ - ToolMessage( - content="Transferred to Bob", - tool_call_id="1", - name=tool.name, - ) - ] - }, - goto="bob", - graph=Command.PARENT, - ) - ] - - # test multiple commands - result = ToolNode([transfer_to_bob, custom_tool]).invoke( - { - "messages": [ - AIMessage( - "", - tool_calls=[ - {"args": {}, "id": "1", "name": "transfer_to_bob"}, - {"args": {}, "id": "2", "name": "custom_transfer_to_bob"}, - ], - ) - ] - } - ) - assert result == [ - Command( - update={ - "messages": [ - ToolMessage( - content="Transferred to Bob", - tool_call_id="1", - name="transfer_to_bob", - ) - ] - }, - goto="bob", - graph=Command.PARENT, - ), - Command( - update={ - "messages": [ - ToolMessage( - content="Transferred to Bob", - tool_call_id="2", - name="custom_transfer_to_bob", - ) - ] - }, - goto="bob", - graph=Command.PARENT, - ), - ] - - # test validation (mismatch between input type and command.update type) - with pytest.raises(ValueError): - - @dec_tool - def list_update_tool(tool_call_id: Annotated[str, InjectedToolCallId]): - """My tool""" - return Command( - update=[ToolMessage(content="foo", tool_call_id=tool_call_id)] - ) - - ToolNode([list_update_tool]).invoke( - { - "messages": [ - AIMessage( - "", - tool_calls=[ - {"args": {}, "id": "1", "name": "list_update_tool"} - ], - ) - ] - } - ) - - # test validation (missing tool message in the update for current graph) - with pytest.raises(ValueError): - - @dec_tool - def no_update_tool(): - """My tool""" - return Command(update={"messages": []}) - - ToolNode([no_update_tool]).invoke( - { - "messages": [ - AIMessage( - "", - tool_calls=[{"args": {}, "id": "1", "name": "no_update_tool"}], - ) - ] - } - ) - - # test validation (tool message with a wrong tool call ID) - with pytest.raises(ValueError): - - @dec_tool - def mismatching_tool_call_id_tool(): - """My tool""" - return Command( - update={"messages": [ToolMessage(content="foo", tool_call_id="2")]} - ) - - ToolNode([mismatching_tool_call_id_tool]).invoke( - { - "messages": [ - AIMessage( - "", - tool_calls=[ - { - "args": {}, - "id": "1", - "name": "mismatching_tool_call_id_tool", - } - ], - ) - ] - } - ) - - # test validation (missing tool message in the update for parent graph is OK) - @dec_tool - def node_update_parent_tool(): - """No update""" - return Command(update={"messages": []}, graph=Command.PARENT) - - assert ToolNode([node_update_parent_tool]).invoke( - { - "messages": [ - AIMessage( - "", - tool_calls=[ - {"args": {}, "id": "1", "name": "node_update_parent_tool"} - ], - ) - ] - } - ) == [Command(update={"messages": []}, graph=Command.PARENT)] - - -@pytest.mark.skipif( - not IS_LANGCHAIN_CORE_030_OR_GREATER, - reason="Langchain core 0.3.0 or greater is required", -) -async def test_tool_node_command_list_input(): - from langchain_core.tools.base import InjectedToolCallId - - @dec_tool - def transfer_to_bob(tool_call_id: Annotated[str, InjectedToolCallId]): - """Transfer to Bob""" - return Command( - update=[ - ToolMessage(content="Transferred to Bob", tool_call_id=tool_call_id) - ], - goto="bob", - graph=Command.PARENT, - ) - - @dec_tool - async def async_transfer_to_bob(tool_call_id: Annotated[str, InjectedToolCallId]): - """Transfer to Bob""" - return Command( - update=[ - ToolMessage(content="Transferred to Bob", tool_call_id=tool_call_id) - ], - goto="bob", - graph=Command.PARENT, - ) - - class CustomToolSchema(BaseModel): - tool_call_id: Annotated[str, InjectedToolCallId] - - class MyCustomTool(BaseTool): - def _run(*args: Any, **kwargs: Any): - return Command( - update=[ - ToolMessage( - content="Transferred to Bob", - tool_call_id=kwargs["tool_call_id"], - ) - ], - goto="bob", - graph=Command.PARENT, - ) - - async def _arun(*args: Any, **kwargs: Any): - return Command( - update=[ - ToolMessage( - content="Transferred to Bob", - tool_call_id=kwargs["tool_call_id"], - ) - ], - goto="bob", - graph=Command.PARENT, - ) - - custom_tool = MyCustomTool( - name="custom_transfer_to_bob", - description="Transfer to bob", - args_schema=CustomToolSchema, - ) - async_custom_tool = MyCustomTool( - name="async_custom_transfer_to_bob", - description="Transfer to bob", - args_schema=CustomToolSchema, - ) - - # test mixing regular tools and tools returning commands - def add(a: int, b: int) -> int: - """Add two numbers""" - return a + b - - result = ToolNode([add, transfer_to_bob]).invoke( - [ - AIMessage( - "", - tool_calls=[ - {"args": {"a": 1, "b": 2}, "id": "1", "name": "add"}, - {"args": {}, "id": "2", "name": "transfer_to_bob"}, - ], - ) - ] - ) - - assert result == [ - [ - ToolMessage( - content="3", - tool_call_id="1", - name="add", - ) - ], - Command( - update=[ - ToolMessage( - content="Transferred to Bob", - tool_call_id="2", - name="transfer_to_bob", - ) - ], - goto="bob", - graph=Command.PARENT, - ), - ] - - # test tools returning commands - - # test sync tools - for tool in [transfer_to_bob, custom_tool]: - result = ToolNode([tool]).invoke( - [AIMessage("", tool_calls=[{"args": {}, "id": "1", "name": tool.name}])] - ) - assert result == [ - Command( - update=[ - ToolMessage( - content="Transferred to Bob", - tool_call_id="1", - name=tool.name, - ) - ], - goto="bob", - graph=Command.PARENT, - ) - ] - - # test async tools - for tool in [async_transfer_to_bob, async_custom_tool]: - result = await ToolNode([tool]).ainvoke( - [AIMessage("", tool_calls=[{"args": {}, "id": "1", "name": tool.name}])] - ) - assert result == [ - Command( - update=[ - ToolMessage( - content="Transferred to Bob", - tool_call_id="1", - name=tool.name, - ) - ], - goto="bob", - graph=Command.PARENT, - ) - ] - - # test multiple commands - result = ToolNode([transfer_to_bob, custom_tool]).invoke( - [ - AIMessage( - "", - tool_calls=[ - {"args": {}, "id": "1", "name": "transfer_to_bob"}, - {"args": {}, "id": "2", "name": "custom_transfer_to_bob"}, - ], - ) - ] - ) - assert result == [ - Command( - update=[ - ToolMessage( - content="Transferred to Bob", - tool_call_id="1", - name="transfer_to_bob", - ) - ], - goto="bob", - graph=Command.PARENT, - ), - Command( - update=[ - ToolMessage( - content="Transferred to Bob", - tool_call_id="2", - name="custom_transfer_to_bob", - ) - ], - goto="bob", - graph=Command.PARENT, - ), - ] - - # test validation (mismatch between input type and command.update type) - with pytest.raises(ValueError): - - @dec_tool - def list_update_tool(tool_call_id: Annotated[str, InjectedToolCallId]): - """My tool""" - return Command( - update={ - "messages": [ToolMessage(content="foo", tool_call_id=tool_call_id)] - } - ) - - ToolNode([list_update_tool]).invoke( - [ - AIMessage( - "", - tool_calls=[{"args": {}, "id": "1", "name": "list_update_tool"}], - ) - ] - ) - - # test validation (missing tool message in the update for current graph) - with pytest.raises(ValueError): - - @dec_tool - def no_update_tool(): - """My tool""" - return Command(update=[]) - - ToolNode([no_update_tool]).invoke( - [ - AIMessage( - "", - tool_calls=[{"args": {}, "id": "1", "name": "no_update_tool"}], - ) - ] - ) - - # test validation (tool message with a wrong tool call ID) - with pytest.raises(ValueError): - - @dec_tool - def mismatching_tool_call_id_tool(): - """My tool""" - return Command(update=[ToolMessage(content="foo", tool_call_id="2")]) - - ToolNode([mismatching_tool_call_id_tool]).invoke( - [ - AIMessage( - "", - tool_calls=[ - {"args": {}, "id": "1", "name": "mismatching_tool_call_id_tool"} - ], - ) - ] - ) - - # test validation (missing tool message in the update for parent graph is OK) - @dec_tool - def node_update_parent_tool(): - """No update""" - return Command(update=[], graph=Command.PARENT) - - assert ToolNode([node_update_parent_tool]).invoke( - [ - AIMessage( - "", - tool_calls=[{"args": {}, "id": "1", "name": "node_update_parent_tool"}], - ) - ] - ) == [Command(update=[], graph=Command.PARENT)] diff --git a/libs/prebuilt/tests/test_validation_node.py b/libs/prebuilt/tests/test_validation_node.py deleted file mode 100644 index 58e670f9a..000000000 --- a/libs/prebuilt/tests/test_validation_node.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Any - -import pytest -from langchain_core.messages import AIMessage -from langchain_core.tools import tool as dec_tool -from pydantic import BaseModel -from pydantic.v1 import BaseModel as BaseModelV1 - -from langgraph.prebuilt import ValidationNode - -pytestmark = pytest.mark.anyio - - -def my_function(some_val: int, some_other_val: str) -> str: - return f"{some_val} - {some_other_val}" - - -class MyModel(BaseModel): - some_val: int - some_other_val: str - - -class MyModelV1(BaseModelV1): - some_val: int - some_other_val: str - - -@dec_tool -def my_tool(some_val: int, some_other_val: str) -> str: - """Cool.""" - return f"{some_val} - {some_other_val}" - - -@pytest.mark.parametrize( - "tool_schema", - [ - my_function, - MyModel, - MyModelV1, - my_tool, - ], -) -@pytest.mark.parametrize("use_message_key", [True, False]) -async def test_validation_node(tool_schema: Any, use_message_key: bool): - validation_node = ValidationNode([tool_schema]) - tool_name = getattr(tool_schema, "name", getattr(tool_schema, "__name__", None)) - inputs = [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": tool_name, - "args": {"some_val": 1, "some_other_val": "foo"}, - "id": "some 0", - }, - { - "name": tool_name, - # Wrong type for some_val - "args": {"some_val": "bar", "some_other_val": "foo"}, - "id": "some 1", - }, - ], - ), - ] - if use_message_key: - inputs = {"messages": inputs} - result = await validation_node.ainvoke(inputs) - if use_message_key: - result = result["messages"] - - def check_results(messages: list): - assert len(messages) == 2 - assert all(m.type == "tool" for m in messages) - assert not messages[0].additional_kwargs.get("is_error") - assert messages[1].additional_kwargs.get("is_error") - - check_results(result) - result_sync = validation_node.invoke(inputs) - if use_message_key: - result_sync = result_sync["messages"] - check_results(result_sync)