LangGraph adapter: tools receive pydantic model instances for object inputs, and the instances leak into flow outputs
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 420
- Forks
- 60
- Avg merge
- 9h 30m
- Merged PRs (30d)
- 4
Description
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the LangGraph adapter entry point, AgentSpecLoader, and trace generated tool args through the tool callable and flow state. Run the provided reproduction with the echo tool, then verify that ServerTool, RemoteTool, and ClientTool receive or expose only JSON values rather than pydantic instances.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100