已开启
主Agent缓存优化对外变更汇总 #46
henry创建于 7月11日
henry
7月11日 评论:
7月11日 评论:
⏱ 耗时: 12.34s | tokens: in=1000 out=50 total=1050 | cache_read=800(80.0%)
网页中也会加一下


7月20日 关联了看板:@hwxsmq的看板 20260720
⏱ 耗时: 12.34s | tokens: in=1000 out=50 total=1050 | cache_read=800(80.0%)
网页中也会加一下


PR #137 全量变更说明书(面向测试人员)
目录
1. 新增环境变量一览
DATAAGENT_CONTEXT_DUMPcontext_dump/run_N/round_N.txt,NL2SQL 每节点写入context_dump/run_N/nl2sql_XX/NN_round_node_action.txtDATAAGENT_CACHE_BREAKPOINT_ANNOTATION⭐ cache_control断点位置和 breakpoint 编号(bp0–bp4),展示 LLM 调用时的真实 cache_control 布局DATAAGENT_CACHE_ANCHOR"1"=开启"0"关闭DATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD4USER_SQLITE_PATHsqlite3.connect(os.environ['USER_SQLITE_PATH'])可直接连接DATAAGENT_LOG_FILE{user_root}/logs/subagent_{session_id}.log)DATAAGENT_LOG_PROCESS_NAME"main""subagent")2. 新增/变更代码常量与 ContextVar
2.1 新增常量
_TZ_CNtimezone(timedelta(hours=8))dataagent/utils/constants.py_ts)统一使用 UTC+8EMBEDDING_DIMENSIONS1024dataagent/common_utils/knowledge_base/utils_inference.py_DEFAULT_PLAN_REQUIRED_TOOL_THRESHOLD4dataagent/core/flex/utils/planner_prompt_builder.pyMAX_EMPTY_FEEDBACK_RETRIES3dataagent/core/flex/nodes/human_feedback.py_EMPTY_FEEDBACK_SENTINEL_TMPLdataagent/core/flex/nodes/human_feedback.py_COLUMN_ERROR_PATTERNre.compile(r"no such column", re.IGNORECASE)dataagent/agents/nl2sql/nodes/reflector.py_SKILL_MD_PATH_PATTERNre.compile(r"skill/[^/]+/SKILL\.md", re.IGNORECASE)dataagent/core/flex/utils/planner_prompt_builder.py2.2 新增 Env 字段
dataagent/core/cbb/agent_env.py的Envdataclass 新增两个字段:ir_recent_turnsint | NoneCONTEXT.recent_turnsmax_tool_result_lengthint | Noneexecutor.max_tool_result_length2.3 新增 ContextVar
_user_sqlite_pathdataagent/actions/tools/local_tool/tools.pyset_user_sqlite_path(path)2.4 新增 Context 属性
context.ir_summary_cachedataagent/core/context/context.pydict[str, str],按tool_call_id缓存首次渲染的 IR 摘要,后续try_replace_with_ir复用缓存避免中段消息内容变化2.5 新增 LoggerConfig 字段
LoggerConfig已有file_path_explicit字段(upstream),本 PR 利用它控制setup_session_log的日志路径切换行为。3. 日志目录行为变更
3.1 日志目录结构(变更前 vs 变更后)
变更前:
变更后:
3.2 变更点明细
~/.dataagent/logs/{timestamp}.log~/.dataagent/{user_id}/logs/{process}_{session_id}.logdataagent_logger.py,agent.py_TZ_CN)dataagent_logger.py,agent.py,main.py,tools.pymain_或subagent_前缀(按 process_name)dataagent_logger.pysubagent_{sid}_{sub_id}.log(冗余 sub_id)subagent_{sid}.log(简洁)tools.pytools.py:_handle_subagent_completedDataAgent.chat()/astream()调用setup_session_log()agent.pysub_agent_entry.py调用reconfigure(LoggerConfig(process_name="subagent", ...))sub_agent_entry.py\ndataagent_logger.py_make_format将 record time 转为 UTC+8 后再格式化dataagent_logger.pysession_id生成时区agent.py,main.py.dataagent目录写入权限_USER_WRITABLE_CANDIDATESsandbox.py3.3 测试关注点
~/.dataagent/{user_id}/logs/下是否存在main_{session_id}.logsubagent_{session_id}.log(无_sub_id后缀)4. Plan 强制创建机制(三层防御)
4.1 问题
Planner 在复杂多步任务中跳过
create_plan直接执行工具,导致:complete_current_todo跟踪,步骤可能重复4.2 三层防御机制
L1: system.md 模板强化(
dataagent/core/managers/prompt_manager/templates/planner/system.md)变更前:
变更后:
同时
user.md中 Task Constraints 从 "Apply when relevant" 改为 "MANDATORY"。L2: todo.md 模板动态告警(
dataagent/core/managers/prompt_manager/templates/planner/todo.md)变更前:无 plan 时只显示静态提示"There is no active work plan"。
变更后:当满足以下任一条件时,todo 段升级为
⚠️ PLAN REQUIRED告警:skill_md_read_without_plan=True:已读 SKILL.md 但无 plantool_call_count >= plan_required_threshold(默认 4):已执行工具调用数达到阈值告警文本包含风险说明(redo/lose state/redundant HITL)和明确指令"call
create_planFIRST"。新增模板变量(
_build_plan_prompt_variables):tool_call_countintskill_md_read_without_planboolplan_required_thresholdintL3: 运行时注入 [SYSTEM POLICY] HumanMessage(
planner_prompt_builder.py)当
has_plan=False且skill_md_read_without_plan=True时,在 messages 列表末尾追加一条HumanMessage:此消息位于 todo_message 之前,作为 system-voiced 硬性提醒。
4.3 辅助函数
_count_executed_tool_messages(state)planner_prompt_builder.pystate['messages']中 ToolMessage 数_has_read_skill_md_without_plan(messages)planner_prompt_builder.pyread_file读取skill/<name>/SKILL.md_plan_required_tool_threshold()planner_prompt_builder.pyDATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD环境变量build_todo_message(context, *, state=None)planner_prompt_builder.pystate参数,用于传递给_build_plan_prompt_variables4.4 测试关注点
create_plantool_call⚠️ PLAN REQUIRED告警[SYSTEM POLICY]HumanMessageDATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD=2可降低告警阈值5. HITL 空反馈防御机制
5.1 问题
terminal_mode 下用户连续空输入(直接回车),Planner 会自主推进决策,可能跳过用户确认步骤。
5.2 变更(
dataagent/core/flex/nodes/human_feedback.py)MAX_EMPTY_FEEDBACK_RETRIES),每次提示"已连续 N 次未提供有效输入""[SYSTEM] 用户连续 {n} 次未提供有效反馈。请先根据当前对话历史如实汇报已完成的操作结果(如有),禁止基于本次空反馈发起新的自主决策或推进新的待确认操作;如需继续,请重新询问用户原始问题以获取明确答复。"empty_attempts=1,日志 warningempty_attempts=1,日志 warning5.3 三路分支行为
5.4 测试关注点
6. messages.json 结构变更
6.1 文件路径
变更前:
{workspace}/.memory/messages.json(可能还有messages.{timestamp}.json归档文件)变更后:
{workspace}/.memory/messages.json(单一文件,不再生成归档文件)6.2 JSON 结构变更
变更前:
{ "messages": [ { "type": "HumanMessage", "content": "...", "additional_kwargs": {} }, { "type": "AIMessage", "content": "...", "tool_calls": [], "invalid_tool_calls": [] } ] }变更后:
{ "messages": [ { "type": "HumanMessage", "content": "...", "additional_kwargs": { "_ts": 1720612000.123 } }, { "type": "AIMessage", "content": "...", "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 1000, "output_tokens": 50, "total_tokens": 1050, "input_cache_read_tokens": 800, "input_cache_creation_tokens": 200, "output_reasoning_tokens": 0 } } ], "round_summaries": [ { "round": 0, "input_tokens": 1000, "output_tokens": 50, "total_tokens": 1050, "input_cache_read_tokens": 800, "input_cache_creation_tokens": 200, "output_reasoning_tokens": 0, "elapsed_sec": 12.34, "cache_hit_rate": 80.0 } ] }6.3 变更点明细
_tsbuild_human_message/Planner._to_ai_message),序列化时对未盖戳消息 fallbackmessage_history.py:_serializeusage_metadata序列化message_history.py:_serializeround_summarieselapsed_sec和cache_hit_ratemessage_history.py:_compute_round_summariesmessages.{timestamp}.jsonmessages.jsonhistory_writer.py:save_messagessanitize参数write_messages_file(sanitize=False)保留完整消息(含 HITL 孤儿 AIMessage)history_writer.py:save_messages_folded标记_folded=True,_compute_round_summaries跳过其_tscompression_utils.py:direct_fold6.4 round_summaries 聚合规则
elapsed_sec= 该轮首末消息_ts之差(跳过_folded消息的_ts)cache_hit_rate=input_cache_read_tokens / input_tokens * 1006.5 测试关注点
messages.json顶层有messages和round_summaries两个 keyusage_metadata,含 6 个 token 字段additional_kwargs含_ts(Unix 时间戳)messages.{timestamp}.json归档文件round_summaries的round从 0 开始递增7. context_dump 目录结构变更
7.1 主 Agent context dump
路径:
{flex_session_memory_dir}/context_dump/run_{run_id}/round_{curr_iter}.txt变更前:仅写入 prompt 内容,无 cache 断点标注。
变更后(
DATAAGENT_CACHE_BREAKPOINT_ANNOTATION=1时):Cache Breakpoint Annotation: ON和断点策略说明[bp N cc](如有 cache_control)⭐ cache_control: {...}⭐ cache_control (dynamic): {...}7.2 NL2SQL context dump(新增)
路径:
{session_root}/workspace/.memory/context_dump/run_{run_id}/nl2sql_{NN}/{seq:02d}_round_{node}_{action}.txtNL2SQLAgent 在
chat()调用时通过_distribute_context_dump_dir为每个 node 分配 dump 目录:nl2sql_01,nl2sql_02, ...(按已有目录数递增)_context_dump_seq计数器7.3 测试关注点
DATAAGENT_CONTEXT_DUMP=1后,context dump 文件生成DATAAGENT_CACHE_BREAKPOINT_ANNOTATION=1后,dump 文件含⭐标记nl2sql_XX/子目录存在8. LLM Client 架构变更
8.1 litellm 替换为 httpx
变更前:
llm_client.py使用litellm.completion(),依赖 litellm SDK。变更后:自实现 httpx chat client(
LLMClient类),零三方 SDK 依赖。LLMCallError(含 status_code, retryable)_extract_detail_tokens_from_dict覆盖 OpenAI/Anthropic/DeepSeek 三格式litellm==1.80.0pyproject.toml移除8.2 命名统一化
_is_qwen_model_supports_explicit_cache_control_apply_qwen_cache_control_with_anchors_apply_cache_control_with_anchors_QWEN_CACHE_CONTROL_CACHE_CONTROL_EPHEMERALDATAAGENT_QWEN_CACHE_ANCHORDATAAGENT_CACHE_ANCHOR8.3 Embedding 也改用 httpx
dataagent/common_utils/knowledge_base/utils_inference.py从litellm.embedding()改为 httpx POST/embeddings。8.4 测试关注点
pip list中不再有 litellmLLMCallError(含 status_code)9. 缓存断点策略变更
9.1 五断点策略
<history_summary>标记或_folded消息)9.2 控制函数
_apply_cache_control_with_anchors(dict_msgs, compress_token_limit, compress_message_cnt):运行时动态分配断点_strip_cache_control(dict_msgs):对不支持显式缓存的模型剥离 cc 标记_supports_explicit_cache_control(model_name):检测模型是否支持显式 cache_control9.3 IR 摘要冻结(P1 stable IR replacement)
try_replace_with_ir首次成功渲染的 IR 摘要按tool_call_id冻结到context.ir_summary_cache,后续调用直接复用缓存内容,避免 trajectory 增长导致中段消息内容变化而截断前缀缓存。9.4 测试关注点
DATAAGENT_CACHE_ANCHOR=0关闭 tail anchor 后,bp3/bp4 不再注入10. Token/Cache 用量追踪变更
10.1 PerformanceCollector 扩展
dataagent/core/utils/performance.py中summarize_llm_usage/build_state_summary/PerformanceCollector新增三个 token 子字段:input_cache_read_tokensinput_cache_creation_tokensoutput_reasoning_tokens10.2 CLI 输出
每轮对话结束后打印一行汇总:
10.3 测试关注点
performance.json中llms字段含 6 个 token 子字段cache_read和cache_ratio11. NL2SQL 节点行为变更
11.1 Selector 节点
empty execution_results时 accept without selectionref_retriesmin(state["ref_retries"], 1),上限 1sel_retries递减11.2 Reflector 节点
<schema>{{ schema }}</schema>)_is_only_column_errors+ schema 为空时 early-break_fix_sql参数(val_res)(val_res, schema_str)11.3 Generator 节点
if self.strategies守卫,跳过并设proceed=Falsestate["sql"]="",proceed=False, 返回_dump_llm_context写入 prompt 快照11.4 Perceptor 节点
list[str]list[str] | None(None 时返回空)next(iter(entry)).split(".")直接解包len(parts)==3,malformed 跳过j["src"].split(".", 1)[1]((dt, meta),) = item.items()next(iter(item.items()))+ dict 检查11.5 sql_rules_changping.md(新增)
dataagent/agents/nl2sql/prompts/user/sql_rules_changping.md:7 条 SQL 规则,禁止 PK-FK 误 join(neutralization_ic50_fits.id = neutralization_experiments.id),给出经neutralization_data中转的正确路径。11.6 测试关注点
<schema>段proceed=False12. Sub-Agent 行为变更
12.1 Sub-Agent 配置注入
_build_nl2sql_sub_agent_config新增参数:user_id:注入到子 Agent 配置USER_IDsession_id:注入到子 Agent 配置SESSION_IDcore_config:从 YAMLTOOLS.config.core_configdeep merge 到子 Agent 配置12.2 Sub-Agent 日志
subagent_{sid}_{sub_id}.logsubagent_{sid}.log(无 sub_id 后缀)sub_agent_entry.py显式reconfigure(LoggerConfig(...))12.3 Sub-Agent session 清理
NL2SQL sub-agent 执行完成后,清理
subagent_{sid}_{sub_id}session 目录(shutil.rmtree)。12.4 run_id 透传
Executor 调用
set_subagent_runtime_context时新增run_id参数,子 Agent 继承父 Agent 的 run_id。12.5 测试关注点
~/.dataagent/{user_id}/logs/subagent_{session_id}.log存在subagent_{sid}_{sub_id}session 目录被清理13. CLI 输出变更
每轮对话结束后新增一行汇总(
_print_turn_summary):out_tok包含output_reasoning_tokenscache_ratio=cache_read / in_tok * 100,无缓存时显示-14. 依赖变更
litellm==1.80.0httpx--verbose --color=yes--verbose --color=yes --ignore=tests/e2e/changping15. 已知限制与遗留 Issue
request_human_feedbacktoolcall_context.show()未安装 pyvis16. 测试验证清单
16.1 UT 验证
cd /home/qianlong/workspace/dataagent source .venv/bin/activate pytest tests/ -x # 预期:1201 passed, 14 skipped, 0 failed14 个 skip 原因:
elasticsearch服务未启动)16.2 ruff 验证
ruff check $(git diff --name-only upstream/main..HEAD -- dataagent/ tests/ | grep '\.py$') ruff format --check $(git diff --name-only upstream/main..HEAD -- dataagent/ tests/ | grep '\.py$') # 预期:All checks passed16.3 E2E 验证
# Qwen uv run tests/e2e/changping/test_performance.py --model openai --mock_port 32001 # Deepseek uv run tests/e2e/changping/test_performance.py --model deepseek --mock_port 32001预期结果:
16.4 关键变更回归测试项
~/.dataagent/{user_id}/logs/main_{session_id}.log存在create_plantool_callDATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD=2,跳过 plan 执行 2 工具⚠️ PLAN REQUIREDmessages+round_summariesDATAAGENT_CONTEXT_DUMP=1context_dump/run_N/round_N.txt生成DATAAGENT_CACHE_BREAKPOINT_ANNOTATION=1⭐标记sqlite3.connect(os.environ['USER_SQLITE_PATH'])subagent_{session_id}.log存在,stderr 捕获pip list | grep litellm<schema>段proceed=False,不崩溃