From 25b601881838d2d99b4af111384606ab520ccff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A6=86=E6=9C=A8?= <43062104+yumusb@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:21:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(llm):=20=E4=B8=80=E6=AC=A1=E6=80=A7=20LLM?= =?UTF-8?q?=20=E8=B0=83=E7=94=A8=E8=A1=A5=E6=8C=82=20session=20id=EF=BC=8C?= =?UTF-8?q?=E8=AE=A9=E8=87=AA=E5=AE=9A=E4=B9=89=E4=BC=9A=E8=AF=9D=E5=A4=B4?= =?UTF-8?q?=E7=9C=9F=E6=AD=A3=E7=94=9F=E6=95=88=20(#175)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自定义会话头(profile 的 session_header_key)的头值取自请求 context 上的 session id,而该 id 由 agentcore 仅在挂载 transcript store 时才写入 context (agentcore.Prompt 中 `if s.writer != nil` 那一行)。 目标拆解(第 0 轮,DecomposeGoalsWithProvider)与冷节点压缩(§4 body 调用) 都是不挂 store 的一次性调用,context 上没有 session id,quotaAwareTransport 因此跳过该头。对 opencode zen 这类「缺 x-opencode-session 直接 400 MissingSessionID」的端点,表现为「第 0 轮目标拆解 400 失败、后续 planner 轮次却完全正常」;压缩失败则只留一行日志,极难关联到本设置。 改为在这两条路径显式挂上按探索稳定的 session id(exp-goals / exp-compactor),与旁路问答、连接测试已有的做法一致。附带修好用量归因: 这两个 id 能被 llmrec.parseSession 正确解析,此前这两条路径的 token 完全 记不进 llm_usage。 --- CHANGELOG.md | 6 ++++++ agent/compaction.go | 7 +++++++ agent/goals.go | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b76ad0..4fc48ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ## [Unreleased] +### LLM + +#### 修复的问题 + +- **修复自定义会话头在「一次性 LLM 调用」路径上不生效**:会话头的头值取自请求 context 上的 session id,而该 id 由 agentcore 仅在挂载了 transcript store 时才写入 context。目标拆解(第 0 轮)与冷节点压缩(§4 body 调用)都是不挂 store 的一次性调用,context 上没有 session id,网关侧读不到该头——对 opencode zen 这类「缺 `x-opencode-session` 直接 400 MissingSessionID」的端点,表现为「第 0 轮目标拆解 400 失败、后续 planner 轮次却完全正常」,且压缩失败只留一行日志、极难关联。现为这两条路径显式挂上按探索稳定的 session id(`exp-goals` / `exp-compactor`):既能正常带上该头,也让 llmrec 能把这两条路径的 token 用量正确归因到对应探索(此前完全记不到)。 + ## [0.3.14] - 2026-09-24 ### 任务列表 diff --git a/agent/compaction.go b/agent/compaction.go index 2dd5dd0..2e2e9e8 100644 --- a/agent/compaction.go +++ b/agent/compaction.go @@ -28,6 +28,7 @@ import ( "github.com/Autumn-27/artex/db" "github.com/Autumn-27/norma/llm" + "github.com/Autumn-27/norma/transcript" ) // Compactor performs background cold-node compaction for many explorations. @@ -85,6 +86,12 @@ func (c *Compactor) OnPlannerRound(ctx context.Context, ts *db.ExplorationStore) defer c.finish(ts.ID()) bg, cancel := context.WithTimeout(context.WithoutCancel(ctx), c.maxDur) defer cancel() + // 压缩是裸 provider 调用(compress 里直接 prov.Complete),不经过 agentcore + // 的会话循环,所以 ctx 上没有 session id;按 session-id 头做提示缓存/粘性 + // 路由的网关(opencode zen 缺 x-opencode-session 直接 400)就收不到该头。 + // 这里补一个按探索稳定的 id:同一探索的所有压缩请求共享它,既能带上头, + // 也让 llmrec 能把这次调用的 token 归因回该探索(此前记不到)。 + bg = transcript.WithSessionID(bg, fmt.Sprintf("exp%d-compactor", ts.ID())) if needMajor { c.major(bg, ts) } else { diff --git a/agent/goals.go b/agent/goals.go index e2c11b9..9bf9e17 100644 --- a/agent/goals.go +++ b/agent/goals.go @@ -3,6 +3,7 @@ package agent import ( "context" "encoding/json" + "fmt" "strings" "github.com/Autumn-27/artex/db" @@ -10,6 +11,7 @@ import ( "github.com/Autumn-27/norma/llm" acperm "github.com/Autumn-27/norma/permission" actool "github.com/Autumn-27/norma/tool" + "github.com/Autumn-27/norma/transcript" ) // goalsDefaultTmpl is the built-in EDITABLE body (段 [A]) of the goals-decomposer @@ -109,6 +111,15 @@ func DecomposeGoalsWithProvider(ctx context.Context, prov llm.Provider, dataDir, if prov == nil { return nil } + // 目标拆解是一次性调用:不挂 transcript store,所以 agentcore 不会往 ctx 上挂 + // session id(它只在有 writer 时才挂,见 agentcore.Prompt)。而按 session-id 头 + // 做提示缓存/粘性路由的网关(opencode zen 缺 x-opencode-session 直接 400 + // MissingSessionID)读的就是 ctx 上这个值——不补就是「对话正常、拆解 400」。 + // 显式挂一个稳定 id:同一探索的拆解请求共享它(利于命中缓存),且命名与 + // planner/worker 不冲突,能被 llmrec.parseSession 正确归因。 + if ts != nil { + ctx = transcript.WithSessionID(ctx, fmt.Sprintf("exp%d-goals", ts.ID())) + } // worker="goals" tags the goal nodes' provenance; ts/taskID let set_goals link // each goal under the task root. This is the catalog's real set_goals tool, so a // web-edited description/schema on it applies here too.