mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-12 02:37:18 +02:00
Add files via upload
This commit is contained in:
96 files changed
+11867
No files matched your search
@@ -0,0 +1,220 @@
|
||||
package multiagent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"cyberstrike-ai/internal/agent"
|
||||
"cyberstrike-ai/internal/einomcp"
|
||||
|
||||
"github.com/cloudwego/eino/adk"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
type einoRunResultBuilderConfig struct {
|
||||
OrchMode string
|
||||
EmptyHint string
|
||||
RunMessages *einoRunMessageAccumulator
|
||||
AssistantOutput *einoAssistantOutputAccumulator
|
||||
SnapshotMCPIDs func() []string
|
||||
ModelFacingTrace func() []adk.Message
|
||||
}
|
||||
|
||||
type einoRunResultBuilder struct {
|
||||
cfg einoRunResultBuilderConfig
|
||||
}
|
||||
|
||||
func newEinoRunResultBuilder(cfg einoRunResultBuilderConfig) *einoRunResultBuilder {
|
||||
return &einoRunResultBuilder{cfg: cfg}
|
||||
}
|
||||
|
||||
func (b *einoRunResultBuilder) BuildPartial(runErr error) (*RunResult, error) {
|
||||
if b == nil || b.cfg.RunMessages == nil || !b.cfg.RunMessages.HasNewMessages() {
|
||||
return nil, runErr
|
||||
}
|
||||
return b.build(true), runErr
|
||||
}
|
||||
|
||||
func (b *einoRunResultBuilder) BuildFinal() *RunResult {
|
||||
if b == nil {
|
||||
return &RunResult{}
|
||||
}
|
||||
return b.build(false)
|
||||
}
|
||||
|
||||
func (b *einoRunResultBuilder) build(partial bool) *RunResult {
|
||||
var runMsgs []adk.Message
|
||||
if b.cfg.RunMessages != nil {
|
||||
runMsgs = b.cfg.RunMessages.Messages()
|
||||
}
|
||||
var lastAssistant string
|
||||
var lastPlanExecuteExecutor string
|
||||
if b.cfg.AssistantOutput != nil {
|
||||
lastAssistant = b.cfg.AssistantOutput.LastAssistant()
|
||||
lastPlanExecuteExecutor = b.cfg.AssistantOutput.LastPlanExecuteExecutor()
|
||||
}
|
||||
var modelFacing []adk.Message
|
||||
if b.cfg.ModelFacingTrace != nil {
|
||||
modelFacing = b.cfg.ModelFacingTrace()
|
||||
}
|
||||
var ids []string
|
||||
if b.cfg.SnapshotMCPIDs != nil {
|
||||
ids = b.cfg.SnapshotMCPIDs()
|
||||
}
|
||||
return buildEinoRunResultFromAccumulated(
|
||||
b.cfg.OrchMode,
|
||||
runMsgs,
|
||||
modelFacing,
|
||||
lastAssistant,
|
||||
lastPlanExecuteExecutor,
|
||||
b.cfg.EmptyHint,
|
||||
ids,
|
||||
partial,
|
||||
)
|
||||
}
|
||||
|
||||
func einoPartialRunLastOutputHint() string {
|
||||
return "[执行未正常结束(用户停止、超时或异常)。续跑时请基于上文已产生的工具与结果继续,勿重复已完成步骤。]\n" +
|
||||
"[Run ended abnormally; continue from the trace above without repeating completed steps.]"
|
||||
}
|
||||
|
||||
func buildEinoRunResultFromAccumulated(
|
||||
orchMode string,
|
||||
runAccumulatedMsgs []adk.Message,
|
||||
persistMsgs []adk.Message,
|
||||
lastAssistant string,
|
||||
lastPlanExecuteExecutor string,
|
||||
emptyHint string,
|
||||
mcpIDs []string,
|
||||
partial bool,
|
||||
) *RunResult {
|
||||
traceForJSON := persistMsgs
|
||||
traceJSON := ""
|
||||
if len(traceForJSON) > 0 {
|
||||
traceForJSON = markModelFacingTraceForPersistence(traceForJSON)
|
||||
if histJSON, err := json.Marshal(traceForJSON); err == nil {
|
||||
traceJSON = string(histJSON)
|
||||
}
|
||||
}
|
||||
cleaned := strings.TrimSpace(lastAssistant)
|
||||
if orchMode == "plan_execute" {
|
||||
if e := strings.TrimSpace(lastPlanExecuteExecutor); e != "" {
|
||||
cleaned = e
|
||||
} else {
|
||||
cleaned = UnwrapPlanExecuteUserText(cleaned)
|
||||
}
|
||||
}
|
||||
if cleaned == "" {
|
||||
if fb := strings.TrimSpace(einoExtractFallbackAssistantFromMsgs(runAccumulatedMsgs)); fb != "" {
|
||||
cleaned = fb
|
||||
}
|
||||
}
|
||||
cleaned = dedupeRepeatedParagraphs(cleaned, 80)
|
||||
cleaned = dedupeParagraphsByLineFingerprint(cleaned, 100)
|
||||
const maxResponseRunes = 100000
|
||||
if rs := []rune(cleaned); len(rs) > maxResponseRunes {
|
||||
cleaned = string(rs[:maxResponseRunes]) + "\n\n... (response truncated / 响应已截断)"
|
||||
}
|
||||
lastOut := cleaned
|
||||
resp := cleaned
|
||||
if partial && cleaned == "" {
|
||||
lastOut = einoPartialRunLastOutputHint()
|
||||
resp = emptyHint
|
||||
}
|
||||
out := &RunResult{
|
||||
Response: resp,
|
||||
MCPExecutionIDs: mcpIDs,
|
||||
LastAgentTraceInput: traceJSON,
|
||||
LastAgentTraceOutput: lastOut,
|
||||
}
|
||||
if !partial && out.Response == "" {
|
||||
out.Response = emptyHint
|
||||
out.LastAgentTraceOutput = out.Response
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func markModelFacingTraceForPersistence(msgs []adk.Message) []adk.Message {
|
||||
out := cloneADKMessagesForTrace(msgs)
|
||||
if len(out) == 0 || out[0] == nil {
|
||||
return out
|
||||
}
|
||||
if out[0].Extra == nil {
|
||||
out[0].Extra = make(map[string]any, 1)
|
||||
}
|
||||
out[0].Extra[agent.ModelFacingTraceVersionKey] = 1
|
||||
return out
|
||||
}
|
||||
|
||||
// einoExtractFallbackAssistantFromMsgs 在「主通道未产出助手正文」时,从 Eino ADK 轨迹中回填用户可见回复。
|
||||
// 典型场景:监督者仅调用 exit(final_result 落在 Tool 消息中),或工具结果已写入历史但 lastAssistant 未更新。
|
||||
//
|
||||
// 优先级:最后一次 exit 工具输出 → 最后一条含 exit 的助手 tool_calls 参数中的 final_result。
|
||||
func einoExtractFallbackAssistantFromMsgs(msgs []adk.Message) string {
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
m := msgs[i]
|
||||
if m == nil || m.Role != schema.Tool {
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(m.ToolName), adk.ToolInfoExit.Name) {
|
||||
continue
|
||||
}
|
||||
content := strings.TrimSpace(m.Content)
|
||||
if content == "" || strings.HasPrefix(content, einomcp.ToolErrorPrefix) {
|
||||
continue
|
||||
}
|
||||
return content
|
||||
}
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
m := msgs[i]
|
||||
if m == nil || m.Role != schema.Assistant {
|
||||
continue
|
||||
}
|
||||
if s := einoExtractExitFinalFromAssistantToolCalls(m); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func einoExtractExitFinalFromAssistantToolCalls(msg *schema.Message) string {
|
||||
if msg == nil || len(msg.ToolCalls) == 0 {
|
||||
return ""
|
||||
}
|
||||
for i := len(msg.ToolCalls) - 1; i >= 0; i-- {
|
||||
tc := msg.ToolCalls[i]
|
||||
if !strings.EqualFold(strings.TrimSpace(tc.Function.Name), adk.ToolInfoExit.Name) {
|
||||
continue
|
||||
}
|
||||
if s := einoParseExitFinalResultArguments(tc.Function.Arguments); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func einoParseExitFinalResultArguments(arguments string) string {
|
||||
arguments = strings.TrimSpace(arguments)
|
||||
if arguments == "" {
|
||||
return ""
|
||||
}
|
||||
var wrap struct {
|
||||
FinalResult json.RawMessage `json:"final_result"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(arguments), &wrap); err != nil || len(wrap.FinalResult) == 0 {
|
||||
return ""
|
||||
}
|
||||
var s string
|
||||
if err := json.Unmarshal(wrap.FinalResult, &s); err == nil {
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
var anyVal interface{}
|
||||
if err := json.Unmarshal(wrap.FinalResult, &anyVal); err != nil {
|
||||
return ""
|
||||
}
|
||||
b, err := json.Marshal(anyVal)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
Reference in new issue
Block a user