Description
Claude 4.6 models (Sonnet 4.6, Opus 4.6) return 400 Bad Request errors when used with CrewAI agents. Anthropic made a breaking API change: Claude 4.6 no longer supports assistant message prefilling, and any request with a trailing assistant message is rejected.
Root Cause
CrewAgentExecutor._append_message() appends every LLM response as role: "assistant" (crew_agent_executor.py#L273):
self._append_message(formatted_answer.text) # role defaults to "assistant"
This builds conversations with consecutive assistant messages:
user: [system prompt + task]
assistant: [LLM response with tool call]
assistant: [tool result + observation] ← consecutive assistant
assistant: [next iteration] ← consecutive assistant
Claude 4.6 interprets the trailing assistant message as a prefill attempt and rejects the request. Earlier Claude models (3.5, 4.5) tolerated this, but 4.6 is strict.
Steps to Reproduce
- Create a CrewAI agent with
llm=LLM(model="anthropic/claude-sonnet-4-6-20250514")
- Give it a task that requires tool use (multiple iterations)
- The agent fails on the second LLM call with:
400 Bad Request: Prefilling assistant messages is no longer supported
Expected Behavior
CrewAI should normalize the message history before sending to the LLM — either by:
- Merging consecutive same-role messages
- Inserting a
user message between consecutive assistant messages
- Using
_format_model_specific_messages() to handle this (it already has Mistral/Ollama handling but not Anthropic)
Environment
- CrewAI version: 1.6.1 (also reproduced conceptually against 1.10.1 source)
- Python: 3.13
- LLM provider: OpenRouter (LiteLLM path)
- Models affected:
anthropic/claude-sonnet-4-6-20250514, anthropic/claude-opus-4-6-20250514
Related Issues
Suggested Fix
Add Anthropic handling to _format_model_specific_messages() in llm.py (around line 1508) to merge consecutive assistant messages, similar to the existing Mistral/Ollama handling:
# Handle Anthropic models — merge consecutive same-role messages
if self.is_anthropic:
merged = [messages[0]]
for msg in messages[1:]:
if msg["role"] == merged[-1]["role"]:
merged[-1]["content"] += "\n\n" + str(msg["content"])
else:
merged.append(msg)
messages = merged
Workaround
Using a LiteLLM input callback to merge consecutive messages before they reach the API:
import litellm
def merge_consecutive_messages(kwargs, **_):
messages = kwargs.get("messages", [])
if len(messages) < 2:
return
merged = [messages[0]]
for msg in messages[1:]:
if msg["role"] == merged[-1]["role"]:
merged[-1]["content"] += "\n\n" + str(msg["content"])
else:
merged.append(msg)
kwargs["messages"] = merged
litellm.input_callback = [merge_consecutive_messages]
Description
Claude 4.6 models (Sonnet 4.6, Opus 4.6) return 400 Bad Request errors when used with CrewAI agents. Anthropic made a breaking API change: Claude 4.6 no longer supports assistant message prefilling, and any request with a trailing assistant message is rejected.
Root Cause
CrewAgentExecutor._append_message()appends every LLM response asrole: "assistant"(crew_agent_executor.py#L273):This builds conversations with consecutive assistant messages:
Claude 4.6 interprets the trailing assistant message as a prefill attempt and rejects the request. Earlier Claude models (3.5, 4.5) tolerated this, but 4.6 is strict.
Steps to Reproduce
llm=LLM(model="anthropic/claude-sonnet-4-6-20250514")Expected Behavior
CrewAI should normalize the message history before sending to the LLM — either by:
usermessage between consecutiveassistantmessages_format_model_specific_messages()to handle this (it already has Mistral/Ollama handling but not Anthropic)Environment
anthropic/claude-sonnet-4-6-20250514,anthropic/claude-opus-4-6-20250514Related Issues
ensure_alternating_roles, but that param is no longer available)Suggested Fix
Add Anthropic handling to
_format_model_specific_messages()inllm.py(around line 1508) to merge consecutive assistant messages, similar to the existing Mistral/Ollama handling:Workaround
Using a LiteLLM input callback to merge consecutive messages before they reach the API: