Messages
Messages are the fundamental communication units that enable agents, models, and tools to exchange information in a structured format.
Overview
Messages in MARSYS:
- Follow OpenAI Format: Compatible with standard LLM APIs
- Support Multi-Agent: Extended for agent-to-agent communication
- Handle Tools: Built-in support for function calling
- Track Context: Maintain conversation history and metadata
- Enable Traceability: Unique IDs for debugging and tracking
Message Structure
Core Message Class
from dataclasses import dataclass, fieldfrom typing import Optional, Union, Dict, List, Anyfrom datetime import datetimeimport uuid@dataclassclass Message:# Core fieldsrole: str # Message role/typecontent: Optional[Union[str, Dict, List]] # Main contentmessage_id: str = field(default_factory=lambda: str(uuid.uuid4()))# Extended fieldsname: Optional[str] = None # Tool/agent nametool_calls: Optional[List[ToolCallMsg]] = None # Tool invocationstool_call_id: Optional[str] = None # For tool responsesagent_calls: Optional[List[AgentCallMsg]] = None # Agent invocations# Additional datastructured_data: Optional[Dict] = None # Structured responsesimages: Optional[List[str]] = None # For vision models# Methodsdef to_llm_dict(self) -> Dict[str, Any]
Message Roles
| Role | Description | Usage |
|---|---|---|
system | System instructions | Initial agent configuration |
user | User input | Human queries, requests |
assistant | AI response | Model/agent responses |
tool | Tool result | Function execution results |
agent_call | Agent invocation | One agent calling another |
agent_response | Agent reply | Response from invoked agent |
error | Error message | Failures and exceptions |
Creating Messages
Basic Messages
from marsys.agents.memory import Message# User messageuser_msg = Message(role="user",content="Analyze the quarterly sales data")# System messagesystem_msg = Message(role="system",content="You are a data analyst specializing in sales trends.")# Assistant responseassistant_msg = Message(role="assistant",content="I'll analyze the quarterly sales data for you.",name="DataAnalyst")# Error messageerror_msg = Message(role="error",content="Failed to connect to database",structured_data={"error_type": "ConnectionError","retry_count": 3})
Tool Messages
# Tool call messagetool_call_msg = Message(role="assistant",content="", # Can be empty when calling toolstool_calls=[ToolCallMsg(id="call_abc123",call_id="call_abc123",type="function",name="analyze_data",arguments='{"dataset": "sales_q4", "metrics": ["revenue", "growth"]}')])# Tool responsetool_response = Message(role="tool",content='{"revenue": 1500000, "growth": "15%", "trend": "positive"}',name="analyze_data",tool_call_id="call_abc123")
Multi-Agent Messages
# Agent A calling Agent Bagent_call = Message(role="agent_call",content="Research the latest AI trends for 2025",name="Researcher",agent_calls=[AgentCallMsg(agent_name="Researcher",request="Research the latest AI trends for 2025")])# Agent B's responseagent_response = Message(role="agent_response",content="Here are the top AI trends for 2025:\n1. Multimodal AI...",name="Researcher",structured_data={"trends": [{"name": "Multimodal AI", "impact": "high"},{"name": "Edge AI", "impact": "medium"}],"sources": ["arxiv.org", "nature.com"]})
Multimodal Messages
MARSYS supports multimodal messages with images for vision models like GPT-4V, Gemini, and Claude.
User Messages with Images
# Message with images (for vision models)vision_msg = Message(role="user",content="What objects are in these images?",images=["base64_encoded_image_1","https://example.com/image.jpg","/path/to/local/image.png"])# Mixed content messagemixed_msg = Message(role="user",content=[{"type": "text", "text": "Analyze this chart:"},{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}])
Tool Results with Images
Tools can return images alongside text results. The framework automatically handles image injection into agent memory:
# Tool that extracts images from a PDFdef extract_pdf_content(pdf_path: str) -> Dict[str, Any]:"""Extract text and images from a PDF file.Returns:Dictionary with 'result' (text) and 'images' (list of paths)"""text = extract_text(pdf_path)image_paths = extract_images_to_temp(pdf_path)return {"result": f"Extracted {len(image_paths)} pages from PDF","images": image_paths # List of file paths}# The tool result message created automatically:tool_result_msg = Message(role="tool",content="Extracted 3 pages from PDF",name="extract_pdf_content",tool_call_id="call_123",images=["/tmp/pdf_page_1.png","/tmp/pdf_page_2.png","/tmp/pdf_page_3.png"])
Task Descriptions with Images
You can include images directly in task descriptions:
# Task with imagestask = {"content": "What is shown in these screenshots? Provide a detailed analysis.","images": ["/path/to/screenshot1.png","/path/to/screenshot2.png"]}# Images are automatically added to the first agent's memoryresult = await Orchestra.run(task=task, topology=topology)
Message Conversion
To LLM Format
# Convert for OpenAI-compatible APIsmessage = Message(role="assistant",content="I found the information you requested.",tool_calls=[...])llm_dict = message.to_llm_dict()# Result:{"role": "assistant","content": "I found the information you requested.","tool_calls": [...]}# Special conversionsagent_call_msg = Message(role="agent_call", content="Task", name="Agent2")llm_dict = agent_call_msg.to_llm_dict()# Converts to user message for LLM:{"role": "user","content": "[Request from Agent1]: Task"}
Message Patterns
Conversation Pattern
# Standard conversation flowconversation = [Message(role="system", content="You are a helpful assistant."),Message(role="user", content="What's the capital of France?"),Message(role="assistant", content="The capital of France is Paris."),Message(role="user", content="What about Germany?"),Message(role="assistant", content="The capital of Germany is Berlin.")]# Convert for LLMllm_messages = [msg.to_llm_dict() for msg in conversation]
Tool Usage Pattern
# Complete tool usage flowtool_flow = [# 1. User requestMessage(role="user", content="What's the weather in Tokyo?"),# 2. Assistant decides to use toolMessage(role="assistant",content="I'll check the weather in Tokyo for you.",tool_calls=[ToolCallMsg(id="call_weather_1",call_id="call_weather_1",type="function",name="get_weather",arguments='{"city": "Tokyo", "units": "celsius"}')]),# 3. Tool returns resultMessage(role="tool",content='{"temp": 22, "conditions": "sunny", "humidity": 65}',name="get_weather",tool_call_id="call_weather_1"),# 4. Assistant incorporates resultMessage(role="assistant",content="The weather in Tokyo is currently 22°C and sunny with 65% humidity.")]
Error Handling Pattern
def handle_with_error_message(func):"""Decorator to convert exceptions to Messages."""async def wrapper(*args, **kwargs):try:return await func(*args, **kwargs)except Exception as e:return Message(role="error",content=str(e),structured_data={"error_type": type(e).__name__,"traceback": traceback.format_exc(),"function": func.__name__,"timestamp": datetime.now().isoformat()})return wrapper@handle_with_error_messageasync def risky_operation():# Operation that might failpass
Multi-Agent Communication Pattern
# Agent coordination flowcoordination_flow = [# 1. Coordinator assigns taskMessage(role="agent_call",content="Analyze market data for Q4",name="DataAnalyst",agent_calls=[AgentCallMsg(agent_name="DataAnalyst",request="Analyze market data for Q4")]),# 2. DataAnalyst processesMessage(role="agent_response",content="Analysis complete. Key findings:",name="DataAnalyst",structured_data={"revenue": 2500000,"growth": "18%","top_products": ["A", "B", "C"]}),# 3. Coordinator requests visualizationMessage(role="agent_call",content="Create charts for this data",name="Visualizer",structured_data={"data": {...}}),# 4. Visualizer respondsMessage(role="agent_response",content="Charts created successfully",name="Visualizer",images=["chart1.png", "chart2.png"])]
Best Practices
1. Preserve Message IDs
# ✅ GOOD - Maintain IDs for trackingoriginal_msg = Message(role="user", content="Hello")print(f"Tracking ID: {original_msg.message_id}")# When forwarding or referencingresponse = Message(role="assistant",content="Hello! How can I help?",structured_data={"in_reply_to": original_msg.message_id})# ❌ BAD - Creating new ID for same messageforwarded = Message(role=original_msg.role,content=original_msg.content# Lost original message_id!)
2. Use Appropriate Roles
# ✅ GOOD - Correct role usagetool_result = Message(role="tool", # Correct role for tool resultscontent=json.dumps(result),name="calculator",tool_call_id="call_123")# ❌ BAD - Wrong roletool_result = Message(role="assistant", # Wrong! Tools aren't assistantscontent=str(result))
3. Structure Tool Responses
# ✅ GOOD - Structured tool responsetool_response = Message(role="tool",content=json.dumps({"success": True,"result": calculation_result,"metadata": {"precision": "high", "method": "numpy"}}),name="advanced_calculator",tool_call_id=call_id)# ❌ BAD - Unstructured responsetool_response = Message(role="tool",content=f"The answer is {result}" # Not JSON!)
4. Handle Errors Gracefully
# ✅ GOOD - Error as messagetry:result = await process_data()except DataError as e:return Message(role="error",content=f"Data processing failed: {e}",structured_data={"error_code": "DATA_001","recoverable": True,"suggestion": "Check data format"})# ❌ BAD - Raw exceptiontry:result = await process_data()except DataError as e:raise # Don't propagate raw exceptions
Advanced Patterns
Message Validation
from typing import Setfrom pydantic import BaseModel, validatorclass ValidatedMessage(BaseModel):role: strcontent: Optional[str] = Nonetool_calls: Optional[List[Dict]] = None@validator('role')def validate_role(cls, v):valid_roles = {'system', 'user', 'assistant', 'tool', 'error'}if v not in valid_roles:raise ValueError(f"Invalid role: {v}")return v@validator('tool_calls')def validate_tool_calls(cls, v):if v:for call in v:if 'id' not in call or 'function' not in call:raise ValueError("Invalid tool call format")return v
Message Filtering
class MessageFilter:@staticmethoddef by_role(messages: List[Message], role: str) -> List[Message]:"""Filter messages by role."""return [m for m in messages if m.role == role]@staticmethoddef by_agent(messages: List[Message], agent_name: str) -> List[Message]:"""Filter messages by agent name."""return [m for m in messages if m.name == agent_name]@staticmethoddef with_tools(messages: List[Message]) -> List[Message]:"""Get messages with tool calls."""return [m for m in messages if m.tool_calls]
Message Chaining
class MessageChain:"""Track related messages in a conversation chain."""def __init__(self, initial: Message):self.chain_id = str(uuid.uuid4())self.messages: List[Message] = [initial]initial.structured_data = initial.structured_data or {}initial.structured_data["chain_id"] = self.chain_iddef add(self, message: Message) -> None:"""Add message to chain."""message.structured_data = message.structured_data or {}message.structured_data["chain_id"] = self.chain_idmessage.structured_data["chain_position"] = len(self.messages)self.messages.append(message)def get_context(self, max_messages: int = 10) -> List[Message]:"""Get recent context from chain."""return self.messages[-max_messages:]def summarize(self) -> Dict[str, Any]:"""Get chain summary."""return {"chain_id": self.chain_id,"message_count": len(self.messages),"roles": list(set(m.role for m in self.messages)),"has_errors": any(m.role == "error" for m in self.messages),"tool_calls": sum(1 for m in self.messages if m.tool_calls)}
Next Steps
Now that you understand messages, explore related topics:
- Memory - How messages are stored and managed
- Agents - How agents create and process messages
- Communication - Multi-agent message patterns
Message System Ready!
You now understand the message system in MARSYS. Messages are the lingua franca that enables all components to communicate effectively.