Add files via upload

This commit is contained in:
公明
2026-08-15 11:11:40 +08:00
committed by GitHub
parent 6db0975223
commit 893f1342ae
7 changed files with 199 additions and 9 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ func RunEinoSingleChatModelAgent(
}
if einoSkillMW != nil {
if einoFSTools && einoLoc != nil {
fsMw, fsErr := subAgentAgenticFilesystemMiddleware(ctx, einoLoc, toolInvokeNotify, einoSingleAgentName, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
fsMw, fsErr := subAgentAgenticFilesystemMiddleware(ctx, einoLoc, toolInvokeNotify, einoSingleAgentName, conversationID, projectID, ma.EinoMiddleware.ReductionRootDir, toolMaxBytesFromMW(&ma.EinoMiddleware), mcpExecBinder, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
if fsErr != nil {
return nil, fmt.Errorf("eino single filesystem 中间件: %w", fsErr)
}
+74 -1
View File
@@ -2,6 +2,7 @@ package multiagent
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -10,11 +11,13 @@ import (
"cyberstrike-ai/internal/config"
"cyberstrike-ai/internal/einomcp"
"cyberstrike-ai/internal/security"
"cyberstrike-ai/internal/tooloutput"
localbk "github.com/cloudwego/eino-ext/adk/backend/local"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/middlewares/filesystem"
"github.com/cloudwego/eino/adk/middlewares/skill"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"go.uber.org/zap"
)
@@ -100,6 +103,11 @@ func subAgentAgenticFilesystemMiddleware(
loc *localbk.Local,
invokeNotify *einomcp.ToolInvokeNotifyHolder,
einoAgentName string,
conversationID string,
projectID string,
reductionRootDir string,
toolMaxBytes int,
binder *MCPExecutionBinder,
beginMonitor func(toolCallID, command string) string,
appendPartialMonitor func(executionID, toolCallID, chunk string),
registerCancelMonitor func(executionID string, cancel context.CancelFunc),
@@ -113,7 +121,7 @@ func subAgentAgenticFilesystemMiddleware(
if loc == nil {
return nil, nil
}
return filesystem.NewTyped[*schema.AgenticMessage](ctx, &filesystem.MiddlewareConfig{
mw, err := filesystem.NewTyped[*schema.AgenticMessage](ctx, &filesystem.MiddlewareConfig{
Backend: loc,
StreamingShell: &einoStreamingShellWrap{
inner: security.NewEinoStreamingShell(),
@@ -130,6 +138,71 @@ func subAgentAgenticFilesystemMiddleware(
shellNoOutputTimeoutSec: shellNoOutputTimeoutSec,
},
})
if err != nil {
return nil, err
}
return &einoAgenticFilesystemToolMiddleware{
TypedChatModelAgentMiddleware: mw,
conversationID: conversationID,
projectID: projectID,
reductionRootDir: reductionRootDir,
toolMaxBytes: toolMaxBytes,
binder: binder,
}, nil
}
type einoAgenticFilesystemToolMiddleware struct {
adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
conversationID string
projectID string
reductionRootDir string
toolMaxBytes int
binder *MCPExecutionBinder
}
func (m *einoAgenticFilesystemToolMiddleware) WrapInvokableToolCall(ctx context.Context, endpoint adk.InvokableToolCallEndpoint, tCtx *adk.ToolContext) (adk.InvokableToolCallEndpoint, error) {
wrapped, err := m.TypedChatModelAgentMiddleware.WrapInvokableToolCall(ctx, endpoint, tCtx)
if err != nil {
return nil, err
}
if tCtx == nil || !isBuiltinEinoADKFilesystemToolName(tCtx.Name) {
return wrapped, nil
}
return func(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
args := parseToolArgumentsObject(argumentsInJSON)
if len(args) > 0 && m.binder != nil {
m.binder.BindArguments(tCtx.CallID, args)
}
result, runErr := wrapped(ctx, argumentsInJSON, opts...)
if runErr != nil {
return result, runErr
}
return m.boundToolResult(tCtx.CallID, result), nil
}, nil
}
func (m *einoAgenticFilesystemToolMiddleware) boundToolResult(toolCallID, result string) string {
if m == nil || m.toolMaxBytes <= 0 || len(result) <= m.toolMaxBytes {
return result
}
return tooloutput.BoundWithSpill(result, m.toolMaxBytes, tooloutput.SpillOpts{
RootDir: m.reductionRootDir,
ProjectID: m.projectID,
ConversationID: m.conversationID,
ExecutionID: toolCallID,
})
}
func parseToolArgumentsObject(raw string) map[string]interface{} {
raw = strings.TrimSpace(raw)
if raw == "" || raw == "{}" || raw == "null" {
return nil
}
var args map[string]interface{}
if err := json.Unmarshal([]byte(raw), &args); err != nil || len(args) == 0 {
return nil
}
return args
}
// agentToolTimeoutMinutes 返回 agent.tool_timeout_minutes(与 executeToolViaMCP 一致);cfg 为 nil 时 0。
@@ -115,7 +115,22 @@ func (e *einoToolResultProgressEmitter) Emit(ctx context.Context, toolName, cont
if e.executeStdoutDup != nil {
e.executeStdoutDup.Record(toolName, content, displayIsErr)
}
recordEinoADKFilesystemToolMonitor(ctx, e.filesystemMonitorAgent, e.filesystemMonitorRecord, e.mcpExecutionBinder, toolName, toolCallID, e.messages(), content, displayIsErr)
if args := e.toolCallArguments(toolCallID, toolName); len(args) > 0 {
data["argumentsObj"] = args
data["arguments"] = mustMarshalToolArguments(args)
}
if execID := recordEinoADKFilesystemToolMonitor(ctx, e.filesystemMonitorAgent, e.filesystemMonitorRecord, e.mcpExecutionBinder, toolName, toolCallID, e.messages(), content, displayIsErr); execID != "" {
if stored := e.filesystemMonitorAgent.MCPExecutionResultText(execID); strings.TrimSpace(stored) != "" {
content = stored
if len(content) > 200 {
preview = content[:200] + "..."
} else {
preview = content
}
data["result"] = content
data["resultPreview"] = preview
}
}
if e.filesystemMonitorAgent != nil && e.mcpExecutionBinder != nil {
if execID := e.mcpExecutionBinder.ExecutionID(toolCallID); execID != "" {
e.filesystemMonitorAgent.UpdateMCPExecutionDisplayResult(execID, content)
@@ -152,3 +167,15 @@ func (e *einoToolResultProgressEmitter) messages() []adk.Message {
}
return e.runMessages.Messages()
}
func (e *einoToolResultProgressEmitter) toolCallArguments(toolCallID, toolName string) map[string]interface{} {
if e == nil {
return nil
}
if e.mcpExecutionBinder != nil {
if args := e.mcpExecutionBinder.Arguments(toolCallID); len(args) > 0 {
return args
}
}
return toolCallArgsFromAccumulated(e.messages(), toolCallID, toolName)
}
@@ -1,6 +1,11 @@
package multiagent
import "testing"
import (
"testing"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/schema"
)
func TestEinoToolResultProgressEmitterInfersPendingAndDedupes(t *testing.T) {
var events []map[string]interface{}
@@ -135,3 +140,44 @@ func TestEinoToolResultProgressEmitterTruncatesPreview(t *testing.T) {
t.Fatalf("preview = %q len=%d", got, len(got))
}
}
func TestEinoToolResultProgressEmitterBackfillsArgumentsFromRunMessages(t *testing.T) {
var data map[string]interface{}
progress := func(eventType, _ string, raw interface{}) {
if eventType == "tool_result" {
data, _ = raw.(map[string]interface{})
}
}
runMessages := newEinoRunMessageAccumulator([]adk.Message{
&schema.Message{
Role: schema.Assistant,
ToolCalls: []schema.ToolCall{{
ID: "call-read",
Type: "function",
Function: schema.FunctionCall{
Name: "read_file",
Arguments: `{"path":"/tmp/requirements.txt"}`,
},
}},
},
})
emitter := newEinoToolResultProgressEmitter(einoToolResultProgressEmitterConfig{
ConversationID: "conv-1",
Progress: progress,
RunMessages: runMessages,
})
if !emitter.Emit(nil, "read_file", "ok", "call-read", false, "lead") {
t.Fatal("expected tool result emit")
}
args, ok := data["argumentsObj"].(map[string]interface{})
if !ok {
t.Fatalf("argumentsObj = %#v", data["argumentsObj"])
}
if args["path"] != "/tmp/requirements.txt" {
t.Fatalf("path = %#v, want /tmp/requirements.txt", args["path"])
}
if data["arguments"] != `{"path":"/tmp/requirements.txt"}` {
t.Fatalf("arguments = %#v", data["arguments"])
}
}
+41 -3
View File
@@ -7,12 +7,16 @@ import (
// MCPExecutionBinder maps ADK toolCallID → MCP monitor execution ID for a single agent run.
type MCPExecutionBinder struct {
mu sync.RWMutex
byToolCall map[string]string
mu sync.RWMutex
byToolCall map[string]string
argsByToolCall map[string]map[string]interface{}
}
func NewMCPExecutionBinder() *MCPExecutionBinder {
return &MCPExecutionBinder{byToolCall: make(map[string]string)}
return &MCPExecutionBinder{
byToolCall: make(map[string]string),
argsByToolCall: make(map[string]map[string]interface{}),
}
}
func (b *MCPExecutionBinder) Bind(toolCallID, executionID string) {
@@ -29,6 +33,19 @@ func (b *MCPExecutionBinder) Bind(toolCallID, executionID string) {
b.mu.Unlock()
}
func (b *MCPExecutionBinder) BindArguments(toolCallID string, args map[string]interface{}) {
if b == nil || len(args) == 0 {
return
}
tid := strings.TrimSpace(toolCallID)
if tid == "" {
return
}
b.mu.Lock()
b.argsByToolCall[tid] = cloneToolArgs(args)
b.mu.Unlock()
}
func (b *MCPExecutionBinder) ExecutionID(toolCallID string) string {
if b == nil {
return ""
@@ -38,3 +55,24 @@ func (b *MCPExecutionBinder) ExecutionID(toolCallID string) string {
defer b.mu.RUnlock()
return b.byToolCall[tid]
}
func (b *MCPExecutionBinder) Arguments(toolCallID string) map[string]interface{} {
if b == nil {
return nil
}
tid := strings.TrimSpace(toolCallID)
b.mu.RLock()
defer b.mu.RUnlock()
return cloneToolArgs(b.argsByToolCall[tid])
}
func cloneToolArgs(args map[string]interface{}) map[string]interface{} {
if len(args) == 0 {
return nil
}
out := make(map[string]interface{}, len(args))
for k, v := range args {
out[k] = v
}
return out
}
@@ -9,9 +9,13 @@ import (
func TestMCPExecutionBinder(t *testing.T) {
b := NewMCPExecutionBinder()
b.Bind("call-1", "exec-1")
b.BindArguments("call-1", map[string]interface{}{"file_path": "/tmp/a.txt"})
if got := b.ExecutionID("call-1"); got != "exec-1" {
t.Fatalf("expected exec-1, got %q", got)
}
if got := b.Arguments("call-1"); got["file_path"] != "/tmp/a.txt" {
t.Fatalf("arguments = %#v", got)
}
if got := b.ExecutionID("missing"); got != "" {
t.Fatalf("expected empty, got %q", got)
}
+4 -2
View File
@@ -52,6 +52,7 @@ type RunResult struct {
type toolCallPendingInfo struct {
ToolCallID string
ToolName string
Arguments map[string]interface{}
EinoAgent string
EinoRole string
}
@@ -232,7 +233,7 @@ func RunDeepAgent(
}
if agenticSkillMW != nil {
if agenticFSTools && agenticLoc != nil {
subFs, fsErr := subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, id, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
subFs, fsErr := subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, id, conversationID, projectID, ma.EinoMiddleware.ReductionRootDir, toolMaxBytesFromMW(&ma.EinoMiddleware), mcpExecBinder, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
if fsErr != nil {
return nil, fmt.Errorf("子代理 %q filesystem 中间件: %w", id, fsErr)
}
@@ -492,7 +493,7 @@ func RunDeepAgent(
}
var peFsMw adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
if agenticSkillMW != nil && agenticFSTools && agenticLoc != nil {
peFsMw, err = subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, "executor", einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
peFsMw, err = subAgentAgenticFilesystemMiddleware(ctx, agenticLoc, toolInvokeNotify, "executor", conversationID, projectID, ma.EinoMiddleware.ReductionRootDir, toolMaxBytesFromMW(&ma.EinoMiddleware), mcpExecBinder, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
if err != nil {
return nil, fmt.Errorf("plan_execute agentic filesystem 中间件: %w", err)
}
@@ -994,6 +995,7 @@ func emitToolCallsFromMessage(
markPending(toolCallPendingInfo{
ToolCallID: toolCallID,
ToolName: display,
Arguments: argsObj,
EinoAgent: agentName,
EinoRole: role,
})