From 225d9ba0d9ce1871d0996ee75183003474efa56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:40:21 +0800 Subject: [PATCH] Add files via upload --- internal/agent/agent.go | 21 ++++++++++++--------- internal/agent/agent_trace.go | 27 +++++++++++++++++++++++++++ internal/agent/agent_trace_test.go | 19 +++++++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 72b1ef90..194d03dd 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -91,15 +91,15 @@ func NewAgent(cfg *config.OpenAIConfig, agentCfg *config.AgentConfig, mcpServer llmClient := openai.NewClient(cfg, httpClient, logger) return &Agent{ - openAIClient: llmClient, - config: cfg, - agentConfig: agentCfg, - mcpServer: mcpServer, - externalMCPMgr: externalMCPMgr, - logger: logger, - maxIterations: maxIterations, - toolNameMapping: make(map[string]string), // 初始化工具名称映射 - toolDescriptionMode: "short", + openAIClient: llmClient, + config: cfg, + agentConfig: agentCfg, + mcpServer: mcpServer, + externalMCPMgr: externalMCPMgr, + logger: logger, + maxIterations: maxIterations, + toolNameMapping: make(map[string]string), // 初始化工具名称映射 + toolDescriptionMode: "short", } } @@ -120,6 +120,9 @@ type ChatMessage struct { ToolName string `json:"tool_name,omitempty"` // ReasoningContent 对应 OpenAI/DeepSeek 的 reasoning_content;思考模式 + 工具调用后续跑须回传(见 DeepSeek 文档)。 ReasoningContent string `json:"reasoning_content,omitempty"` + // ModelFacingTrace is runtime-only metadata: true means Content was already the exact + // payload seen at the model boundary and must be restored byte-for-byte. + ModelFacingTrace bool `json:"-"` } // MarshalJSON 自定义JSON序列化,将tool_calls中的arguments转换为JSON字符串 diff --git a/internal/agent/agent_trace.go b/internal/agent/agent_trace.go index 9628ce2c..c701a7f2 100644 --- a/internal/agent/agent_trace.go +++ b/internal/agent/agent_trace.go @@ -5,6 +5,31 @@ import ( "strings" ) +const ModelFacingTraceVersionKey = "cyberstrike_model_facing_trace_version" + +// IsModelFacingTraceJSON reports whether a persisted trace was produced from the final +// model-boundary state. Legacy traces have no version marker and require one-time migration. +func IsModelFacingTraceJSON(traceInputJSON string) bool { + var raw []map[string]interface{} + if err := json.Unmarshal([]byte(strings.TrimSpace(traceInputJSON)), &raw); err != nil { + return false + } + for _, msg := range raw { + extra, _ := msg["extra"].(map[string]interface{}) + v, ok := extra[ModelFacingTraceVersionKey] + if !ok { + continue + } + switch n := v.(type) { + case float64: + return n >= 1 + case int: + return n >= 1 + } + } + return false +} + // ParseTraceMessages 解析落库的 last_react_input(OpenAI 风格 messages JSON 数组)。 func ParseTraceMessages(traceInputJSON string) ([]ChatMessage, error) { traceInputJSON = strings.TrimSpace(traceInputJSON) @@ -15,6 +40,7 @@ func ParseTraceMessages(traceInputJSON string) ([]ChatMessage, error) { if err := json.Unmarshal([]byte(traceInputJSON), &raw); err != nil { return nil, err } + modelFacing := IsModelFacingTraceJSON(traceInputJSON) out := make([]ChatMessage, 0, len(raw)) for _, msgMap := range raw { msg := ChatMessage{} @@ -23,6 +49,7 @@ func ParseTraceMessages(traceInputJSON string) ([]ChatMessage, error) { continue } msg.Role = role + msg.ModelFacingTrace = modelFacing if content, ok := msgMap["content"].(string); ok { msg.Content = content } diff --git a/internal/agent/agent_trace_test.go b/internal/agent/agent_trace_test.go index c248255f..47546c7f 100644 --- a/internal/agent/agent_trace_test.go +++ b/internal/agent/agent_trace_test.go @@ -55,3 +55,22 @@ func TestMergeAssistantTraceOutput(t *testing.T) { t.Fatalf("expected merged output, got %q", out[len(out)-1].Content) } } + +func TestParseTraceMessagesMarksVersionedModelFacingTrace(t *testing.T) { + raw := `[{"role":"system","content":"s","extra":{"cyberstrike_model_facing_trace_version":1}},{"role":"user","content":"u"},{"role":"tool","content":"exact","tool_call_id":"c1"}]` + if !IsModelFacingTraceJSON(raw) { + t.Fatal("versioned trace not detected") + } + msgs, err := ParseTraceMessages(raw) + if err != nil { + t.Fatal(err) + } + for i, msg := range msgs { + if !msg.ModelFacingTrace { + t.Fatalf("message %d missing model-facing marker", i) + } + } + if IsModelFacingTraceJSON(`[{"role":"user","content":"legacy"}]`) { + t.Fatal("legacy trace incorrectly marked model-facing") + } +}