Skip to main content

Language Models as Routers

Language models can act as intelligent routers, using their reasoning capabilities to determine which function or handler should process a user's request. This allows you to build applications where the model routes requests to appropriate logic based on user intent.

Probabilistic Routing with Deterministic Outputs​

Language models can route requests deterministically by using function calling. When provided with a set of function definitions, the model will:

  • Execute the function most relevant to the user query
  • Not execute any function if the query is out of scope
package main

import (
"context"
"fmt"
"log"
"math/rand"
"os"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
ctx := context.Background()

provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")

// Define available routes as tools
weatherTool := types.Tool{
Name: "getWeather",
Description: "Get the weather in a location",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"location": map[string]interface{}{
"type": "string",
"description": "The location to get the weather for",
},
},
"required": []string{"location"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
location := input["location"].(string)
temperature := 72 + rand.Intn(21) - 10
return map[string]interface{}{
"location": location,
"temperature": temperature,
}, nil
},
}

// Test different queries
queries := []string{
"What is the weather in San Francisco?", // getWeather called
"What is the weather in New York?", // getWeather called
"What events are happening in London?", // No function called
}

for _, query := range queries {
fmt.Printf("\nQuery: %s\n", query)

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
System: "You are a friendly weather assistant!",
Prompt: query,
Tools: []types.Tool{weatherTool},
})
if err != nil {
log.Fatal(err)
}

if len(result.ToolCalls) > 0 {
fmt.Printf("Routed to: %s\n", result.ToolCalls[0].ToolName)
} else {
fmt.Println("No routing - general response")
}

fmt.Printf("Response: %s\n", result.Text)
}
}

This emergent ability to choose whether a function should be executed is the model performing "reasoning". Combined with function calling, this enables language models to act as routers.

Language Models as Application Routers​

Traditionally, developers write explicit routing logic:

// Traditional routing
mux := http.NewServeMux()
mux.HandleFunc("/login", handleLogin)
mux.HandleFunc("/user/{username}", handleUserProfile)
mux.HandleFunc("/api/events", handleEvents)

With language models as routers, the model determines which handler to invoke based on user intent:

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)

// Define application routes as tools
func createRoutes() []types.Tool {
return []types.Tool{
{
Name: "getUserProfile",
Description: "Get a user's profile information",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"username": map[string]interface{}{
"type": "string",
"description": "The username to look up",
},
},
"required": []string{"username"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
username := input["username"].(string)
return map[string]interface{}{
"username": username,
"name": "John Doe",
"email": "john@example.com",
"joined": "2023-01-15",
}, nil
},
},
{
Name: "searchEvents",
Description: "Search for events",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]interface{}{
"type": "string",
"description": "Search query",
},
"limit": map[string]interface{}{
"type": "number",
"description": "Maximum number of results",
},
},
"required": []string{"query"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
query := input["query"].(string)
limit := 10
if l, ok := input["limit"].(float64); ok {
limit = int(l)
}

return map[string]interface{}{
"query": query,
"results": []string{"Event 1", "Event 2", "Event 3"},
"total": limit,
}, nil
},
},
{
Name: "login",
Description: "Handle user authentication",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
return map[string]interface{}{
"loginUrl": "/login",
"message": "Please log in to continue",
}, nil
},
},
}
}

func routeRequest(ctx context.Context, userQuery string) (*ai.GenerateTextResult, error) {
provider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})
model, _ := provider.LanguageModel("claude-sonnet-4-5")

routes := createRoutes()

return ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
System: "You are an intelligent router. Call the appropriate function based on the user's request.",
Prompt: userQuery,
Tools: routes,
})
}

func main() {
ctx := context.Background()

queries := []string{
"Show me John's profile",
"Find upcoming tech events",
"I need to log in",
}

for _, query := range queries {
fmt.Printf("\n=== User Query: %s ===\n", query)

result, err := routeRequest(ctx, query)
if err != nil {
log.Fatal(err)
}

if len(result.ToolCalls) > 0 {
fmt.Printf("Routed to: %s\n", result.ToolCalls[0].ToolName)
fmt.Printf("Parameters: %v\n", result.ToolCalls[0].Arguments)
}

fmt.Printf("Response: %s\n", result.Text)
}
}

Routing by Parameters​

