26/08/2026 03:00am

Golang The Series EP.172: Function Calling (Teach AI to Invoke Go Functions)
#Golang AI
#Function Calling Go
#Go OpenAI API
#Build AI Agent
#SuperDev Academy
#Superdev Academy
Welcome to EP.172! In our previous episode, we laid the groundwork for AI Agents and the ReAct reasoning loop. Today, we are getting our hands dirty with the absolute core foundation of building autonomous agents: Function Calling (also known as Tool Calling).
Function Calling is the mechanism that empowers AI models (LLMs) to interact with the outside world. Instead of simply replying with conversational text, the AI analyzes the user's prompt and outputs a structured JSON payload containing a "Function Name" and its "Arguments". Our Go backend catches this, executes the actual function, and feeds the real-world data back to the AI!
The Function Calling Workflow
Plaintext
[1. User Request] ──> "Can you check the stock for SKU-1024?"
│
▼
[2. Go App Call LLM] <--- Sends the prompt + Tool Definition (JSON Schema)
│
▼
[3. LLM Processing] <--- AI selects 'GetStockQuantity' and extracts the SKU
│
▼
[4. LLM Response] <--- Returns Tool Calls (Name: "GetStockQuantity", Args: {"sku": "SKU-1024"})
│
▼
[5. Go App Execution] <--- Go parses the tool name -> Executes GetStockQuantity("SKU-1024")
│
▼
[6. Return Result] <--- Sends the result (e.g., "150 items") back to the LLM
│
▼
[7. Final AI Answer] ──> "There are currently 150 units of SKU-1024 in stock."
Registering the Tool Schema in Go
To let the AI know what our Go functions can do, we need to define a JSON Schema for each tool. We have to tell the AI:
Name: The identifier of the function.
Description: What the function does (this is crucial, as the AI uses this to decide when to use it).
Parameters: The expected arguments, their data types, and which ones are
Required.
Writing the Go Code for Function Calling
To make things easy to digest, let's break the code down into 4 logical steps.
Part 1: The Native Go Function
First, let's write our standard business logic. Imagine this is a function that checks warehouse inventory.
Go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
)
// --- 1. Define the native Go function ---
// GetStockQuantity simulates a database query to get stock count
func GetStockQuantity(sku string) (int, error) {
// In a real app, you would query your database here
stocks := map[string]int{
"SKU-1024": 150,
"SKU-2048": 0,
"SKU-9999": 42,
}
qty, exists := stocks[sku]
if !exists {
return 0, fmt.Errorf("item SKU %s not found in the system", sku)
}
return qty, nil
}
Part 2: Defining the Schema and Calling the AI
Next, we create an openai.Tool to describe our function to the AI, and we send it along with the user's prompt.
Go
func main() {
client := openai.NewClient("YOUR_OPENAI_API_KEY")
// --- 2. Create the Tool Definition to register with the AI ---
stockTool := openai.Tool{
Type: openai.ToolTypeFunction,
Function: &openai.FunctionDefinition{
Name: "GetStockQuantity",
Description: "Fetch the current inventory stock quantity using the item's SKU",
Parameters: jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"sku": {
Type: jsonschema.String,
Description: "The item SKU, e.g., SKU-1024",
},
},
Required: []string{"sku"}, // Tell AI it MUST provide this
},
},
}
userQuery := "Can you tell me how many units of SKU-1024 we have left?"
fmt.Printf("👤 User: %s\n\n", userQuery)
messages := []openai.ChatCompletionMessage{
{Role: openai.ChatMessageRoleUser, Content: userQuery},
}
// --- 3. Send the initial request along with our Tools ---
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: messages,
Tools: []openai.Tool{stockTool},
},
)
if err != nil {
log.Fatalf("API Error: %v", err)
}
msg := resp.Choices[0].Message
Part 3: Catching the AI's Decision and Executing Code
When the AI replies, we check if it decided to use a tool. If it did, we parse the arguments it generated, map them to a Go struct, and run our native function.
Go
// --- 4. Check if the AI decided to call a tool ---
if len(msg.ToolCalls) > 0 {
toolCall := msg.ToolCalls[0]
fmt.Printf("🤖 AI decided to use Tool: %s\n", toolCall.Function.Name)
fmt.Printf("📦 Arguments parsed by AI: %s\n", toolCall.Function.Arguments)
// Parse the JSON arguments generated by the AI
var args struct {
SKU string `json:"sku"`
}
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &args); err != nil {
log.Fatalf("Failed to parse tool arguments: %v", err)
}
// --- 5. Execute the actual Go function ---
var toolResult string
if toolCall.Function.Name == "GetStockQuantity" {
qty, err := GetStockQuantity(args.SKU)
if err != nil {
toolResult = fmt.Sprintf(`{"error": "%s"}`, err.Error())
} else {
// Wrap the result in JSON to send back
toolResult = fmt.Sprintf(`{"sku": "%s", "quantity": %d}`, args.SKU, qty)
}
}
fmt.Printf("⚙️ Result from Go Execution: %s\n\n", toolResult)
Part 4: Sending the Result Back for the Final Answer
This is the final, crucial step. We must append the AI's "thought process" (the Assistant message) and the result of our Go execution (the Tool message) back into the history, then hit the API one last time so the AI can summarize the answer for the user.
Go
// --- 6. Send the tool execution result back to the AI ---
messages = append(messages, msg) // Keep the AI's context/decision in history
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleTool, // Mark this as a Tool response
Content: toolResult,
ToolCallID: toolCall.ID, // MUST match the ID the AI originally sent
})
finalResp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: messages,
},
)
if err != nil {
log.Fatalf("API Error on Final Response: %v", err)
}
// 7. Get the final human-readable answer!
fmt.Printf("🤖 Final AI Answer: %s\n", finalResp.Choices[0].Message.Content)
} else {
// If the AI didn't need a tool and answered directly
fmt.Printf("🤖 Standard AI Answer: %s\n", msg.Content)
}
}
Security Guardrails & Best Practices
Never Trust AI Arguments Blindly: Always treat the arguments generated by the AI (e.g.,
args.SKU) as untrusted user input. Validate and sanitize them in Go before executing Database queries or making external API calls to prevent things like SQL Injection or unauthorized actions.Type Safety Handling: LLMs can occasionally hallucinate types (e.g., passing a string
"1024"instead of an integer1024). Usingjson.Unmarshalinto a strictly typed Go struct ensures that your application catches these type mismatches safely before they reach your business logic.
🎯 Daily Mission
Try running the code above in your local project and watch the console to see the Tool Calls in action.
Challenge for you: Imagine you have a second tool called CalculateDiscount(customerTier string, totalAmount float64). How would you structure the routing in Go (switch toolCall.Function.Name) to handle 10 or 20 different tools cleanly without writing a massive, ugly if-else chain? (Hint: Think about using a Map of function pointers!).
❓ Frequently Asked Questions (FAQ)
Can I pass multiple tools to the AI at the same time?
Absolutely. You just append multiple schemas into the []openai.Tool{} slice. The AI is smart enough to read their descriptions and pick the right one based on the user's prompt. It can even call multiple tools simultaneously (Parallel Tool Calling).
What happens if the AI generates bad arguments or invalid JSON?
If json.Unmarshal fails in Go, catch the error, wrap it in a string (e.g., "Invalid parameter type, expected integer"), and send it right back to the AI as a Tool Message. The AI will read the error, realize its mistake, and usually correct itself on the next loop!
Why do we have to append(messages, msg) back to the array?
It is a strict requirement from the OpenAI API. If a tool was called, the conversation history must include the exact Assistant message containing the ToolCalls intent, immediately followed by the Tool message containing the result. This maintains the AI's working memory.
Summary
In this episode, we walked through the Function Calling mechanics step-by-step—from defining JSON schemas, catching tool calls, executing native Go code, to feeding the results back to the LLM. This is the ultimate bridge that connects a trapped AI to the real world.
Next up (EP.173): Now that we have mastered Function Calling in Go, we are going to build one of the most powerful and practical enterprise agents: The Database Agent (Text-to-SQL). We will teach the AI to read your database schema, convert natural language into SQL queries, execute them safely, and summarize the data. Don't miss it, Gophers!
Follow Superdev Academy on all platforms:
🔵 Facebook: Superdev Academy Thailand
🎬 YouTube: Superdev Academy Channel
📸 Instagram: @superdevacademy
🎬 TikTok: @superdevacademy
🌐 Website: superdevacademy.com