基于 ReAct Agent 的 KV Cache 演示


文档摘要

源文件:chapter2/kv-cache/README.md 基于 ReAct Agent 的 KV Cache 演示 一个综合演示,借助一个带本地文件系统工具的 ReAct 模式 Agent,展示 KV(Key-Value)缓存在 LLM 中的重要性。本项目通过六种不同的实现模式——一种正确、五种错误——说明看似微小的改动如何让 KV Cache 失效,从而严重影响性能。 🎯 概览 本项目实现了一个 ReAct(Reasoning and Acting)Agent,使用当前的 Moonshot Kimi 模型(默认 )分析代码项目。

源文件:chapter2/kv-cache/README.md

基于 ReAct Agent 的 KV Cache 演示

一个综合演示,借助一个带本地文件系统工具的 ReAct 模式 Agent,展示 KV(Key-Value)缓存在 LLM 中的重要性。本项目通过六种不同的实现模式——一种正确、五种错误——说明看似微小的改动如何让 KV Cache 失效,从而严重影响性能。

🎯 概览

本项目实现了一个 ReAct(Reasoning and Acting)Agent,使用当前的 Moonshot Kimi 模型(默认 kimi-k2.6)分析代码项目。Agent 采用标准的 OpenAI 工具调用格式,并演示了六种不同的实现模式——一种正确、五种错误——展示看似微小的改动如何让 KV Cache 失效并大幅拖累性能。

关于模型。 在 Moonshot 线上端点上,当前整个 Kimi 家族(kimi-k2.5 / kimi-k2.6 / kimi-k2.7* / kimi-k3)都是推理模型:它们会输出 reasoning_content,且只接受 temperature=1(代码会自动处理这一点)。它们上报 cached_tokens,这正是本实验要测量的对象。与之相对,旧的非推理 moonshot-v1-* chat 模型上报 cached_tokens,因此无法演示缓存效果。我们默认使用 kimi-k2.6,因为它与同族其他模型一样会上报缓存命中,但推理开销最轻,能让 TTFT 的噪声最小。由于任何推理模型都会带来可变的思考延迟,缓存命中率 / 缓存比 这两列(具有确定性)才是这里最稳健的信号;请把 TTFT 视作次要指标。

什么是 KV Cache?

KV Cache 存储的是 Transformer 模型注意力机制产生的键值对。当对话上下文保持稳定时,这些缓存值可以被复用,从而大幅减少计算量、改善响应延迟(尤其是首 token 延迟,即 Time to First Token,TTFT)。

🚀 特性

  • ReAct 模式 Agent:实现推理与行动模式,系统地执行任务
  • 标准工具调用:使用 OpenAI 标准工具调用格式(无需手动解析)
  • 本地文件系统工具:安全实现 read_filefindgrep 命令
  • 稳健的错误处理:工具失败时仍继续执行,把错误作为结果回传
  • 六种实现模式:演示正确与错误的 KV Cache 模式
  • 全面的指标:跟踪 TTFT、总时长、缓存命中/未命中、token 用量
  • 离线对比报告--report):无需 API Key,从已保存的结果文件渲染跨策略对比表
  • 成本估算:对比表附带一列示意性的计费 token / 节省比例(可通过 --cache-price-ratio 配置)
  • 详细日志:深入洞察缓存行为与性能影响
  • 智能完成判定:自动把不带工具调用的响应视为最终答案
  • 参数过滤:安全处理意外的工具参数而不中断

📦 实现模式

