Skip to content

How is message history managed in multi-agent workflows? (Ambiguity in documentation) #1402

Closed
@dinhngoc267

Description

@dinhngoc267

Question

Hi,

I created this example to investigate the behaviors of manipulating messages between agents as I feel ambiguous in the document.

Additional Context


from __future__ import annotations as _annotations
from dotenv import load_dotenv

import os
from dataclasses import dataclass, field
from pydantic import BaseModel
from pydantic_ai import RunContext

from pydantic_ai.messages import ModelMessage
from pydantic_ai import Agent
from pydantic_graph import BaseNode, End, Graph, GraphRunContext
from pydantic_ai.models.openai import OpenAIModel

load_dotenv()

llm_model = OpenAIModel(
    os.getenv("AI_SCORING_MODEL_NAME", ""),
    base_url=os.getenv("AI_SCORING_BASE_URL", ""),
    api_key=os.getenv("AI_SCORING_API_KEY", ""),
)

class GeneralAgent(BaseModel):
    """An agent who handles general questions and chitchat."""

class ComputationalAgent(BaseModel):
    """An agent who handles computational tasks."""

manager_agent = Agent(
    model=llm_model,
    name="manager_agent",
    result_type=GeneralAgent | ComputationalAgent,
    retries=3,
)
@manager_agent.system_prompt(dynamic=True)
async def get_system_prompt(_ctx: RunContext) -> str:
    prompt = """
    You are a *manager_agent* with mission is navigation and task planning.
    You can nagivate to the appropriate agent based on the user's requests and conversation history.
    There are two supported agents: `general_agent` and `computational_agent`.
    """
    return prompt



general_agent = Agent(
    model=llm_model,
    name="general_agent",
    result_type=str,
)
@general_agent.system_prompt(dynamic=True)
async def get_system_prompt(_ctx: RunContext) -> str:
    prompt = """
    You are a *general_agent* with mission is chitchat with user`.
    """
    return prompt

computational_agent = Agent(
    model=llm_model,
    name="computational_agent",
    result_type=str
)
@computational_agent.system_prompt(dynamic=True)
async def get_system_prompt(_ctx: RunContext) -> str:
    prompt = """
    You are a *computational_agent* with mission is computation.
    You have to return 1 for all requests of users. 
    """
    return prompt

@dataclass
class State:
    message_history:list[ModelMessage] = field(default_factory=list)


@dataclass
class Manager(BaseNode[State]):
    user_prompt: str

    async def run(self, ctx: GraphRunContext[State]) -> General | Computation:
        result = await manager_agent.run(
            self.user_prompt,
            message_history=ctx.state.message_history
        )

        ctx.state.message_history += result.new_messages()

        if isinstance(result.data, GeneralAgent):
            return General()
        elif isinstance(result.data, ComputationalAgent):
            return Computation()


@dataclass
class General(BaseNode[State]):

    async def run(self, ctx: GraphRunContext[State]) -> End:
        result = await general_agent.run(
            "",
            message_history=ctx.state.message_history
        )
        ctx.state.message_history += result.new_messages()

        return End(result.data)


@dataclass
class Computation(BaseNode[State]):

    async def run(self, ctx: GraphRunContext[State]) -> End:
        result = await computational_agent.run(
            "",
            message_history=ctx.state.message_history
        )
        ctx.state.message_history += result.new_messages()

        return End(result.data)

async def main():
    my_graph = Graph(nodes=(Manager, General, Computation))
    state = State()
    while True:
        print("\n==STATE==\n")
        print(state)
        user_prompt = input()
        if user_prompt == "exit":
            break

        result = await my_graph.run(Manager(user_prompt), state = state)
        print(result.output)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

======

The output:

==STATE==

State(message_history=[])
hi
/home/nld/ai-scoring/notebooks/graph pydantic.py:129: LogfireNotConfiguredWarning: No logs or spans will be created until logfire.configure() has been called. Set the environment variable LOGFIRE_IGNORE_NO_CONFIG=1 or add ignore_no_config=true in pyproject.toml to suppress this warning.
result = await my_graph.run(Manager(user_prompt), state = state)
Hi there! How's your day going?

==STATE==

