Files
Enterprise-Knowledge-Base/internal/agent/tools/skill_execute.go
T
nullkey 33f7e7da66 feat: support agent skills
feat:support agent skills
2026-02-04 20:08:49 +08:00

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
}