Schemas in Sapiens define the structure and validation rules for tool parameters and structured responses. They use JSON Schema format and integrate with the github.com/sashabaranov/go-openai/jsonschema
package for automatic generation from Go types.
Schemas are used in two main areas:
"parameter_name": {
Type: jsonschema.String,
Description: "A text parameter",
}
"amount": {
Type: jsonschema.Number,
Description: "A numeric value",
}
"enabled": {
Type: jsonschema.Boolean,
Description: "A true/false value",
}
"tags": {
Type: jsonschema.Array,
Description: "A list of items",
Items: &jsonschema.Definition{
Type: jsonschema.String,
},
}
"metadata": {
Type: jsonschema.Object,
Description: "A nested object",
Properties: map[string]jsonschema.Definition{
"key": {Type: jsonschema.String},
"value": {Type: jsonschema.String},
},
}
Restrict parameter values to a specific set of options:
"priority": {
Type: jsonschema.String,
Enum: []string{"low", "medium", "high", "urgent"},
Description: "Task priority level",
}
"temperature_unit": {
Type: jsonschema.String,
Enum: []string{"celsius", "fahrenheit", "kelvin"},
Description: "Temperature measurement unit",
}
Define what parameters your tools accept:
import "github.com/sashabaranov/go-openai/jsonschema"
agent.AddTool(
"get_weather",
"Get current weather for a location",
map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Description: "City and country (e.g., 'London, UK')",
},
"unit": {
Type: jsonschema.String,
Enum: []string{"celsius", "fahrenheit"},
Description: "Temperature unit",
},
},
[]string{"location"}, // Required parameters
weatherToolFunc,
)
agent.AddTool(
"create_task",
"Create a new task in the project management system",
map[string]jsonschema.Definition{
"title": {
Type: jsonschema.String,
Description: "Task title",
},
"description": {
Type: jsonschema.String,
Description: "Detailed task description",
},
"priority": {
Type: jsonschema.String,
Enum: []string{"low", "medium", "high", "urgent"},
Description: "Task priority",
},
"due_date": {
Type: jsonschema.String,
Description: "Due date in YYYY-MM-DD format",
},
"assignee": {
Type: jsonschema.String,
Description: "Email of the person to assign the task to",
},
"tags": {
Type: jsonschema.Array,
Description: "List of tags for categorization",
Items: &jsonschema.Definition{
Type: jsonschema.String,
},
},
"estimated_hours": {
Type: jsonschema.Number,
Description: "Estimated hours to complete",
},
},
[]string{"title", "description"}, // Only title and description are required
createTaskFunc,
)
Define the format for structured agent responses using Go structs:
// Define the response structure
type WeatherResponse struct {
Temperature string `json:"temperature"`
Condition string `json:"condition"`
Humidity string `json:"humidity"`
Location string `json:"location"`
}
var weatherResponse WeatherResponse
// Set the schema on the agent
agent.SetResponseSchema(
"weather_response",
"Structured weather information",
true, // strict validation
weatherResponse,
)
type AnalysisResult struct {
Summary string `json:"summary"`
Confidence float64 `json:"confidence"`
Steps []Step `json:"steps"`
Metadata Metadata `json:"metadata"`
}
type Step struct {
StepNumber int `json:"step_number"`
Description string `json:"description"`
Result string `json:"result"`
Success bool `json:"success"`
}
type Metadata struct {
ProcessingTime string `json:"processing_time"`
ToolsUsed []string `json:"tools_used"`
AdditionalInfo map[string]string `json:"additional_info"`
}
var analysisResult AnalysisResult
agent.SetResponseSchema(
"analysis_result",
"Detailed analysis with steps and metadata",
true,
analysisResult,
)
// After getting a response from the agent
resp, err := agent.Ask(messages)
if err != nil {
log.Fatalf("Error: %v", err)
}
// Parse the structured response
var result AnalysisResult
err = agent.ParseResponse(resp, &result)
if err != nil {
log.Fatalf("Parse error: %v", err)
}
// Access structured data
fmt.Printf("Summary: %s\n", result.Summary)
fmt.Printf("Confidence: %.2f\n", result.Confidence)
for _, step := range result.Steps {
fmt.Printf("Step %d: %s -> %s\n", step.StepNumber, step.Description, step.Result)
}
Always provide clear, helpful descriptions:
// Good
"email": {
Type: jsonschema.String,
Description: "Valid email address in format user@example.com",
}
// Better
"email": {
Type: jsonschema.String,
Description: "Valid email address for notifications (e.g., john.doe@company.com)",
}
When parameters have limited valid values, use enums:
"status": {
Type: jsonschema.String,
Enum: []string{"pending", "in_progress", "completed", "cancelled"},
Description: "Current status of the task",
}
Always specify which parameters are required:
agent.AddTool(
"send_email",
"Send an email message",
parameters,
[]string{"to", "subject", "body"}, // Required
emailFunc,
)
Choose the correct JSON schema type:
// For counts, IDs, quantities
"quantity": {Type: jsonschema.Number}
// For text, names, descriptions
"name": {Type: jsonschema.String}
// For true/false values
"is_active": {Type: jsonschema.Boolean}
// For lists
"items": {Type: jsonschema.Array}
agent.AddTool(
"file_operations",
"Perform file system operations",
map[string]jsonschema.Definition{
"operation": {
Type: jsonschema.String,
Enum: []string{"read", "write", "delete", "list", "move"},
Description: "Type of file operation to perform",
},
"path": {
Type: jsonschema.String,
Description: "File or directory path",
},
"content": {
Type: jsonschema.String,
Description: "Content to write (only for write operation)",
},
"new_path": {
Type: jsonschema.String,
Description: "New path for move operation",
},
"recursive": {
Type: jsonschema.Boolean,
Description: "Whether to perform operation recursively",
},
},
[]string{"operation", "path"},
fileOperationsFunc,
)
agent.AddTool(
"database_query",
"Execute database queries",
map[string]jsonschema.Definition{
"query_type": {
Type: jsonschema.String,
Enum: []string{"select", "insert", "update", "delete"},
Description: "Type of SQL query",
},
"table": {
Type: jsonschema.String,
Description: "Database table name",
},
"conditions": {
Type: jsonschema.Object,
Description: "Query conditions as key-value pairs",
},
"fields": {
Type: jsonschema.Array,
Description: "Fields to select or update",
Items: &jsonschema.Definition{
Type: jsonschema.String,
},
},
"limit": {
Type: jsonschema.Number,
Description: "Maximum number of results",
},
},
[]string{"query_type", "table"},
databaseQueryFunc,
)
agent.AddTool(
"api_call",
"Make HTTP API calls",
map[string]jsonschema.Definition{
"method": {
Type: jsonschema.String,
Enum: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
Description: "HTTP method",
},
"url": {
Type: jsonschema.String,
Description: "API endpoint URL",
},
"headers": {
Type: jsonschema.Object,
Description: "HTTP headers as key-value pairs",
},
"body": {
Type: jsonschema.String,
Description: "Request body (JSON string)",
},
"timeout": {
Type: jsonschema.Number,
Description: "Request timeout in seconds",
},
},
[]string{"method", "url"},
apiCallFunc,
)
Sapiens automatically generates JSON schemas from Go structs for structured responses:
// This struct
type UserProfile struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Active bool `json:"active"`
Tags []string `json:"tags"`
Metadata map[string]interface{} `json:"metadata"`
}
// Automatically becomes this schema:
// {
// "type": "object",
// "properties": {
// "id": {"type": "integer"},
// "name": {"type": "string"},
// "email": {"type": "string"},
// "active": {"type": "boolean"},
// "tags": {
// "type": "array",
// "items": {"type": "string"}
// },
// "metadata": {"type": "object"}
// },
// "required": ["id", "name", "email", "active", "tags", "metadata"]
// }
Handle schema-related errors gracefully:
// When parsing structured responses
var result MyStruct
err := agent.ParseResponse(resp, &result)
if err != nil {
log.Printf("Schema parsing error: %v", err)
// Fall back to plain text response
fmt.Println("Plain response:", resp.Choices[0].Message.Content)
return
}
// When setting response schemas
schema := agent.SetResponseSchema("my_schema", "Description", true, result)
if schema == nil {
log.Printf("Failed to set response schema")
// Continue without structured output
}
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/sashabaranov/go-openai/jsonschema"
)
type TaskResult struct {
TaskID string `json:"task_id"`
Status string `json:"status"`
Description string `json:"description"`
Tags []string `json:"tags"`
CreatedAt string `json:"created_at"`
}
func main() {
llm := NewGemini(os.Getenv("GEMINI_API_KEY"))
agent := NewAgent(
context.Background(),
llm.Client(),
llm.GetDefaultModel(),
"You are a task management assistant",
)
// Add tool with complex schema
agent.AddTool(
"create_task",
"Create a new task",
map[string]jsonschema.Definition{
"title": {
Type: jsonschema.String,
Description: "Task title",
},
"priority": {
Type: jsonschema.String,
Enum: []string{"low", "medium", "high"},
Description: "Task priority",
},
"tags": {
Type: jsonschema.Array,
Description: "Task tags",
Items: &jsonschema.Definition{Type: jsonschema.String},
},
},
[]string{"title"},
func(params map[string]string) string {
return `{
"task_id": "task_123",
"status": "created",
"description": "Task created successfully",
"tags": ["work", "urgent"],
"created_at": "2024-01-15T10:30:00Z"
}`
},
)
// Set structured response schema
var taskResult TaskResult
agent.SetResponseSchema("task_result", "Task operation result", true, taskResult)
// Use the agent
message := NewMessages()
resp, err := agent.Ask(message.MergeMessages(
message.UserMessage("Create a high priority task called 'Review documentation'"),
))
if err != nil {
log.Fatalf("Error: %v", err)
}
// Parse structured response
err = agent.ParseResponse(resp, &taskResult)
if err != nil {
log.Printf("Parse error: %v", err)
fmt.Println("Plain response:", resp.Choices[0].Message.Content)
} else {
fmt.Printf("Task created: %s (ID: %s, Status: %s)\n",
taskResult.Description, taskResult.TaskID, taskResult.Status)
}
}