mirror of
https://github.com/ollama/ollama.git
synced 2026-07-06 07:41:05 +00:00
137 lines
3.2 KiB
Go
137 lines
3.2 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/ollama/ollama/agent"
|
|
"github.com/ollama/ollama/agent/skills"
|
|
"github.com/ollama/ollama/api"
|
|
)
|
|
|
|
type Skill struct {
|
|
catalog *skills.Catalog
|
|
}
|
|
|
|
func NewSkill(catalog *skills.Catalog) *Skill {
|
|
return &Skill{catalog: catalog}
|
|
}
|
|
|
|
func (s *Skill) Name() string {
|
|
return "skill"
|
|
}
|
|
|
|
func (s *Skill) Description() string {
|
|
return "Load the full SKILL.md instructions for an installed agent skill by name."
|
|
}
|
|
|
|
func (s *Skill) Schema() api.ToolFunction {
|
|
props := api.NewToolPropertiesMap()
|
|
props.Set("name", api.ToolProperty{
|
|
Type: api.PropertyType{"string"},
|
|
Description: "Name of the skill to load.",
|
|
})
|
|
return api.ToolFunction{
|
|
Name: s.Name(),
|
|
Description: s.Description(),
|
|
Parameters: api.ToolFunctionParameters{
|
|
Type: "object",
|
|
Required: []string{"name"},
|
|
Properties: props,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (s *Skill) Execute(_ context.Context, _ agent.ToolContext, args map[string]any) (agent.ToolResult, error) {
|
|
name, _ := args["name"].(string)
|
|
name = skills.NormalizeName(name)
|
|
if name == "" {
|
|
return agent.ToolResult{}, fmt.Errorf("name parameter is required")
|
|
}
|
|
if s.catalog == nil || s.catalog.Empty() {
|
|
return agent.ToolResult{}, fmt.Errorf("no skills are installed")
|
|
}
|
|
|
|
skill, ok := s.catalog.Find(name)
|
|
if !ok {
|
|
return agent.ToolResult{}, fmt.Errorf("unknown skill: %s", name)
|
|
}
|
|
content, err := SkillResultContent(skill)
|
|
if err != nil {
|
|
return agent.ToolResult{}, err
|
|
}
|
|
return agent.ToolResult{Content: content}, nil
|
|
}
|
|
|
|
func ManualSkillMessages(skill skills.Skill, request string, ordinal int) ([]api.Message, error) {
|
|
content, err := SkillResultContent(skill)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
args := api.NewToolCallFunctionArguments()
|
|
args.Set("name", skill.Name)
|
|
callID := manualSkillToolCallID(skill.Name, ordinal)
|
|
|
|
userContent := strings.TrimSpace(request)
|
|
if userContent == "" {
|
|
userContent = fmt.Sprintf("Use the %s skill.", skill.Name)
|
|
}
|
|
|
|
return []api.Message{
|
|
{Role: "user", Content: userContent},
|
|
{
|
|
Role: "assistant",
|
|
ToolCalls: []api.ToolCall{{
|
|
ID: callID,
|
|
Function: api.ToolCallFunction{
|
|
Name: "skill",
|
|
Arguments: args,
|
|
},
|
|
}},
|
|
},
|
|
{Role: "tool", ToolName: "skill", ToolCallID: callID, Content: content},
|
|
}, nil
|
|
}
|
|
|
|
func SkillResultContent(skill skills.Skill) (string, error) {
|
|
content, err := skill.Read()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString("Loaded skill: ")
|
|
b.WriteString(skill.Name)
|
|
b.WriteByte('\n')
|
|
b.WriteString("Skill directory: ")
|
|
b.WriteString(skill.Dir)
|
|
b.WriteString("\nResolve relative file references from the skill directory.\n\n")
|
|
b.WriteString(content)
|
|
return b.String(), nil
|
|
}
|
|
|
|
func manualSkillToolCallID(skillName string, ordinal int) string {
|
|
name := strings.Trim(strings.Map(func(r rune) rune {
|
|
switch {
|
|
case r >= 'a' && r <= 'z':
|
|
return r
|
|
case r >= 'A' && r <= 'Z':
|
|
return r
|
|
case r >= '0' && r <= '9':
|
|
return r
|
|
case r == '-' || r == '_':
|
|
return r
|
|
default:
|
|
return '-'
|
|
}
|
|
}, skillName), "-")
|
|
if name == "" {
|
|
name = "skill"
|
|
}
|
|
if ordinal <= 0 {
|
|
return "manual-skill-" + name
|
|
}
|
|
return fmt.Sprintf("manual-skill-%d-%s", ordinal, name)
|
|
}
|