# Messages & Types **Part 1: Foundation - Lesson 2** > Structuring conversation data for AI agents ## Overview In Lesson 1, you learned about Runnables - the foundation of composability. Now we'll explore how to structure the *data* that flows through these Runnables, specifically for conversational AI systems. Messages are the lingua franca of AI agents. They provide a standardized way to represent conversations, tool calls, system instructions, and more. By the end of this lesson, you'll understand why proper message typing is crucial for building reliable agents. You will see tools in use. Everything about tools will be explained in depth in the 3. main part of this tutorial. ## Why Does This Matter? ### The Problem: Unstructured Conversations Imagine building a chatbot without message types: ```javascript // Bad: Everything is just strings const conversation = [ "You are a helpful assistant", "What's the weather?", "The weather is sunny", "Thanks!" ]; // Questions: // - Which messages are from the user? // - Which are from the AI? // - Which is the system instruction? // - How do we handle tool calls? // - What about metadata like timestamps? ``` This quickly becomes unmaintainable. You can't tell who said what, when, or why. ### The Solution: Typed Messages ```javascript // Good: Structured message types const conversation = [ new SystemMessage("You are a helpful assistant"), new HumanMessage("What's the weather?"), new AIMessage("The weather is sunny"), new HumanMessage("Thanks!") ]; // Now we can: // - Filter by message type // - Format differently for display // - Track metadata automatically // - Handle tool calls properly // - Validate conversation structure ``` ## Learning Objectives By the end of this lesson, you will: - ✅ Understand the four core message types - ✅ Implement a robust message class hierarchy - ✅ Add metadata and timestamps automatically - ✅ Format messages for LLM consumption - ✅ Build a conversation history manager - ✅ Handle special message types (tool calls, function results) ## Core Message Types Every conversational AI system needs these four fundamental message types: ### 1. SystemMessage **Purpose**: Instructions that shape the AI's behavior **Characteristics**: - Always at the start of conversations - Not visible to end users - Defines the AI's role, personality, constraints - Typically set by developers, not users **Example**: ```javascript new SystemMessage( `You are a helpful Python programming tutor. Explain concepts clearly with code examples. Always encourage learning.` ) ``` **When to use**: - Setting AI personality - Defining response format - Adding constraints or rules - Providing context ### 2. HumanMessage **Purpose**: Input from the user/human **Characteristics**: - User questions, commands, or statements - What the AI should respond to - Can contain multiple paragraphs - May include context or files **Example**: ```javascript new HumanMessage( "How do I reverse a string in Python?" ) ``` **When to use**: - User input in chatbots - Queries to agents - Commands to execute ### 3. AIMessage **Purpose**: Responses from the AI/assistant **Characteristics**: - The AI's text responses - Can include reasoning, answers, questions - May contain tool calls (function requests) - Generated by the LLM **Example**: ```javascript new AIMessage( "Here's how to reverse a string in Python:\n\n" + "```python\ntext = 'hello'\nreversed_text = text[::-1]\n```" ) ``` **When to use**: - LLM responses - Agent outputs - Generated text ### 4. ToolMessage **Purpose**: Results from tool/function execution **Characteristics**: - Returns data from external functions - Links back to the AI's tool call - Often structured data (JSON) - Input for AI's next response **Example**: ```javascript new ToolMessage( JSON.stringify({ temperature: 72, condition: "sunny" }), "get_weather" // tool name ) ``` **When to use**: - Returning function results to the AI - Providing external data - Completing tool calls ## Message Flow in Conversations Here's how messages typically flow in an agent conversation: ``` 1. System → "You are a helpful assistant with access to a calculator" 2. Human → "What's 123 * 456?" 3. AI → [Calls calculator tool with 123, 456] 4. Tool → [Returns 56088] 5. AI → "The result of 123 * 456 is 56,088" 6. Human → "Thanks!" 7. AI → "You're welcome!" ``` ## Implementation Let's build our message system from the ground up. ### Step 1: Base Message Class Every message type will inherit from this base: **Location:** `src/core/message.js` ```javascript /** * BaseMessage - Foundation for all message types * * Contains common functionality: * - Content storage * - Metadata tracking * - Timestamps * - Serialization */ export class BaseMessage { constructor(content, additionalKwargs = {}) { this.content = content; this.additionalKwargs = additionalKwargs; this.timestamp = Date.now(); this.id = this.generateId(); } /** * Generate unique ID for this message */ generateId() { return `msg_${this.timestamp}_${Math.random().toString(36).substr(2, 9)}`; } /** * Get the message type (overridden in subclasses) */ get type() { throw new Error('Subclass must implement type getter'); } /** * Convert to JSON for storage/transmission */ toJSON() { return { id: this.id, type: this.type, content: this.content, timestamp: this.timestamp, ...this.additionalKwargs }; } /** * Create message from JSON */ static fromJSON(json) { const MessageClass = MESSAGE_TYPES[json.type]; if (!MessageClass) { throw new Error(`Unknown message type: ${json.type}`); } const message = new MessageClass(json.content, json.additionalKwargs); message.id = json.id; message.timestamp = json.timestamp; return message; } /** * Format for display */ toString() { const date = new Date(this.timestamp).toLocaleTimeString(); return `[${date}] ${this.type}: ${this.content}`; } } ``` **Key design decisions**: - ✅ `content` is always a string (or can be converted to one) - ✅ `additionalKwargs` allows extension without changing the API - ✅ `timestamp` is added automatically for tracking - ✅ `id` ensures we can reference specific messages - ✅ `toJSON()` / `fromJSON()` enable persistence ### Step 2: System Message **Location:** `src/core/message.js` ```javascript /** * SystemMessage - Instructions for the AI * * Sets the context, role, and constraints for the assistant. * Typically appears at the start of conversations. */ export class SystemMessage extends BaseMessage { constructor(content, additionalKwargs = {}) { super(content, additionalKwargs); } get type() { return 'system'; } /** * System messages often need special formatting */ toPromptFormat() { return { role: 'system', content: this.content }; } } ``` **Usage**: ```javascript const systemMsg = new SystemMessage( "You are a expert Python programmer. Be concise." ); console.log(systemMsg.type); // "system" console.log(systemMsg.content); // "You are a expert..." ``` ### Step 3: Human Message **Location:** `src/core/message.js` ```javascript /** * HumanMessage - User input * * Represents messages from the human/user. * The primary input the AI responds to. */ export class HumanMessage extends BaseMessage { constructor(content, additionalKwargs = {}) { super(content, additionalKwargs); } get type() { return 'human'; } toPromptFormat() { return { role: 'user', content: this.content }; } } ``` **Usage**: ```javascript const humanMsg = new HumanMessage("What's the capital of France?"); console.log(humanMsg.type); // "human" ``` ### Step 4: AI Message (with Tool Calls) **Location:** `src/core/message.js` ```javascript /** * AIMessage - Assistant responses * * Represents messages from the AI assistant. * Can include tool calls for function execution. */ export class AIMessage extends BaseMessage { constructor(content, additionalKwargs = {}) { super(content, additionalKwargs); // Tool calls are requests to execute functions this.toolCalls = additionalKwargs.toolCalls || []; } get type() { return 'ai'; } /** * Check if this message requests tool execution */ hasToolCalls() { return this.toolCalls.length > 0; } /** * Get specific tool call by index */ getToolCall(index = 0) { return this.toolCalls[index]; } toPromptFormat() { const formatted = { role: 'assistant', content: this.content }; if (this.hasToolCalls()) { formatted.tool_calls = this.toolCalls; } return formatted; } } ``` **Usage**: ```javascript // Simple response const aiMsg1 = new AIMessage("The capital of France is Paris."); // Response with tool call const aiMsg2 = new AIMessage("Let me calculate that for you.", { toolCalls: [{ id: 'call_123', type: 'function', function: { name: 'calculator', arguments: JSON.stringify({ operation: 'multiply', a: 5, b: 3 }) } }] }); console.log(aiMsg2.hasToolCalls()); // true ``` ### Step 5: Tool Message **Location:** `src/core/message.js` ```javascript /** * ToolMessage - Tool execution results * * Contains the output from executing a tool/function. * Sent back to the AI to inform its next response. */ export class ToolMessage extends BaseMessage { constructor(content, toolCallId, additionalKwargs = {}) { super(content, additionalKwargs); this.toolCallId = toolCallId; } get type() { return 'tool'; } toPromptFormat() { return { role: 'tool', content: this.content, tool_call_id: this.toolCallId }; } } ``` **Usage**: ```javascript const toolMsg = new ToolMessage( JSON.stringify({ result: 15 }), 'call_123' // Links back to the AI's tool call ); console.log(toolMsg.type); // "tool" console.log(toolMsg.toolCallId); // "call_123" ``` ### Step 6: Message Type Registry To support `fromJSON()`, we need a registry: ```javascript /** * Registry mapping type strings to message classes */ export const MESSAGE_TYPES = { 'system': SystemMessage, 'human': HumanMessage, 'ai': AIMessage, 'tool': ToolMessage }; ``` ## Complete Implementation Here's everything together: ```javascript /** * Message System - Typed conversation data structures * * @module core/message */ /** * BaseMessage - Foundation for all message types */ export class BaseMessage { constructor(content, additionalKwargs = {}) { if (content === undefined || content === null) { throw new Error('Message content cannot be undefined or null'); } this.content = String(content); // Ensure string this.additionalKwargs = additionalKwargs; this.timestamp = Date.now(); this.id = this.generateId(); } generateId() { return `msg_${this.timestamp}_${Math.random().toString(36).substr(2, 9)}`; } get type() { throw new Error('Subclass must implement type getter'); } toJSON() { return { id: this.id, type: this.type, content: this.content, timestamp: this.timestamp, ...this.additionalKwargs }; } static fromJSON(json) { const MessageClass = MESSAGE_TYPES[json.type]; if (!MessageClass) { throw new Error(`Unknown message type: ${json.type}`); } const message = new MessageClass(json.content, json.additionalKwargs); message.id = json.id; message.timestamp = json.timestamp; return message; } toString() { const date = new Date(this.timestamp).toLocaleTimeString(); return `[${date}] ${this.type.toUpperCase()}: ${this.content}`; } /** * Format for LLM consumption */ toPromptFormat() { throw new Error('Subclass must implement toPromptFormat()'); } } /** * SystemMessage - AI instructions and context */ export class SystemMessage extends BaseMessage { get type() { return 'system'; } toPromptFormat() { return { role: 'system', content: this.content }; } } /** * HumanMessage - User input */ export class HumanMessage extends BaseMessage { get type() { return 'human'; } toPromptFormat() { return { role: 'user', content: this.content }; } } /** * AIMessage - Assistant responses */ export class AIMessage extends BaseMessage { constructor(content, additionalKwargs = {}) { super(content, additionalKwargs); this.toolCalls = additionalKwargs.toolCalls || []; } get type() { return 'ai'; } hasToolCalls() { return this.toolCalls.length > 0; } getToolCall(index = 0) { return this.toolCalls[index]; } toPromptFormat() { const formatted = { role: 'assistant', content: this.content }; if (this.hasToolCalls()) { formatted.tool_calls = this.toolCalls; } return formatted; } } /** * ToolMessage - Function execution results */ export class ToolMessage extends BaseMessage { constructor(content, toolCallId, additionalKwargs = {}) { super(content, additionalKwargs); this.toolCallId = toolCallId; } get type() { return 'tool'; } toPromptFormat() { return { role: 'tool', content: this.content, tool_call_id: this.toolCallId }; } } /** * Message type registry */ export const MESSAGE_TYPES = { 'system': SystemMessage, 'human': HumanMessage, 'ai': AIMessage, 'tool': ToolMessage }; export default { BaseMessage, SystemMessage, HumanMessage, AIMessage, ToolMessage, MESSAGE_TYPES }; ``` ## Real-World Examples ### Example 1: Simple Conversation ```javascript const conversation = [ new SystemMessage("You are a helpful math tutor."), new HumanMessage("What's 5 + 3?"), new AIMessage("5 + 3 equals 8."), new HumanMessage("Thanks!"), new AIMessage("You're welcome!") ]; // Display conversation conversation.forEach(msg => { console.log(msg.toString()); }); // Output: // [10:30:45] SYSTEM: You are a helpful math tutor. // [10:30:46] HUMAN: What's 5 + 3? // [10:30:47] AI: 5 + 3 equals 8. // [10:30:48] HUMAN: Thanks! // [10:30:49] AI: You're welcome! ``` ### Example 2: Tool Call Flow ```javascript // User asks a question requiring calculation const messages = [ new SystemMessage("You are an assistant with access to a calculator."), new HumanMessage("What's 1234 * 5678?") ]; // AI decides to use calculator const aiWithToolCall = new AIMessage( "I'll calculate that for you.", { toolCalls: [{ id: 'call_abc123', type: 'function', function: { name: 'calculator', arguments: JSON.stringify({ operation: 'multiply', a: 1234, b: 5678 }) } }] } ); messages.push(aiWithToolCall); // Tool executes and returns result const toolResult = new ToolMessage( JSON.stringify({ result: 7006652 }), 'call_abc123' ); messages.push(toolResult); // AI incorporates result in final response const finalResponse = new AIMessage( "The result of 1234 × 5678 is 7,006,652." ); messages.push(finalResponse); console.log('Has tool calls?', aiWithToolCall.hasToolCalls()); // true console.log('Tool call:', aiWithToolCall.getToolCall(0)); ``` ### Example 3: Conversation Persistence ```javascript // Save conversation to JSON const conversation = [ new SystemMessage("You are helpful."), new HumanMessage("Hello!"), new AIMessage("Hi there!") ]; const json = conversation.map(msg => msg.toJSON()); const saved = JSON.stringify(json, null, 2); console.log('Saved:', saved); // Later: Load conversation from JSON const loaded = JSON.parse(saved); const restored = loaded.map(msgData => BaseMessage.fromJSON(msgData)); console.log('Restored:', restored.length, 'messages'); restored.forEach(msg => console.log(msg.toString())); ``` ### Example 4: Filtering Messages ```javascript const history = [ new SystemMessage("You are helpful."), new HumanMessage("Hi"), new AIMessage("Hello!"), new HumanMessage("How are you?"), new AIMessage("I'm doing well!") ]; // Get only human messages const humanMessages = history.filter(msg => msg.type === 'human'); console.log('Human said:', humanMessages.map(m => m.content)); // Get only AI messages const aiMessages = history.filter(msg => msg.type === 'ai'); console.log('AI said:', aiMessages.map(m => m.content)); // Get last N messages (sliding window) const lastThree = history.slice(-3); console.log('Recent:', lastThree.map(m => m.toString())); ``` ### Example 5: Custom Metadata ```javascript // Add custom metadata to messages const userMsg = new HumanMessage("Hello!", { userId: 'user_123', sessionId: 'sess_456', language: 'en' }); const aiMsg = new AIMessage("Hi there!", { model: 'llama-3.1', temperature: 0.7, tokens: 150 }); console.log('User metadata:', userMsg.additionalKwargs); console.log('AI metadata:', aiMsg.additionalKwargs); // Metadata is preserved in JSON const json = userMsg.toJSON(); console.log(json.userId); // 'user_123' ``` ## Advanced Patterns ### Pattern 1: Message Builder For complex message construction: ```javascript class MessageBuilder { constructor() { this.messages = []; } system(content) { this.messages.push(new SystemMessage(content)); return this; // Chainable } human(content, metadata = {}) { this.messages.push(new HumanMessage(content, metadata)); return this; } ai(content, metadata = {}) { this.messages.push(new AIMessage(content, metadata)); return this; } build() { return this.messages; } } // Usage const conversation = new MessageBuilder() .system("You are helpful.") .human("Hello!") .ai("Hi there!") .human("How are you?") .ai("I'm great!") .build(); ``` ### Pattern 2: Conversation History Manager ```javascript class ConversationHistory { constructor(maxMessages = 100) { this.messages = []; this.maxMessages = maxMessages; } add(message) { this.messages.push(message); // Keep only last N messages (sliding window) if (this.messages.length > this.maxMessages) { // Always keep system message if it exists const systemMsg = this.messages.find(m => m.type === 'system'); const recentMessages = this.messages.slice(-this.maxMessages + 1); this.messages = systemMsg ? [systemMsg, ...recentMessages.filter(m => m.type !== 'system')] : recentMessages; } } getAll() { return [...this.messages]; // Return copy } getLast(n = 1) { return this.messages.slice(-n); } getByType(type) { return this.messages.filter(msg => msg.type === type); } clear() { // Keep system message const systemMsg = this.messages.find(m => m.type === 'system'); this.messages = systemMsg ? [systemMsg] : []; } toPromptFormat() { return this.messages.map(msg => msg.toPromptFormat()); } save() { return JSON.stringify(this.messages.map(m => m.toJSON())); } static load(json) { const data = JSON.parse(json); const history = new ConversationHistory(); history.messages = data.map(msgData => BaseMessage.fromJSON(msgData)); return history; } } // Usage const history = new ConversationHistory(maxMessages: 50); history.add(new SystemMessage("You are helpful.")); history.add(new HumanMessage("Hi")); history.add(new AIMessage("Hello!")); console.log('Total messages:', history.getAll().length); console.log('Last message:', history.getLast()[0].content); // Format for LLM const formatted = history.toPromptFormat(); // [{ role: 'system', content: '...' }, { role: 'user', content: '...' }, ...] ``` ### Pattern 3: Message Validation ```javascript class MessageValidator { static validate(message) { const errors = []; // Check content if (!message.content || message.content.trim().length === 0) { errors.push('Message content cannot be empty'); } // Check type if (!MESSAGE_TYPES[message.type]) { errors.push(`Invalid message type: ${message.type}`); } // Check tool messages have tool call ID if (message.type === 'tool' && !message.toolCallId) { errors.push('Tool messages must have a toolCallId'); } return { valid: errors.length === 0, errors }; } static validateConversation(messages) { const errors = []; // First message should be system message (recommended) if (messages.length > 0 && messages[0].type !== 'system') { errors.push('Conversation should start with a system message'); } // Tool messages should follow AI messages with tool calls for (let i = 1; i < messages.length; i++) { const prev = messages[i - 1]; const curr = messages[i]; if (curr.type === 'tool') { if (prev.type !== 'ai' || !prev.hasToolCalls()) { errors.push( `Tool message at index ${i} should follow AI message with tool calls` ); } } } return { valid: errors.length === 0, errors }; } } // Usage const msg = new HumanMessage("Hello!"); const result = MessageValidator.validate(msg); console.log('Valid?', result.valid); console.log('Errors:', result.errors); ``` ### Pattern 4: Message Formatting Utilities ```javascript class MessageFormatter { /** * Format messages for display in UI */ static toDisplayFormat(messages) { return messages.map(msg => { const time = new Date(msg.timestamp).toLocaleTimeString(); const icon = this.getIcon(msg.type); return { id: msg.id, icon, time, type: msg.type, content: msg.content, sender: this.getSenderName(msg.type) }; }); } static getIcon(type) { const icons = { 'system': '⚙️', 'human': '👤', 'ai': '🤖', 'tool': '🛠️' }; return icons[type] || '💬'; } static getSenderName(type) { const names = { 'system': 'System', 'human': 'You', 'ai': 'Assistant', 'tool': 'Tool' }; return names[type] || 'Unknown'; } /** * Format messages for LLM (OpenAI-style) */ static toOpenAIFormat(messages) { return messages.map(msg => { const formatted = msg.toPromptFormat(); // Map our types to OpenAI's expected roles const roleMap = { 'human': 'user', 'ai': 'assistant', 'system': 'system', 'tool': 'tool' }; formatted.role = roleMap[msg.type] || msg.type; return formatted; }); } /** * Create markdown representation */ static toMarkdown(messages) { return messages.map(msg => { const sender = this.getSenderName(msg.type); const time = new Date(msg.timestamp).toLocaleString(); return `**${sender}** (${time})\n\n${msg.content}\n\n---\n`; }).join('\n'); } } // Usage const messages = [ new SystemMessage("You are helpful."), new HumanMessage("Hi"), new AIMessage("Hello!") ]; console.log('Display format:', MessageFormatter.toDisplayFormat(messages)); console.log('OpenAI format:', MessageFormatter.toOpenAIFormat(messages)); console.log('Markdown:', MessageFormatter.toMarkdown(messages)); ``` ## Integration with LLMs Messages need to be formatted for the LLM. Here's how different models expect them: ### OpenAI-Style Format ```javascript function formatForOpenAI(messages) { return messages.map(msg => ({ role: msg.type === 'human' ? 'user' : msg.type, content: msg.content })); } ``` ### Llama-Style Format (with chat template) ```javascript function formatForLlama(messages) { // Llama uses special tokens let formatted = ''; for (const msg of messages) { if (msg.type === 'system') { formatted += `<>\n${msg.content}\n<>\n\n`; } else if (msg.type === 'human') { formatted += `[INST] ${msg.content} [/INST]`; } else if (msg.type === 'ai') { formatted += `${msg.content}`; } } return formatted; } ``` ### Our Flexible Approach ```javascript class MessageFormatter { static format(messages, style = 'openai') { const formatters = { 'openai': this.formatOpenAI, 'llama': this.formatLlama, 'raw': this.formatRaw }; const formatter = formatters[style]; if (!formatter) { throw new Error(`Unknown format style: ${style}`); } return formatter(messages); } } ``` ## Debugging Tips ### Tip 1: Pretty Print Conversations ```javascript function printConversation(messages) { console.log('\n=== Conversation ===\n'); messages.forEach((msg, idx) => { console.log(`${idx + 1}. ${msg.toString()}`); }); console.log('\n===================\n'); } ``` ### Tip 2: Visualize Message Flow ```javascript function visualizeFlow(messages) { const flow = messages.map(msg => { const icon = msg.type === 'human' ? '→' : '←'; return `${icon} ${msg.type}: ${msg.content.substring(0, 50)}...`; }); console.log('\nMessage Flow:'); flow.forEach(line => console.log(line)); } ``` ### Tip 3: Inspect Metadata ```javascript function inspectMetadata(message) { console.log('Message Details:'); console.log('- ID:', message.id); console.log('- Type:', message.type); console.log('- Timestamp:', new Date(message.timestamp).toISOString()); console.log('- Content length:', message.content.length); console.log('- Metadata:', message.additionalKwargs); if (message.type === 'ai' && message.hasToolCalls()) { console.log('- Tool calls:', message.toolCalls.length); } } ``` ## Common Mistakes ### ❌ Mistake 1: Wrong Message Order ```javascript // Bad: AI message before human input const bad = [ new AIMessage("Hello!"), new HumanMessage("Hi") ]; ``` **Fix**: Always respond TO something ```javascript const good = [ new HumanMessage("Hi"), new AIMessage("Hello!") ]; ``` ### ❌ Mistake 2: Forgetting System Message ```javascript // Bad: No context for the AI const bad = [ new HumanMessage("Write code") ]; ``` **Fix**: Always set context ```javascript const good = [ new SystemMessage("You are a coding assistant."), new HumanMessage("Write code") ]; ``` ### ❌ Mistake 3: Not Linking Tool Messages ```javascript // Bad: Tool message without proper ID const bad = new ToolMessage("result", undefined); ``` **Fix**: Always link to the tool call ```javascript const toolCallId = aiMessage.getToolCall(0).id; const good = new ToolMessage("result", toolCallId); ``` ### ❌ Mistake 4: Modifying Message Content ```javascript // Bad: Changing message after creation const msg = new HumanMessage("Hello"); msg.content = "Hi"; // Don't do this! ``` **Fix**: Create a new message ```javascript const newMsg = new HumanMessage("Hi"); ``` ## Mental Model Think of messages as a timeline: ``` Time → ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━→ ⚙️ System: "You are helpful" │ ├→ Human: "What's 2+2?" │ ├→ AI: "Let me calculate..." │ └─ [Tool Call: calculator(2, 2)] │ ├→ Tool: "4" │ └→ AI: "2+2 equals 4" ``` Each message is immutable - once created, it represents a moment in time. ## Exercises Practice what you've learned! ### Exercise 5: Build a Message Formatter Create a function that formats messages for console display with colors and icons. **Requirements**: - Different colors for each message type - Icons for visual distinction - Timestamp display - Content truncation for long messages **Starter code**: `exercises/02-message-formatter.js` ### Exercise 6: Implement Conversation Validation Build a validator that checks conversation structure. **Rules to check**: - System message should be first (if present) - Tool messages must follow AI messages with tool calls - No empty messages - Alternating human/AI after system message **Starter code**: `exercises/02-conversation-validator.js` ### Exercise 7: Create a Chat History Manager Build a class that manages conversation history with: - Add messages - Get last N messages - Filter by type - Save/load from JSON - Sliding window (max messages) **Starter code**: `exercises/02-chat-history.js` ### Exercise 8: Tool Call Flow Simulate a complete tool call flow: 1. Human asks a question requiring a tool 2. AI responds with a tool call 3. Tool executes and returns result 4. AI incorporates result in response **Starter code**: `exercises/02-tool-flow.js` ## Summary Congratulations! You now understand message types and why they're crucial for AI agents. ### Key Takeaways 1. **Four core types**: System, Human, AI, Tool 2. **Structured data**: Messages have types, timestamps, IDs, metadata 3. **Immutability**: Messages represent moments in time 4. **Tool calls**: AI messages can request function execution 5. **Serialization**: Messages can be saved/loaded as JSON ### Why This Matters - ✅ **Clarity**: Know who said what, when - ✅ **Debugging**: Track conversation flow easily - ✅ **Persistence**: Save and restore conversations - ✅ **Validation**: Ensure proper structure - ✅ **Formatting**: Adapt to different LLM formats ### Building Blocks Messages are the data structure that flows through Runnables: ```javascript // Messages flow through Runnables const conversation = [ new SystemMessage("You are helpful"), new HumanMessage("Hello") ]; // Runnables process messages const response = await chatModel.invoke(conversation); // Returns: AIMessage("Hi there!") ``` ## Next Steps In the next lesson, we'll wrap **node-llama-cpp** as a Runnable that works with our message types! **Preview**: You'll learn: - Loading local LLMs - Converting messages to prompts - Streaming responses - Managing model context ➡️ [Continue to Lesson 3: The LLM Wrapper](03-llm-wrapper.md) ## Additional Resources - [OpenAI Chat Format Documentation](https://platform.openai.com/docs/guides/chat) - [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) - [JSON Schema for Validation](https://json-schema.org/) ## Questions & Discussion **Q: Why not just use strings?** A: Strings don't capture metadata, can't distinguish between types, and make debugging harder. Typed messages provide structure and context. **Q: Can I add custom message types?** A: Yes! Extend `BaseMessage` and register in `MESSAGE_TYPES`: ```javascript class CustomMessage extends BaseMessage { get type() { return 'custom'; } } MESSAGE_TYPES['custom'] = CustomMessage; ``` **Q: How do I handle multi-modal content (images)?** A: Store in `additionalKwargs`: ```javascript new HumanMessage("What's in this image?", { images: ['data:image/png;base64,...'] }) ``` **Q: Should I validate every message?** A: For production, yes. For development, optional but helpful for catching bugs early. --- **Built with ❤️ for learners who want to understand AI agents deeply** [← Previous: Runnable](01-runnable.md) | [Tutorial Index](../README.md) | [Next: LLM Wrapper →](03-llm-wrapper.md)