Language models can extract parameters from natural language and route accordingly:

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func searchArtworks(ctx context.Context, artist string) ([]string, error) {
// Simulated database query
artworks := map[string][]string{
"van gogh": {"Starry Night", "Sunflowers", "Irises"},
"picasso": {"Guernica", "Les Demoiselles d'Avignon", "The Weeping Woman"},
"monet": {"Water Lilies", "Impression, Sunrise", "Haystacks"},
}

if works, exists := artworks[artist]; exists {
return works, nil
}

return []string{}, fmt.Errorf("no artworks found for %s", artist)
}

func main() {
ctx := context.Background()

provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")

searchTool := types.Tool{
Name: "searchArtworks",
Description: "Search for artworks by a specific artist",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"artist": map[string]interface{}{
"type": "string",
"description": "The artist's name",
},
},
"required": []string{"artist"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
artist := input["artist"].(string)
works, err := searchArtworks(ctx, artist)
if err != nil {
return nil, err
}

return map[string]interface{}{
"artist": artist,
"artworks": works,
}, nil
},
}

queries := []string{
"Show me paintings by Van Gogh",
"Find Picasso's work",
"What did Monet paint?",
}

for _, query := range queries {
fmt.Printf("\n=== Query: %s ===\n", query)

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
System: "You are an art search assistant.",
Prompt: query,
Tools: []types.Tool{searchTool},
})
if err != nil {
log.Fatal(err)
}

if len(result.ToolCalls) > 0 {
fmt.Printf("Routed to: %s with artist=%v\n",
result.ToolCalls[0].ToolName,
result.ToolCalls[0].Arguments["artist"])
}

fmt.Printf("Response: %s\n", result.Text)
}
}

Routing by Sequence​

Language models can perform multi-step routing for complex tasks:

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)

func main() {
ctx := context.Background()

provider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})
model, _ := provider.LanguageModel("claude-sonnet-4-5")

// Define sequential operations as tools
tools := []types.Tool{
{
Name: "lookupCalendar",
Description: "Check the user's calendar for availability",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"date": map[string]interface{}{
"type": "string",
"description": "Date to check (YYYY-MM-DD)",
},
},
"required": []string{"date"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
return map[string]interface{}{
"available": []string{"5:00 PM", "6:00 PM", "7:00 PM"},
}, nil
},
},
{
Name: "lookupFriendsCalendar",
Description: "Check friends' calendars for availability",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"friends": map[string]interface{}{
"type": "array",
"items": map[string]string{"type": "string"},
},
"date": map[string]interface{}{
"type": "string",
},
},
"required": []string{"friends", "date"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
return map[string]interface{}{
"mutuallyAvailable": []string{"6:00 PM", "7:00 PM"},
}, nil
},
},
{
Name: "searchNearbyPlaces",
Description: "Search for nearby happy hour spots",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"category": map[string]interface{}{
"type": "string",
},
"time": map[string]interface{}{
"type": "string",
},
},
"required": []string{"category"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
return map[string]interface{}{
"places": []map[string]interface{}{
{"name": "The Rooftop Bar", "address": "123 Main St"},
{"name": "Happy Hour Spot", "address": "456 Oak Ave"},
},
}, nil
},
},
{
Name: "createEvent",
Description: "Create a calendar event and send invites",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"title": map[string]interface{}{
"type": "string",
},
"date": map[string]interface{}{
"type": "string",
},
"time": map[string]interface{}{
"type": "string",
},
"location": map[string]interface{}{
"type": "string",
},
"attendees": map[string]interface{}{
"type": "array",
"items": map[string]string{"type": "string"},
},
},
"required": []string{"title", "date", "time", "location", "attendees"},
},
Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
return map[string]interface{}{
"eventId": "evt_123",
"status": "created",
"invites": "sent",
}, nil
},
},
}

// Create agent that routes through the sequence
eventAgent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
System: "You are a helpful calendar assistant. Use the available tools to help plan events.",
Tools: tools,
MaxSteps: 20,
OnToolCall: func(toolCall types.ToolCall) {
fmt.Printf("Step: Calling %s\n", toolCall.ToolName)
},
})

result, err := eventAgent.Execute(ctx, "Schedule a happy hour with Sarah and Mike for tomorrow evening. Find a good spot nearby.")
if err != nil {
log.Fatal(err)
}

fmt.Printf("\n=== Agent Response ===\n%s\n", result.Text)
fmt.Printf("\nCompleted in %d steps\n", len(result.Steps))
}

Output:

Step: Calling lookupCalendar
Step: Calling lookupFriendsCalendar
Step: Calling searchNearbyPlaces
Step: Calling createEvent

=== Agent Response ===
I've scheduled a happy hour for tomorrow at 6:00 PM at The Rooftop Bar (123 Main St).
Invites have been sent to Sarah and Mike.

