Skip to content
Docs

Dynamic Tool Injection for AI Agents

AI agent platforms often face a challenge: static tool instructions in prompts cannot adapt to available tools, user permissions, or task context. When an agent sees tools it is not authorized to use, it wastes reasoning steps attempting calls that will fail. When it does not see tools that are available, it cannot leverage capabilities that would resolve the task. This mismatch between visible tools and actual capabilities results in 30-40% task failure rates.

Dynamic tool instruction injection solves this by adapting agent prompts at runtime based on context, permissions, and task requirements. Rather than giving every agent access to every tool (which overwhelms the model’s tool selection) or hardcoding tool sets per agent (which cannot adapt to user permissions), the injector selects relevant, authorized tools per request and generates tool instructions dynamically.

Beluga AI’s prompt package combined with tool registry enables dynamic tool injection. The system analyzes task context, filters tools by permissions, and injects relevant tool instructions into agent prompts at runtime. This ensures agents see only authorized, relevant tools for each specific task.

The architecture has three stages: context analysis (what kind of task is this?), permission filtering (what tools is this user allowed to use?), and prompt injection (generate tool instructions from the filtered set). This separation of concerns means permission policies can be updated without touching the relevance logic, and new tools can be added to the registry without modifying the injection pipeline.

┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Agent │───▶│ Context │───▶│ Tool │
│ Request │ │ Analyzer │ │ Selector │
└──────────────┘ └──────────────┘ └──────┬───────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Enhanced │◀───│ Prompt │◀───│ Permission │
│ Prompt │ │ Injector │ │ Filter │
└──────────────┘ └──────────────┘ └──────────────┘
┌─────┴────────┐
│ Tool │
│ Registry │
└──────────────┘

The tool injector analyzes task context, selects relevant tools, filters by permissions, and dynamically injects tool instructions into agent prompts. It uses Beluga AI’s prompt template system (prompt.NewPromptTemplate) for structured prompt generation and the tool registry (tool.Registry) for tool discovery. The permission checker runs between selection and injection, acting as a security boundary that ensures no unauthorized tool instructions reach the agent.

package main
import (
"context"
"fmt"
"github.com/lookatitude/beluga-ai/agent"
"github.com/lookatitude/beluga-ai/prompt"
"github.com/lookatitude/beluga-ai/tool"
_ "github.com/lookatitude/beluga-ai/tool/providers/http"
)
// ToolInstructionInjector dynamically injects tool instructions into prompts.
type ToolInstructionInjector struct {
toolRegistry tool.Registry
promptTemplate *prompt.PromptTemplate
permissionChecker *PermissionChecker
}
func NewToolInstructionInjector(ctx context.Context) (*ToolInstructionInjector, error) {
template, err := prompt.NewPromptTemplate(`
You are an AI agent with access to the following tools:
{{range .tools}}
- {{.Name}}: {{.Description}}
Usage: {{.Usage}}
{{end}}
Use these tools to complete the task: {{.task}}
`)
if err != nil {
return nil, fmt.Errorf("create prompt template: %w", err)
}
return &ToolInstructionInjector{
toolRegistry: tool.DefaultRegistry,
promptTemplate: template,
permissionChecker: NewPermissionChecker(),
}, nil
}
// InjectToolInstructions builds a context-aware prompt with relevant, authorized tools.
func (t *ToolInstructionInjector) InjectToolInstructions(
ctx context.Context,
basePrompt string,
userID string,
taskContext map[string]string,
) (string, error) {
// Select relevant tools based on task context
allTools := t.toolRegistry.List()
relevantTools := t.selectRelevantTools(ctx, allTools, taskContext)
// Filter by user permissions
var authorizedTools []tool.Tool
for _, tl := range relevantTools {
if t.permissionChecker.HasPermission(ctx, userID, tl.Name()) {
authorizedTools = append(authorizedTools, tl)
}
}
// Generate tool instructions
toolInstructions, err := t.promptTemplate.Format(map[string]any{
"tools": authorizedTools,
"task": taskContext["task"],
})
if err != nil {
return "", fmt.Errorf("format prompt: %w", err)
}
return basePrompt + "\n\n" + toolInstructions, nil
}
func (t *ToolInstructionInjector) selectRelevantTools(
ctx context.Context,
allTools []tool.Tool,
taskContext map[string]string,
) []tool.Tool {
var relevant []tool.Tool
taskType := taskContext["task_type"]
for _, tl := range allTools {
if t.isToolRelevant(tl, taskType) {
relevant = append(relevant, tl)
}
}
return relevant
}
func (t *ToolInstructionInjector) isToolRelevant(tl tool.Tool, taskType string) bool {
// Implement relevance logic based on tool metadata and task type
return true // Simplified
}

Integrate the tool injector with an agent to enable dynamic tool selection:

