From 9103ff838ace4258ada2220dc8942d0de6172458 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:35:12 -0400 Subject: [PATCH] =?UTF-8?q?feat(llmrec):=20=E5=BD=95=E5=88=B6=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=BD=93=E8=A1=A5=E8=AE=B0=E5=AE=9E=E9=99=85=E5=8F=91?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E6=80=9D=E8=80=83=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recorder 携带配置级 thinking_type/reasoning_effort(这些在 norma buildBody 里从 provider 配置注入 HTTP body,不在 CompletionRequest 上,故录制原先完全看不到)。 序列化时写入 thinking:{type,effort},type 取有效值——每请求 req.Thinking 覆盖优先 于配置级(如 compaction 摘要强制 disabled),与 buildBody 判定一致。前端录制查看页 原样美化 JSON,自动展示该字段。 Claude-Session: https://claude.ai/code/session_01Bs2rrsVHUBTxRusm9espwB --- llmrec/llmrec.go | 37 ++++++++++++++++++++++++++++--------- server/server.go | 4 ++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/llmrec/llmrec.go b/llmrec/llmrec.go index d4c3d43..1bbd55c 100644 --- a/llmrec/llmrec.go +++ b/llmrec/llmrec.go @@ -41,16 +41,25 @@ func TaskIDFrom(ctx context.Context) string { type Recorder struct { inner llm.Provider pg *db.DB - model string // model name (from config, not in CompletionRequest) - prof string // LLM profile name (from llm_profiles) - enabled func() bool // reports whether recording is currently on; nil = always record + model string // model name (from config, not in CompletionRequest) + prof string // LLM profile name (from llm_profiles) + // thinkingType / reasoningEffort 是配置级思考参数(思考开关 / 思考强度)。它们在 + // norma 的 buildBody() 里从 provider 配置注入真正的 HTTP body,不出现在 + // CompletionRequest 上,故 Recorder 需在此单独带一份,序列化时写进录制。 + thinkingType string + reasoningEffort string + enabled func() bool // reports whether recording is currently on; nil = always record } // Wrap returns a Provider that records calls to pg when enabled() reports true. -// profName is the LLM profile name (e.g. "default"); may be empty. A nil enabled -// predicate records unconditionally (backward-compatible default). -func Wrap(inner llm.Provider, pg *db.DB, model, profName string, enabled func() bool) *Recorder { - return &Recorder{inner: inner, pg: pg, model: model, prof: profName, enabled: enabled} +// profName is the LLM profile name (e.g. "default"); may be empty. thinkingType / +// reasoningEffort are the config-level thinking params actually sent to the API +// (empty = not sent). A nil enabled predicate records unconditionally. +func Wrap(inner llm.Provider, pg *db.DB, model, profName, thinkingType, reasoningEffort string, enabled func() bool) *Recorder { + return &Recorder{ + inner: inner, pg: pg, model: model, prof: profName, + thinkingType: thinkingType, reasoningEffort: reasoningEffort, enabled: enabled, + } } // parseSession extracts task id and worker role from session strings like @@ -103,7 +112,7 @@ func (r *Recorder) Stream(ctx context.Context, req llm.CompletionRequest) iter.S // Serialize the request only when storing bodies (this is the expensive part). reqBody := "" if recordBodies { - reqBody = serializeRequest(req) + reqBody = r.serializeRequest(req) } return func(yield func(llm.StreamEvent, error) bool) { @@ -255,12 +264,22 @@ func (r *Recorder) record(req llm.CompletionRequest, session, taskID, worker, re } // serializeRequest builds a JSON representation of the completion request. -func serializeRequest(req llm.CompletionRequest) string { +func (r *Recorder) serializeRequest(req llm.CompletionRequest) string { m := map[string]any{ "system": req.System, "messages": req.Messages, "max_tokens": req.MaxTokens, } + // 记录本次调用实际发出的思考参数。type 采用「有效值」:每请求覆盖 req.Thinking + // 优先于配置级 thinkingType(与 norma buildBody 的判定一致,如 compaction 摘要会 + // 强制 disabled);effort 无每请求覆盖,直接取配置值。两者皆空则不写 thinking 字段。 + effType := r.thinkingType + if req.Thinking != "" { + effType = req.Thinking + } + if effType != "" || r.reasoningEffort != "" { + m["thinking"] = map[string]string{"type": effType, "effort": r.reasoningEffort} + } if len(req.Tools) > 0 { // Store tool names only (full schemas are huge). names := make([]string, len(req.Tools)) diff --git a/server/server.go b/server/server.go index 36895b6..fefd45e 100644 --- a/server/server.go +++ b/server/server.go @@ -410,7 +410,7 @@ func (s *Server) applyLLM(cfg agent.Config) error { s.cfgMu.Lock() profName := s.llmProf s.cfgMu.Unlock() - prov = llmrec.Wrap(prov, s.m.PG(), cfg.Model, profName, s.m.LLMRecordEnabled) + prov = llmrec.Wrap(prov, s.m.PG(), cfg.Model, profName, cfg.ThinkingType, cfg.ReasoningEffort, s.m.LLMRecordEnabled) } // LLM 轮询(默认关):把激活配置包进故障转移链,当前配置不可用时自动切下一个。 // 只影响「走全局激活配置」的这条路径——agent 绑定 / 任务 pin 的走 providerForProfile, @@ -504,7 +504,7 @@ func (s *Server) providerForProfile(id int64) (llm.Provider, agent.Config, bool) } // Wrap with recorder, tagged with this profile's name. if p, _ := s.m.pg.ProfileByID(id); p != nil { - prov = llmrec.Wrap(prov, s.m.PG(), cfg.Model, p.Name, s.m.LLMRecordEnabled) + prov = llmrec.Wrap(prov, s.m.PG(), cfg.Model, p.Name, cfg.ThinkingType, cfg.ReasoningEffort, s.m.LLMRecordEnabled) } s.provCacheMu.Lock() if generation != s.provCacheGen {