Pre-submission Checklist / 提交前检查
FAQ Check / FAQ 检查
Current State / 当前状态
目前 AxonHub 在提取会话 ID 时,默认只考虑 AH-Trace-Id:
|
// traceHeaderName returns the name of the header used for trace IDs. |
|
func traceHeaderName(config tracing.Config) string { |
|
if config.TraceHeader != "" { |
|
return config.TraceHeader |
|
} |
|
|
|
return "AH-Trace-Id" |
|
} |
|
|
|
// getTraceIDFromHeader extracts the trace ID from the request headers. |
|
func getTraceIDFromHeader(c *gin.Context, config tracing.Config) string { |
|
traceID := c.GetHeader(traceHeaderName(config)) |
|
if traceID != "" { |
|
return traceID |
|
} |
|
|
|
for _, header := range config.ExtraTraceHeaders { |
|
traceID = c.GetHeader(header) |
|
if traceID != "" { |
|
return traceID |
|
} |
|
} |
|
|
|
return "" |
|
} |
|
|
|
// tryGetTraceIDFromBody attempts to extract a trace ID from the request body |
|
// based on the configured ExtraTraceBodyFields. |
|
func tryGetTraceIDFromBody(c *gin.Context, config tracing.Config) (string, error) { |
|
if len(config.ExtraTraceBodyFields) == 0 { |
|
return "", nil |
|
} |
|
|
|
body, err := io.ReadAll(c.Request.Body) |
|
if err != nil { |
|
return "", fmt.Errorf("failed to read request body: %w", err) |
|
} |
|
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(body)) |
|
if len(body) == 0 { |
|
return "", nil |
|
} |
|
|
|
for _, field := range config.ExtraTraceBodyFields { |
|
result := gjson.GetBytes(body, field) |
|
if result.Exists() && result.String() != "" { |
|
return result.String(), nil |
|
} |
|
} |
|
|
|
return "", nil |
|
} |
或者特别适配了 Claude Code、Codex 的请求:
|
func tryExtractTraceIDFromClaudeCodeRequest(c *gin.Context, config tracing.Config) (string, error) { |
|
if c.Request.Method != http.MethodPost { |
|
return "", nil |
|
} |
|
|
|
path := c.Request.URL.Path |
|
if path != "/anthropic/v1/messages" && path != "/v1/messages" { |
|
return "", nil |
|
} |
|
|
|
bodyBytes, err := io.ReadAll(c.Request.Body) |
|
if err != nil { |
|
return "", fmt.Errorf("failed to read request body: %w", err) |
|
} |
|
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) |
|
if len(bodyBytes) == 0 { |
|
return "", nil |
|
} |
|
|
|
userID := gjson.GetBytes(bodyBytes, "metadata.user_id").String() |
|
if userID == "" { |
|
return "", nil |
|
} |
|
|
|
uid := claudecode.ParseUserID(userID) |
|
if uid == nil { |
|
return "", nil |
|
} |
|
|
|
traceID := uid.SessionID |
|
|
|
log.Debug(c.Request.Context(), "Extracted trace ID from claude code payload", log.String("trace_id", traceID)) |
|
|
|
return traceID, nil |
|
} |
|
|
|
// tryExtractTraceIDFromCodexRequest extracts the trace ID from the Codex session header. |
|
func tryExtractTraceIDFromCodexRequest(c *gin.Context) string { |
|
traceID := codex.GetSessionIDFromHeaders(c.Request.Header) |
|
if traceID == "" { |
|
return "" |
|
} |
|
|
|
traceSource := "codex header" |
|
if strings.TrimSpace(c.GetHeader(codex.SessionHeader)) == "" { |
|
traceSource = "codex turn metadata" |
|
} |
|
log.Debug(c.Request.Context(), "Extracted trace ID from "+traceSource, log.String("trace_id", traceID)) |
|
|
|
return traceID |
|
} |
Problem or Bottleneck / 问题或瓶颈
但是,OpenCode 默认并不发送以上支持的三种类型的任何 Trace 字段。
这会导致缓存命中率大大下降,AxonHub 将每次请求都作为单独的会话,从而轮询请求(可以看到当存在 2 个健康后端时,缓存命中率降低到 100/2=50% 左右。实际数值略高于 50% 是因为我还有其他单后端的渠道模型):
Proposed Optimization / 优化方案
OpenCode 会在 Header 发送两个相关字段来标识会话上下文:
https://github.com/anomalyco/opencode/blob/031f82adc89e254bed8bc7a3a88fde5c4066dc8b/packages/opencode/src/session/llm/request.ts#L177-L181
x-session-affinity:会话 ID,同一个会话使用相同的 ID;
x-parent-session-id:如果当前是 Subagent 请求,则包含父会话的 ID。
当前可以通过 server.trace.extra_trace_headers 配置来支持该字段,例如传入环境变量 AXONHUB_SERVER_TRACE_EXTRA_TRACE_HEADERS=["x-session-affinity"]。但鉴于中转站用户使用 OpenCode 的流行度较高,可能可以考虑默认支持该字段。
Expected Benefit / 期望收益
将能够正确识别 OpenCode 的会话列表,较大程度提高多渠道调度的缓存命中率。
Optimization Type / 优化类型
Performance / 性能
Affected Area / 影响区域
Channel Management / 渠道管理
Additional Context / 其他补充信息
No response
Pre-submission Checklist / 提交前检查
FAQ Check / FAQ 检查
Current State / 当前状态
目前 AxonHub 在提取会话 ID 时,默认只考虑
AH-Trace-Id:axonhub/internal/server/middleware/trace.go
Lines 23 to 74 in a240989
或者特别适配了 Claude Code、Codex 的请求:
axonhub/internal/server/middleware/trace.go
Lines 154 to 205 in a240989
Problem or Bottleneck / 问题或瓶颈
但是,OpenCode 默认并不发送以上支持的三种类型的任何 Trace 字段。
这会导致缓存命中率大大下降,AxonHub 将每次请求都作为单独的会话,从而轮询请求(可以看到当存在 2 个健康后端时,缓存命中率降低到 100/2=50% 左右。实际数值略高于 50% 是因为我还有其他单后端的渠道模型):
Proposed Optimization / 优化方案
OpenCode 会在 Header 发送两个相关字段来标识会话上下文:
https://github.com/anomalyco/opencode/blob/031f82adc89e254bed8bc7a3a88fde5c4066dc8b/packages/opencode/src/session/llm/request.ts#L177-L181
x-session-affinity:会话 ID,同一个会话使用相同的 ID;x-parent-session-id:如果当前是 Subagent 请求,则包含父会话的 ID。当前可以通过
server.trace.extra_trace_headers配置来支持该字段,例如传入环境变量AXONHUB_SERVER_TRACE_EXTRA_TRACE_HEADERS=["x-session-affinity"]。但鉴于中转站用户使用 OpenCode 的流行度较高,可能可以考虑默认支持该字段。Expected Benefit / 期望收益
将能够正确识别 OpenCode 的会话列表,较大程度提高多渠道调度的缓存命中率。
Optimization Type / 优化类型
Performance / 性能
Affected Area / 影响区域
Channel Management / 渠道管理
Additional Context / 其他补充信息
No response