Skip to content

LangGraph adapter: tools receive pydantic model instances for object inputs, and the instances leak into flow outputs #252

Description

@fede-kamel

Description

In the LangGraph adapter, tools whose inputs are object-typed properties do not receive plain dictionaries: they receive instances of the pydantic models that the adapter generates from the JSON schema. Those instances then leak into the flow state, the final flow outputs and client-tool interrupt payloads.

Agent Spec tools are written against JSON values, and every other runtime (Wayflow, AutoGen, Agent Framework) passes dictionaries, so the same tool_registry callable behaves differently on LangGraph.

Reproduction

from pyagentspec.adapters.langgraph import AgentSpecLoader
from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge
from pyagentspec.flows.flow import Flow
from pyagentspec.flows.nodes import EndNode, StartNode, ToolNode
from pyagentspec.property import Property
from pyagentspec.tools import ServerTool

request = Property(json_schema={
    "title": "request", "type": "object",
    "properties": {"customer_id": {"type": "string"}, "priority": {"type": "string", "default": "normal"}},
    "required": ["customer_id"], "additionalProperties": False,
})
echo = ServerTool(name="echo", description="Echoes", inputs=[request], outputs=[request])
start = StartNode(name="start", inputs=[request])
node = ToolNode(name="echo_node", tool=echo)
end = EndNode(name="end", outputs=[request])
flow = Flow(
    name="f", start_node=start, nodes=[start, node, end],
    control_flow_connections=[ControlFlowEdge(name="a", from_node=start, to_node=node),
                              ControlFlowEdge(name="b", from_node=node, to_node=end)],
    data_flow_connections=[
        DataFlowEdge(name="i", source_node=start, source_output="request", destination_node=node, destination_input="request"),
        DataFlowEdge(name="o", source_node=node, source_output="request", destination_node=end, destination_input="request"),
    ],
)

def echo_tool(request):
    print(type(request))          # <class 'pydantic...request'>, not dict
    return request

graph = AgentSpecLoader(tool_registry={"echo": echo_tool}).load_component(flow)
print(graph.invoke({"inputs": {"request": {"customer_id": "C-1"}}})["outputs"])
# {'request': request(customer_id='C-1', priority=None)}   <- pydantic instance in the flow outputs

Wayflow (wayflowcore 26.3.0) passes a dict to the tool and returns a dict.

The same happens for RemoteTool (the templated request body renders the model's repr, e.g. customer_id='C-1', instead of the JSON value) and for ClientTool (the interrupt payload contains model instances).

Expected behavior

Tools receive plain JSON values (dictionaries, lists, scalars) for object-typed inputs, and flow outputs / interrupt payloads only contain JSON values.

Root cause

The adapter builds a pydantic args_schema for each tool from the Agent Spec input properties. LangChain's BaseTool._parse_input validates the call with that model and passes getattr(result, field) to the callable, so nested object fields arrive as model instances. Nothing converts them back before the callable runs or before values are written to the flow state.

Environment

  • pyagentspec main (26.4.0.dev0), langgraph 1.2.4, langchain-core 1.4.9, Python 3.12

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions