diff --git a/integrations/langgraph/python/ag_ui_langgraph/utils.py b/integrations/langgraph/python/ag_ui_langgraph/utils.py index e310fc67da..30a0af7a96 100644 --- a/integrations/langgraph/python/ag_ui_langgraph/utils.py +++ b/integrations/langgraph/python/ag_ui_langgraph/utils.py @@ -404,6 +404,9 @@ def agui_messages_to_langchain(messages: List[AGUIMessage]) -> List[BaseMessage] id=message.id, content=message.content, tool_call_id=message.tool_call_id, + # Carry the AG-UI failure signal onto LangChain's tool-result status, so a + # client-reported tool failure is not delivered to the model as a success. + status="error" if message.error else "success", )) else: raise ValueError(f"Unsupported message role: {role}") diff --git a/integrations/langgraph/python/tests/test_message_conversion.py b/integrations/langgraph/python/tests/test_message_conversion.py index cc98bcd5e4..92a9b29f73 100644 --- a/integrations/langgraph/python/tests/test_message_conversion.py +++ b/integrations/langgraph/python/tests/test_message_conversion.py @@ -82,6 +82,22 @@ def test_tool_message(self): assert isinstance(result[0], ToolMessage) assert result[0].content == "42" assert result[0].tool_call_id == "tc1" + # A tool result with no error maps to LangChain's default "success" status. + assert result[0].status == "success" + + def test_tool_message_error_maps_to_status(self): + # A client-reported tool failure must reach the model as an error, not a + # silent success -- AG-UI's ToolMessage.error becomes status="error". + msg = AGUIToolMessage( + id="t1", + role="tool", + content="Tool failed: invalid id", + tool_call_id="tc1", + error="invalid id", + ) + result = agui_messages_to_langchain([msg]) + assert isinstance(result[0], ToolMessage) + assert result[0].status == "error" def test_multimodal_with_url(self): msg = AGUIUserMessage( diff --git a/integrations/langgraph/typescript/src/message-conversion.test.ts b/integrations/langgraph/typescript/src/message-conversion.test.ts index 8aed2935d8..f040b2ff23 100644 --- a/integrations/langgraph/typescript/src/message-conversion.test.ts +++ b/integrations/langgraph/typescript/src/message-conversion.test.ts @@ -78,6 +78,23 @@ describe("Message Conversion - All Types", () => { expect(result[0].type).toBe("tool"); expect(result[0].content).toBe("42"); expect(result[0].tool_call_id).toBe("tc1"); + // A tool result with no error maps to LangChain's default "success" status. + expect(result[0].status).toBe("success"); + }); + + it("should map a tool message error onto LangChain's status flag", () => { + // A client-reported tool failure must reach the model as an error, not a + // silent success — AG-UI's ToolMessage.error becomes status: "error". + const msg: Message = { + id: "t1", + role: "tool", + content: "Tool failed: invalid id", + toolCallId: "tc1", + error: "invalid id", + }; + const result: any[] = aguiMessagesToLangChain([msg]); + expect(result[0].type).toBe("tool"); + expect(result[0].status).toBe("error"); }); it("should throw for unsupported role", () => { diff --git a/integrations/langgraph/typescript/src/utils.ts b/integrations/langgraph/typescript/src/utils.ts index 54b66649dd..3d05a481fc 100644 --- a/integrations/langgraph/typescript/src/utils.ts +++ b/integrations/langgraph/typescript/src/utils.ts @@ -435,6 +435,9 @@ export function aguiMessagesToLangChain(messages: Message[]): LangGraphMessage[] type: message.role, tool_call_id: message.toolCallId, id: message.id, + // Carry the AG-UI failure signal onto LangChain's tool-result status, so a + // client-reported tool failure is not delivered to the model as a success. + status: message.error ? "error" : "success", } as LangGraphMessage); break; default: