Repository: langchain-ai/langgraph-supervisor-py

Issue:
ChatAnthropic fails to execute tool calls in langgraph-supervisor framework
When using ChatAnthropic with the langgraph-supervisor framework, tool calls fail to execute correctly, unlike when using ChatOpenAI.

```python
from langchain_openai import ChatOpenAI
from langgraph_supervisor import create_supervisor
from langgraph.prebuilt import create_react_agent

# Works with OpenAI
model = ChatOpenAI(model="gpt-4o")

# Fails when changed to:
# from langchain_anthropic import ChatAnthropic
# model = ChatAnthropic(model="claude-3-7-sonnet-latest")

# Tool definitions
def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b

def multiply(a: float, b: float) -> float:
    """Multiply two numbers."""
    return a * b

def web_search(query: str) -> str:
    """Search the web for information."""
    return "FAANG company headcount data..."

# Create agents and workflow
math_agent = create_react_agent(...)
research_agent = create_react_agent(...)
workflow = create_supervisor([research_agent, math_agent], model=model, ...)

# This works with OpenAI but fails with Anthropic
result = app.invoke({
    "messages": [
        {
            "role": "user",
            "content": "what's the combined headcount of the FAANG companies in 2024?"
        }
    ]
})

Discussion:
@vbarda: what exactly is failing? could you provide a more detailed description?@YoungjaeDev: @vbarda 

https://gist.github.com/YoungjaeDev/1ad6277460baa13ce9b5fbceaa3aa813@YoungjaeDev: @vbarda 

but openai api works well!
https://gist.github.com/YoungjaeDev/e8cf63273064953c2ca45c00a7eb5d57@vbarda: ah, this is an anthropic quirk. @YoungjaeDev you can do a few things to improve this:

1/ set `include_agent_name="inline"`
2/ set `output_mode="full_history"`
3/ try to improve the prompt to force the supervisor to always respond at the end

closing since not an issue with the library itself@YoungjaeDev: @vbarda 

I tried the methods you suggested, but the problem still persists. This seems to be related to Anthropic's message processing order. As confirmed in another issue (https://github.com/anthropics/claude-code/issues/473), the Anthropic API is very strict about the order of tool_use and tool_result, and an error occurs if a text block is inserted between them. Due to this difference in message order processing, code that works well with OpenAI doesn't work with Anthropic.@vbarda: Could you provide a langsmith trace?@YoungjaeDev: > Could you provide a langsmith trace?

https://smith.langchain.com/public/c72af281-dfc2-4866-a4a6-93440eaf4363/r@vbarda: Thanks - will investigate and get back to you@vbarda: @YoungjaeDev found and fixed the issue, thanks for reporting!

Solution patch:
diff --git a/langgraph_supervisor/agent_name.py b/langgraph_supervisor/agent_name.py
index 23e24d0..c211d84 100644
--- a/langgraph_supervisor/agent_name.py
+++ b/langgraph_supervisor/agent_name.py
@@ -40,7 +40,7 @@ def add_inline_agent_name(message: BaseMessage) -> BaseMessage:
         non_text_blocks = [block for block in message.content if block["type"] != "text"]
         content = text_blocks[0]["text"] if text_blocks else ""
         formatted_content = f"<name>{message.name}</name><content>{content}</content>"
-        formatted_message.content = non_text_blocks + [{"type": "text", "text": formatted_content}]
+        formatted_message.content = [{"type": "text", "text": formatted_content}] + non_text_blocks
     else:
         formatted_message.content = (
             f"<name>{message.name}</name><content>{formatted_message.content}</content>"
@@ -83,7 +83,7 @@ def remove_inline_agent_name(message: BaseMessage) -> BaseMessage:
     if is_content_blocks_content:
         content_blocks = non_text_blocks
         if parsed_content:
-            content_blocks.append({"type": "text", "text": parsed_content})
+            content_blocks = [{"type": "text", "text": parsed_content}] + content_blocks
 
         parsed_message.content = content_blocks
     else:


Test patch:
diff --git a/tests/test_agent_name.py b/tests/test_agent_name.py
index 7967b78..f473e07 100644
--- a/tests/test_agent_name.py
+++ b/tests/test_agent_name.py
@@ -32,8 +32,8 @@ def test_add_inline_agent_name_content_blocks():
     ai_message = AIMessage(content=content_blocks, name="assistant")
     result = add_inline_agent_name(ai_message)
     assert result.content == [
-        {"type": "image", "image_url": "http://example.com/image.jpg"},
         {"type": "text", "text": "<name>assistant</name><content>Hello world</content>"},
+        {"type": "image", "image_url": "http://example.com/image.jpg"},
     ]
 
     # Test that content blocks without text blocks are returned unchanged
@@ -41,9 +41,9 @@ def test_add_inline_agent_name_content_blocks():
         {"type": "image", "image_url": "http://example.com/image.jpg"},
         {"type": "file", "file_url": "http://example.com/document.pdf"},
     ]
-    expected_content_blocks = content_blocks + [
+    expected_content_blocks = [
         {"type": "text", "text": "<name>assistant</name><content></content>"}
-    ]
+    ] + content_blocks
     ai_message = AIMessage(content=content_blocks, name="assistant")
     result = add_inline_agent_name(ai_message)
 
@@ -85,19 +85,19 @@ def test_remove_inline_agent_name_content_blocks():
     result = remove_inline_agent_name(ai_message)
 
     expected_content = [
-        {"type": "image", "image_url": "http://example.com/image.jpg"},
         {"type": "text", "text": "Hello world"},
+        {"type": "image", "image_url": "http://example.com/image.jpg"},
     ]
     assert result.content == expected_content
     assert result.name == "assistant"
 
     # Test that content blocks without text blocks are returned unchanged
     content_blocks = [
+        {"type": "text", "text": "<name>assistant</name><content></content>"},
         {"type": "image", "image_url": "http://example.com/image.jpg"},
         {"type": "file", "file_url": "http://example.com/document.pdf"},
-        {"type": "text", "text": "<name>assistant</name><content></content>"},
     ]
-    expected_content_blocks = content_blocks[:-1]
+    expected_content_blocks = content_blocks[1:]
     ai_message = AIMessage(content=content_blocks, name="assistant")
     result = remove_inline_agent_name(ai_message)
     assert result.content == expected_content_blocks