mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
feat: 插件工具按需拉取(get_plugin_tools) + 设备命令类型拆分(shell-cmd/homeagent-cmd)
1. 提示词去污染: buildToolCatalog 从全量工具定义改为按插件分组摘要 (插件名 + 工具数 + 能力概览), 完整工具定义由新工具 get_plugin_tools(plugin_name) 动态拉取; 新增 executeGetPluginTools 支持按插件过滤 stageHost/io 工具 2. 设备命令类型: device_ctl_cmdrun 的 command 支持前缀区分 - shell-cmd <cmd> -> 设备端执行原生 shell - homeagent-<cap> -> 设备端 HomeAgent 内置能力(如 camerasue/screensue) - homeagent-cmd <cap> -> 同上(兼容写法) PushCmd 增加 cmd_type 字段下发给设备端分发 3. 测试: TestExecuteGetPluginTools 验证按插件拉取/全部摘要/未知插件
This commit is contained in:
33
internal/agent/core/gpttest_test.go
Normal file
33
internal/agent/core/gpttest_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||
)
|
||||
|
||||
func TestExecuteGetPluginTools(tmp *testing.T) {
|
||||
sh := NewStageHost()
|
||||
sh.RegisterTool("weather_current", sdk.ToolDef{Name: "weather_current", Plugin: "weather", Description: "当前天气", Parameters: map[string]interface{}{"type": "object"}}, nil)
|
||||
sh.RegisterTool("weather_forecast", sdk.ToolDef{Name: "weather_forecast", Plugin: "weather", Description: "天气预报"}, nil)
|
||||
sh.RegisterTool("device_ctl_cmdrun", sdk.ToolDef{Name: "device_ctl_cmdrun", Plugin: "remotedevice", Description: "下发命令"}, nil)
|
||||
|
||||
a := New(AgentConfig{ID: "t", StageHost: sh})
|
||||
|
||||
out := a.executeGetPluginTools("weather")
|
||||
if !strings.Contains(out, "weather_current") || !strings.Contains(out, "weather_forecast") {
|
||||
tmp.Fatalf("weather tools missing:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "device_ctl_cmdrun") {
|
||||
tmp.Fatalf("should not contain other plugin:\n%s", out)
|
||||
}
|
||||
all := a.executeGetPluginTools("")
|
||||
if !strings.Contains(all, "weather_current") || !strings.Contains(all, "device_ctl_cmdrun") {
|
||||
tmp.Fatalf("all tools missing:\n%s", all)
|
||||
}
|
||||
none := a.executeGetPluginTools("nope")
|
||||
if !strings.Contains(none, "没有可用的工具") {
|
||||
tmp.Fatalf("unknown plugin msg:\n%s", none)
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
@ -50,3 +51,61 @@ func (a *Agent) resolveToolPlugin(name string) string {
|
||||
}
|
||||
return "core"
|
||||
}
|
||||
|
||||
// executeGetPluginTools 返回指定插件的完整工具定义(名称/参数/用途)。
|
||||
// 支持按插件名拉取, 未指定时返回全部插件的工具摘要。
|
||||
func (a *Agent) executeGetPluginTools(pluginName string) string {
|
||||
type toolItem struct {
|
||||
name, desc string
|
||||
params interface{}
|
||||
}
|
||||
var items []toolItem
|
||||
|
||||
// 收集 StageHost(SDK 插件)工具
|
||||
if a.stageHost != nil {
|
||||
for _, d := range a.stageHost.GetToolDefs() {
|
||||
plg := d.Plugin
|
||||
if plg == "" {
|
||||
plg = a.resolveToolPlugin(d.Name)
|
||||
}
|
||||
if pluginName != "" && plg != pluginName {
|
||||
continue
|
||||
}
|
||||
items = append(items, toolItem{d.Name, d.Description, d.Parameters})
|
||||
}
|
||||
}
|
||||
// 收集 IOManager(设备/通道)工具
|
||||
if a.io != nil {
|
||||
for _, d := range a.io.GetAllTools() {
|
||||
plg := a.resolveToolPlugin(d.Name)
|
||||
if pluginName != "" && plg != pluginName {
|
||||
continue
|
||||
}
|
||||
items = append(items, toolItem{d.Name, d.Description, d.Parameters})
|
||||
}
|
||||
}
|
||||
if len(items) == 0 {
|
||||
if pluginName != "" {
|
||||
return fmt.Sprintf("插件 %s 没有可用的工具定义", pluginName)
|
||||
}
|
||||
return "当前没有可用的工具定义"
|
||||
}
|
||||
var sb strings.Builder
|
||||
if pluginName == "" {
|
||||
sb.WriteString(fmt.Sprintf("共 %d 个工具:\n", len(items)))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("插件 %s 共 %d 个工具:\n", pluginName, len(items)))
|
||||
}
|
||||
for _, it := range items {
|
||||
sb.WriteString(fmt.Sprintf("\n### %s\n", it.name))
|
||||
if it.desc != "" {
|
||||
sb.WriteString(it.desc + "\n")
|
||||
}
|
||||
if it.params != nil {
|
||||
if b, err := json.Marshal(it.params); err == nil && len(b) < 600 {
|
||||
sb.WriteString("参数: " + string(b) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@ -61,6 +61,9 @@ func (a *Agent) executeToolCallInner(tc agentAPI.ToolCall) string {
|
||||
return a.executeOutputListChannels()
|
||||
case tc.Name == "plgreload":
|
||||
return a.executePluginReload()
|
||||
case tc.Name == "get_plugin_tools":
|
||||
pluginName, _ := tc.Arguments["plugin_name"].(string)
|
||||
return a.executeGetPluginTools(pluginName)
|
||||
case tc.Name == "spawn_child":
|
||||
return a.executeSpawnChild(tc)
|
||||
case tc.Name == "child_result":
|
||||
|
||||
@ -98,31 +98,42 @@ func (a *Agent) buildToolCatalog() string {
|
||||
if len(defs) == 0 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString("\n\n【可用工具列表】")
|
||||
seen := make(map[string]bool)
|
||||
for _, d := range defs {
|
||||
t, ok := d.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fn, ok := t["function"].(map[string]interface{})
|
||||
// 仅注入插件/通道能力摘要,避免全量工具定义污染 system prompt。
|
||||
// 每个插件列:名称 + 能力描述 + 工具数。完整工具定义由 get_plugin_tools 按需拉取。
|
||||
byPlugin := map[string]int{} // plugin -> 工具数
|
||||
pluginDesc := map[string]string{} // plugin -> 首个工具描述(作能力概览)
|
||||
var order []string
|
||||
for _, t := range defs {
|
||||
fn, ok := t.(map[string]interface{})["function"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name, _ := fn["name"].(string)
|
||||
if name == "" || seen[name] {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
desc, _ := fn["description"].(string)
|
||||
sb.WriteString(fmt.Sprintf("\n- %s", name))
|
||||
if desc != "" {
|
||||
if len(desc) > 80 {
|
||||
desc = desc[:80] + "..."
|
||||
}
|
||||
sb.WriteString(": " + desc)
|
||||
plg := a.resolveToolPlugin(name)
|
||||
if _, seen := byPlugin[plg]; !seen {
|
||||
order = append(order, plg)
|
||||
}
|
||||
byPlugin[plg]++
|
||||
if pluginDesc[plg] == "" {
|
||||
desc, _ := fn["description"].(string)
|
||||
if len(desc) > 60 {
|
||||
desc = desc[:60] + "..."
|
||||
}
|
||||
pluginDesc[plg] = desc
|
||||
}
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString("\n\n【可用工具能力】\n")
|
||||
sb.WriteString("工具按插件分组注册。需要某个插件的具体工具时,调用 get_plugin_tools(\"{插件名}\") 获取该插件的完整工具定义(名称/参数/用途)。\n")
|
||||
for _, plg := range order {
|
||||
sb.WriteString(fmt.Sprintf("- %s (%d 个工具)", plg, byPlugin[plg]))
|
||||
if d := pluginDesc[plg]; d != "" {
|
||||
sb.WriteString(": " + d)
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
@ -422,6 +433,21 @@ func (a *Agent) buildToolDefs() []interface{} {
|
||||
})
|
||||
}
|
||||
|
||||
// 按插件动态拉取工具定义(避免全量注入提示词污染)
|
||||
tools = append(tools, map[string]interface{}{
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": "get_plugin_tools",
|
||||
"description": "获取指定插件的完整工具定义(名称/参数/用途)。参数 plugin_name 传插件名(见系统提示的【可用工具能力】列表)。省略时返回全部插件的工具摘要。",
|
||||
"parameters": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"plugin_name": map[string]interface{}{"type": "string", "description": "插件名,如 qq / remotedevice / weather", "default": ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
tools = append(tools, map[string]interface{}{
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
|
||||
Reference in New Issue
Block a user