From 6c4e035314d2a1cd8966aaddb2bf57763afb5da9 Mon Sep 17 00:00:00 2001 From: "congxiao.wxx" Date: Wed, 17 Jun 2026 15:04:27 +0800 Subject: [PATCH] Keep tool errors in the tool channel Convert LangGraph tool failures into TOOL_RESULT events so downstream consumers treat them as tool outcomes instead of run-level errors. Constraint: preserve existing non-tool error handling. Rejected: keep emitting EventType.ERROR for on_tool_error | that escalates tool failures into run failures. Confidence: high Scope-risk: narrow Directive: future tool-error handling should stay aligned with TOOL_RESULT unless the protocol contract changes. Tested: uv run pytest -q tests/unittests/integration/test_langgraph_events.py tests/unittests/integration/test_langgraph_to_agent_event.py; git diff --check Not-tested: full repository test suite Change-Id: Ie7c4a6264afcbb7613b5382075978e82f9c7ef30 Co-developed-by: Codex --- .../integration/langgraph/agent_converter.py | 9 ++++---- .../integration/test_langgraph_events.py | 23 ++++++++++--------- .../test_langgraph_to_agent_event.py | 23 ++++++++++--------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/agentrun/integration/langgraph/agent_converter.py b/agentrun/integration/langgraph/agent_converter.py index ac91e73..f00a46b 100644 --- a/agentrun/integration/langgraph/agent_converter.py +++ b/agentrun/integration/langgraph/agent_converter.py @@ -925,17 +925,16 @@ def _convert_astream_events_event( else: error_message = str(error) - # 发送 ERROR 事件 + # 发送 TOOL_RESULT 事件,避免把工具失败升级成 run error yield AgentResult( - event=EventType.ERROR, + event=EventType.TOOL_RESULT, data={ - "message": ( + "id": tool_call_id, + "result": ( f"Tool '{tool_name}' error: {error_message}" if tool_name else error_message ), - "code": "TOOL_ERROR", - "tool_call_id": tool_call_id, }, ) diff --git a/tests/unittests/integration/test_langgraph_events.py b/tests/unittests/integration/test_langgraph_events.py index d5820f6..0e51714 100644 --- a/tests/unittests/integration/test_langgraph_events.py +++ b/tests/unittests/integration/test_langgraph_events.py @@ -746,7 +746,7 @@ def test_on_tool_error(self): """测试 on_tool_error 事件 输入: on_tool_error with error - 输出: ERROR 事件 + 输出: TOOL_RESULT 事件 """ event = { "event": "on_tool_error", @@ -761,11 +761,10 @@ def test_on_tool_error(self): results = list(AgentRunConverter().to_agui_events(event)) assert len(results) == 1 - assert results[0].event == EventType.ERROR - assert "weather_tool" in results[0].data["message"] - assert "ValueError" in results[0].data["message"] - assert results[0].data["code"] == "TOOL_ERROR" - assert results[0].data["tool_call_id"] == "run_123" + assert results[0].event == EventType.TOOL_RESULT + assert "weather_tool" in results[0].data["result"] + assert "ValueError" in results[0].data["result"] + assert results[0].data["id"] == "run_123" def test_on_tool_error_with_runtime_tool_call_id(self): """测试 on_tool_error 使用 runtime 中的 tool_call_id""" @@ -786,7 +785,8 @@ class FakeRuntime: results = list(AgentRunConverter().to_agui_events(event)) assert len(results) == 1 - assert results[0].data["tool_call_id"] == "call_original_id" + assert results[0].event == EventType.TOOL_RESULT + assert results[0].data["id"] == "call_original_id" def test_on_tool_error_with_string_error(self): """测试 on_tool_error 使用字符串错误""" @@ -803,7 +803,8 @@ def test_on_tool_error_with_string_error(self): results = list(AgentRunConverter().to_agui_events(event)) assert len(results) == 1 - assert "Division by zero" in results[0].data["message"] + assert results[0].event == EventType.TOOL_RESULT + assert "Division by zero" in results[0].data["result"] def test_on_llm_error(self): """测试 on_llm_error 事件 @@ -878,7 +879,7 @@ def test_tool_error_in_complete_flow(self): 流程: 1. on_tool_start (TOOL_CALL_CHUNK) - 2. on_tool_error (ERROR) + 2. on_tool_error (TOOL_RESULT) """ events = [ { @@ -903,9 +904,9 @@ def test_tool_error_in_complete_flow(self): chunk_events = filter_agent_events( all_results, EventType.TOOL_CALL_CHUNK ) - error_events = filter_agent_events(all_results, EventType.ERROR) + error_events = filter_agent_events(all_results, EventType.TOOL_RESULT) assert len(chunk_events) == 1 assert len(error_events) == 1 assert chunk_events[0].data["id"] == "run_risky" - assert error_events[0].data["tool_call_id"] == "run_risky" + assert error_events[0].data["id"] == "run_risky" diff --git a/tests/unittests/integration/test_langgraph_to_agent_event.py b/tests/unittests/integration/test_langgraph_to_agent_event.py index 6607feb..74933d1 100644 --- a/tests/unittests/integration/test_langgraph_to_agent_event.py +++ b/tests/unittests/integration/test_langgraph_to_agent_event.py @@ -744,7 +744,7 @@ def test_on_tool_error(self): """测试 on_tool_error 事件 输入: on_tool_error with error - 输出: ERROR 事件 + 输出: TOOL_RESULT 事件 """ event = { "event": "on_tool_error", @@ -759,11 +759,10 @@ def test_on_tool_error(self): results = list(AgentRunConverter().to_agui_events(event)) assert len(results) == 1 - assert results[0].event == EventType.ERROR - assert "weather_tool" in results[0].data["message"] - assert "ValueError" in results[0].data["message"] - assert results[0].data["code"] == "TOOL_ERROR" - assert results[0].data["tool_call_id"] == "run_123" + assert results[0].event == EventType.TOOL_RESULT + assert "weather_tool" in results[0].data["result"] + assert "ValueError" in results[0].data["result"] + assert results[0].data["id"] == "run_123" def test_on_tool_error_with_runtime_tool_call_id(self): """测试 on_tool_error 使用 runtime 中的 tool_call_id""" @@ -784,7 +783,8 @@ class FakeRuntime: results = list(AgentRunConverter().to_agui_events(event)) assert len(results) == 1 - assert results[0].data["tool_call_id"] == "call_original_id" + assert results[0].event == EventType.TOOL_RESULT + assert results[0].data["id"] == "call_original_id" def test_on_tool_error_with_string_error(self): """测试 on_tool_error 使用字符串错误""" @@ -801,7 +801,8 @@ def test_on_tool_error_with_string_error(self): results = list(AgentRunConverter().to_agui_events(event)) assert len(results) == 1 - assert "Division by zero" in results[0].data["message"] + assert results[0].event == EventType.TOOL_RESULT + assert "Division by zero" in results[0].data["result"] def test_on_llm_error(self): """测试 on_llm_error 事件 @@ -876,7 +877,7 @@ def test_tool_error_in_complete_flow(self): 流程: 1. on_tool_start (TOOL_CALL_CHUNK) - 2. on_tool_error (ERROR) + 2. on_tool_error (TOOL_RESULT) """ events = [ { @@ -901,9 +902,9 @@ def test_tool_error_in_complete_flow(self): chunk_events = filter_agent_events( all_results, EventType.TOOL_CALL_CHUNK ) - error_events = filter_agent_events(all_results, EventType.ERROR) + error_events = filter_agent_events(all_results, EventType.TOOL_RESULT) assert len(chunk_events) == 1 assert len(error_events) == 1 assert chunk_events[0].data["id"] == "run_risky" - assert error_events[0].data["tool_call_id"] == "run_risky" + assert error_events[0].data["id"] == "run_risky"