pydantic-ai icon indicating copy to clipboard operation
pydantic-ai copied to clipboard

An assistant message with 'tool_calls' must be followed by tool messages

Open uriafranko opened this issue 11 months ago • 3 comments

A weird error is showing on an agent that runs multiple times in parallel

research_agent = Agent(
    model="openai:gpt-4o-mini",
    name="research_agent",
    deps_type=ResearchDependencies,
    result_type=ResearchResult,
    retries=3,
)

Error message

Error code: 400 - {'error': {'message': "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_peVwb5yZhjdZ3xtJZAX8dDRV", 'type': 'invalid_request_error', 'param': 'messages', 'code': None}}

any idea why it could happen?

uriafranko avatar Dec 29 '24 13:12 uriafranko

I think this might be fixed in the latest pydantic-ai, what version are you using?

If it's still occurring with the latest version, I would need an MRE to be able to look into this further.

samuelcolvin avatar Dec 30 '24 12:12 samuelcolvin

I think this might be fixed in the latest pydantic-ai, what version are you using?

If it's still occurring with the latest version, I would need an MRE to be able to look into this further.

I was on 0.0.15 and was getting the same error. I've upgraded to 0.0.16 and it appears to be fixed.

tealtony avatar Dec 31 '24 13:12 tealtony

I keep on getting this error as well, seems volatile.

Using pydantic-ai 0.0.17.

Downgraded to 0.0.16 and still have the same experience. For some cases it works, others it fails, only difference is the input_data per row in the dataframe. The input data is not the problem as this runs on langchain and has sufficient volume.

Errors: Error code: 400 - {'error': {'message': "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_8DeVnHtAruahS0pOkggrfUkA", 'type': 'invalid_request_error', 'param': 'messages', 'code': None}}

Error code: 400 - {'error': {'message': "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_qO2nOccG4MBRyx1OOdql3YgA", 'type': 'invalid_request_error', 'param': 'messages', 'code': None}}

Code below:

from typing_extensions import NotRequired, TypedDict
from pydantic import Field
import pandas as pd
import numpy as np
from src.utils import load_yaml_from_file
from src.utils_ai import *
from schemas.gpt.animal import Animal
from pydantic import BaseModel
from pydantic_ai import RunContext, ModelRetry
from pydantic_ai.settings import ModelSettings

class ThemeCount(TypedDict):
    theme: Annotated[str, Field(description="The specific theme")]
    count: Annotated[int, Field(description="Number of times this theme appears")]

class AnimalThemes(TypedDict):
    themes: Annotated[List[ThemeCount], Field(
        description="The underlying themes associated with this animal and their counts"
    )]
    count: Annotated[int, Field(description="Number of times this animal appears")]

class AnimalStats(TypedDict):
    total_count: Annotated[int, Field(description="Total number of animals in this category")]
    animals: Annotated[Dict[str, AnimalThemes], Field(
        description="Dictionary mapping animals to their themes and counts"
    )]

class Animal(TypedDict):
    positive_animals: Annotated[AnimalStats, Field(
        description="Statistics and details for positively mentioned animals"
    )]
    negative_animals: NotRequired[Annotated[AnimalStats, Field(
        description="Statistics and details for negatively mentioned animals"
    )]]

# CONFIGURE AGENT
extract_config = load_yaml_from_file("prompts/sys-extract-analytics.yml")
model_settings = ModelSettings(
    temperature=extract_config['model_temperature']
)

model_provider_name = extract_config['model_provider_name']
model_type =  extract_config['model_type']

if model_provider_name == 'openai':
    model_name = f"{model_provider_name}:{model_type}"
else:
    model_name = model_type
model_name = cast(KnownModelName, model_name)

class InputData(BaseModel):
    survey_question: str
    answers: str

analytics_agent = Agent(
    model=model_name,
    system_prompt=extract_config['prompt'],
    retries=10,
    result_type=Animal,
    deps_type=InputData,
    model_settings=model_settings
)

@analytics_agent.system_prompt
def add_input_data(ctx: RunContext[InputData]) -> str:
    return f"Questions asked: {ctx.deps.survey_question}, Answers: {ctx.deps.answers}"

@analytics_agent.result_validator
def validate_result(ctx: RunContext[None], result: Animal) -> Animal:
    try:
        if result['negative_animals']['total_count'] == 0 and result['positive_animals']['total_count'] == 0:
            raise ModelRetry(
                "No analytics returned, reflect on what went wrong and correct it, there is underlying data to construct an analysis from."
            )
    except (KeyError, TypeError) as e:
        raise ModelRetry(
            "Invalid result structure. Please ensure both positive and negative animals are included with their counts."
        )
    return result