package main
import (
"context"
"fmt"
"github.com/lookatitude/beluga-ai/agent"
"github.com/lookatitude/beluga-ai/llm"
"github.com/lookatitude/beluga-ai/schema"
_ "github.com/lookatitude/beluga-ai/llm/providers/openai"
)
// ContextAwareAgent uses dynamic tool injection.
type ContextAwareAgent struct {
agent agent.Agent
injector *ToolInstructionInjector
userID string
}
func NewContextAwareAgent(ctx context.Context, userID string) (*ContextAwareAgent, error) {
model, err := llm.New("openai", nil)
if err != nil {
return nil, fmt.Errorf("create model: %w", err)
}
ag, err := agent.New("base", agent.Config{
Model: model,
Name: "context-aware-agent",
})
if err != nil {
return nil, fmt.Errorf("create agent: %w", err)
}
injector, err := NewToolInjectio nInjector(ctx)
if err != nil {
return nil, fmt.Errorf("create injector: %w", err)
}
return &ContextAwareAgent{
agent: ag,
injector: injector,
userID: userID,
}, nil
}
func (a *ContextAwareAgent) Execute(ctx context.Context, taskContext map[string]string) (string, error) {
basePrompt := "Complete the following task:"
// Inject tools dynamically based on context and permissions
enhancedPrompt, err := a.injector.InjectToolInstructions(
ctx,
basePrompt,
a.userID,
taskContext,
)
if err != nil {
return "", fmt.Errorf("inject tools: %w", err)
}
// Execute agent with enhanced prompt
msgs := []schema.Message{
&schema.SystemMessage{Parts: []schema.ContentPart{
schema.TextPart{Text: enhancedPrompt},
}},
&schema.HumanMessage{Parts: []schema.ContentPart{
schema.TextPart{Text: taskContext["task"]},
}},
}
resp, err := a.agent.Run(ctx, msgs)
if err != nil {
return "", fmt.Errorf("agent execution: %w", err)
}
return resp.Parts[0].(schema.TextPart).Text, nil
}

Implement robust permission checking to ensure users only see authorized tools:

package main
import (
"context"
"sync"
)
type PermissionChecker struct {
mu sync.RWMutex
permissions map[string]map[string]bool // userID -> toolName -> allowed
}
func NewPermissionChecker() *PermissionChecker {
return &PermissionChecker{
permissions: make(map[string]map[string]bool),
}
}
func (p *PermissionChecker) HasPermission(ctx context.Context, userID, toolName string) bool {
p.mu.RLock()
defer p.mu.RUnlock()
userPerms, ok := p.permissions[userID]
if !ok {
return false
}
return userPerms[toolName]
}
func (p *PermissionChecker) GrantPermission(userID, toolName string) {
p.mu.Lock()
defer p.mu.Unlock()
if p.permissions[userID] == nil {
p.permissions[userID] = make(map[string]bool)
}
p.permissions[userID][toolName] = true
}

Track tool injection metrics to monitor performance and selection quality:

import (
"github.com/lookatitude/beluga-ai/o11y"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
func (t *ToolInjectio nInjector) InjectWithMonitoring(
ctx context.Context,
basePrompt string,
userID string,
taskContext map[string]string,
) (string, error) {
tracer := otel.Tracer("tool-injection")
ctx, span := tracer.Start(ctx, "tool.inject")
defer span.End()
span.SetAttributes(
attribute.String("user_id", userID),
attribute.String("task_type", taskContext["task_type"]),
)
enhancedPrompt, err := t.InjectToolInstructions(ctx, basePrompt, userID, taskContext)
if err != nil {
span.RecordError(err)
return "", err
}
// Track injection success
span.SetAttributes(attribute.Bool("injection_success", true))
return enhancedPrompt, nil
}

Implement relevance scoring to ensure high-quality tool selection:

func (t *ToolInstructionInjector) selectRelevantToolsWithScoring(
ctx context.Context,
allTools []tool.Tool,
taskContext map[string]string,
) []tool.Tool {
type scoredTool struct {
tool tool.Tool
score float64
}
var scored []scoredTool
for _, tl := range allTools {
score := t.calculateRelevanceScore(tl, taskContext)
if score > 0.5 { // Threshold
scored = append(scored, scoredTool{tool: tl, score: score})
}
}
// Sort by score and return top K
sort.Slice(scored, func(i, j int) bool {
return scored[i].score > scored[j].score
})
maxTools := 10
if len(scored) < maxTools {
maxTools = len(scored)
}
result := make([]tool.Tool, maxTools)
for i := 0; i < maxTools; i++ {
result[i] = scored[i].tool
}
return result
}
func (t *ToolInstructionInjector) calculateRelevanceScore(
tl tool.Tool,
taskContext map[string]string,
) float64 {
// Implement scoring based on tool metadata, task type, and historical usage
return 0.8 // Simplified
}

Dynamic tool injection delivered significant improvements:

MetricBeforeAfterImprovement
Task Success Rate (%)60-708724-45%
Tool Selection Accuracy (%)659242%
Permission Violations15/month0100% reduction
Context Relevance (%)609152%
Agent Efficiency Score6/108.7/1045%