抽屉优化
This commit is contained in:
1 parent
7234874797
commit
d968727460
14 files changed
+669
-154
No files matched your search
+14
-14
@@ -8,11 +8,11 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Autumn-27/norma/skill"
|
||||
actool "github.com/Autumn-27/norma/tool"
|
||||
"github.com/Autumn-27/artex/agent"
|
||||
"github.com/Autumn-27/artex/db"
|
||||
"github.com/Autumn-27/artex/traffic"
|
||||
"github.com/Autumn-27/norma/skill"
|
||||
actool "github.com/Autumn-27/norma/tool"
|
||||
)
|
||||
|
||||
// wireAgentAugment connects the PG agent_visibility table into the agent runtime:
|
||||
@@ -155,7 +155,7 @@ func wireAgentAugment(pg *db.DB, skillDir string, hostTools func() ([]actool.Cor
|
||||
if !contains(boundAgents, a.Key) {
|
||||
continue // only defer names this agent is actually bound to
|
||||
}
|
||||
allNames = append(allNames, name) // schema withheld from the prompt
|
||||
allNames = append(allNames, name) // schema withheld from the prompt
|
||||
globalNames = append(globalNames, name) // advertised in the deferred block
|
||||
globalSet[name] = true
|
||||
if unlock != nil {
|
||||
@@ -235,7 +235,7 @@ func wireTools(pg *db.DB, domainReg map[string]actool.CoreTool) {
|
||||
// interactive_shell is on, so Bash points at shell_open for interactive programs
|
||||
// without ever referencing a tool that isn't injected (§14.1/§14.2).
|
||||
const bashInteractiveShellNote = "\n\n需要【交互输入】的程序(msfconsole / ssh 交互登录 / mysql、psql、python 等 REPL / 密码或 yes/no 提示 / nc 反弹 shell)不要用 Bash(它没有 stdin、会卡住),改用 shell_open 开交互会话(用完 shell_close)。一次性、非交互命令仍用 Bash。"
|
||||
agent.ToolResolve = func(_ context.Context, agentKey string, tools []actool.CoreTool) []actool.CoreTool {
|
||||
agent.ToolResolve = func(ctx context.Context, agentKey string, tools []actool.CoreTool) []actool.CoreTool {
|
||||
rows, err := pg.ListTools()
|
||||
if err != nil {
|
||||
log.Printf("[tools] 读取工具表失败,按代码默认放行: %v", err)
|
||||
@@ -245,6 +245,14 @@ func wireTools(pg *db.DB, domainReg map[string]actool.CoreTool) {
|
||||
for _, t := range rows {
|
||||
byKey[t.Key] = t
|
||||
}
|
||||
runInfo := agent.RunInfoFrom(ctx)
|
||||
resolve := func(t actool.CoreTool, row *db.Tool) actool.CoreTool {
|
||||
var schema map[string]any
|
||||
if len(row.Schema) > 0 {
|
||||
_ = json.Unmarshal(row.Schema, &schema)
|
||||
}
|
||||
return meterTool(agent.DecorateTool(t, row.Description, schema), pg, row.Key, agentKey, runInfo)
|
||||
}
|
||||
out := tools[:0:0]
|
||||
for _, t := range tools {
|
||||
row, known := byKey[t.Name()]
|
||||
@@ -255,11 +263,7 @@ func wireTools(pg *db.DB, domainReg map[string]actool.CoreTool) {
|
||||
if !row.Enabled || !contains(row.Agents, agentKey) {
|
||||
continue // disabled globally or not bound to this agent → drop
|
||||
}
|
||||
var schema map[string]any
|
||||
if len(row.Schema) > 0 {
|
||||
_ = json.Unmarshal(row.Schema, &schema)
|
||||
}
|
||||
out = append(out, agent.DecorateTool(t, row.Description, schema))
|
||||
out = append(out, resolve(t, row))
|
||||
}
|
||||
// inject: domain tools bound to this agent in the DB but absent from the
|
||||
// incoming list. Covers agents (Auto, custom) whose base only has DefaultTools
|
||||
@@ -279,11 +283,7 @@ func wireTools(pg *db.DB, domainReg map[string]actool.CoreTool) {
|
||||
if !ok {
|
||||
continue // not a domain tool; custom/host tools are injected via hostTools()
|
||||
}
|
||||
var schema map[string]any
|
||||
if len(row.Schema) > 0 {
|
||||
_ = json.Unmarshal(row.Schema, &schema)
|
||||
}
|
||||
out = append(out, agent.DecorateTool(inst, row.Description, schema))
|
||||
out = append(out, resolve(inst, row))
|
||||
}
|
||||
}
|
||||
// shell hints: user-defined kind="shell" tools are not callable — they are
|
||||
|
||||
@@ -734,6 +734,16 @@ func (s *Server) pgListTools(w http.ResponseWriter, r *http.Request) {
|
||||
if ts == nil {
|
||||
ts = []*db.Tool{}
|
||||
}
|
||||
// Usage is best-effort decoration. Runtime tool resolution keeps using the plain
|
||||
// catalog query, so agent assembly never pays for this aggregate.
|
||||
counts, countErr := pg.ToolUsageCounts()
|
||||
if countErr != nil {
|
||||
log.Printf("[tools] 读取调用统计失败: %v", countErr)
|
||||
} else {
|
||||
for _, tool := range ts {
|
||||
tool.Calls = counts[tool.Key]
|
||||
}
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"tools": ts})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
actool "github.com/Autumn-27/norma/tool"
|
||||
|
||||
"github.com/Autumn-27/artex/agent"
|
||||
"github.com/Autumn-27/artex/db"
|
||||
)
|
||||
|
||||
type toolUsageRecorder interface {
|
||||
InsertToolUsage(*db.ToolUsage) error
|
||||
}
|
||||
|
||||
// meteredTool records one row immediately before a catalog tool is invoked. It
|
||||
// embeds the resolved tool so schema overrides, defaults and permission behavior
|
||||
// remain unchanged.
|
||||
type meteredTool struct {
|
||||
actool.CoreTool
|
||||
recorder toolUsageRecorder
|
||||
toolKey string
|
||||
agentKey string
|
||||
ri agent.RunInfo
|
||||
}
|
||||
|
||||
func meterTool(t actool.CoreTool, recorder toolUsageRecorder, toolKey, agentKey string, ri agent.RunInfo) actool.CoreTool {
|
||||
if recorder == nil {
|
||||
return t
|
||||
}
|
||||
return &meteredTool{CoreTool: t, recorder: recorder, toolKey: toolKey, agentKey: agentKey, ri: ri}
|
||||
}
|
||||
|
||||
func (m *meteredTool) Call(ctx context.Context, input json.RawMessage, tc *actool.ToolContext) (actool.Result, error) {
|
||||
if err := m.recorder.InsertToolUsage(&db.ToolUsage{
|
||||
ToolKey: m.toolKey,
|
||||
AgentKey: m.agentKey,
|
||||
TaskID: m.ri.TaskID,
|
||||
ExplorationID: m.ri.ExplorationID,
|
||||
IntentID: m.ri.IntentID,
|
||||
SessionID: m.ri.SessionID,
|
||||
}); err != nil {
|
||||
log.Printf("[toolusage] insert %s: %v", m.toolKey, err)
|
||||
}
|
||||
return m.CoreTool.Call(ctx, input, tc)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
actool "github.com/Autumn-27/norma/tool"
|
||||
|
||||
"github.com/Autumn-27/artex/agent"
|
||||
"github.com/Autumn-27/artex/db"
|
||||
)
|
||||
|
||||
type recordingToolUsage struct {
|
||||
rows []*db.ToolUsage
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *recordingToolUsage) InsertToolUsage(row *db.ToolUsage) error {
|
||||
copy := *row
|
||||
r.rows = append(r.rows, ©)
|
||||
return r.err
|
||||
}
|
||||
|
||||
func TestMeterToolRecordsAttributionAndDelegates(t *testing.T) {
|
||||
calls := 0
|
||||
base := actool.Build(actool.Spec{
|
||||
Name: "record_fact",
|
||||
Run: func(context.Context, json.RawMessage, *actool.ToolContext) (actool.Result, error) {
|
||||
calls++
|
||||
return actool.Result{}, nil
|
||||
},
|
||||
})
|
||||
recorder := &recordingToolUsage{}
|
||||
ri := agent.RunInfo{TaskID: 42, ExplorationID: 8, IntentID: 9, SessionID: "session-1"}
|
||||
wrapped := meterTool(base, recorder, "record_fact", "worker", ri)
|
||||
|
||||
if _, err := wrapped.Call(context.Background(), json.RawMessage(`{"secret":"not persisted"}`), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Fatalf("delegate calls: want 1, got %d", calls)
|
||||
}
|
||||
if len(recorder.rows) != 1 {
|
||||
t.Fatalf("usage rows: want 1, got %d", len(recorder.rows))
|
||||
}
|
||||
got := recorder.rows[0]
|
||||
if got.ToolKey != "record_fact" || got.AgentKey != "worker" || got.TaskID != 42 ||
|
||||
got.ExplorationID != 8 || got.IntentID != 9 || got.SessionID != "session-1" {
|
||||
t.Fatalf("unexpected attribution: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeterToolFailureDoesNotBreakInvocation(t *testing.T) {
|
||||
calls := 0
|
||||
base := actool.Build(actool.Spec{
|
||||
Name: "list_facts",
|
||||
Run: func(context.Context, json.RawMessage, *actool.ToolContext) (actool.Result, error) {
|
||||
calls++
|
||||
return actool.Result{}, nil
|
||||
},
|
||||
})
|
||||
recorder := &recordingToolUsage{err: errors.New("ledger unavailable")}
|
||||
wrapped := meterTool(base, recorder, "list_facts", "worker", agent.RunInfo{})
|
||||
|
||||
if _, err := wrapped.Call(context.Background(), nil, nil); err != nil {
|
||||
t.Fatalf("metering error leaked into tool call: %v", err)
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Fatalf("delegate calls: want 1, got %d", calls)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user