user_prompt = load_yaml_from_file("prompts/user-animal.yml")['prompt']

df = pd.read_csv("data/gpt-input.csv")
df['success'] = 0
out_df = pd.DataFrame()

for row_index, row in df.iterrows():
    if row['success'] == 0:
        report_name = row['report_name']
        report_section_name = row['report_section_name']

        input_data = InputData(survey_question=row['question_and_previous_question'], answers=row['data'])
        
        try:
            response = analytics_agent.run_sync(user_prompt=user_prompt, deps=input_data)
            tmp_df = pd.DataFrame(flatten_animal_data(response.data))
            tmp_df['report_name'] = report_name
            tmp_df['report_section_name'] = report_section_name
            out_df = pd.concat([out_df, tmp_df], axis=0)
            df.loc[row_index, 'success'] = 1
        except Exception as e:
            print(f"Failed for {report_name}:{report_section_name} with error: {e}")```

XanderHorn avatar Jan 07 '25 14:01 XanderHorn

This issue is stale, and will be closed in 3 days if no reply is received.

github-actions[bot] avatar Jan 15 '25 14:01 github-actions[bot]

I also keep seeing this on 0.0.19. It is not consistent. I see it on 1 out of 3 runs.

gurvinder-dhillon avatar Jan 19 '25 04:01 gurvinder-dhillon

I got same. in 0.0.17 and 0.0.19

onuratakan avatar Jan 21 '25 09:01 onuratakan

Seeing the same, v0.0.20 for what I would expect to be a simple straightforward use case.

some_agent = Agent(  
    'openai:gpt-4o',  
    deps_type=InputType,
    result_type=ActionsDict,  
    system_prompt=prompt,
    tools=[] 
)

and variants (without deps_type, without tools, simplified ActionsDict pydantic, etc).

silkspace avatar Jan 28 '25 22:01 silkspace

Same here for v0.0.20

def change_language(conversation: ConversationParts) -> LanguageChangeResponse:
    change_language_agent = Agent(
        'openai:gpt-4o',
        result_type=Language,
        system_prompt=CHANGE_LANGUAGE_PROMPT,
    )

    return change_language_agent.run_sync(str(conversation)).data

Very strange, it is not happening in other agents that I have in the same project, just in this one

guillermo-rauda avatar Jan 29 '25 07:01 guillermo-rauda

Adding parallel_tool_calls=False in the model_settings in the Agent definition on v0.0.20 seems to fix the problem. model_settings=dict(parallel_tool_calls=False)

walter-bd avatar Jan 29 '25 15:01 walter-bd

Maybe just one more data point, I also encounter this primarily when running Agent.run_sync with nested result_type models, only when using OpenAI LLMs (not when using Anthropic LLMs). model_settings=dict(parallel_tool_calls=False) does indeed help.

maxschulz-COL avatar Feb 12 '25 12:02 maxschulz-COL

Same, it feels like nested result_type calls with OpenAI LLMs are more likely to cause issues.

Acture avatar Feb 13 '25 12:02 Acture

I am using version 0.0.24 and noticed this error as well.

In my case, it was caused by the agent calling a tool that was not defined. I forgot the tool name in the prompt but didn't provide the tool to the agent.

The minimal repro looks like this:

    def calculate(text: str) -> str:
        return f"Calculated: {text}"

    model = OpenAIModel("gpt-4o-mini", openai_client=client)
    agent = Agent(
        model,
        tools=[calculate],
        system_prompt="Check the user question, if the user is asking for help you MUST call the tool `validate_query`",
    )
    result = await agent.run("How you can help me?")
    assert result is not None

There must be a tool assigned to the agent otherwise, the agent will not try to call it.

pvasek avatar Feb 20 '25 15:02 pvasek

Another reason is that when the tool call is valid in the model's view, but invalid in pydantic_ai's view, pydantic_ai will append a plain text error message, not a tool result message.

e.g. https://github.com/pydantic/pydantic-ai/blob/dfc919c686212b92e45069a926e6cd37be74ca3f/pydantic_ai_slim/pydantic_ai/_agent_graph.py#L484

BeautyyuYanli avatar Feb 21 '25 04:02 BeautyyuYanli

Seeing the same error. Could it be because of parallel tool calls as shown here https://github.com/langchain-ai/langchain/discussions/23733

xtfocus avatar Feb 24 '25 04:02 xtfocus

Can someone in this long issue give me an MRE that I reproduce?

Kludex avatar Mar 28 '25 09:03 Kludex