pythonerror-handlinglangchainagentlanggraph

Handling LangGraph ToolExceptions in a REPL


I have a LangGraph agent that is supposed to interact with the user in multi-round conversations and perform modifications to a kubernetes environment. I am using an external Model Context Protocol server to provide the tools to interact with the k8s configuration and for now my agent construction is simply the LangChain create_agent function.
Now, the agent could use the tools incorrectly (e.g. attempting to delete a non-existant k8s pod) and the tool would raise a ToolException whose description could aid the model in refining its approach. Since the state of the agent requires a ToolMessage corresponding to each AIMessage that corresponds to a tool call I want to catch the exception and append its description as the response ToolMessage in the state of the agent. However, I am getting a hard-to-debug error that suggests I am going about this incorrectly.

Here is my agent code:

from langchain.agents import create_agent
from langchain_core.messages import BaseMessage
from langchain.messages import ToolMessage, AIMessage
from langgraph.graph import MessagesState
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import ToolException

class MyAgent:
    def __init__(self):
        model = ...
        tools = []

        self.config = {...}

        self.agent = create_agent(
            model,
            tools=tools,
            state_schema=MessagesState,
            checkpointer=InMemorySaver()
        )

    def run(self,cmd:dict) -> BaseMessage:
        try:
            result = self.agent.invoke(cmd,self.config)
            return result["messages"][-1]
        except ToolException as expt:
            
            # ERROR HANDLING
            # get state snapshot to find out tool call id
            snapshot = self.agent.get_state(self.config)
            last_tool_call = snapshot.values["messages"][-1].tool_calls[0]
            # compose toolmessage to signal the error
            error_tool_message = ToolMessage(
                content=f"Couldn't execute tool because of: {expt}",
                tool_call_id=last_tool_call["id"]
            )

            # ERROR COMES FROM HERE
            # update the state
            self.agent.update_state(
                self.config,
                {"messages":[error_tool_message]}
            )

            # return error notification to the user
            return AIMessage(
                f"Couldn't execute because of tool error: {expt}"
            )

When I try to run the agent with a message that asks it to delete a non-existent pod I get the following error message:

Traceback (most recent call last):
  File "/home/ubuntu/dev/elster/src/elster/agent.py", line 144, in run
    result = self.event_loop.run_until_complete(
        self.agent.ainvoke(cmd,config=self.config)
    )
  File "/home/ubuntu/.local/share/uv/python/cpython-3.13.9-linux-x86_64-gnu/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/pregel/main.py", line 3182, in ainvoke
    async for chunk in self.astream(
    ...<29 lines>...
            chunks.append(chunk)
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/pregel/main.py", line 3000, in astream
    async for _ in runner.atick(
    ...<13 lines>...
            yield o
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/pregel/_runner.py", line 304, in atick
    await arun_with_retry(
    ...<15 lines>...
    )
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/pregel/_retry.py", line 137, in arun_with_retry
    return await task.proc.ainvoke(task.input, config)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/_internal/_runnable.py", line 705, in ainvoke
    input = await asyncio.create_task(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^
        step.ainvoke(input, config, **kwargs), context=context
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/_internal/_runnable.py", line 473, in ainvoke
    ret = await self.afunc(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain/tools/tool_node.py", line 733, in _afunc
    outputs = await asyncio.gather(*coros)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain/tools/tool_node.py", line 1066, in _arun_one
    return await self._execute_tool_async(tool_request, input_type, config)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain/tools/tool_node.py", line 1015, in _execute_tool_async
    content = _handle_tool_error(e, flag=self._handle_tool_errors)
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain/tools/tool_node.py", line 389, in _handle_tool_error
    content = flag(e)  # type: ignore [assignment, call-arg]
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain/tools/tool_node.py", line 352, in _default_handle_tool_errors
    raise e
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain/tools/tool_node.py", line 970, in _execute_tool_async
    response = await tool.ainvoke(call_args, config)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_core/tools/structured.py", line 63, in ainvoke
    return await super().ainvoke(input, config, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_core/tools/base.py", line 601, in ainvoke
    return await self.arun(tool_input, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_core/tools/base.py", line 969, in arun
    raise error_to_raise
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_core/tools/base.py", line 938, in arun
    response = await coro_with_context(coro, context)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_core/tools/structured.py", line 117, in _arun
    return await self.coroutine(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_mcp_adapters/tools.py", line 292, in call_tool
    return _convert_call_tool_result(call_tool_result)
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_mcp_adapters/tools.py", line 80, in _convert_call_tool_result
    raise ToolException(tool_content)
langchain_core.tools.base.ToolException: failed to delete pod aaa in namespace : pods "aaa" not found
During task with name 'tools' and id '8fb89a71-87c5-ea74-9154-9fbc2b3b2503'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ubuntu/.local/share/uv/python/cpython-3.13.9-linux-x86_64-gnu/lib/python3.13/runpy.py", line 198, in _run_module_as_main
    return _run_code(code, main_globals, None,
                     "__main__", mod_spec)
  File "/home/ubuntu/.local/share/uv/python/cpython-3.13.9-linux-x86_64-gnu/lib/python3.13/runpy.py", line 88, in _run_code
    exec(code, run_globals)
    ~~~~^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.vscode-server/extensions/ms-python.debugpy-2025.14.1-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/__main__.py", line 71, in <module>
    cli.main()
    ~~~~~~~~^^
  File "/home/ubuntu/.vscode-server/extensions/ms-python.debugpy-2025.14.1-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/../debugpy/server/cli.py", line 508, in main
    run()
    ~~~^^
  File "/home/ubuntu/.vscode-server/extensions/ms-python.debugpy-2025.14.1-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/../debugpy/server/cli.py", line 358, in run_file
    runpy.run_path(target, run_name="__main__")
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.vscode-server/extensions/ms-python.debugpy-2025.14.1-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py", line 310, in run_path
    return _run_module_code(code, init_globals, run_name, pkg_name=pkg_name, script_name=fname)
  File "/home/ubuntu/.vscode-server/extensions/ms-python.debugpy-2025.14.1-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py", line 127, in _run_module_code
    _run_code(code, mod_globals, init_globals, mod_name, mod_spec, pkg_name, script_name)
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.vscode-server/extensions/ms-python.debugpy-2025.14.1-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py", line 118, in _run_code
    exec(code, run_globals)
    ~~~~^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/src/elster/main.py", line 104, in <module>
    main()
    ~~~~^^
  File "/home/ubuntu/dev/elster/src/elster/main.py", line 97, in main
    msg = agent.run(cmd)["messages"][-1]
          ~~~~~~~~~^^^^^
  File "/home/ubuntu/dev/elster/src/elster/agent.py", line 155, in run
    self.agent.update_state(self.config,{"messages":[error_tool_message]})
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/pregel/main.py", line 2359, in update_state
    return self.bulk_update_state(config, [[StateUpdate(values, as_node, task_id)]])
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/pregel/main.py", line 1883, in bulk_update_state
    current_config = perform_superstep(current_config, superstep)
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/pregel/main.py", line 1818, in perform_superstep
    run.invoke(
    ~~~~~~~~~~^
        values,
        ^^^^^^^
    ...<23 lines>...
        ),
        ^^
    )
    ^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langchain_core/runnables/base.py", line 3090, in invoke
    input_ = context.run(step.invoke, input_, config)
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/_internal/_runnable.py", line 400, in invoke
    ret = self.func(*args, **kwargs)
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/graph/_branch.py", line 167, in _route
    return self._finish(writer, input, result, config)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/dev/elster/.venv/lib/python3.13/site-packages/langgraph/graph/_branch.py", line 203, in _finish
    r if isinstance(r, Send) else self.ends[r] for r in result
                                  ~~~~~~~~~^^^
KeyError: 'model'

What am I doing incorrectly here?


Solution

  • from langchain.agents import create_agent
    from langchain_core.messages import AIMessage, ToolMessage, HumanMessage
    from langchain_core.tools import tool, ToolException
    from langgraph.graph import MessagesState
    from langgraph.checkpoint.memory import InMemorySaver
    from langchain_openai import ChatOpenAI
    
    
    @tool
    def delete_pod(pod_name: str) -> str:
        if pod_name == "nonexistent":
            raise ToolException(f"Pod '{pod_name}' not found")
        return f"Pod '{pod_name}' deleted successfully"
    
    
    
    def make_agent():
        model = some model
        tools = [delete_pod]
        checkpointer = InMemorySaver()
    
        agent = create_agent(
            model=model.get_client(),
            tools=tools,
            state_schema=MessagesState,
            checkpointer=checkpointer,
        )
        return agent
    
    
    
    class MyAgent:
        def __init__(self):
            self.agent = make_agent()
            self.config = {"configurable": {"thread_id": "abc123"}}
    
        def run(self, cmd):
            try:
                # Run normally
                result = self.agent.invoke(cmd, self.config)
                return result["messages"][-1]
    
            except ToolException as expt:
               
                snapshot = self.agent.get_state(self.config)
                last_tool_call = snapshot.values["messages"][-1].tool_calls[0]
    
    
                error_tool_message = ToolMessage(
                    content=f"Couldn't execute tool because of: {expt}",
                    tool_call_id=last_tool_call["id"],
                )
    
                # update state properly: specify as_node="tools"
                self.agent.update_state(
                    self.config,
                    {"messages": [error_tool_message]},
                    as_node="tools"
                )
    
                return AIMessage(content=f"Tool failed with error: {expt}")
    
    
    
    if __name__ == "__main__":
        agent = MyAgent()
    
        print(">>> Trying with valid pod")
        msg = agent.run({"messages": [HumanMessage(content="Delete pod testpod")]})
        print(msg.content)
    
        print("\n>>> Trying with non-existent pod")
        msg = agent.run({"messages": [HumanMessage(content="Delete pod nonexistent")]})
        print(msg.content)
    

    You are missing as_node in the self.agent.update_state() try updating the as_node and it should work fine.

    Above I have attached a working code.