Add files via upload

This commit is contained in:
公明
2026-08-15 01:45:01 +08:00
committed by GitHub
parent 31f80b2f0c
commit a6631a5cde
46 changed files with 4672 additions and 317 deletions
+199 -167
View File
@@ -5,13 +5,10 @@ import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"unicode/utf8"
"cyberstrike-ai/internal/agent"
@@ -19,16 +16,16 @@ import (
"cyberstrike-ai/internal/config"
"cyberstrike-ai/internal/database"
"cyberstrike-ai/internal/einomcp"
"cyberstrike-ai/internal/openai"
"cyberstrike-ai/internal/project"
"cyberstrike-ai/internal/reasoning"
"cyberstrike-ai/internal/security"
einoopenai "github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/filesystem"
"github.com/cloudwego/eino/adk/prebuilt/deep"
"github.com/cloudwego/eino/adk/prebuilt/supervisor"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"go.uber.org/zap"
@@ -122,7 +119,7 @@ func RunDeepAgent(
})
}
einoLoc, einoSkillMW, einoFSTools, skillsRoot, einoErr := prepareEinoSkills(ctx, appCfg.SkillsDir, ma, logger)
agenticLoc, agenticSkillMW, agenticFSTools, agenticSkillsRoot, einoErr := prepareEinoAgenticSkills(ctx, appCfg.SkillsDir, ma, logger)
if einoErr != nil {
return nil, einoErr
}
@@ -156,40 +153,28 @@ func RunDeepAgent(
toolInvokeNotify := einomcp.NewToolInvokeNotifyHolder()
mainDefs := ag.ToolsForRole(roleTools)
httpClient := &http.Client{
Timeout: 30 * time.Minute,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 300 * time.Second,
KeepAlive: 300 * time.Second,
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 30 * time.Second,
ResponseHeaderTimeout: 60 * time.Minute,
},
baseHTTPClient := newEinoBaseHTTPClient()
modelFactory := newEinoOpenAIChatModelFactory(baseHTTPClient, reasoningClient, logger)
agenticModelFactory := newEinoOpenAIAgenticChatModelFactory(baseHTTPClient, reasoningClient, logger)
agenticModelRetryCfg := newEinoAgenticModelRetryConfig(&ma.EinoMiddleware, logger, "multiagent")
agenticModelFailoverCfg, err := newEinoAgenticModelFailoverConfig(ctx, appCfg, &ma.EinoMiddleware, einoModelModeNormal, agenticModelFactory, logger, "multiagent", progress, orchMode, conversationID)
if err != nil {
return nil, err
}
// 若配置为 Claude provider,注入自动桥接 transport,对 Eino 透明走 Anthropic Messages API
httpClient = openai.NewEinoHTTPClient(&appCfg.OpenAI, httpClient)
openai.AttachSummarizationDiagTransport(httpClient, logger)
maxCompletionTokens := appCfg.OpenAI.MaxCompletionTokensEffective()
baseModelCfg := &einoopenai.ChatModelConfig{
APIKey: appCfg.OpenAI.APIKey,
BaseURL: strings.TrimSuffix(appCfg.OpenAI.BaseURL, "/"),
Model: appCfg.OpenAI.Model,
HTTPClient: httpClient,
MaxCompletionTokens: &maxCompletionTokens,
}
reasoning.ApplyToEinoChatModelConfig(baseModelCfg, &appCfg.OpenAI, reasoningClient)
logEinoAgenticModelGate(
logger,
"multiagent",
orchMode,
evaluateEinoAgenticModelGate(agenticModelGateFactory(agenticModelFactory, appCfg.OpenAI, einoModelModeNormal), einoAgenticRuntimeSupportV0914()),
)
deepMaxIter := agentMaxIterations(appCfg)
var subAgents []adk.Agent
var subAgents []adk.TypedAgent[*schema.AgenticMessage]
var supervisorSubAgents []adk.Agent
if orchMode != "plan_execute" {
subAgents = make([]adk.Agent, 0, len(effectiveSubs))
subAgents = make([]adk.TypedAgent[*schema.AgenticMessage], 0, len(effectiveSubs))
supervisorSubAgents = make([]adk.Agent, 0, len(effectiveSubs))
for _, sub := range effectiveSubs {
id := strings.TrimSpace(sub.ID)
if id == "" {
@@ -218,11 +203,10 @@ func RunDeepAgent(
}
}
baseSubModel, err := einoopenai.NewChatModel(ctx, baseModelCfg)
subModel, err := agenticModelFactory(ctx, appCfg.OpenAI, einoModelModeNormal)
if err != nil {
return nil, fmt.Errorf("子代理 %q ChatModel: %w", id, err)
return nil, fmt.Errorf("子代理 %q AgenticModel: %w", id, err)
}
subModel := newStreamToolCallIndexRepairModel(baseSubModel)
subDefs := ag.ToolsForRole(roleTools)
subTools, err := einomcp.ToolsFromDefinitions(ag, holder, subDefs, recorder, nil, toolInvokeNotify, id)
@@ -230,41 +214,41 @@ func RunDeepAgent(
return nil, fmt.Errorf("子代理 %q 工具: %w", id, err)
}
subToolsForCfg, subPre, subToolSearchActive, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWSub, subTools, einoLoc, skillsRoot, conversationID, projectID, logger)
subToolsForCfg, subPre, subToolSearchActive, err := prependEinoAgenticMiddlewares(ctx, &ma.EinoMiddleware, einoMWSub, subTools, agenticLoc, agenticSkillsRoot, conversationID, projectID, logger)
if err != nil {
return nil, fmt.Errorf("子代理 %q eino 中间件: %w", id, err)
}
subMax := resolveMaxIterations(appCfg, sub.MaxIterations)
subSumMw, err := newEinoSummarizationMiddleware(ctx, subModel, appCfg, &ma.EinoMiddleware, conversationID, db, projectID, logger)
subSumMw, err := newEinoAgenticSummarizationMiddleware(ctx, subModel, appCfg, &ma.EinoMiddleware, conversationID, db, projectID, logger)
if err != nil {
return nil, fmt.Errorf("子代理 %q summarization 中间件: %w", id, err)
return nil, fmt.Errorf("子代理 %q agentic summarization 中间件: %w", id, err)
}
var subHandlers []adk.ChatModelAgentMiddleware
var subHandlers []adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
if len(subPre) > 0 {
subHandlers = append(subHandlers, subPre...)
}
if einoSkillMW != nil {
if einoFSTools && einoLoc != nil {
subFs, fsErr := subAgentFilesystemMiddleware(ctx, einoLoc, toolInvokeNotify, id, einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
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)
if fsErr != nil {
return nil, fmt.Errorf("子代理 %q filesystem 中间件: %w", id, fsErr)
}
subHandlers = append(subHandlers, subFs)
}
subHandlers = append(subHandlers, einoSkillMW)
subHandlers = append(subHandlers, agenticSkillMW)
}
subHandlers = appendEinoChatModelTailMiddlewares(subHandlers, einoChatModelTailConfig{
logger: logger,
phase: "sub_agent:" + id,
summarization: subSumMw,
modelName: appCfg.OpenAI.Model,
maxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
toolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
conversationID: conversationID,
middlewareConfig: &ma.EinoMiddleware,
subHandlers = appendEinoAgenticChatModelTailMiddlewares(subHandlers, einoChatModelTailConfig{
logger: logger,
phase: "sub_agent:" + id,
agenticSummarization: subSumMw,
modelName: appCfg.OpenAI.Model,
maxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
toolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
conversationID: conversationID,
middlewareConfig: &ma.EinoMiddleware,
})
subInstrFinal := project.AppendVisionImageAnalysisIfReady(instr, appCfg.Vision.Ready())
@@ -280,11 +264,11 @@ func RunDeepAgent(
zap.Bool("tool_search_middleware", subToolSearchActive),
)
}
sa, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
sa, err := newEinoAgenticChatModelAgent(ctx, einoAgenticChatModelAgentConfig{
Name: id,
Description: desc,
Instruction: subInstrFinal,
GenModelInput: literalInstructionGenModelInput,
GenModelInput: literalAgenticInstructionGenModelInput,
Model: subModel,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
@@ -299,27 +283,21 @@ func RunDeepAgent(
},
EmitInternalEvents: true,
},
MaxIterations: subMax,
Handlers: subHandlers,
MaxIterations: subMax,
Handlers: subHandlers,
ModelRetryConfig: agenticModelRetryCfg,
ModelFailoverConfig: agenticModelFailoverCfg,
})
if err != nil {
return nil, fmt.Errorf("子代理 %q: %w", id, err)
}
subAgents = append(subAgents, sa)
if adapted := newEinoAgenticMessageAgentAdapter(sa); adapted != nil {
supervisorSubAgents = append(supervisorSubAgents, adapted)
}
}
}
baseMainModel, err := einoopenai.NewChatModel(ctx, baseModelCfg)
if err != nil {
return nil, fmt.Errorf("多代理主模型: %w", err)
}
mainModel := newStreamToolCallIndexRepairModel(baseMainModel)
mainSumMw, err := newEinoSummarizationMiddleware(ctx, mainModel, appCfg, &ma.EinoMiddleware, conversationID, db, projectID, logger)
if err != nil {
return nil, fmt.Errorf("多代理主 summarization 中间件: %w", err)
}
modelFacingTrace := newModelFacingTraceHolder()
// 与 deep.Config.Name / supervisor 主代理 Name 一致。
@@ -346,7 +324,10 @@ func RunDeepAgent(
if err != nil {
return nil, err
}
mainToolsForCfg, mainOrchestratorPre, mainToolSearchActive, err := prependEinoMiddlewares(ctx, &ma.EinoMiddleware, einoMWMain, mainTools, einoLoc, skillsRoot, conversationID, projectID, logger)
var mainToolsForCfg []tool.BaseTool
var mainToolSearchActive bool
var mainAgenticOrchestratorPre []adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
mainToolsForCfg, mainAgenticOrchestratorPre, mainToolSearchActive, err = prependEinoAgenticMiddlewares(ctx, &ma.EinoMiddleware, einoMWMain, mainTools, agenticLoc, agenticSkillsRoot, conversationID, projectID, logger)
if err != nil {
return nil, err
}
@@ -388,8 +369,8 @@ func RunDeepAgent(
var deepBackend filesystem.Backend
var deepShell filesystem.StreamingShell
if einoLoc != nil && einoFSTools {
deepBackend = einoLoc
if agenticLoc != nil && agenticFSTools {
deepBackend = agenticLoc
deepShell = &einoStreamingShellWrap{
inner: security.NewEinoStreamingShell(),
invokeNotify: toolInvokeNotify,
@@ -406,8 +387,21 @@ func RunDeepAgent(
}
}
var mainModel model.AgenticModel
var mainSumMw adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
if orchMode != "plan_execute" {
mainModel, err = agenticModelFactory(ctx, appCfg.OpenAI, einoModelModeNormal)
if err != nil {
return nil, fmt.Errorf("多代理主 AgenticModel: %w", err)
}
mainSumMw, err = newEinoAgenticSummarizationMiddleware(ctx, mainModel, appCfg, &ma.EinoMiddleware, conversationID, db, projectID, logger)
if err != nil {
return nil, fmt.Errorf("多代理主 agentic summarization 中间件: %w", err)
}
}
// noNestedTaskMiddleware 必须在最外层(最先拦截),防止 skill 或其他中间件内部触发 task 调用绕过检测。
deepHandlers := []adk.ChatModelAgentMiddleware{newNoNestedTaskMiddleware()}
deepHandlers := []adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]{newNoNestedAgenticTaskMiddleware()}
var taskBlackboardSupplement string
if appCfg.Project.Enabled && db != nil {
if pid := strings.TrimSpace(projectID); pid != "" {
@@ -416,44 +410,44 @@ func RunDeepAgent(
}
}
}
if mw := newTaskContextEnrichMiddleware(runtimeUserMessage, history, ma.SubAgentUserContextMaxRunesEffective(), taskBlackboardSupplement); mw != nil {
if mw := newAgenticTaskContextEnrichMiddleware(runtimeUserMessage, history, ma.SubAgentUserContextMaxRunesEffective(), taskBlackboardSupplement); mw != nil {
deepHandlers = append(deepHandlers, mw)
}
if len(mainOrchestratorPre) > 0 {
deepHandlers = append(deepHandlers, mainOrchestratorPre...)
if len(mainAgenticOrchestratorPre) > 0 {
deepHandlers = append(deepHandlers, mainAgenticOrchestratorPre...)
}
if einoSkillMW != nil {
deepHandlers = append(deepHandlers, einoSkillMW)
if agenticSkillMW != nil {
deepHandlers = append(deepHandlers, agenticSkillMW)
}
deepHandlers = appendEinoChatModelTailMiddlewares(deepHandlers, einoChatModelTailConfig{
logger: logger,
phase: "deep_orchestrator",
summarization: mainSumMw,
modelName: appCfg.OpenAI.Model,
maxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
toolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
conversationID: conversationID,
trace: modelFacingTrace,
middlewareConfig: &ma.EinoMiddleware,
deepHandlers = appendEinoAgenticChatModelTailMiddlewares(deepHandlers, einoChatModelTailConfig{
logger: logger,
phase: "deep_orchestrator",
agenticSummarization: mainSumMw,
modelName: appCfg.OpenAI.Model,
maxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
toolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
conversationID: conversationID,
trace: modelFacingTrace,
middlewareConfig: &ma.EinoMiddleware,
})
supHandlers := []adk.ChatModelAgentMiddleware{}
if len(mainOrchestratorPre) > 0 {
supHandlers = append(supHandlers, mainOrchestratorPre...)
supHandlers := []adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]{}
if len(mainAgenticOrchestratorPre) > 0 {
supHandlers = append(supHandlers, mainAgenticOrchestratorPre...)
}
if einoSkillMW != nil {
supHandlers = append(supHandlers, einoSkillMW)
if agenticSkillMW != nil {
supHandlers = append(supHandlers, agenticSkillMW)
}
supHandlers = appendEinoChatModelTailMiddlewares(supHandlers, einoChatModelTailConfig{
logger: logger,
phase: "supervisor_orchestrator",
summarization: mainSumMw,
modelName: appCfg.OpenAI.Model,
maxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
toolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
conversationID: conversationID,
trace: modelFacingTrace,
middlewareConfig: &ma.EinoMiddleware,
supHandlers = appendEinoAgenticChatModelTailMiddlewares(supHandlers, einoChatModelTailConfig{
logger: logger,
phase: "supervisor_orchestrator",
agenticSummarization: mainSumMw,
modelName: appCfg.OpenAI.Model,
maxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
toolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
conversationID: conversationID,
trace: modelFacingTrace,
middlewareConfig: &ma.EinoMiddleware,
})
mainToolsCfg := adk.ToolsConfig{
@@ -470,45 +464,42 @@ func RunDeepAgent(
EmitInternalEvents: true,
}
deepOutKey, taskGen := deepExtrasFromConfig(ma)
deepAgenticOutKey, agenticTaskGen := deepAgenticExtrasFromConfig(ma)
var da adk.Agent
switch orchMode {
case "plan_execute":
plannerModelCfg := &einoopenai.ChatModelConfig{
APIKey: appCfg.OpenAI.APIKey,
BaseURL: strings.TrimSuffix(appCfg.OpenAI.BaseURL, "/"),
Model: appCfg.OpenAI.Model,
HTTPClient: httpClient,
MaxCompletionTokens: &maxCompletionTokens,
}
reasoning.ApplyPlanExecutePlannerModelConfig(plannerModelCfg, &appCfg.OpenAI)
basePEMainModel, perr := einoopenai.NewChatModel(ctx, plannerModelCfg)
peMainModel, perr := modelFactory(ctx, appCfg.OpenAI, einoModelModePlanner)
if perr != nil {
return nil, fmt.Errorf("plan_execute 规划模型: %w", perr)
}
peMainModel := newStreamToolCallIndexRepairModel(basePEMainModel)
if logger != nil {
logger.Info("plan_execute: planner/replanner 使用无 reasoning 的独立 ChatModelToolChoiceForced 兼容)",
zap.String("model", appCfg.OpenAI.Model),
)
}
baseExecModel, perr := einoopenai.NewChatModel(ctx, baseModelCfg)
execModel, perr := modelFactory(ctx, appCfg.OpenAI, einoModelModeNormal)
if perr != nil {
return nil, fmt.Errorf("plan_execute 执行器模型: %w", perr)
}
execModel := newStreamToolCallIndexRepairModel(baseExecModel)
// 构建 filesystem 中间件(与 Deep sub-agent 一致)
var peFsMw adk.ChatModelAgentMiddleware
if einoSkillMW != nil && einoFSTools && einoLoc != nil {
peFsMw, err = subAgentFilesystemMiddleware(ctx, einoLoc, toolInvokeNotify, "executor", einoExecBegin, einoExecAppendPartial, einoExecRegisterCancel, einoExecUnregisterCancel, einoExecFinish, agentToolTimeoutMinutes(appCfg), agentToolWaitTimeoutSeconds(appCfg), agentShellNoOutputTimeoutSeconds(appCfg), nil)
agenticExecModel, perr := agenticModelFactory(ctx, appCfg.OpenAI, einoModelModeNormal)
if perr != nil {
return nil, fmt.Errorf("plan_execute 执行器 AgenticModel: %w", perr)
}
planRewriteSumMw, perr := newEinoSummarizationMiddleware(ctx, execModel, appCfg, &ma.EinoMiddleware, conversationID, db, projectID, logger)
if perr != nil {
return nil, fmt.Errorf("plan_execute planner/replanner summarization: %w", perr)
}
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)
if err != nil {
return nil, fmt.Errorf("plan_execute filesystem 中间件: %w", err)
return nil, fmt.Errorf("plan_execute agentic filesystem 中间件: %w", err)
}
}
peRoot, perr := NewPlanExecuteRoot(ctx, &PlanExecuteRootArgs{
MainToolCallingModel: peMainModel,
ExecModel: execModel,
AgenticExecModel: agenticExecModel,
OrchInstruction: orchInstruction,
ToolsCfg: mainToolsCfg,
ExecMaxIter: deepMaxIter,
@@ -520,15 +511,15 @@ func RunDeepAgent(
ProjectID: projectID,
Logger: logger,
ModelName: appCfg.OpenAI.Model,
// 与 Deep/Supervisor 主代理同源:patch / reduction / toolsearch / plantask(见 buildPlanExecuteExecutorHandlers)。
ExecPreMiddlewares: mainOrchestratorPre,
SkillMiddleware: einoSkillMW,
FilesystemMiddleware: peFsMw,
ModelFacingTrace: modelFacingTrace,
// 与 Deep/Supervisor 主代理同源:typed patch / reduction / toolsearch / plantask(见 buildPlanExecuteAgenticExecutorHandlers)。
AgenticExecPreMiddlewares: mainAgenticOrchestratorPre,
AgenticSkillMiddleware: agenticSkillMW,
AgenticFilesystemMiddleware: peFsMw,
ModelFacingTrace: modelFacingTrace,
PlannerReplannerRewriteHandlers: appendEinoChatModelTailMiddlewares(nil, einoChatModelTailConfig{
logger: logger,
phase: "plan_execute_planner_replanner",
summarization: mainSumMw,
summarization: planRewriteSumMw,
modelName: appCfg.OpenAI.Model,
maxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
toolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
@@ -536,40 +527,44 @@ func RunDeepAgent(
skipTrace: true,
middlewareConfig: &ma.EinoMiddleware,
}),
AgenticModelRetryConfig: agenticModelRetryCfg,
AgenticModelFailoverConfig: agenticModelFailoverCfg,
})
if perr != nil {
return nil, perr
}
da = peRoot
case "supervisor":
supCfg := &adk.ChatModelAgentConfig{
Name: orchestratorName,
Description: orchDescription,
Instruction: supInstr,
GenModelInput: literalInstructionGenModelInput,
Model: mainModel,
ToolsConfig: mainToolsCfg,
MaxIterations: deepMaxIter,
Handlers: supHandlers,
Exit: &adk.ExitTool{},
supCfg := einoAgenticChatModelAgentConfig{
Name: orchestratorName,
Description: orchDescription,
Instruction: supInstr,
GenModelInput: literalAgenticInstructionGenModelInput,
Model: mainModel,
ToolsConfig: mainToolsCfg,
MaxIterations: deepMaxIter,
Handlers: supHandlers,
Exit: &adk.ExitTool{},
ModelRetryConfig: agenticModelRetryCfg,
ModelFailoverConfig: agenticModelFailoverCfg,
}
if deepOutKey != "" {
supCfg.OutputKey = deepOutKey
if deepAgenticOutKey != "" {
supCfg.OutputKey = deepAgenticOutKey
}
superChat, serr := adk.NewChatModelAgent(ctx, supCfg)
superChat, serr := newEinoAgenticChatModelAgentAdapter(ctx, supCfg)
if serr != nil {
return nil, fmt.Errorf("supervisor 主代理: %w", serr)
return nil, fmt.Errorf("supervisor agentic 主代理: %w", serr)
}
supRoot, serr := supervisor.New(ctx, &supervisor.Config{
Supervisor: superChat,
SubAgents: subAgents,
SubAgents: supervisorSubAgents,
})
if serr != nil {
return nil, fmt.Errorf("supervisor.New: %w", serr)
}
da = supRoot
default:
dcfg := &deep.Config{
dcfg := &deep.TypedConfig[*schema.AgenticMessage]{
Name: orchestratorName,
Description: orchDescription,
ChatModel: mainModel,
@@ -582,18 +577,20 @@ func RunDeepAgent(
StreamingShell: deepShell,
Handlers: deepHandlers,
ToolsConfig: mainToolsCfg,
ModelRetryConfig: agenticModelRetryCfg,
ModelFailoverConfig: agenticModelFailoverCfg,
}
if deepOutKey != "" {
dcfg.OutputKey = deepOutKey
if deepAgenticOutKey != "" {
dcfg.OutputKey = deepAgenticOutKey
}
if taskGen != nil {
dcfg.TaskToolDescriptionGenerator = taskGen
if agenticTaskGen != nil {
dcfg.TaskToolDescriptionGenerator = agenticTaskGen
}
dDeep, derr := deep.New(ctx, dcfg)
dDeep, derr := deep.NewTyped[*schema.AgenticMessage](ctx, dcfg)
if derr != nil {
return nil, fmt.Errorf("deep.New: %w", derr)
return nil, fmt.Errorf("deep.NewTyped[AgenticMessage]: %w", derr)
}
da = dDeep
da = newEinoAgenticMessageAgentAdapter(dDeep)
}
baseMsgs := historyToMessages(history, appCfg, &ma.EinoMiddleware)
@@ -625,8 +622,8 @@ func RunDeepAgent(
StreamsMainAssistant: streamsMainAssistant,
EinoRoleTag: einoRoleTag,
CheckpointDir: ma.EinoMiddleware.CheckpointDir,
RunRetryMaxAttempts: ma.EinoMiddleware.RunRetryMaxAttempts,
RunRetryMaxBackoffSec: ma.EinoMiddleware.RunRetryMaxBackoffSec,
RunRetryMaxAttempts: RunRetryMaxAttemptsFromConfig(&ma.EinoMiddleware),
RunRetryMaxBackoffSec: int(einoRunRetryMaxBackoffFromConfig(&ma.EinoMiddleware).Seconds()),
McpIDsMu: &mcpIDsMu,
McpIDs: &mcpIDs,
FilesystemMonitorAgent: ag,
@@ -639,6 +636,7 @@ func RunDeepAgent(
MaxTotalTokens: appCfg.OpenAI.MaxTotalTokens,
ToolMaxBytes: toolMaxBytesFromMW(&ma.EinoMiddleware),
ModelName: appCfg.OpenAI.Model,
MiddlewareConfig: &ma.EinoMiddleware,
EmptyResponseMessage: "(Eino multi-agent orchestration completed but no assistant text was captured. Check process details or logs.) " +
"(Eino 多代理编排已完成,但未捕获到助手文本输出。请查看过程详情或日志。)",
}, baseMsgs)
@@ -819,7 +817,9 @@ func toolCallStableID(tc schema.ToolCall) string {
return ""
}
// toolCallDisplayName 避免前端「未知工具」:DeepAgent 内置 task 等可能延迟写入 function.name。
// toolCallDisplayName returns the visible tool name once the model stream has
// produced a concrete function name. Anonymous stream fragments are filtered
// before progress emission instead of being guessed as task calls.
func toolCallDisplayName(tc schema.ToolCall) string {
if n := strings.TrimSpace(tc.Function.Name); n != "" {
return n
@@ -827,7 +827,7 @@ func toolCallDisplayName(tc schema.ToolCall) string {
if n := strings.TrimSpace(tc.Type); n != "" && !strings.EqualFold(n, "function") {
return n
}
return "task"
return ""
}
// toolCallsSignatureFlush 用于去重键;无 id/index 时用占位 pos,避免流末帧缺 id 时整条工具事件丢失。
@@ -835,13 +835,24 @@ func toolCallsSignatureFlush(msg *schema.Message) string {
if msg == nil || len(msg.ToolCalls) == 0 {
return ""
}
parts := make([]string, 0, len(msg.ToolCalls))
for i, tc := range msg.ToolCalls {
visible := filterVisibleToolCallsForProgress(msg.ToolCalls)
if len(visible) == 0 {
return ""
}
parts := make([]string, 0, len(visible))
for i, tc := range visible {
id := toolCallStableID(tc)
if id == "" {
id = fmt.Sprintf("pos:%d", i)
}
parts = append(parts, id+"|"+toolCallDisplayName(tc))
name := toolCallDisplayName(tc)
if name == "" {
continue
}
parts = append(parts, id+"|"+name)
}
if len(parts) == 0 {
return ""
}
sort.Strings(parts)
return strings.Join(parts, ";")
@@ -853,8 +864,9 @@ func toolCallsRichSignature(msg *schema.Message) string {
if base == "" {
return ""
}
parts := make([]string, 0, len(msg.ToolCalls))
for _, tc := range msg.ToolCalls {
visible := filterVisibleToolCallsForProgress(msg.ToolCalls)
parts := make([]string, 0, len(visible))
for _, tc := range visible {
id := toolCallStableID(tc)
arg := tc.Function.Arguments
if len(arg) > 240 {
@@ -909,6 +921,10 @@ func emitToolCallsFromMessage(
if msg == nil || len(msg.ToolCalls) == 0 || progress == nil {
return
}
visibleToolCalls := filterVisibleToolCallsForProgress(msg.ToolCalls)
if len(visibleToolCalls) == 0 {
return
}
if subAgentToolStep == nil {
subAgentToolStep = make(map[string]int)
}
@@ -945,14 +961,14 @@ func emitToolCallsFromMessage(
if isSubToolRound {
role = "sub"
}
progress("tool_calls_detected", fmt.Sprintf("检测到 %d 个工具调用", len(msg.ToolCalls)), map[string]interface{}{
"count": len(msg.ToolCalls),
progress("tool_calls_detected", fmt.Sprintf("检测到 %d 个工具调用", len(visibleToolCalls)), map[string]interface{}{
"count": len(visibleToolCalls),
"conversationId": conversationID,
"source": "eino",
"einoAgent": agentName,
"einoRole": role,
})
for idx, tc := range msg.ToolCalls {
for idx, tc := range visibleToolCalls {
argStr := strings.TrimSpace(tc.Function.Arguments)
if argStr == "" && len(tc.Extra) > 0 {
if b, mErr := json.Marshal(tc.Extra); mErr == nil {
@@ -973,8 +989,7 @@ func emitToolCallsFromMessage(
// with an earlier batch in the same agent run.
toolCallID = fmt.Sprintf("eino-stream-%d-%d", fallbackToolCallSequence.Add(1), *tc.Index)
}
// Record pending tool calls for later tool_result correlation / recovery flushing.
// We intentionally record even for unknown tools to avoid "running" badge getting stuck.
// Record visible pending tool calls for later tool_result correlation / recovery flushing.
if markPending != nil && toolCallID != "" {
markPending(toolCallPendingInfo{
ToolCallID: toolCallID,
@@ -989,7 +1004,7 @@ func emitToolCallsFromMessage(
"argumentsObj": argsObj,
"toolCallId": toolCallID,
"index": idx + 1,
"total": len(msg.ToolCalls),
"total": len(visibleToolCalls),
"conversationId": conversationID,
"source": "eino",
"einoAgent": agentName,
@@ -998,6 +1013,23 @@ func emitToolCallsFromMessage(
}
}
func filterVisibleToolCallsForProgress(calls []schema.ToolCall) []schema.ToolCall {
if len(calls) == 0 {
return nil
}
out := make([]schema.ToolCall, 0, len(calls))
for _, tc := range calls {
if _, ok := modelOutputRecoveryFromToolCall(tc); ok {
continue
}
if toolCallDisplayName(tc) == "" {
continue
}
out = append(out, tc)
}
return out
}
// dedupeRepeatedParagraphs 去掉完全相同的连续/重复段落,缓解多代理各自复述同一列表。
func dedupeRepeatedParagraphs(s string, minLen int) string {
if s == "" || minLen <= 0 {