generated from kgod/ai-review-template
提交
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
"""langgraph.prebuilt exposes a higher-level API for creating and executing agents and tools."""
|
||||
|
||||
from langgraph.prebuilt._tool_call_transformer import ToolCallTransformer
|
||||
from langgraph.prebuilt.chat_agent_executor import create_react_agent
|
||||
from langgraph.prebuilt.tool_node import (
|
||||
InjectedState,
|
||||
InjectedStore,
|
||||
ToolNode,
|
||||
ToolRuntime,
|
||||
tools_condition,
|
||||
)
|
||||
from langgraph.prebuilt.tool_validator import ValidationNode
|
||||
|
||||
__all__ = [
|
||||
"create_react_agent",
|
||||
"ToolNode",
|
||||
"ToolCallTransformer",
|
||||
"tools_condition",
|
||||
"ValidationNode",
|
||||
"InjectedState",
|
||||
"InjectedStore",
|
||||
"ToolRuntime",
|
||||
]
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,117 @@
|
||||
"""In-process handle for a single tool call's streaming execution.
|
||||
|
||||
Mirrors the shape of `ChatModelStream` from langchain-core but simpler —
|
||||
a tool has one output channel, no content-block multiplexing. Populated
|
||||
by `ToolCallTransformer` as `tool-started` / `tool-output-delta` /
|
||||
`tool-finished` / `tool-error` events flow in on the `tools` channel.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator, Iterator
|
||||
from typing import Any
|
||||
|
||||
from langgraph.stream.stream_channel import StreamChannel
|
||||
|
||||
|
||||
class ToolCallStream:
|
||||
"""Scoped view of a single tool call's lifecycle.
|
||||
|
||||
Yielded on `run.tool_calls` once per `tool-started` event. Fields
|
||||
are populated as events arrive:
|
||||
|
||||
- `tool_call_id`, `tool_name`, `input`: stable from the start event.
|
||||
- `output_deltas`: a `StreamChannel` of delta chunks. Iterate (sync or
|
||||
async) to consume partial output in arrival order.
|
||||
- `output`: terminal payload from `tool-finished`, or `None` if the
|
||||
call failed or is still in flight.
|
||||
- `error`: terminal error string from `tool-error`, or `None` if the
|
||||
call succeeded or is still in flight.
|
||||
- `completed`: True once a terminal event (`tool-finished` or
|
||||
`tool-error`) has been observed.
|
||||
|
||||
`ToolCallStream` is not meant to be constructed by end users — it's
|
||||
produced by `ToolCallTransformer` as events flow through the mux.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tool_call_id: str,
|
||||
tool_name: str,
|
||||
input: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Initialize a fresh handle for a tool call.
|
||||
|
||||
Args:
|
||||
tool_call_id: The `tool_call_id` from the AIMessage.
|
||||
tool_name: The tool's name.
|
||||
input: The tool's input arguments (as reported by
|
||||
`on_tool_start`), or `None` if none were captured.
|
||||
"""
|
||||
self.tool_call_id = tool_call_id
|
||||
self.tool_name = tool_name
|
||||
self.input = input
|
||||
self._output_deltas: StreamChannel[Any] = StreamChannel()
|
||||
self.output: Any = None
|
||||
self.error: str | None = None
|
||||
self.completed = False
|
||||
|
||||
@property
|
||||
def output_deltas(self) -> StreamChannel[Any]:
|
||||
"""The channel of streamed `tool-output-delta` payloads.
|
||||
|
||||
Iterate (sync or async depending on how the run was started)
|
||||
to consume partial output in arrival order. The log closes when
|
||||
the tool finishes or errors.
|
||||
"""
|
||||
return self._output_deltas
|
||||
|
||||
def _bind(self, *, is_async: bool) -> None:
|
||||
"""Bind the deltas log to sync or async iteration.
|
||||
|
||||
Called by `ToolCallTransformer` when constructing this handle so
|
||||
the log matches the enclosing mux's mode.
|
||||
"""
|
||||
self._output_deltas._bind(is_async=is_async)
|
||||
|
||||
def _push_delta(self, delta: Any) -> None:
|
||||
self._output_deltas.push(delta)
|
||||
|
||||
def _finish(self, output: Any) -> None:
|
||||
self.output = output
|
||||
self.completed = True
|
||||
self._output_deltas.close()
|
||||
|
||||
def _fail(self, message: str) -> None:
|
||||
self.error = message
|
||||
self.completed = True
|
||||
self._output_deltas.close()
|
||||
|
||||
def __iter__(self) -> Iterator[Any]:
|
||||
"""Iterate delta chunks synchronously.
|
||||
|
||||
Equivalent to `iter(self.output_deltas)`. Raises `TypeError` if
|
||||
the underlying log is bound to async mode.
|
||||
"""
|
||||
return iter(self._output_deltas)
|
||||
|
||||
def __aiter__(self) -> AsyncIterator[Any]:
|
||||
"""Iterate delta chunks asynchronously.
|
||||
|
||||
Equivalent to `aiter(self.output_deltas)`. Raises `TypeError`
|
||||
if the underlying log is bound to sync mode.
|
||||
"""
|
||||
return self._output_deltas.__aiter__()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
status = (
|
||||
"completed"
|
||||
if self.completed and self.error is None
|
||||
else "failed"
|
||||
if self.completed
|
||||
else "running"
|
||||
)
|
||||
return (
|
||||
f"ToolCallStream(tool_call_id={self.tool_call_id!r}, "
|
||||
f"tool_name={self.tool_name!r}, status={status})"
|
||||
)
|
||||
@@ -0,0 +1,135 @@
|
||||
"""Transformer that projects `tools` channel events into `ToolCallStream`s."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from langgraph.stream._types import ProtocolEvent, StreamTransformer
|
||||
from langgraph.stream.stream_channel import StreamChannel
|
||||
|
||||
from langgraph.prebuilt._tool_call_stream import ToolCallStream
|
||||
|
||||
|
||||
class ToolCallTransformer(StreamTransformer):
|
||||
"""Project `tools` channel events into `ToolCallStream` handles.
|
||||
|
||||
Each `tool-started` event spawns a `ToolCallStream`, pushed onto
|
||||
`run.tool_calls`. Subsequent `tool-output-delta` events append to
|
||||
that stream's deltas log; `tool-finished` and `tool-error` close it.
|
||||
|
||||
Native transformer — the `tool_calls` projection is exposed as a
|
||||
direct attribute on the run stream.
|
||||
|
||||
A nameless `StreamChannel[ToolCallStream]` is used (no protocol
|
||||
auto-forwarding) because the live handles are not serializable and
|
||||
should not be injected into the main event log. Wire consumers
|
||||
subscribe to the `tools` channel instead, where the raw protocol
|
||||
events flow through untouched by this transformer (`process`
|
||||
returns `True`).
|
||||
|
||||
Registered explicitly by users at compile time via
|
||||
`builder.compile(transformers=[ToolCallTransformer])` — not a
|
||||
default built-in, so the `tools` channel is user-opt-in.
|
||||
"""
|
||||
|
||||
_native = True
|
||||
required_stream_modes = ("tools",)
|
||||
|
||||
def __init__(self, scope: tuple[str, ...] = ()) -> None:
|
||||
super().__init__(scope)
|
||||
self._log: StreamChannel[ToolCallStream] = StreamChannel()
|
||||
self._active: dict[str, ToolCallStream] = {}
|
||||
self._is_async = False
|
||||
self._pump_fn: Callable[[], bool] | None = None
|
||||
self._apump_fn: Callable[[], Awaitable[bool]] | None = None
|
||||
|
||||
def init(self) -> dict[str, Any]:
|
||||
return {"tool_calls": self._log}
|
||||
|
||||
def _bind_pump(self, fn: Callable[[], bool]) -> None:
|
||||
"""Wire the sync pull callback onto this transformer.
|
||||
|
||||
Called by `StreamMux.bind_pump`. Stored so each new
|
||||
`ToolCallStream` created by `process` can wire its deltas log
|
||||
for pump-driven iteration.
|
||||
"""
|
||||
self._pump_fn = fn
|
||||
self._is_async = False
|
||||
|
||||
def _bind_apump(self, fn: Callable[[], Awaitable[bool]]) -> None:
|
||||
"""Async counterpart to `_bind_pump`."""
|
||||
self._apump_fn = fn
|
||||
self._is_async = True
|
||||
|
||||
def _new_stream(
|
||||
self,
|
||||
tool_call_id: str,
|
||||
tool_name: str,
|
||||
tool_input: dict[str, Any] | None,
|
||||
) -> ToolCallStream:
|
||||
stream = ToolCallStream(tool_call_id, tool_name, tool_input)
|
||||
stream._bind(is_async=self._is_async)
|
||||
if self._apump_fn is not None:
|
||||
stream._output_deltas._arequest_more = self._apump_fn
|
||||
if self._pump_fn is not None:
|
||||
stream._output_deltas._request_more = self._pump_fn
|
||||
return stream
|
||||
|
||||
def process(self, event: ProtocolEvent) -> bool:
|
||||
if event["method"] != "tools":
|
||||
return True
|
||||
|
||||
# Only project events emitted at this transformer's scope. Subgraph
|
||||
# events still flow through the parent's mux (the parent's main
|
||||
# event log keeps them) but they belong to the child mini-mux's
|
||||
# `tool_calls` projection, not the parent's.
|
||||
if tuple(event["params"]["namespace"]) != self.scope:
|
||||
return True
|
||||
|
||||
data = event["params"]["data"]
|
||||
tool_call_id = data.get("tool_call_id")
|
||||
if tool_call_id is None:
|
||||
return True
|
||||
event_type = data.get("event")
|
||||
|
||||
stream: ToolCallStream | None
|
||||
if event_type == "tool-started":
|
||||
stream = self._new_stream(
|
||||
tool_call_id,
|
||||
data.get("tool_name", ""),
|
||||
data.get("input"),
|
||||
)
|
||||
self._active[tool_call_id] = stream
|
||||
self._log.push(stream)
|
||||
elif event_type == "tool-output-delta":
|
||||
stream = self._active.get(tool_call_id)
|
||||
if stream is not None:
|
||||
stream._push_delta(data.get("delta"))
|
||||
elif event_type == "tool-finished":
|
||||
stream = self._active.pop(tool_call_id, None)
|
||||
if stream is not None:
|
||||
stream._finish(data.get("output"))
|
||||
elif event_type == "tool-error":
|
||||
stream = self._active.pop(tool_call_id, None)
|
||||
if stream is not None:
|
||||
stream._fail(data.get("message", ""))
|
||||
|
||||
# Pass-through — wire consumers subscribe to the `tools` channel
|
||||
# directly and reconstruct handles client-side.
|
||||
return True
|
||||
|
||||
def finalize(self) -> None:
|
||||
"""Close any still-active tool streams left open at run end."""
|
||||
for stream in self._active.values():
|
||||
if not stream.completed:
|
||||
stream._finish(None)
|
||||
self._active.clear()
|
||||
|
||||
def fail(self, err: BaseException) -> None:
|
||||
"""Fail any still-active tool streams when the run errors."""
|
||||
message = str(err)
|
||||
for stream in self._active.values():
|
||||
if not stream.completed:
|
||||
stream._fail(message)
|
||||
self._active.clear()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
from typing import Literal
|
||||
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV10
|
||||
from typing_extensions import TypedDict, deprecated
|
||||
|
||||
|
||||
@deprecated(
|
||||
"HumanInterruptConfig has been moved to `langchain.agents.interrupt`. Please update your import to `from langchain.agents.interrupt import HumanInterruptConfig`.",
|
||||
category=LangGraphDeprecatedSinceV10,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@deprecated(
|
||||
"ActionRequest has been moved to `langchain.agents.interrupt`. Please update your import to `from langchain.agents.interrupt import ActionRequest`.",
|
||||
category=LangGraphDeprecatedSinceV10,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@deprecated(
|
||||
"HumanInterrupt has been moved to `langchain.agents.interrupt`. Please update your import to `from langchain.agents.interrupt import HumanInterrupt`.",
|
||||
category=LangGraphDeprecatedSinceV10,
|
||||
)
|
||||
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: str | None
|
||||
|
||||
|
||||
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
|
||||
args: 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: None | str | ActionRequest
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,221 @@
|
||||
"""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. If multiple tool calls are requested, they will be run in parallel.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import (
|
||||
Any,
|
||||
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 langgraph._internal._runnable import RunnableCallable
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV10
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
from pydantic.v1 import ValidationError as ValidationErrorV1
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
def _default_format_error(
|
||||
error: BaseException,
|
||||
call: ToolCall,
|
||||
schema: type[BaseModel] | type[BaseModelV1],
|
||||
) -> str:
|
||||
"""Default error formatting function."""
|
||||
return f"{repr(error)}\n\nRespond after fixing all validation errors."
|
||||
|
||||
|
||||
@deprecated(
|
||||
"ValidationNode is deprecated. Please use `create_agent` from `langchain.agents` with custom tool error handling.",
|
||||
category=LangGraphDeprecatedSinceV10,
|
||||
)
|
||||
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.
|
||||
|
||||
!!! 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).
|
||||
|
||||
Returns:
|
||||
(Union[Dict[str, List[ToolMessage]], Sequence[ToolMessage]]): A list of
|
||||
`ToolMessage` objects with the validated content or error messages.
|
||||
|
||||
Example:
|
||||
```python title="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()
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schemas: Sequence[BaseTool | type[BaseModel] | Callable],
|
||||
*,
|
||||
format_error: Callable[[BaseException, ToolCall, type[BaseModel]], str]
|
||||
| None = None,
|
||||
name: str = "validation",
|
||||
tags: list[str] | None = None,
|
||||
) -> None:
|
||||
"""Initialize the ValidationNode.
|
||||
|
||||
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.
|
||||
"""
|
||||
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: 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: 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}
|
||||
Reference in New Issue
Block a user