Skip to content

Commit 6c4e035

Browse files
committed
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 <noreply@openai.com>
1 parent 0490828 commit 6c4e035

3 files changed

Lines changed: 28 additions & 27 deletions

File tree

agentrun/integration/langgraph/agent_converter.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -925,17 +925,16 @@ def _convert_astream_events_event(
925925
else:
926926
error_message = str(error)
927927

928-
# 发送 ERROR 事件
928+
# 发送 TOOL_RESULT 事件,避免把工具失败升级成 run error
929929
yield AgentResult(
930-
event=EventType.ERROR,
930+
event=EventType.TOOL_RESULT,
931931
data={
932-
"message": (
932+
"id": tool_call_id,
933+
"result": (
933934
f"Tool '{tool_name}' error: {error_message}"
934935
if tool_name
935936
else error_message
936937
),
937-
"code": "TOOL_ERROR",
938-
"tool_call_id": tool_call_id,
939938
},
940939
)
941940

tests/unittests/integration/test_langgraph_events.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ def test_on_tool_error(self):
746746
"""测试 on_tool_error 事件
747747
748748
输入: on_tool_error with error
749-
输出: ERROR 事件
749+
输出: TOOL_RESULT 事件
750750
"""
751751
event = {
752752
"event": "on_tool_error",
@@ -761,11 +761,10 @@ def test_on_tool_error(self):
761761
results = list(AgentRunConverter().to_agui_events(event))
762762

763763
assert len(results) == 1
764-
assert results[0].event == EventType.ERROR
765-
assert "weather_tool" in results[0].data["message"]
766-
assert "ValueError" in results[0].data["message"]
767-
assert results[0].data["code"] == "TOOL_ERROR"
768-
assert results[0].data["tool_call_id"] == "run_123"
764+
assert results[0].event == EventType.TOOL_RESULT
765+
assert "weather_tool" in results[0].data["result"]
766+
assert "ValueError" in results[0].data["result"]
767+
assert results[0].data["id"] == "run_123"
769768

770769
def test_on_tool_error_with_runtime_tool_call_id(self):
771770
"""测试 on_tool_error 使用 runtime 中的 tool_call_id"""
@@ -786,7 +785,8 @@ class FakeRuntime:
786785
results = list(AgentRunConverter().to_agui_events(event))
787786

788787
assert len(results) == 1
789-
assert results[0].data["tool_call_id"] == "call_original_id"
788+
assert results[0].event == EventType.TOOL_RESULT
789+
assert results[0].data["id"] == "call_original_id"
790790

791791
def test_on_tool_error_with_string_error(self):
792792
"""测试 on_tool_error 使用字符串错误"""
@@ -803,7 +803,8 @@ def test_on_tool_error_with_string_error(self):
803803
results = list(AgentRunConverter().to_agui_events(event))
804804

805805
assert len(results) == 1
806-
assert "Division by zero" in results[0].data["message"]
806+
assert results[0].event == EventType.TOOL_RESULT
807+
assert "Division by zero" in results[0].data["result"]
807808

808809
def test_on_llm_error(self):
809810
"""测试 on_llm_error 事件
@@ -878,7 +879,7 @@ def test_tool_error_in_complete_flow(self):
878879
879880
流程:
880881
1. on_tool_start (TOOL_CALL_CHUNK)
881-
2. on_tool_error (ERROR)
882+
2. on_tool_error (TOOL_RESULT)
882883
"""
883884
events = [
884885
{
@@ -903,9 +904,9 @@ def test_tool_error_in_complete_flow(self):
903904
chunk_events = filter_agent_events(
904905
all_results, EventType.TOOL_CALL_CHUNK
905906
)
906-
error_events = filter_agent_events(all_results, EventType.ERROR)
907+
error_events = filter_agent_events(all_results, EventType.TOOL_RESULT)
907908

908909
assert len(chunk_events) == 1
909910
assert len(error_events) == 1
910911
assert chunk_events[0].data["id"] == "run_risky"
911-
assert error_events[0].data["tool_call_id"] == "run_risky"
912+
assert error_events[0].data["id"] == "run_risky"

tests/unittests/integration/test_langgraph_to_agent_event.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ def test_on_tool_error(self):
744744
"""测试 on_tool_error 事件
745745
746746
输入: on_tool_error with error
747-
输出: ERROR 事件
747+
输出: TOOL_RESULT 事件
748748
"""
749749
event = {
750750
"event": "on_tool_error",
@@ -759,11 +759,10 @@ def test_on_tool_error(self):
759759
results = list(AgentRunConverter().to_agui_events(event))
760760

761761
assert len(results) == 1
762-
assert results[0].event == EventType.ERROR
763-
assert "weather_tool" in results[0].data["message"]
764-
assert "ValueError" in results[0].data["message"]
765-
assert results[0].data["code"] == "TOOL_ERROR"
766-
assert results[0].data["tool_call_id"] == "run_123"
762+
assert results[0].event == EventType.TOOL_RESULT
763+
assert "weather_tool" in results[0].data["result"]
764+
assert "ValueError" in results[0].data["result"]
765+
assert results[0].data["id"] == "run_123"
767766

768767
def test_on_tool_error_with_runtime_tool_call_id(self):
769768
"""测试 on_tool_error 使用 runtime 中的 tool_call_id"""
@@ -784,7 +783,8 @@ class FakeRuntime:
784783
results = list(AgentRunConverter().to_agui_events(event))
785784

786785
assert len(results) == 1
787-
assert results[0].data["tool_call_id"] == "call_original_id"
786+
assert results[0].event == EventType.TOOL_RESULT
787+
assert results[0].data["id"] == "call_original_id"
788788

789789
def test_on_tool_error_with_string_error(self):
790790
"""测试 on_tool_error 使用字符串错误"""
@@ -801,7 +801,8 @@ def test_on_tool_error_with_string_error(self):
801801
results = list(AgentRunConverter().to_agui_events(event))
802802

803803
assert len(results) == 1
804-
assert "Division by zero" in results[0].data["message"]
804+
assert results[0].event == EventType.TOOL_RESULT
805+
assert "Division by zero" in results[0].data["result"]
805806

806807
def test_on_llm_error(self):
807808
"""测试 on_llm_error 事件
@@ -876,7 +877,7 @@ def test_tool_error_in_complete_flow(self):
876877
877878
流程:
878879
1. on_tool_start (TOOL_CALL_CHUNK)
879-
2. on_tool_error (ERROR)
880+
2. on_tool_error (TOOL_RESULT)
880881
"""
881882
events = [
882883
{
@@ -901,9 +902,9 @@ def test_tool_error_in_complete_flow(self):
901902
chunk_events = filter_agent_events(
902903
all_results, EventType.TOOL_CALL_CHUNK
903904
)
904-
error_events = filter_agent_events(all_results, EventType.ERROR)
905+
error_events = filter_agent_events(all_results, EventType.TOOL_RESULT)
905906

906907
assert len(chunk_events) == 1
907908
assert len(error_events) == 1
908909
assert chunk_events[0].data["id"] == "run_risky"
909-
assert error_events[0].data["tool_call_id"] == "run_risky"
910+
assert error_events[0].data["id"] == "run_risky"

0 commit comments

Comments
 (0)