ollie/cmd/olliesrv/internal/agent/tool_match.go

108 lines
2.3 KiB
Go

package agent
import (
"sort"
"strings"
"sync"
"ollie/embedding"
"ollie/skills"
"ollie/toolsrv/protocol"
)
// Tool matching configuration.
const (
toolMatchThreshold = 0.35
toolMatchLimit = 5
)
var (
// Shared embedding model for tool matching.
toolEmbedModel *embedding.Model
toolEmbedErr error
toolEmbedOnce sync.Once
)
// getToolEmbedModel returns the shared embedding model, loading it on first call.
func getToolEmbedModel() *embedding.Model {
toolEmbedOnce.Do(func() {
toolEmbedModel, toolEmbedErr = embedding.LoadModel(skills.DefaultModelDir())
})
return toolEmbedModel
}
// matchTools finds tools relevant to the user's input from the loaded tools
// and returns a hint block telling the model to call them.
func matchTools(input string, tools []protocol.ToolInfo) string {
if len(tools) == 0 {
return ""
}
model := getToolEmbedModel()
if model == nil {
return ""
}
// Convert tools to embedding items
items := make([]embedding.Item, 0, len(tools))
for _, ti := range tools {
if ti.Description == "" {
continue
}
searchText := ti.Description
if ti.Prompt != "" {
searchText += " Usage: " + ti.Prompt
}
items = append(items, embedding.Item{
Name: ti.Name,
Description: searchText,
})
}
if len(items) == 0 {
return ""
}
// Build index and match
idx, err := embedding.NewIndex(model, items)
if err != nil {
return ""
}
workflowText := normalizeWorkflow(input).SearchText()
workflow := normalizeWorkflow(input)
searchInput := input
if workflowText != "" {
searchInput += " Workflow: " + workflowText
}
results, err := idx.Match(searchInput, toolMatchThreshold, toolMatchLimit)
if err != nil {
return ""
}
// Apply workflow adjustments
for i := range results {
results[i].Score += workflowToolAdjustment(results[i].Item.Name, workflow)
}
sort.SliceStable(results, func(i, j int) bool {
return results[i].Score > results[j].Score
})
if len(results) == 0 {
return ""
}
var sb strings.Builder
sb.WriteString("<tool-hints>\n")
sb.WriteString("Relevant tools for this request:\n\n")
for _, r := range results {
sb.WriteString("→ `")
sb.WriteString(r.Item.Name)
sb.WriteString("({...})` — ")
sb.WriteString(r.Item.Description)
sb.WriteString("\n")
}
sb.WriteString("</tool-hints>\n")
return sb.String()
}