oracle / oracle/agent-spec

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

Open
#252 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.