33f7e7da66
feat:support agent skills
190 lines
5.6 KiB
Go
190 lines
5.6 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/Tencent/WeKnora/internal/agent/skills"
|
|
"github.com/Tencent/WeKnora/internal/logger"
|
|
"github.com/Tencent/WeKnora/internal/types"
|
|
"github.com/Tencent/WeKnora/internal/utils"
|
|
)
|
|
|
|
// Tool name constant for execute_skill_script
|
|
|
|
var executeSkillScriptTool = BaseTool{
|
|
name: ToolExecuteSkillScript,
|
|
description: `Execute a script from a skill in a sandboxed environment.
|
|
|
|
## Usage
|
|
- Use this tool to run utility scripts bundled with a skill
|
|
- Scripts are executed in an isolated sandbox for security
|
|
- Only scripts from loaded skills can be executed
|
|
|
|
## When to Use
|
|
- When a skill's instructions reference a utility script (e.g., "Run scripts/analyze_form.py")
|
|
- When automation or data processing is needed as part of skill workflow
|
|
- For deterministic operations where script execution is more reliable than generating code
|
|
|
|
## Security
|
|
- Scripts run in a sandboxed environment with limited permissions
|
|
- Network access is disabled by default
|
|
- File access is restricted to the skill directory
|
|
|
|
## Returns
|
|
- Script stdout and stderr output
|
|
- Exit code indicating success (0) or failure (non-zero)`,
|
|
schema: utils.GenerateSchema[ExecuteSkillScriptInput](),
|
|
}
|
|
|
|
// ExecuteSkillScriptInput defines the input parameters for the execute_skill_script tool
|
|
type ExecuteSkillScriptInput struct {
|
|
SkillName string `json:"skill_name" jsonschema:"Name of the skill containing the script"`
|
|
ScriptPath string `json:"script_path" jsonschema:"Relative path to the script within the skill directory (e.g. scripts/analyze.py)"`
|
|
Args []string `json:"args,omitempty" jsonschema:"Optional command-line arguments to pass to the script"`
|
|
}
|
|
|
|
// ExecuteSkillScriptTool allows the agent to execute skill scripts in a sandbox
|
|
type ExecuteSkillScriptTool struct {
|
|
BaseTool
|
|
skillManager *skills.Manager
|
|
}
|
|
|
|
// NewExecuteSkillScriptTool creates a new execute_skill_script tool instance
|
|
func NewExecuteSkillScriptTool(skillManager *skills.Manager) *ExecuteSkillScriptTool {
|
|
return &ExecuteSkillScriptTool{
|
|
BaseTool: executeSkillScriptTool,
|
|
skillManager: skillManager,
|
|
}
|
|
}
|
|
|
|
// Execute executes the execute_skill_script tool
|
|
func (t *ExecuteSkillScriptTool) Execute(ctx context.Context, args json.RawMessage) (*types.ToolResult, error) {
|
|
logger.Infof(ctx, "[Tool][ExecuteSkillScript] Execute started")
|
|
|
|
// Parse input
|
|
var input ExecuteSkillScriptInput
|
|
if err := json.Unmarshal(args, &input); err != nil {
|
|
logger.Errorf(ctx, "[Tool][ExecuteSkillScript] Failed to parse args: %v", err)
|
|
return &types.ToolResult{
|
|
Success: false,
|
|
Error: fmt.Sprintf("Failed to parse args: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// Validate required fields
|
|
if input.SkillName == "" {
|
|
return &types.ToolResult{
|
|
Success: false,
|
|
Error: "skill_name is required",
|
|
}, nil
|
|
}
|
|
|
|
if input.ScriptPath == "" {
|
|
return &types.ToolResult{
|
|
Success: false,
|
|
Error: "script_path is required",
|
|
}, nil
|
|
}
|
|
|
|
// Check if skill manager is available
|
|
if t.skillManager == nil || !t.skillManager.IsEnabled() {
|
|
return &types.ToolResult{
|
|
Success: false,
|
|
Error: "Skills are not enabled",
|
|
}, nil
|
|
}
|
|
|
|
// Execute the script in sandbox
|
|
logger.Infof(ctx, "[Tool][ExecuteSkillScript] Executing script: %s/%s with args: %v",
|
|
input.SkillName, input.ScriptPath, input.Args)
|
|
|
|
result, err := t.skillManager.ExecuteScript(ctx, input.SkillName, input.ScriptPath, input.Args)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "[Tool][ExecuteSkillScript] Script execution failed: %v", err)
|
|
return &types.ToolResult{
|
|
Success: false,
|
|
Error: fmt.Sprintf("Script execution failed: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// Build output
|
|
var builder strings.Builder
|
|
builder.WriteString(fmt.Sprintf("=== Script Execution: %s/%s ===\n\n", input.SkillName, input.ScriptPath))
|
|
|
|
if len(input.Args) > 0 {
|
|
builder.WriteString(fmt.Sprintf("**Arguments**: %v\n", input.Args))
|
|
}
|
|
|
|
builder.WriteString(fmt.Sprintf("**Exit Code**: %d\n", result.ExitCode))
|
|
builder.WriteString(fmt.Sprintf("**Duration**: %v\n\n", result.Duration))
|
|
|
|
if result.Killed {
|
|
builder.WriteString("**Warning**: Script was terminated (timeout or killed)\n\n")
|
|
}
|
|
|
|
if result.Stdout != "" {
|
|
builder.WriteString("## Standard Output\n\n")
|
|
builder.WriteString("```\n")
|
|
builder.WriteString(result.Stdout)
|
|
if !strings.HasSuffix(result.Stdout, "\n") {
|
|
builder.WriteString("\n")
|
|
}
|
|
builder.WriteString("```\n\n")
|
|
}
|
|
|
|
if result.Stderr != "" {
|
|
builder.WriteString("## Standard Error\n\n")
|
|
builder.WriteString("```\n")
|
|
builder.WriteString(result.Stderr)
|
|
if !strings.HasSuffix(result.Stderr, "\n") {
|
|
builder.WriteString("\n")
|
|
}
|
|
builder.WriteString("```\n\n")
|
|
}
|
|
|
|
if result.Error != "" {
|
|
builder.WriteString("## Error\n\n")
|
|
builder.WriteString(result.Error)
|
|
builder.WriteString("\n")
|
|
}
|
|
|
|
// Determine success based on exit code
|
|
success := result.IsSuccess()
|
|
|
|
resultData := map[string]interface{}{
|
|
"skill_name": input.SkillName,
|
|
"script_path": input.ScriptPath,
|
|
"args": input.Args,
|
|
"exit_code": result.ExitCode,
|
|
"stdout": result.Stdout,
|
|
"stderr": result.Stderr,
|
|
"duration_ms": result.Duration.Milliseconds(),
|
|
"killed": result.Killed,
|
|
}
|
|
|
|
logger.Infof(ctx, "[Tool][ExecuteSkillScript] Script completed with exit code: %d", result.ExitCode)
|
|
|
|
return &types.ToolResult{
|
|
Success: success,
|
|
Output: builder.String(),
|
|
Data: resultData,
|
|
Error: func() string {
|
|
if !success {
|
|
if result.Error != "" {
|
|
return result.Error
|
|
}
|
|
return fmt.Sprintf("Script exited with code %d", result.ExitCode)
|
|
}
|
|
return ""
|
|
}(),
|
|
}, nil
|
|
}
|
|
|
|
// Cleanup releases any resources
|
|
func (t *ExecuteSkillScriptTool) Cleanup(ctx context.Context) error {
|
|
return nil
|
|
}
|