orca.ai/pkg/actor/tool_worker.go
大森 6b94476347 Initial commit: Orca Agent Framework
Core features:
- Microkernel architecture with Actor model
- Session management with JSONL persistence
- Tool system (5 built-in tools)
- Skill system with SKILL.md parsing
- Sandbox security execution
- Ollama integration with gemma4:e4b
- Prompt-based tool calling (compatible with native function calling)
- REPL interface

11 packages, all tests passing
2026-05-08 00:55:48 +08:00

154 lines
4.4 KiB
Go

package actor
import (
"context"
"encoding/json"
"fmt"
"github.com/orca/orca/pkg/bus"
"github.com/orca/orca/pkg/tool"
)
// ToolWorker is an agent that processes tool call messages by executing
// tools through the tool.Manager.
//
// It implements the Agent interface and handles MsgTypeToolCall messages.
// When a tool call is received, it extracts the tool name and arguments
// from the message content, executes the tool via the Manager, and
// returns a MsgTypeToolResult with the execution result.
type ToolWorker struct {
*BaseAgent
manager *tool.Manager
}
// NewToolWorker creates a new ToolWorker agent with the given id and tool manager.
// The agent is started automatically upon creation.
func NewToolWorker(id string, manager *tool.Manager) *ToolWorker {
w := &ToolWorker{
BaseAgent: NewBaseAgent(id, "tool_worker"),
manager: manager,
}
w.SetHandler(w.handleMessage)
if err := w.Start(); err != nil {
panic(fmt.Sprintf("tool_worker: failed to start: %v", err))
}
return w
}
// handleMessage routes incoming messages to the appropriate handler.
func (w *ToolWorker) handleMessage(ctx context.Context, msg bus.Message) (bus.Message, error) {
switch msg.Type {
case bus.MsgTypeToolCall:
return w.handleToolCall(ctx, msg)
case bus.MsgTypeTaskRequest:
return w.handleTask(ctx, msg)
case bus.MsgTypeSystem:
return w.handleSystem(ctx, msg)
default:
return bus.Message{}, fmt.Errorf("tool_worker %s: unsupported message type %s", w.ID(), msg.Type)
}
}
// handleToolCall processes a tool call by executing the named tool
// with the provided arguments.
//
// The msg.Content is expected to contain a JSON object with:
// - "name": the tool name (string)
// - "arguments": the tool arguments (object)
//
// Or alternatively, msg.Content can be a string in the format:
// tool_name(arg1=val1, arg2=val2)
func (w *ToolWorker) handleToolCall(ctx context.Context, msg bus.Message) (bus.Message, error) {
w.setStatus(StatusWaitingForTool)
defer w.setStatus(StatusProcessing)
toolName, args, err := parseToolCallContent(msg.Content)
if err != nil {
return bus.Message{
ID: msg.ID + "-result",
Type: bus.MsgTypeToolResult,
From: w.ID(),
To: msg.From,
Content: map[string]interface{}{"error": err.Error()},
}, nil
}
// Execute the tool
result, err := w.manager.Execute(toolName, ctx, args)
if err != nil {
return bus.Message{
ID: msg.ID + "-result",
Type: bus.MsgTypeToolResult,
From: w.ID(),
To: msg.From,
Content: map[string]interface{}{"error": err.Error()},
}, nil
}
return bus.Message{
ID: msg.ID + "-result",
Type: bus.MsgTypeToolResult,
From: w.ID(),
To: msg.From,
Content: result,
}, nil
}
// parseToolCallContent extracts the tool name and arguments from various
// content formats.
func parseToolCallContent(content interface{}) (string, map[string]interface{}, error) {
switch v := content.(type) {
case map[string]interface{}:
// Format: {"name": "tool_name", "arguments": {...}}
name, ok := v["name"].(string)
if !ok || name == "" {
return "", nil, fmt.Errorf("tool call content missing 'name' field")
}
args, _ := v["arguments"].(map[string]interface{})
if args == nil {
args = make(map[string]interface{})
}
return name, args, nil
case string:
// Try JSON format
var parsed map[string]interface{}
if err := json.Unmarshal([]byte(v), &parsed); err == nil {
name, ok := parsed["name"].(string)
if ok && name != "" {
args, _ := parsed["arguments"].(map[string]interface{})
if args == nil {
args = make(map[string]interface{})
}
return name, args, nil
}
}
return "", nil, fmt.Errorf("cannot parse tool call from string content: %s", v)
default:
return "", nil, fmt.Errorf("unsupported tool call content type: %T", content)
}
}
// handleTask processes a task request by returning a task response.
func (w *ToolWorker) handleTask(ctx context.Context, msg bus.Message) (bus.Message, error) {
return bus.Message{
ID: msg.ID + "-response",
Type: bus.MsgTypeTaskResponse,
From: w.ID(),
To: msg.From,
Content: msg.Content,
}, nil
}
// handleSystem processes internal system messages.
func (w *ToolWorker) handleSystem(ctx context.Context, msg bus.Message) (bus.Message, error) {
return bus.Message{
ID: msg.ID + "-ack",
Type: bus.MsgTypeSystem,
From: w.ID(),
To: msg.From,
Content: "tool_worker acknowledged",
}, nil
}