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 {
|
type Recorder struct {
|
||||||
inner llm.Provider
|
inner llm.Provider
|
||||||
pg *db.DB
|
pg *db.DB
|
||||||
model string // model name (from config, not in CompletionRequest)
|
model string // model name (from config, not in CompletionRequest)
|
||||||
prof string // LLM profile name (from llm_profiles)
|
prof string // LLM profile name (from llm_profiles)
|
||||||
enabled func() bool // reports whether recording is currently on; nil = always record
|
// 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.
|
// 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
|
// profName is the LLM profile name (e.g. "default"); may be empty. thinkingType /
|
||||||
// predicate records unconditionally (backward-compatible default).
|
// reasoningEffort are the config-level thinking params actually sent to the API
|
||||||
func Wrap(inner llm.Provider, pg *db.DB, model, profName string, enabled func() bool) *Recorder {
|
// (empty = not sent). A nil enabled predicate records unconditionally.
|
||||||
return &Recorder{inner: inner, pg: pg, model: model, prof: profName, enabled: enabled}
|
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
|
// 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).
|
// Serialize the request only when storing bodies (this is the expensive part).
|
||||||
reqBody := ""
|
reqBody := ""
|
||||||
if recordBodies {
|
if recordBodies {
|
||||||
reqBody = serializeRequest(req)
|
reqBody = r.serializeRequest(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(yield func(llm.StreamEvent, error) bool) {
|
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.
|
// 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{
|
m := map[string]any{
|
||||||
"system": req.System,
|
"system": req.System,
|
||||||
"messages": req.Messages,
|
"messages": req.Messages,
|
||||||
"max_tokens": req.MaxTokens,
|
"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 {
|
if len(req.Tools) > 0 {
|
||||||
// Store tool names only (full schemas are huge).
|
// Store tool names only (full schemas are huge).
|
||||||
names := make([]string, len(req.Tools))
|
names := make([]string, len(req.Tools))
|
||||||
|
|||||||
+2
-2
@@ -410,7 +410,7 @@ func (s *Server) applyLLM(cfg agent.Config) error {
|
|||||||
s.cfgMu.Lock()
|
s.cfgMu.Lock()
|
||||||
profName := s.llmProf
|
profName := s.llmProf
|
||||||
s.cfgMu.Unlock()
|
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 轮询(默认关):把激活配置包进故障转移链,当前配置不可用时自动切下一个。
|
// LLM 轮询(默认关):把激活配置包进故障转移链,当前配置不可用时自动切下一个。
|
||||||
// 只影响「走全局激活配置」的这条路径——agent 绑定 / 任务 pin 的走 providerForProfile,
|
// 只影响「走全局激活配置」的这条路径——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.
|
// Wrap with recorder, tagged with this profile's name.
|
||||||
if p, _ := s.m.pg.ProfileByID(id); p != nil {
|
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()
|
s.provCacheMu.Lock()
|
||||||
if generation != s.provCacheGen {
|
if generation != s.provCacheGen {
|
||||||
|
|||||||
Reference in new issue
Block a user