feat(llmrec): 录制请求体补记实际发出的思考参数
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
This commit is contained in:
1 parent
863e44ec24
commit
9103ff838a
2 files changed
+30
-11
No files matched your search
+28
-9
@@ -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))
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user