优化: 上下文管理与提示词链路批
- T1 usage 口径: anthropic usage 两层容错+cache 三口径归一(prompt=input+read+creation) - T2 压缩水位判据: compress_should_trigger 双模式+ratio 钳位,开关关逐字节等价 - T3 跨会话检索: history_search 工具(两跳式)+message_repo 关键词检索+prompt 指引 - T4 代语义闭环: status 终局补写+飞书停靠/换代推卡+交付引导+watcher 兜底补漏 - 段序缓存重排: build_runtime_prompt 恒静段上移/工作上下文压尾(前缀缓存失效面收窄) - 智谱缓存解析: anthropic 臂补 prompt_tokens_details.cached_tokens 嵌套回退 - 工时禁令: 提示词双语加「禁止预估工时,AI Coding 工时无意义」 - 拖拽文件落盘: 非图片附件存引用行,防整段文本灌入输入框 - 事实修正: 提示词「40+ 工具」→「80+」(基线 81);tool_failure_memory 测试串行锁除竞态 - UI 拆分: TopBar/ToolResultBody 壳层化(top-bar/tool-result-body 子件目录)
This commit is contained in:
@@ -806,12 +806,21 @@ impl LlmProvider for AnthropicCompatProvider {
|
||||
other => warn!(block_type = other, "Anthropic 响应含未知 content 块类型,已忽略"),
|
||||
}
|
||||
}
|
||||
// 口径归一(G1,P0-A/u-claw 三口径,与 anthropic_helpers message_start 同表):
|
||||
// anthropic input_tokens 只含未缓存输入,不含 cache_read/creation 两段;
|
||||
// 落库 prompt_tokens 对齐 openai/deepseek 全量输入语义(hit/miss ⊆ prompt),
|
||||
// 故 prompt = input + read + creation,total = prompt + output。
|
||||
// cache 字段:creation(写入)= miss 全价语义,read(命中)= hit 低价。
|
||||
let prompt_tokens = resp
|
||||
.usage
|
||||
.input_tokens
|
||||
.saturating_add(resp.usage.cache_read())
|
||||
.saturating_add(resp.usage.cache_creation_input_tokens);
|
||||
let usage = TokenUsage {
|
||||
prompt_tokens: resp.usage.input_tokens,
|
||||
prompt_tokens,
|
||||
completion_tokens: resp.usage.output_tokens,
|
||||
total_tokens: resp.usage.input_tokens + resp.usage.output_tokens,
|
||||
// anthropic cache 字段:creation(写入)= miss 全价语义,read(命中)= hit 低价
|
||||
prompt_cache_hit_tokens: resp.usage.cache_read_input_tokens,
|
||||
total_tokens: prompt_tokens.saturating_add(resp.usage.output_tokens),
|
||||
prompt_cache_hit_tokens: resp.usage.cache_read(),
|
||||
prompt_cache_miss_tokens: resp.usage.cache_creation_input_tokens,
|
||||
reasoning_tokens: 0,
|
||||
};
|
||||
@@ -1016,6 +1025,47 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
/// 辅助:构造带缓存分项的 message_start 事件(cache_read/cache_creation,P0-A 三口径)
|
||||
fn message_start_with_cache(input_tokens: u32, cache_read: u32, cache_creation: u32) -> String {
|
||||
format!(
|
||||
r#"{{"type":"message_start","message":{{"usage":{{"input_tokens":{},"cache_read_input_tokens":{},"cache_creation_input_tokens":{},"output_tokens":0}}}}}}"#,
|
||||
input_tokens, cache_read, cache_creation
|
||||
)
|
||||
}
|
||||
|
||||
/// 辅助:构造智谱 anthropic 兼容端点形态的 message_start(隐式缓存命中走嵌套
|
||||
/// prompt_tokens_details.cached_tokens,无顶层 cache 字段——官方缓存文档口径)
|
||||
fn message_start_zhipu_nested(input_tokens: u32, cached_tokens: u32) -> String {
|
||||
format!(
|
||||
r#"{{"type":"message_start","message":{{"usage":{{"input_tokens":{},"prompt_tokens_details":{{"cached_tokens":{}}},"output_tokens":0}}}}}}"#,
|
||||
input_tokens, cached_tokens
|
||||
)
|
||||
}
|
||||
|
||||
/// 智谱嵌套形态回退:顶层 cache 字段缺席时从 prompt_tokens_details.cached_tokens
|
||||
/// 取命中量,prompt=input+cached(隐式缓存无写入分项,miss=0)
|
||||
#[test]
|
||||
fn anthropic_zhipu_nested_cache_tokens_fallback() {
|
||||
let mut acc: Option<TokenUsage> = None;
|
||||
let c = apply_anthropic_event(&message_start_zhipu_nested(500, 4000), &mut acc);
|
||||
let u = c.usage.expect("usage 应随 message_start 带出");
|
||||
assert_eq!(u.prompt_cache_hit_tokens, 4000);
|
||||
assert_eq!(u.prompt_cache_miss_tokens, 0);
|
||||
// 口径归一:prompt = input + cached
|
||||
assert_eq!(u.prompt_tokens, 4500);
|
||||
}
|
||||
|
||||
/// 顶层优先:官方 Anthropic 端点回顶层字段时嵌套值(即便异常并存)不覆盖顶层
|
||||
#[test]
|
||||
fn anthropic_top_level_cache_read_wins_over_nested() {
|
||||
let mut acc: Option<TokenUsage> = None;
|
||||
let frame = r#"{"type":"message_start","message":{"usage":{"input_tokens":100,"cache_read_input_tokens":200,"prompt_tokens_details":{"cached_tokens":999},"output_tokens":0}}}"#;
|
||||
let c = apply_anthropic_event(frame, &mut acc);
|
||||
let u = c.usage.expect("usage 应随 message_start 带出");
|
||||
assert_eq!(u.prompt_cache_hit_tokens, 200);
|
||||
assert_eq!(u.prompt_tokens, 300);
|
||||
}
|
||||
|
||||
/// 辅助:构造 message_delta 事件(output_tokens 为累计值)
|
||||
fn message_delta(output_tokens: u32) -> String {
|
||||
format!(
|
||||
@@ -1135,6 +1185,48 @@ mod tests {
|
||||
assert!(c.usage.is_none(), "无 usage 时 message_stop usage 为 None");
|
||||
}
|
||||
|
||||
/// 三口径映射锁定(G1,P0-A):anthropic input_tokens 不含缓存两段,落库 prompt_tokens
|
||||
/// 对齐 openai/deepseek 全量输入语义(hit/miss ⊆ prompt)→ prompt = input + read +
|
||||
/// creation;message_delta 的 total 重算基于累加器 prompt(已含缓存段)自动继承新口径。
|
||||
#[test]
|
||||
fn anthropic_message_start_cache_three_caliber_prompt_sums_cache_segments() {
|
||||
let mut acc: Option<TokenUsage> = None;
|
||||
let c = apply_anthropic_event(&message_start_with_cache(100, 64, 36), &mut acc);
|
||||
let u0 = c.usage.expect("message_start 应带出初始 usage 快照");
|
||||
assert_eq!(u0.prompt_tokens, 200, "prompt = input(100)+read(64)+creation(36)");
|
||||
assert_eq!(u0.prompt_cache_hit_tokens, 64, "cache_read → hit 原样映射");
|
||||
assert_eq!(u0.prompt_cache_miss_tokens, 36, "cache_creation → miss 原样映射");
|
||||
assert_eq!(u0.total_tokens, 200, "初始 total 同口径");
|
||||
|
||||
// message_delta 累计 output 后 total 重算:继承含缓存段 prompt 基数(200+30)
|
||||
let _ = apply_anthropic_event(&message_delta(30), &mut acc);
|
||||
let a = acc.as_ref().unwrap();
|
||||
assert_eq!(a.prompt_tokens, 200, "delta 不改 prompt");
|
||||
assert_eq!(a.total_tokens, 230, "total = prompt(含缓存两段)+output");
|
||||
}
|
||||
|
||||
/// 缺失容错(G2,P0-A):端点 200 但 usage 键整体缺失/usage 对象缺计数字段时,
|
||||
/// 不再整帧反序列化失败硬炸(三路走查 #3「有效文本回复整体丢弃」),全零兜底
|
||||
/// 下行走既有估算轨(round_usage_effective prompt==0 → 估算打标),对齐 OpenAI 臂。
|
||||
#[test]
|
||||
fn anthropic_response_missing_or_partial_usage_deserializes_not_fatal() {
|
||||
// usage 键整体缺失
|
||||
let resp: AnthropicResponse = serde_json::from_str(
|
||||
r#"{"id":"msg_1","model":"claude-x","content":[{"type":"text","text":"hi"}],"stop_reason":"end_turn"}"#,
|
||||
)
|
||||
.expect("usage 键缺失不应炸帧");
|
||||
assert_eq!(resp.usage.input_tokens, 0, "缺 usage → 全零兜底走估算轨");
|
||||
assert_eq!(resp.usage.output_tokens, 0);
|
||||
|
||||
// usage 对象存在但缺计数字段(部分网关只回 cache 分项)
|
||||
let resp2: AnthropicResponse = serde_json::from_str(
|
||||
r#"{"id":"msg_2","model":"claude-x","content":[],"usage":{"cache_read_input_tokens":7}}"#,
|
||||
)
|
||||
.expect("usage 缺 input/output 字段不应炸帧");
|
||||
assert_eq!(resp2.usage.input_tokens, 0);
|
||||
assert_eq!(resp2.usage.cache_read_input_tokens, 7, "已有分项不丢");
|
||||
}
|
||||
|
||||
/// content_block_start (tool_use) 带 id+name
|
||||
#[test]
|
||||
fn anthropic_content_block_start_tool_use() {
|
||||
|
||||
Reference in New Issue
Block a user