mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-04 00:03:59 +00:00
feat: expose tool owner plugin in stage context
- add Plugin field to ToolDef, ToolCall, ToolResult - track tool owner in StageHost - annotate before/after_toolcall stage context with tool plugin - add RegisterStageOwnTools() for plugin-scoped tool listeners - keep interrupt input source/output channel context in stage messages
This commit is contained in:
@ -249,6 +249,17 @@ sdk.RegisterTool("weather_query", sdk.ToolDef{
|
|||||||
| `before_output` | 输出前 | 格式适配 |
|
| `before_output` | 输出前 | 格式适配 |
|
||||||
| `after_output` | 输出后 | 统计日志 |
|
| `after_output` | 输出后 | 统计日志 |
|
||||||
|
|
||||||
|
其中 `before_toolcall` / `after_toolcall` 阶段的 `StageContext` 会附带当前工具归属插件:
|
||||||
|
- `ctx.ToolCalls[i].Plugin`
|
||||||
|
- `ctx.ToolResults[i].Plugin`
|
||||||
|
|
||||||
|
如果只想监听**当前插件自己的工具调用**,可使用:
|
||||||
|
|
||||||
|
```go
|
||||||
|
s.RegisterStageOwnTools(sdk.StageBeforeToolcall, handler)
|
||||||
|
s.RegisterStageOwnTools(sdk.StageAfterToolcall, handler)
|
||||||
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sdk.RegisterStage(sdk.StageOnInput, func(ctx *sdk.StageContext) error {
|
sdk.RegisterStage(sdk.StageOnInput, func(ctx *sdk.StageContext) error {
|
||||||
if ctx.UserID == "blocked_user" {
|
if ctx.UserID == "blocked_user" {
|
||||||
|
|||||||
@ -674,6 +674,11 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri
|
|||||||
"total_tokens": resp.TokenUsage.Total,
|
"total_tokens": resp.TokenUsage.Total,
|
||||||
}
|
}
|
||||||
stageCtx.ToolCalls = convertToolCalls(resp.ToolCalls)
|
stageCtx.ToolCalls = convertToolCalls(resp.ToolCalls)
|
||||||
|
for i := range stageCtx.ToolCalls {
|
||||||
|
if stageCtx.ToolCalls[i].Plugin == "" {
|
||||||
|
stageCtx.ToolCalls[i].Plugin = a.resolveToolPlugin(stageCtx.ToolCalls[i].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
if a.runStage(sdk.StagePostAction, stageCtx) {
|
if a.runStage(sdk.StagePostAction, stageCtx) {
|
||||||
return *stageCtx.Response, toolsUsed, nil
|
return *stageCtx.Response, toolsUsed, nil
|
||||||
}
|
}
|
||||||
@ -686,10 +691,11 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri
|
|||||||
|
|
||||||
for _, tc := range resp.ToolCalls {
|
for _, tc := range resp.ToolCalls {
|
||||||
toolsUsed = append(toolsUsed, tc.Name)
|
toolsUsed = append(toolsUsed, tc.Name)
|
||||||
log.Printf("[agent] executing tool: %s (id=%s)", tc.Name, tc.ID)
|
pluginName := a.resolveToolPlugin(tc.Name)
|
||||||
|
log.Printf("[agent] executing tool: %s (plugin=%s, id=%s)", tc.Name, pluginName, tc.ID)
|
||||||
|
|
||||||
// === Stage: before_toolcall — 插件可拒绝/改参 ===
|
// === Stage: before_toolcall — 插件可拒绝/改参 ===
|
||||||
sdkTC := sdk.ToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments}
|
sdkTC := sdk.ToolCall{ID: tc.ID, Name: tc.Name, Plugin: pluginName, Arguments: tc.Arguments}
|
||||||
stageCtx.ToolCalls = []sdk.ToolCall{sdkTC}
|
stageCtx.ToolCalls = []sdk.ToolCall{sdkTC}
|
||||||
stageCtx.ToolResults = nil
|
stageCtx.ToolResults = nil
|
||||||
if a.runStage(sdk.StageBeforeToolcall, stageCtx) {
|
if a.runStage(sdk.StageBeforeToolcall, stageCtx) {
|
||||||
@ -698,6 +704,7 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri
|
|||||||
msgs = append(msgs, agentAPI.Message{Role: "tool", ToolCallID: tc.ID, Content: result})
|
msgs = append(msgs, agentAPI.Message{Role: "tool", ToolCallID: tc.ID, Content: result})
|
||||||
a.publishEvent(events.EventToolCall, map[string]interface{}{
|
a.publishEvent(events.EventToolCall, map[string]interface{}{
|
||||||
"tool": tc.Name,
|
"tool": tc.Name,
|
||||||
|
"plugin": pluginName,
|
||||||
"args": tc.Arguments,
|
"args": tc.Arguments,
|
||||||
"result": result,
|
"result": result,
|
||||||
"status": "denied",
|
"status": "denied",
|
||||||
@ -710,7 +717,7 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri
|
|||||||
log.Printf("[agent] tool %s result: %s", tc.Name, truncateStr(result, 100))
|
log.Printf("[agent] tool %s result: %s", tc.Name, truncateStr(result, 100))
|
||||||
|
|
||||||
// === Stage: after_toolcall — 插件可改结果 ===
|
// === Stage: after_toolcall — 插件可改结果 ===
|
||||||
stageCtx.ToolResults = []sdk.ToolResult{{CallID: tc.ID, Name: tc.Name, Success: true, Result: result}}
|
stageCtx.ToolResults = []sdk.ToolResult{{CallID: tc.ID, Name: tc.Name, Plugin: pluginName, Success: true, Result: result}}
|
||||||
a.runStage(sdk.StageAfterToolcall, stageCtx)
|
a.runStage(sdk.StageAfterToolcall, stageCtx)
|
||||||
if len(stageCtx.ToolResults) > 0 {
|
if len(stageCtx.ToolResults) > 0 {
|
||||||
if r, ok := stageCtx.ToolResults[0].Result.(string); ok {
|
if r, ok := stageCtx.ToolResults[0].Result.(string); ok {
|
||||||
@ -723,6 +730,7 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri
|
|||||||
|
|
||||||
a.publishEvent(events.EventToolCall, map[string]interface{}{
|
a.publishEvent(events.EventToolCall, map[string]interface{}{
|
||||||
"tool": tc.Name,
|
"tool": tc.Name,
|
||||||
|
"plugin": pluginName,
|
||||||
"args": tc.Arguments,
|
"args": tc.Arguments,
|
||||||
"result": result,
|
"result": result,
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
@ -2577,6 +2585,18 @@ func truncateStr(s string, max int) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Agent) resolveToolPlugin(name string) string {
|
||||||
|
if a.stageHost != nil {
|
||||||
|
if plugin := a.stageHost.ToolPlugin(name); plugin != "" {
|
||||||
|
return plugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx := strings.IndexByte(name, '_'); idx > 0 {
|
||||||
|
return name[:idx]
|
||||||
|
}
|
||||||
|
return "core"
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Agent) injectSourceContext(stageCtx *sdk.StageContext, evt *agentIO.InputEvent) {
|
func (a *Agent) injectSourceContext(stageCtx *sdk.StageContext, evt *agentIO.InputEvent) {
|
||||||
if stageCtx == nil || evt == nil {
|
if stageCtx == nil || evt == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -8,16 +8,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StageHost struct {
|
type StageHost struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
toolDefs []sdk.ToolDef
|
toolDefs []sdk.ToolDef
|
||||||
tools map[string]sdk.ToolHandler
|
tools map[string]sdk.ToolHandler
|
||||||
stages map[sdk.Stage][]sdk.StageHandler
|
toolPlugins map[string]string
|
||||||
|
stages map[sdk.Stage][]sdk.StageHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStageHost() *StageHost {
|
func NewStageHost() *StageHost {
|
||||||
return &StageHost{
|
return &StageHost{
|
||||||
tools: make(map[string]sdk.ToolHandler),
|
tools: make(map[string]sdk.ToolHandler),
|
||||||
stages: make(map[sdk.Stage][]sdk.StageHandler),
|
toolPlugins: make(map[string]string),
|
||||||
|
stages: make(map[sdk.Stage][]sdk.StageHandler),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +29,11 @@ func (h *StageHost) RegisterTool(name string, def sdk.ToolDef, handler sdk.ToolH
|
|||||||
if _, exists := h.tools[name]; exists {
|
if _, exists := h.tools[name]; exists {
|
||||||
return fmt.Errorf("tool %s already registered", name)
|
return fmt.Errorf("tool %s already registered", name)
|
||||||
}
|
}
|
||||||
|
if def.Plugin == "" {
|
||||||
|
def.Plugin = inferToolPlugin(name)
|
||||||
|
}
|
||||||
h.tools[name] = handler
|
h.tools[name] = handler
|
||||||
|
h.toolPlugins[name] = def.Plugin
|
||||||
h.toolDefs = append(h.toolDefs, def)
|
h.toolDefs = append(h.toolDefs, def)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -59,6 +65,21 @@ func (h *StageHost) ExecuteTool(name string, args map[string]interface{}) (inter
|
|||||||
return handler(args)
|
return handler(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *StageHost) ToolPlugin(name string) string {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
return h.toolPlugins[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferToolPlugin(name string) string {
|
||||||
|
for i := 0; i < len(name); i++ {
|
||||||
|
if name[i] == '_' {
|
||||||
|
return name[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// RunStage 并行调用同阶段所有注册的处理函数。
|
// RunStage 并行调用同阶段所有注册的处理函数。
|
||||||
// 各 handler 共享 *StageContext,通过其内置 RWMutex 安全读写:
|
// 各 handler 共享 *StageContext,通过其内置 RWMutex 安全读写:
|
||||||
// - 只读操作先调用 ctx.RLock() / defer ctx.RUnlock()
|
// - 只读操作先调用 ctx.RLock() / defer ctx.RUnlock()
|
||||||
|
|||||||
@ -63,18 +63,21 @@ type MemItem struct {
|
|||||||
type ToolCall struct {
|
type ToolCall struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Plugin string `json:"plugin,omitempty"`
|
||||||
Arguments map[string]interface{} `json:"arguments"`
|
Arguments map[string]interface{} `json:"arguments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolResult struct {
|
type ToolResult struct {
|
||||||
CallID string `json:"call_id"`
|
CallID string `json:"call_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Plugin string `json:"plugin,omitempty"`
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Result interface{} `json:"result"`
|
Result interface{} `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolDef struct {
|
type ToolDef struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Plugin string `json:"plugin,omitempty"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Parameters map[string]interface{} `json:"parameters"`
|
Parameters map[string]interface{} `json:"parameters"`
|
||||||
}
|
}
|
||||||
@ -225,6 +228,9 @@ func (s *PluginSDK) LLM() LLMAPI { return s.llm }
|
|||||||
func (s *PluginSDK) Settings() SettingsAPI { return s.sett }
|
func (s *PluginSDK) Settings() SettingsAPI { return s.sett }
|
||||||
|
|
||||||
func (s *PluginSDK) RegisterTool(name string, def ToolDef, handler ToolHandler) error {
|
func (s *PluginSDK) RegisterTool(name string, def ToolDef, handler ToolHandler) error {
|
||||||
|
if def.Plugin == "" {
|
||||||
|
def.Plugin = s.name
|
||||||
|
}
|
||||||
if s.regTool != nil {
|
if s.regTool != nil {
|
||||||
return s.regTool(name, def, handler)
|
return s.regTool(name, def, handler)
|
||||||
}
|
}
|
||||||
@ -237,6 +243,33 @@ func (s *PluginSDK) RegisterStage(stage Stage, handler StageHandler) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterStageOwnTools 仅在 before_toolcall / after_toolcall 阶段监听当前插件自己的工具调用。
|
||||||
|
// 其他阶段会退化为普通 RegisterStage。
|
||||||
|
func (s *PluginSDK) RegisterStageOwnTools(stage Stage, handler StageHandler) {
|
||||||
|
if s.regStage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if stage != StageBeforeToolcall && stage != StageAfterToolcall {
|
||||||
|
s.regStage(stage, handler)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.regStage(stage, func(ctx *StageContext) error {
|
||||||
|
ctx.RLock()
|
||||||
|
match := false
|
||||||
|
switch stage {
|
||||||
|
case StageBeforeToolcall:
|
||||||
|
match = len(ctx.ToolCalls) > 0 && ctx.ToolCalls[0].Plugin == s.name
|
||||||
|
case StageAfterToolcall:
|
||||||
|
match = len(ctx.ToolResults) > 0 && ctx.ToolResults[0].Plugin == s.name
|
||||||
|
}
|
||||||
|
ctx.RUnlock()
|
||||||
|
if !match {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return handler(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PluginSDK) RegisterPluginAPI(name string) error {
|
func (s *PluginSDK) RegisterPluginAPI(name string) error {
|
||||||
if s.regAPI != nil {
|
if s.regAPI != nil {
|
||||||
return s.regAPI(name)
|
return s.regAPI(name)
|
||||||
|
|||||||
Reference in New Issue
Block a user