// Command tools demonstrates the canonical tool-call loop against the raw // Model API: offer tools, execute the calls the model makes, feed results // back, repeat until a final answer. (The agent package automates exactly // this loop; see examples/agent.) package main import ( "context" "encoding/json" "flag" "fmt" "log" "gitea.stevedudenhoeffer.com/steve/majordomo" ) func main() { model := flag.String("model", "ollama-cloud/minimax-m3:cloud", "model spec") flag.Parse() weather := majordomo.Tool{ Name: "get_weather", Description: "Get the current weather for a city.", Parameters: json.RawMessage(`{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}`), Handler: func(_ context.Context, args json.RawMessage) (any, error) { var p struct { City string `json:"city"` } if err := json.Unmarshal(args, &p); err != nil { return nil, err } // A real implementation would call a weather API. return map[string]any{"city": p.City, "temp_c": 21, "sky": "partly cloudy"}, nil }, } box := majordomo.NewToolbox("demo", weather) m, err := majordomo.Parse(*model) if err != nil { log.Fatalf("parse: %v", err) } ctx := context.Background() msgs := []majordomo.Message{majordomo.UserText("What's the weather in Tokyo right now?")} for range 5 { resp, err := m.Generate(ctx, majordomo.Request{Messages: msgs}, majordomo.WithToolbox(box)) if err != nil { log.Fatalf("generate: %v", err) } msgs = append(msgs, resp.Message()) if len(resp.ToolCalls) == 0 { fmt.Printf("[%s] %s\n", resp.Model, resp.Text()) return } results := make([]majordomo.ToolResult, 0, len(resp.ToolCalls)) for _, call := range resp.ToolCalls { fmt.Printf("→ model called %s(%s)\n", call.Name, call.Arguments) results = append(results, box.Execute(ctx, call)) } msgs = append(msgs, majordomo.ToolResultsMessage(results...)) } log.Fatal("no final answer within 5 steps") }