Tool loop in Go
The model can call two functions, find_book and availability, as many times as it needs; the loop ends when it answers in text. It uses plain HTTP, so there is nothing to install beyond the standard library unless noted.
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
type Call struct {
ID string `json:"id"`
Type string `json:"type"`
Function struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
} `json:"function"`
}
type Message struct {
Role string `json:"role"`
Content any `json:"content"`
ToolCalls []Call `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}
var tools = json.RawMessage(`[
{"type": "function", "function": {"name": "find_book", "description": "Search the catalogue by title words. Returns ISBNs.",
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "availability", "description": "Copies available now for an ISBN, and when the next one is due back.",
"parameters": {"type": "object", "properties": {"isbn": {"type": "string"}}, "required": ["isbn"]}}}
]`)
type book struct {
Title string
Copies, OnLoan int
DueBack string
}
var library = map[string]book{
"978-0141439518": {"Pride and Prejudice", 3, 3, "2026-10-19"},
"978-0007119318": {"The Hobbit", 2, 1, ""},
}
func runTool(name, rawArgs string) any {
var args map[string]string
if err := json.Unmarshal([]byte(rawArgs), &args); err != nil {
return map[string]string{"error": "arguments were not valid JSON"}
}
switch name {
case "find_book":
var hits []map[string]string
for isbn, b := range library {
if strings.Contains(strings.ToLower(b.Title), strings.ToLower(args["query"])) {
hits = append(hits, map[string]string{"isbn": isbn, "title": b.Title})
}
}
return map[string]any{"results": hits}
case "availability":
b, ok := library[args["isbn"]]
if !ok {
return map[string]string{"error": "no book with ISBN " + args["isbn"]}
}
free := b.Copies - b.OnLoan
res := map[string]any{"available": free, "next_due_back": nil}
if free == 0 {
res["next_due_back"] = b.DueBack
}
return res
}
return map[string]string{"error": "unknown tool " + name}
}
func complete(messages []Message) (Message, error) {
body, _ := json.Marshal(map[string]any{"model": "Amara S1.2", "stream": false, "max_tokens": 800, "tools": tools, "messages": messages})
req, _ := http.NewRequest("POST", "https://app.syntic.ai/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SYNTIC_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return Message{}, err
}
defer resp.Body.Close()
var out struct {
Choices []struct{ Message Message } `json:"choices"`
Error *struct{ Code, Message string }
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return Message{}, err
}
if out.Error != nil {
return Message{}, fmt.Errorf("%d %s: %s", resp.StatusCode, out.Error.Code, out.Error.Message)
}
return out.Choices[0].Message, nil
}
func main() {
messages := []Message{
{Role: "system", Content: "You help members of a town library. Use the tools; never guess availability."},
{Role: "user", Content: "Can I borrow Pride and Prejudice today? If not, when?"},
}
for round := 0; round < 8; round++ {
msg, err := complete(messages)
if err != nil {
panic(err)
}
if len(msg.ToolCalls) == 0 {
fmt.Println(msg.Content)
return
}
messages = append(messages, msg)
for _, c := range msg.ToolCalls {
result, _ := json.Marshal(runTool(c.Function.Name, c.Function.Arguments))
messages = append(messages, Message{Role: "tool", ToolCallID: c.ID, Content: string(result)})
}
}
fmt.Println("Sorry, I couldn't finish that request.")
}The three rules
- Append the assistant message with its
tool_callsexactly as received. - Add one
toolmessage per call, with the matchingtool_call_id. - Send the same
toolslist on every round.
Errors from your functions are returned to the model as results so it can recover, and the round limit stops a runaway loop.
Related
Was this page helpful?