1. ✅ 正确实现(correct

在整个对话过程中保持上下文稳定:

  • 固定的系统提示
  • 一致的工具顺序
  • 稳定的消息历史格式
  • 没有不必要的上下文变化

2. ❌ 动态系统提示(dynamic_system

每次请求都在系统提示中加入时间戳:

  • 每次迭代都重建整个消息上下文
  • 每次调用都使整段缓存失效
  • 显著增加 TTFT
  • 浪费计算资源

3. ❌ 打乱工具顺序(shuffled_tools

每次请求都随机重排工具列表:

  • 每次迭代都用打乱后的工具重建上下文
  • 功能完全相同却破坏了缓存
  • 说明一致顺序的重要性
  • 展示微小改动如何影响缓存

4. ❌ 动态用户档案(dynamic_profile

在上下文中加入不断变化的用户积分:

  • 每次迭代都按更新后的积分重建上下文
  • 引入不必要的动态信息
  • 为无关改动强制让缓存失效
  • 模拟生产环境中的常见反模式

5. ❌ 滑动窗口(sliding_window

只保留最近 5 条消息:

  • 每次迭代都用不同的消息窗口重建上下文
  • 表面上缩短了上下文长度
  • 实际上破坏了缓存连续性
  • 说明截断策略为何可能适得其反

6. ❌ 文本格式(text_format

把对话历史格式化成纯文本而非结构化消息:

  • 每次迭代都用文本格式重建上下文
  • 破坏了预期的消息格式
  • 阻止缓存被正确利用
  • 说明遵循 API 约定的重要性

🔑 关键实现细节

要让错误模式下的 KV Cache 真正失效,必须在每次迭代的开始就把整个消息上下文从零重建。

实现上通过以下方式保证这一点:

  1. CORRECT 模式

    • 在首次迭代中一次性构建好消息列表
    • 整个执行过程中始终复用同一个列表
    • 把新消息(assistant 响应、工具结果)追加到这个持久列表
    • 结果:上下文稳定 → KV Cache 高效工作
  2. 错误模式

    • 每次迭代开始时从对话历史重建整个消息列表
    • 单次迭代内部仍会追加工具结果以保证 API 流程正确
    • 但下一次迭代又从重建的上下文重新开始
    • 结果:上下文变化 → KV Cache 失效

重要:两种模式在单次迭代内部都会追加工具结果,以保证 API 看到完整的对话流。关键区别在于错误模式会在每次新迭代开始时丢弃消息列表并重建,从而强制让缓存失效。

🛠️ 安装

# Navigate to the project directory cd chapter2/kv-cache # Install dependencies pip install -r requirements.txt # Set your Kimi API key export MOONSHOT_API_KEY="your-api-key-here"

通用回退(OpenRouter):未设置 MOONSHOT_API_KEY / KIMI_API_KEY 时,只要
配置了 OPENROUTER_API_KEY,实验会自动改走 OpenRouter(kimi-* 会映射为
moonshotai/kimi-k2)。设置了 Moonshot key 时行为完全不变。

📖 用法

交互模式(默认)

# Run interactive mode selection menu python main.py # You'll see a menu like: # ============================================================ # KV CACHE DEMONSTRATION - MODE SELECTION # ============================================================ # # Select a mode to run: # # 1. ✅ Correct Implementation - Optimal KV cache usage # 2. ❌ Dynamic System Prompt - Adds timestamps # 3. ❌ Shuffled Tools - Randomizes tool order # 4. ❌ Dynamic Profile - Updates user credits # 5. ❌ Sliding Window - Keeps only recent messages # 6. ❌ Text Format - Plain text instead of structured # 7. 📊 Compare All - Run all modes and compare # # 0. Exit

命令行选项

CLI 自带中文 --help;运行 python main.py --help 查看完整列表。关键参数:

Flag 说明
--mode MODE 运行单个策略(correct / dynamic_system / shuffled_tools / dynamic_profile / sliding_window / text_format)
--compare 依次运行全部策略并打印横向对比表(需要 API Key)
--report 离线:从已保存的 result_*.json / comparison_*.json 生成对比表,无需 API Key
--input ... 配合 --report 指定结果文件 / 通配符 / 目录(默认扫描当前目录)
--model MODEL 选择模型(默认 kimi-k2.6;同族 kimi-k2.5 / kimi-k3 亦可,均会上报 cached_tokens
--output PATH 指定结果 JSON 的输出路径(默认按模式 + 时间戳自动命名)
--cache-price-ratio R 成本估算中缓存 token 相对正常 token 的计费比例(默认 0.1,即一折),仅作示意
--task, --root-dir 自定义任务 / 文件工具根目录
# Run specific mode directly (bypasses menu) python main.py --mode correct # Pick a model and write to a named file python main.py --mode sliding_window --model kimi-k2.6 --output run.json # Run comparison across all modes (needs API key) python main.py --compare # Disable interactive mode python main.py --no-interactive --mode correct

离线对比报告(无需 API Key)

实时运行需要 Moonshot/Kimi API Key。要读取已保存的结果文件并用一条命令打印跨策略对比表:

# Uses the result_*.json files already in this directory python main.py --report # Or point at specific files / a directory, and change the assumed cache discount python main.py --report --input result_correct_*.json result_text_format_*.json python main.py --report --cache-price-ratio 0.5

该表格跨策略对比缓存命中率、缓存比、TTFT 延迟、总时长,以及示意性的计费 token / 节省比例估算。报告既能解析旧的单模式文件(指标以 AgentMetrics(...) 字符串形式存储),也能解析较新的 dict 格式文件,因此既有的结果依然可用。

Bill.Tok / Save% 两列是实测 token 数与你提供的 --cache-price-ratio 的透明函数——
它们是对成本影响的示意,并非某个具体服务商的报价。

自定义任务

# Provide custom task via command line python main.py --mode correct --task "Read all README files and summarize their contents" # Use different root directory python main.py --mode correct --root-dir ../.. --task "Analyze the project structure"

📊 指标解读

性能指标

  • TTFT(首 token 延迟):生成第一个 token 所需的时间——对用户体验至关重要
    • 按迭代跟踪以展示缓存收益
    • 第一次迭代:冷启动(无缓存)
    • 后续迭代:缓存加持下应有改善
  • TTFT 分析
    • 逐迭代跟踪展示缓存效果
    • 平均 TTFT 反映整体性能
    • 改善百分比量化缓存收益
  • 总执行时间:任务执行的完整耗时
  • 迭代次数:完成的 ReAct 循环数
  • 工具调用数:工具被调用的次数

缓存统计

  • 已缓存 token:从缓存中取回的 token 数
  • 缓存命中:成功的缓存取回
  • 缓存未命中:失败的缓存取回,需要重新计算
  • 缓存命中率:成功使用缓存的比例

token 用量

  • Prompt Tokens:输入 prompt 中的 token 数
  • Completion Tokens:模型生成的 token 数
  • 缓存比(Cache Ratio):prompt token 中由缓存提供的比例

📈 预期结果

对比各实现时,你应观察到:

  1. 正确实现

    • 第一次之后的每次迭代都上报缓存 token(稳定前缀被复用)
    • 低而稳定的首 token 延迟
    • 前缀(系统提示 + 工具 + 之前的轮次)由缓存提供
  2. 错误实现

    • 缓存比崩溃式下降——例如打乱工具列表会把 prompt token 中由缓存提供的比例降到正确运行的大约三分之一
    • TTFT 持续偏高(把历史改写成文本或打乱工具在实测中让首 token 延迟翻倍以上)
    • 总时长更长(text_format 高达正确运行的约 2.4 倍)

在推理模型上(当前整个 Kimi 家族都会推理)TTFT 还带有隐藏思考 token 带来的额外
方差,因此缓存比一列是该效应最干净的证据。另请注意,在原本稳定的前缀末尾追加
动态数据(如 dynamic_system / dynamic_profile 所做的那样)只会让缓存从该点起失效
——它之前的基础前缀仍然能缓存——因此它们的总览缓存比可能看起来接近 correct,而
总时长仍然退化。教训是:让动态数据完全离开前缀。

🔍 输出示例

📊 Performance Metrics: • Time to First Token (TTFT): 0.823 seconds • TTFT per iteration: Iteration 1: 0.823s Iteration 2: 0.234s (with cache) Iteration 3: 0.198s (with cache) Iteration 4: 0.187s (with cache) Iteration 5: 0.192s (with cache) • TTFT Analysis: First iteration: 0.823s Last iteration: 0.192s Average (after first): 0.203s Improvement: 76.7%

对比表(--report 读取本目录下已保存的结果文件)

以下是单次 --compare 运行的真实实测数字——六种策略在同一个任务下(kimi-k2.6,根目录 = 当前文件夹,任务 = "找出所有 Python 文件、读 main.py + agent.py、用 3 句话总结"),随后用 python main.py --report 读回:

Mode Iters 1st TTFT Avg TTFT Total(s) Prompt Cached Hit% Cache% Bill.Tok Save% ---------------------------------------------------------------------------------------------------------------- correct 3 2.328 6.054 18.163 7,567 768 100.0 10.1 6,876 9.1 dynamic_profile 3 2.206 5.986 17.962 7,652 768 100.0 10.0 6,961 9.0 dynamic_system 3 2.497 8.085 24.260 7,639 768 100.0 10.1 6,948 9.0 shuffled_tools 3 7.818 11.122 33.369 7,568 256 100.0 3.4 7,338 3.0 sliding_window 5 2.234 3.649 14.704 2,224 1,510 100.0 67.9 865 61.1 text_format 3 6.189 14.432 43.297 7,430 674 100.0 9.1 6,823 8.2

读表:shuffled_tools 重排的是位于前缀前部的工具定义,因此它的缓存比从 10.1% 崩溃到 3.4%,首 token 延迟也从约 2.3s 飙升到约 7.8s。text_format 每轮都把历史重建成一大块纯文本,总时长约为正确运行的 2.4 倍sliding_window 之所以缓存比高,只是因为截断让 prompt 本身变小了(token 更少且大多是已缓存的)——这提醒我们:在小 prompt 上拿到的高比例并不等于一次高效的运行。

Cache% 是 prompt token 中由缓存提供的比例;Hit% 是看到任意缓存的迭代比例。Bill.Tok/Save% 假设缓存 token 按正常 token 的 --cache-price-ratio(默认 0.1)计费——属示意,非服务商报价。六行全部来自一次 --compare 运行,因此可直接横向对比;用 python main.py --compare 可重新生成以复现。

💡 关键洞见

  1. 稳定的上下文至关重要:对消息上下文的任何改动都会让 KV Cache 失效
  2. 顺序很重要:即使重排完全相同的内容也会破坏缓存
  3. 避免动态元数据:时间戳、计数器等不断变化的数据会损害性能
  4. 正确格式化:使用 API 预期的消息格式以获得最佳缓存效果
  5. 上下文连续性:保留完整历史往往比截断表现更好

🏗️ 架构

kv-cache/ ├── agent.py # ReAct agent implementation with different modes ├── main.py # Main script for running experiments ├── demo_quick.py # Quick demonstration script ├── test_tools.py # Test local file system tools ├── test_error_handling.py # Test error recovery capabilities ├── test_completion.py # Test final answer detection ├── requirements.txt # Project dependencies ├── README.md # This file └── *.log # Generated log files

组件

  • KVCacheAgent:实现 ReAct 模式的主 Agent 类
  • LocalFileTools:安全的文件系统操作(read、find、grep)
  • KVCacheMode:定义不同实现模式的枚举
  • AgentMetrics:跟踪性能指标的数据类

错误处理

Agent 实现了稳健的错误处理:

  • 工具错误:工具失败(如文件未找到)时,错误作为工具结果返回
  • 参数错误:意外的工具参数会被自动过滤
  • 继续执行:工具失败时 Agent 仍继续执行
  • 错误上报:错误会回传给模型,模型可确认并绕开
  • 安全:根目录之外的访问会被拒绝并给出清晰的错误信息

🔧 进阶配置

环境变量

# API Configuration export MOONSHOT_API_KEY="your-key" # Logging Level export LOG_LEVEL="DEBUG" # INFO, WARNING, ERROR

自定义实现模式

你可以扩展 agent.py 中的 KVCacheMode 枚举来新增模式:

class KVCacheMode(Enum): CUSTOM = "custom" # Your new mode

然后在对应方法中实现其行为:

  • _get_system_prompt()
  • _get_tools()
  • _format_messages()

📝 KV Cache 最佳实践

基于本演示,请遵循以下实践:

  1. 保持系统提示稳定:避免加入时间戳或请求专属数据
  2. 保持一致的工具顺序:不要动态打乱或重排工具
  3. 避免不必要的元数据:不要包含计数器、积分等变化值
  4. 使用正确的消息格式:遵循 API 预期的对话结构
  5. 保留上下文连续性:避免激进的截断策略
  6. 面向缓存设计:设计 prompt 与上下文时把缓存纳入考虑

🐛 故障排查

正确实现下 TTFT 仍然偏高

  • 检查这是否是首次请求(冷启动)
  • 核实 API Key 与模型可用性
  • 确保网络连接稳定

缓存命中为零

  • 确认使用的是当前的 Kimi 模型(kimi-k2.5 / kimi-k2.6 / kimi-k3)——它们会上报 cached_tokens。非推理的 moonshot-v1-* 模型上报缓存 token,因此即便服务端实际命中,这一指标也会读到 0。
  • 检查上下文是否真的稳定
  • 查看日志中的缓存失效模式

工具执行错误

  • 核实根目录权限
  • 检查文件路径是否在根目录内
  • 确保文件存在且可读

📚 参考文献

📄 许可证

本项目是 AI Agent Book 教学材料的一部分。


作者与出处
原作者: bojieli
来源:bojieli
许可证:Apache-2.0
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: bojieli 转发
评论区 (0)
U