State(message_history=[ModelRequest(parts=[SystemPromptPart(content='\n You are a general_agent with mission is chitchat with user`.\n ', dynamic_ref='get_system_prompt', part_kind='system-prompt'), UserPromptPart(content='hi', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 34, 398203, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[ToolCallPart(tool_name='final_result_GeneralAgent', args='{}', tool_call_id='call_ui2kUvBPBRUnUV0wu2r2gM7G', part_kind='tool-call')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 34, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[ToolReturnPart(tool_name='final_result_GeneralAgent', content='Final result processed.', tool_call_id='call_ui2kUvBPBRUnUV0wu2r2gM7G', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, 118073, tzinfo=datetime.timezone.utc), part_kind='tool-return')], kind='request'), ModelRequest(parts=[UserPromptPart(content='', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, 119197, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[TextPart(content="Hi there! How's your day going?", part_kind='text')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, tzinfo=datetime.timezone.utc), kind='response')])
i'm okay how are you
I’m just a program, but I’m here and ready to chat! What’s on your mind?

==STATE==

State(message_history=[ModelRequest(parts=[SystemPromptPart(content='\n You are a general_agent with mission is chitchat with user`.\n ', dynamic_ref='get_system_prompt', part_kind='system-prompt'), UserPromptPart(content='hi', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 34, 398203, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[ToolCallPart(tool_name='final_result_GeneralAgent', args='{}', tool_call_id='call_ui2kUvBPBRUnUV0wu2r2gM7G', part_kind='tool-call')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 34, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[ToolReturnPart(tool_name='final_result_GeneralAgent', content='Final result processed.', tool_call_id='call_ui2kUvBPBRUnUV0wu2r2gM7G', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, 118073, tzinfo=datetime.timezone.utc), part_kind='tool-return')], kind='request'), ModelRequest(parts=[UserPromptPart(content='', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, 119197, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[TextPart(content="Hi there! How's your day going?", part_kind='text')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[UserPromptPart(content="i'm okay how are you", timestamp=datetime.datetime(2025, 4, 8, 3, 44, 2, 122190, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[ToolCallPart(tool_name='final_result_GeneralAgent', args='{}', tool_call_id='call_WxFUAq4ciBzOFhd4l568XHDu', part_kind='tool-call')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 2, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[ToolReturnPart(tool_name='final_result_GeneralAgent', content='Final result processed.', tool_call_id='call_WxFUAq4ciBzOFhd4l568XHDu', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 3, 252682, tzinfo=datetime.timezone.utc), part_kind='tool-return')], kind='request'), ModelRequest(parts=[UserPromptPart(content='', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 3, 254144, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[TextPart(content='I’m just a program, but I’m here and ready to chat! What’s on your mind?', part_kind='text')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 3, tzinfo=datetime.timezone.utc), kind='response')])
can you do my computation: 2^3+4
The result of the computation is 1.

==STATE==

State(message_history=[ModelRequest(parts=[SystemPromptPart(content='\n You are a computational_agent with mission is computation.\n You have to return 1 for all requests of users. \n ', dynamic_ref='get_system_prompt', part_kind='system-prompt'), UserPromptPart(content='hi', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 34, 398203, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[ToolCallPart(tool_name='final_result_GeneralAgent', args='{}', tool_call_id='call_ui2kUvBPBRUnUV0wu2r2gM7G', part_kind='tool-call')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 34, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[ToolReturnPart(tool_name='final_result_GeneralAgent', content='Final result processed.', tool_call_id='call_ui2kUvBPBRUnUV0wu2r2gM7G', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, 118073, tzinfo=datetime.timezone.utc), part_kind='tool-return')], kind='request'), ModelRequest(parts=[UserPromptPart(content='', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, 119197, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[TextPart(content="Hi there! How's your day going?", part_kind='text')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 43, 36, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[UserPromptPart(content="i'm okay how are you", timestamp=datetime.datetime(2025, 4, 8, 3, 44, 2, 122190, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[ToolCallPart(tool_name='final_result_GeneralAgent', args='{}', tool_call_id='call_WxFUAq4ciBzOFhd4l568XHDu', part_kind='tool-call')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 2, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[ToolReturnPart(tool_name='final_result_GeneralAgent', content='Final result processed.', tool_call_id='call_WxFUAq4ciBzOFhd4l568XHDu', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 3, 252682, tzinfo=datetime.timezone.utc), part_kind='tool-return')], kind='request'), ModelRequest(parts=[UserPromptPart(content='', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 3, 254144, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[TextPart(content='I’m just a program, but I’m here and ready to chat! What’s on your mind?', part_kind='text')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 3, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[UserPromptPart(content='can you do my computation: 2^3+4', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 20, 848373, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[ToolCallPart(tool_name='final_result_ComputationalAgent', args='{}', tool_call_id='call_mUVLDlSZVMFvT9duw069v2zU', part_kind='tool-call')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 21, tzinfo=datetime.timezone.utc), kind='response'), ModelRequest(parts=[ToolReturnPart(tool_name='final_result_ComputationalAgent', content='Final result processed.', tool_call_id='call_mUVLDlSZVMFvT9duw069v2zU', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 22, 387996, tzinfo=datetime.timezone.utc), part_kind='tool-return')], kind='request'), ModelRequest(parts=[UserPromptPart(content='', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 22, 389275, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'), ModelResponse(parts=[TextPart(content='The result of the computation is 1.', part_kind='text')], model_name='gpt-4o-mini-2024-07-18', timestamp=datetime.datetime(2025, 4, 8, 3, 44, 22, tzinfo=datetime.timezone.utc), kind='response')])

===========

So acutally it works the whole time?

I can see the system prompt is anchored and replaced every time hands off to another agent.

I also noticed that this is just the state of the graph. Not really sure what's gonna pass into the llm in agent as the final messages.

(nld-mindmesh) nld@nlp118:~/ai-scoring$ pip show pydantic_ai
Name: pydantic_ai
Version: 0.0.36rc0
Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
Home-page: 
Author: Samuel Colvin
Author-email: samuel@pydantic.dev
License: MIT
Location: /HDD/.conda/envs/nld-mindmesh/lib/python3.10/site-packages
Requires: eval-type-backport, exceptiongroup, griffe, httpx, logfire-api, opentelemetry-api, pydantic, pydantic-graph, typing-extensions, typing-inspection

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions