Skip to content

Using Mistral Agents with tool and message_history leads to "Unexpected role 'user' after role 'tool'" #1210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
TKaluza opened this issue Mar 22, 2025 · 3 comments

Comments

@TKaluza
Copy link

TKaluza commented Mar 22, 2025

Initial Checks

  • I confirm that I'm using the latest version of Pydantic AI

Description

When using the Mistral model with pydantic-ai agents, passing message history from a previous result to a new run causes an error. The API returns a 400 error with the message "Unexpected role 'user' after role 'tool'".

Example Code

from pydantic_ai.models.mistral import MistralModel
from pydantic_ai.agent import Agent
from pydantic import BaseModel, Field

class PoemInfo(BaseModel):
    title: str = Field(..., description="Create a fitting titel for your poem")
    creator: str = Field(..., description="the creator of the poem")
    text: str = Field(..., description="The complete poem")

model = MistralModel('mistral-small-latest', api_key="API_KEY_HERE")
agent = Agent(model=model, result_type=PoemInfo)

# This works fine
result0 = agent.run_sync("Make a Poem, about singing like a bird on a motorbike")

# This also works fine
result1 = agent.run_sync("repeat the last answer")

# This causes the error
result2 = agent.run_sync("repeat the last answer", message_history=result0.new_messages())

### LOG Error Message
mistralai.models.sdkerror.SDKError: API error occurred: Status 400
{"object":"error","message":"Unexpected role 'user' after role 'tool'","type":"invalid_request_error","param":null,"code":null}

Python, Pydantic AI & LLM client version

Python 3.12.3 on ubuntu 24.04 amd64
mistralai==1.6.0
pydantic-ai==0.0.43
@TKaluza
Copy link
Author

TKaluza commented Mar 22, 2025

I have tested it with OpenAIModel('gpt-4o-mini', ...) and AnthropicModel('claude-3-5-haiku-latest', ... both work as expected.

@TKaluza
Copy link
Author

TKaluza commented Mar 24, 2025

if someone needs a hotfix:

def new_mixtral_messages(result: AgentRunResult) -> list[ModelMessage]:
    """
    Converts messages to a format compatible with Mistral:
    - Converts ToolCallPart to TextPart containing the args as text
    - Skips ToolReturnPart entirely
    - Preserves message metadata and other part types
    """
    new_messages = []

    for msg in result.new_messages():
        new_parts = []
        for part in msg.parts:
            if isinstance(part, ToolCallPart):
                # Convert tool call to text, handling both dict and string args
                content = part.args_as_json_str() if isinstance(part.args, dict) else str(part.args)
                new_parts.append(TextPart(content=content))
            elif isinstance(part, ToolReturnPart):
                # Skip tool returns
                continue
            else:
                # Keep all other parts unchanged
                new_parts.append(part)

        # Create the appropriate message type
        if isinstance(msg, ModelResponse):
            new_msg = ModelResponse(
                parts=new_parts,
                model_name=msg.model_name,
                timestamp=msg.timestamp
            )
        elif isinstance(msg, ModelRequest):
            new_msg = ModelRequest(parts=new_parts)
        else:
            raise NotImplemented("There should not be another type of message!")
        new_messages.append(new_msg)
    return new_messages

@tamir-alltrue-ai
Copy link

@TKaluza How did you incorporate this method into your agent?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants