The Messages system in Sapiens provides utilities for creating and managing conversation messages in the proper format for different LLM providers. It abstracts the complexity of message formatting and provides a simple interface for building conversations.
The Messages system handles:
type Messages struct {
// Empty struct - acts as a factory for creating messages
}
The Messages struct serves as a factory for creating openai.ChatCompletionMessage
objects with the correct formatting.
NewMessages() *Messages
Creates a new Messages instance for building conversation messages.
message := NewMessages()
Create messages from the user/human perspective.
UserMessage(content) ChatCompletionMessage
func (a *Messages) UserMessage(msg string) openai.ChatCompletionMessage
Example:
message := NewMessages()
userMsg := message.UserMessage("Hello, how can you help me today?")
Create messages from the AI assistant perspective.
AgentMessage(content) ChatCompletionMessage
func (a *Messages) AgentMessage(msg string) openai.ChatCompletionMessage
Example:
message := NewMessages()
assistantMsg := message.AgentMessage("I'm here to help! What would you like to know?")
Create messages representing tool/function call responses.
ToolMessage(id, name, content) ChatCompletionMessage
func (a *Messages) ToolMessage(id, name, msg string) openai.ChatCompletionMessage
Parameters:
id
: The tool call ID from the LLM’s requestname
: The name of the tool that was calledmsg
: The response content from the toolExample:
message := NewMessages()
toolMsg := message.ToolMessage("call_123", "get_weather", `{"temperature":"25°C", "condition":"sunny"}`)
MergeMessages(...messages) []ChatCompletionMessage
Combines multiple messages into a single slice for sending to the agent.
func (a *Messages) MergeMessages(messages ...openai.ChatCompletionMessage) []openai.ChatCompletionMessage
Example:
message := NewMessages()
messages := message.MergeMessages(
message.UserMessage("What's the weather like?"),
message.AgentMessage("I'll check the weather for you."),
message.ToolMessage("call_123", "get_weather", `{"temp":"25°C"}`),
)
package main
import (
"context"
"fmt"
"log"
"os"
)
func main() {
llm := NewGemini(os.Getenv("GEMINI_API_KEY"))
agent := NewAgent(
context.Background(),
llm.Client(),
llm.GetDefaultModel(),
"You are a helpful assistant",
)
// Create message builder
message := NewMessages()
// Send a simple user message
resp, err := agent.Ask(message.MergeMessages(
message.UserMessage("Hello! Can you help me with Go programming?"),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("Response:", resp.Choices[0].Message.Content)
}
func multiTurnConversation() {
llm := NewGemini(os.Getenv("GEMINI_API_KEY"))
agent := NewAgent(
context.Background(),
llm.Client(),
llm.GetDefaultModel(),
"You are a helpful assistant",
)
message := NewMessages()
// First interaction
resp1, err := agent.Ask(message.MergeMessages(
message.UserMessage("What is Go programming language?"),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
// The agent automatically maintains conversation history
// Second interaction builds on the first
resp2, err := agent.Ask(message.MergeMessages(
message.UserMessage("Can you show me a simple example?"),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("First response:", resp1.Choices[0].Message.Content)
fmt.Println("Second response:", resp2.Choices[0].Message.Content)
}
func complexConversation() {
message := NewMessages()
// Build a conversation with multiple message types
conversationHistory := message.MergeMessages(
message.UserMessage("I need help with a programming problem."),
message.AgentMessage("I'd be happy to help! What programming problem are you working on?"),
message.UserMessage("I need to create a REST API in Go."),
message.AgentMessage("Great! Let me help you with that. What specific part do you need help with?"),
message.UserMessage("How do I handle JSON requests?"),
)
// Send the entire conversation context
resp, err := agent.Ask(conversationHistory)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("Response:", resp.Choices[0].Message.Content)
}
When tools are involved, the message flow becomes more complex, but the Messages system handles it seamlessly:
func toolConversation() {
llm := NewGemini(os.Getenv("GEMINI_API_KEY"))
agent := NewAgent(
context.Background(),
llm.Client(),
llm.GetDefaultModel(),
"You are a helpful assistant with access to tools",
)
// Add a tool (simplified)
agent.AddTool("get_weather", "Get weather info", weatherParams, []string{"location"}, weatherFunc)
message := NewMessages()
// User asks for weather - this will trigger tool usage automatically
resp, err := agent.Ask(message.MergeMessages(
message.UserMessage("What's the weather in New York?"),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
// The agent handles:
// 1. User message
// 2. Tool call execution
// 3. Tool response processing
// 4. Final response generation
fmt.Println("Final response:", resp.Choices[0].Message.Content)
}
openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: "Your message content here",
}
openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleAssistant,
Content: "Assistant response content",
}
openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleTool,
Content: "Tool response content",
ToolCallID: "call_id_from_llm",
Name: "tool_name",
}
Always use the Messages builder for consistency:
// Good
message := NewMessages()
userMsg := message.UserMessage("Hello")
// Avoid direct creation
// userMsg := openai.ChatCompletionMessage{...}
Use MergeMessages
to make conversation flows clear:
messages := message.MergeMessages(
message.UserMessage("Question 1"),
message.UserMessage("Question 2"),
message.UserMessage("Question 3"),
)
Don’t manually create tool messages - the agent handles tool call responses automatically:
// The agent automatically handles this flow:
// 1. User message
// 2. LLM decides to call tool
// 3. Tool executes
// 4. Tool response added as tool message
// 5. Final response generated
For complex scenarios, build conversations step by step:
message := NewMessages()
conversation := []openai.ChatCompletionMessage{}
// Add messages incrementally
conversation = append(conversation, message.UserMessage("Start conversation"))
conversation = append(conversation, message.AgentMessage("Agent response"))
conversation = append(conversation, message.UserMessage("Follow-up"))
// Send all at once
resp, err := agent.Ask(conversation)
The Messages system is simple and rarely produces errors, but you should handle agent errors:
message := NewMessages()
resp, err := agent.Ask(message.MergeMessages(
message.UserMessage("Your question here"),
))
if err != nil {
switch {
case strings.Contains(err.Error(), "context"):
log.Printf("Context error: %v", err)
case strings.Contains(err.Error(), "tool"):
log.Printf("Tool execution error: %v", err)
default:
log.Printf("General error: %v", err)
}
return
}
Here’s a complete example showing different message types and patterns:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/sashabaranov/go-openai/jsonschema"
)
func main() {
// Setup
llm := NewGemini(os.Getenv("GEMINI_API_KEY"))
agent := NewAgent(
context.Background(),
llm.Client(),
llm.GetDefaultModel(),
"You are a helpful assistant",
)
// Add a simple tool
agent.AddTool(
"calculate",
"Perform mathematical calculations",
map[string]jsonschema.Definition{
"expression": {
Type: jsonschema.String,
Description: "Math expression to evaluate",
},
},
[]string{"expression"},
func(params map[string]string) string {
// Simple calculator logic
return `{"result": "42", "expression": "` + params["expression"] + `"}`
},
)
// Create message builder
message := NewMessages()
// Example 1: Simple question
fmt.Println("=== Simple Question ===")
resp1, err := agent.Ask(message.MergeMessages(
message.UserMessage("What is artificial intelligence?"),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("Response:", resp1.Choices[0].Message.Content)
// Example 2: Question that requires tool usage
fmt.Println("\n=== Tool Usage ===")
resp2, err := agent.Ask(message.MergeMessages(
message.UserMessage("What is 15 * 37 + 125?"),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("Response:", resp2.Choices[0].Message.Content)
// Example 3: Multi-message conversation
fmt.Println("\n=== Multi-Message ===")
resp3, err := agent.Ask(message.MergeMessages(
message.UserMessage("I'm learning Go programming."),
message.UserMessage("Can you explain goroutines?"),
message.UserMessage("Show me a simple example."),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("Response:", resp3.Choices[0].Message.Content)
}