Completed in 4 steps

Intent-Based Routing​

Route to different application logic based on user intent:

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

type RouteHandler func(context.Context, map[string]interface{}, types.ToolExecutionOptions) (interface{}, error)

type Route struct {
Name string
Description string
Parameters map[string]interface{}
Handler RouteHandler
}

func createApplicationRoutes() []types.Tool {
routes := []Route{
{
Name: "processPayment",
Description: "Process a payment transaction",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"amount": map[string]interface{}{
"type": "number",
"description": "Payment amount",
},
"currency": map[string]interface{}{
"type": "string",
"description": "Currency code (USD, EUR, etc.)",
},
},
"required": []string{"amount", "currency"},
},
Handler: func(ctx context.Context, params map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
amount := params["amount"].(float64)
currency := params["currency"].(string)

// Process payment
return map[string]interface{}{
"transactionId": "txn_123",
"amount": amount,
"currency": currency,
"status": "success",
}, nil
},
},
{
Name: "getOrderStatus",
Description: "Check the status of an order",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"orderId": map[string]interface{}{
"type": "string",
"description": "Order ID to check",
},
},
"required": []string{"orderId"},
},
Handler: func(ctx context.Context, params map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
orderId := params["orderId"].(string)

return map[string]interface{}{
"orderId": orderId,
"status": "shipped",
"eta": "2024-01-15",
}, nil
},
},
{
Name: "requestRefund",
Description: "Request a refund for an order",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"orderId": map[string]interface{}{
"type": "string",
},
"reason": map[string]interface{}{
"type": "string",
},
},
"required": []string{"orderId", "reason"},
},
Handler: func(ctx context.Context, params map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
orderId := params["orderId"].(string)
reason := params["reason"].(string)

return map[string]interface{}{
"refundId": "ref_456",
"orderId": orderId,
"reason": reason,
"status": "processing",
}, nil
},
},
}

// Convert routes to tools
var tools []types.Tool
for _, route := range routes {
r := route // Capture for closure
tools = append(tools, types.Tool{
Name: r.Name,
Description: r.Description,
Parameters: r.Parameters,
Execute: types.ToolExecutor(r.Handler),
})
}

return tools
}

func handleUserQuery(ctx context.Context, query string) error {
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")

tools := createApplicationRoutes()

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
System: "You are a customer service assistant. Route user requests to the appropriate function.",
Prompt: query,
Tools: tools,
})
if err != nil {
return err
}

fmt.Printf("Query: %s\n", query)
if len(result.ToolCalls) > 0 {
fmt.Printf("Routed to: %s\n", result.ToolCalls[0].ToolName)
fmt.Printf("Parameters: %v\n", result.ToolCalls[0].Arguments)
}
fmt.Printf("Response: %s\n\n", result.Text)

return nil
}

func main() {
ctx := context.Background()

queries := []string{
"I need to pay $50 in USD",
"Where is my order #12345?",
"I want to return order #67890 because it's damaged",
}

for _, query := range queries {
if err := handleUserQuery(ctx, query); err != nil {
log.Fatal(err)
}
}
}

Best Practices​

1. Clear Tool Descriptions​

// Good - Clear and specific
types.Tool{
Name: "searchProducts",
Description: "Search for products in the catalog by name, category, or description. Returns up to 10 matching products.",
}

// Bad - Vague
types.Tool{
Name: "search",
Description: "Search for things",
}

2. Validate Parameters​

Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
amount, ok := input["amount"].(float64)
if !ok || amount <= 0 {
return nil, fmt.Errorf("invalid amount")
}

// Process with validated parameter
return processPayment(ctx, amount)
},

3. Handle Routing Fallbacks​

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: query,
Tools: routes,
})

if len(result.ToolCalls) == 0 {
// No route matched - handle gracefully
return handleGeneralQuery(ctx, query)
}

4. Log Routing Decisions​

OnToolCall: func(toolCall types.ToolCall) {
log.Printf("Routing: %s -> %s (params: %v)",
userQuery,
toolCall.ToolName,
toolCall.Arguments)
},

5. Monitor Routing Accuracy​

type RoutingMetrics struct {
TotalRequests int64
RoutedCorrectly int64
RoutedIncorrectly int64
}

func (m *RoutingMetrics) RecordRouting(correct bool) {
m.TotalRequests++
if correct {
m.RoutedCorrectly++
} else {
m.RoutedIncorrectly++
}
}

Next Steps​