已开启
主Agent缓存优化对外变更汇总 #46
henry创建于  7月11日
henry成员
7月11日 创建

PR #137 全量变更说明书(面向测试人员)

分支0709(base upstream/main 76af81f
生产代码:36 文件 / +2,607 / -641 行
测试代码:32 文件 / +11,065 / -199 行
文档:7 文件 / +944 行
UT:1201 passed, 14 skipped, 0 failed
ruff:57 文件全量通过


目录

  1. 新增环境变量一览
  2. 新增/变更代码常量与 ContextVar
  3. 日志目录行为变更
  4. Plan 强制创建机制(三层防御)
  5. HITL 空反馈防御机制
  6. messages.json 结构变更
  7. context_dump 目录结构变更
  8. LLM Client 架构变更
  9. 缓存断点策略变更
  10. Token/Cache 用量追踪变更
  11. NL2SQL 节点行为变更
  12. Sub-Agent 行为变更
  13. CLI 输出变更
  14. 依赖变更
  15. 已知限制与遗留 Issue
  16. 测试验证清单

1. 新增环境变量一览

环境变量 默认值 作用 引入 commit
DATAAGENT_CONTEXT_DUMP 未设置=关闭 开启后,主 Agent 每轮 prompt 写入 context_dump/run_N/round_N.txt,NL2SQL 每节点写入 context_dump/run_N/nl2sql_XX/NN_round_node_action.txt G1+G4
DATAAGENT_CACHE_BREAKPOINT_ANNOTATION 未设置=关闭 开启后,context dump 文件中标注 ⭐ cache_control 断点位置和 breakpoint 编号(bp0–bp4),展示 LLM 调用时的真实 cache_control 布局 G2
DATAAGENT_CACHE_ANCHOR "1"=开启 控制是否启用 tail anchor 缓存断点(bp3/bp4)。设为 "0" 关闭 G2
DATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD 4 无 plan 时已执行工具调用数达到此阈值后,todo 段升级为 PLAN REQUIRED 告警 G1
USER_SQLITE_PATH 由 executor 注入 bash 子进程通过此环境变量获取 DATABASE.config.path,skill 脚本 sqlite3.connect(os.environ['USER_SQLITE_PATH']) 可直接连接 G1
DATAAGENT_LOG_FILE 自动生成 sub-agent 子进程日志文件路径(由父进程注入,格式 {user_root}/logs/subagent_{session_id}.log G3
DATAAGENT_LOG_PROCESS_NAME "main" sub-agent 子进程日志 process_name(由父进程注入为 "subagent" G3

注意DATAAGENT_CONTEXT_DUMPget_env(字符串非空判断)改为 get_env_bool(布尔判断),行为更严格——仅 "1"/"true"/"yes" 等真值才开启。


2. 新增/变更代码常量与 ContextVar

2.1 新增常量

常量 位置 作用
_TZ_CN timezone(timedelta(hours=8)) dataagent/utils/constants.py 东八区时区常量,所有时间戳(日志、session_id、context dump、messages.json _ts)统一使用 UTC+8
EMBEDDING_DIMENSIONS 1024 dataagent/common_utils/knowledge_base/utils_inference.py Embedding 向量维度常量
_DEFAULT_PLAN_REQUIRED_TOOL_THRESHOLD 4 dataagent/core/flex/utils/planner_prompt_builder.py 无 plan 时工具调用告警阈值
MAX_EMPTY_FEEDBACK_RETRIES 3 dataagent/core/flex/nodes/human_feedback.py HITL 空反馈最大重试次数(仅 terminal_mode)
_EMPTY_FEEDBACK_SENTINEL_TMPL 模板字符串 dataagent/core/flex/nodes/human_feedback.py 空反馈 sentinel 文本,注入到 planner 强制重新询问
_COLUMN_ERROR_PATTERN re.compile(r"no such column", re.IGNORECASE) dataagent/agents/nl2sql/nodes/reflector.py 列错误检测正则
_SKILL_MD_PATH_PATTERN re.compile(r"skill/[^/]+/SKILL\.md", re.IGNORECASE) dataagent/core/flex/utils/planner_prompt_builder.py 检测是否已读 SKILL.md

2.2 新增 Env 字段

dataagent/core/cbb/agent_env.pyEnv dataclass 新增两个字段:

字段 类型 YAML 来源 作用
ir_recent_turns int | None CONTEXT.recent_turns IR 替换的 recent_turns 阈值,控制多少轮内的 ToolMessage 保留全文
max_tool_result_length int | None 节点级 executor.max_tool_result_length ToolMessage 兜底截断长度

2.3 新增 ContextVar

ContextVar 位置 作用
_user_sqlite_path dataagent/actions/tools/local_tool/tools.py bash 子进程 USER_SQLITE_PATH 注入,executor 每次工具调用前 set_user_sqlite_path(path)

2.4 新增 Context 属性

属性 位置 作用
context.ir_summary_cache dataagent/core/context/context.py dict[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 变更后)

变更前

~/.dataagent/logs/
  └── 20260710_143012_123456.log          # 全局唯一日志文件,UTC 时间戳

变更后

~/.dataagent/{user_id}/logs/               # per-user 目录
  ├── main_{session_id}.log                # 主 Agent 会话日志(UTC+8)
  └── subagent_{session_id}.log            # sub-agent 日志(不再带 worker_sub_id 后缀)

3.2 变更点明细

变更项 变更前 变更后 影响文件
日志路径 全局 ~/.dataagent/logs/{timestamp}.log per-user ~/.dataagent/{user_id}/logs/{process}_{session_id}.log dataagent_logger.py, agent.py
时间戳时区 UTC UTC+8 (_TZ_CN) dataagent_logger.py, agent.py, main.py, tools.py
日志文件前缀 无前缀 main_subagent_ 前缀(按 process_name) dataagent_logger.py
sub-agent 日志名 subagent_{sid}_{sub_id}.log(冗余 sub_id) subagent_{sid}.log(简洁) tools.py
sub-agent stderr 丢弃 捕获前 30 行回传主 Agent logger(debug 级别) tools.py:_handle_subagent_completed
会话日志启动时机 DataAgent.chat() / astream() 调用 setup_session_log() agent.py
sub-agent 日志初始化 无显式配置 sub_agent_entry.py 调用 reconfigure(LoggerConfig(process_name="subagent", ...)) sub_agent_entry.py
日志格式末尾 无换行 每条日志末尾追加 \n dataagent_logger.py
日志时间格式化 loguru 默认 UTC _make_format 将 record time 转为 UTC+8 后再格式化 dataagent_logger.py
session_id 生成时区 UTC UTC+8 agent.py, main.py
.dataagent 目录写入权限 未列入可写目录 加入 _USER_WRITABLE_CANDIDATES sandbox.py

3.3 测试关注点

  • 验证:运行一次 CLI chat 后,检查 ~/.dataagent/{user_id}/logs/ 下是否存在 main_{session_id}.log
  • 验证:日志文件内时间戳为 UTC+8(与本地时间一致)
  • 验证:sub-agent 调用后,同目录下出现 subagent_{session_id}.log(无 _sub_id 后缀)
  • 验证:sub-agent 的 stderr 出现在主 Agent 日志的 debug 级别(前 30 行)

4. Plan 强制创建机制(三层防御)

4.1 问题

Planner 在复杂多步任务中跳过 create_plan 直接执行工具,导致:

  • complete_current_todo 跟踪,步骤可能重复
  • 跨轮次状态丢失
  • HITL 可能被误触发

4.2 三层防御机制

L1: system.md 模板强化(dataagent/core/managers/prompt_manager/templates/planner/system.md

变更前

- **When to plan first:** If the task needs exploration, multiple tools, unclear data/schema,
  or several dependent steps, **create a work plan before substantive execution**...

变更后

- **When to plan first (MANDATORY, not optional):** You MUST call **`create_plan`** before
  any substantive tool execution when **ANY** of the following is true:
  1. The task matches a skill whose `SKILL.md` describes a multi-step `## Workflow` (2+ ordered steps).
  2. The task requires 2+ dependent tool calls.
  3. The task touches a database and needs multi-table joins or schema exploration.
  4. The user explicitly requests an artifact or workflow-shaped deliverable.

同时 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 但无 plan
  • tool_call_count >= plan_required_threshold(默认 4):已执行工具调用数达到阈值

告警文本包含风险说明(redo/lose state/redundant HITL)和明确指令"call create_plan FIRST"。

新增模板变量(_build_plan_prompt_variables):

变量 类型 说明
tool_call_count int 已执行的 ToolMessage 数
skill_md_read_without_plan bool 是否已读 SKILL.md 但无 plan
plan_required_threshold int 告警阈值(环境变量可配)

L3: 运行时注入 [SYSTEM POLICY] HumanMessage(planner_prompt_builder.py

has_plan=Falseskill_md_read_without_plan=True 时,在 messages 列表末尾追加一条 HumanMessage

[SYSTEM POLICY] A skill's SKILL.md has been read but no `create_plan` has been called.
Per the Work Plan policy, multi-step skill workflows MUST be registered as a plan
before substantive execution. Call `create_plan` now with the SKILL.md `## Workflow`
steps as `todos`, then proceed with the first todo.

此消息位于 todo_message 之前,作为 system-voiced 硬性提醒。

4.3 辅助函数

函数 位置 作用
_count_executed_tool_messages(state) planner_prompt_builder.py 统计 state['messages'] 中 ToolMessage 数
_has_read_skill_md_without_plan(messages) planner_prompt_builder.py 扫描历史,检测是否曾 read_file 读取 skill/<name>/SKILL.md
_plan_required_tool_threshold() planner_prompt_builder.py 读取 DATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD 环境变量
build_todo_message(context, *, state=None) planner_prompt_builder.py 新增 state 参数,用于传递给 _build_plan_prompt_variables

4.4 测试关注点

  • 验证 L1:复杂多步任务(如 changping e2e Q1)首轮 Planner 输出应包含 create_plan tool_call
  • 验证 L2:如果 Planner 跳过 plan 直接执行 4+ 工具调用,todo 段应出现 ⚠️ PLAN REQUIRED 告警
  • 验证 L3:如果已读 SKILL.md 但无 plan,messages 列表末尾应出现 [SYSTEM POLICY] HumanMessage
  • 环境变量:设置 DATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD=2 可降低告警阈值

5. HITL 空反馈防御机制

5.1 问题

terminal_mode 下用户连续空输入(直接回车),Planner 会自主推进决策,可能跳过用户确认步骤。

5.2 变更(dataagent/core/flex/nodes/human_feedback.py

变更项 变更前 变更后
terminal_mode 空反馈 一次 input() 即返回空串 最多重试 3 次(MAX_EMPTY_FEEDBACK_RETRIES),每次提示"已连续 N 次未提供有效输入"
空反馈最终处理 直接返回空串 超过重试上限后注入 sentinel 文本
sentinel 文本 "[SYSTEM] 用户连续 {n} 次未提供有效反馈。请先根据当前对话历史如实汇报已完成的操作结果(如有),禁止基于本次空反馈发起新的自主决策或推进新的待确认操作;如需继续,请重新询问用户原始问题以获取明确答复。"
session-resume 空反馈 无处理 记录 empty_attempts=1,日志 warning
interrupt 空反馈 无处理 记录 empty_attempts=1,日志 warning

5.3 三路分支行为

路径 节点内重试 sentinel 注入 说明
terminal_mode ✅ 最多 3 次 ✅ 超限后注入 唯一支持节点内重试的路径
session-resume ❌ 一次性 ✅ 注入 无法在节点内重试(语义限制)
LangGraph interrupt ❌ 一次性 ✅ 注入 interrupt 从头重新执行,无法维护 attempt 计数

5.4 测试关注点

  • 验证:terminal_mode 下连续 3 次空输入后,planner 收到 sentinel 文本而非空串
  • 验证:sentinel 文本包含"禁止基于本次空反馈发起新的自主决策"

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 变更点明细

变更项 变更前 变更后 影响
消息时间戳 _ts 消息创建时盖戳(build_human_message / Planner._to_ai_message),序列化时对未盖戳消息 fallback message_history.py:_serialize
usage_metadata 序列化 不序列化 AIMessage 序列化时写入 6 字段 usage_metadata message_history.py:_serialize
round_summaries 按轮次聚合 token/cache 统计,含 elapsed_seccache_hit_rate message_history.py:_compute_round_summaries
归档文件 每次写 messages.{timestamp}.json 移除归档逻辑,全量覆写 messages.json history_writer.py:save_messages
sanitize 参数 write_messages_file(sanitize=False) 保留完整消息(含 HITL 孤儿 AIMessage) history_writer.py:save_messages
_folded 标记 折叠摘要消息标记 _folded=True_compute_round_summaries 跳过其 _ts compression_utils.py:direct_fold

6.4 round_summaries 聚合规则

  • 按 HumanMessage 分轮(连续 HumanMessage 合并为同一轮)
  • 每轮累加 AIMessage 的 usage_metadata
  • elapsed_sec = 该轮首末消息 _ts 之差(跳过 _folded 消息的 _ts
  • cache_hit_rate = input_cache_read_tokens / input_tokens * 100

6.5 测试关注点

  • 验证messages.json 顶层有 messagesround_summaries 两个 key
  • 验证:每条 AIMessage 有 usage_metadata,含 6 个 token 字段
  • 验证:每条消息的 additional_kwargs_ts(Unix 时间戳)
  • 验证:不再出现 messages.{timestamp}.json 归档文件
  • 验证round_summariesround 从 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)
  • 显式 cc 标注 ⭐ cache_control: {...}
  • 动态注入的 cc 标注 ⭐ cache_control (dynamic): {...}
  • 文件尾标注断点总数和说明

7.2 NL2SQL context dump(新增)

路径{session_root}/workspace/.memory/context_dump/run_{run_id}/nl2sql_{NN}/{seq:02d}_round_{node}_{action}.txt

NL2SQLAgent 在 chat() 调用时通过 _distribute_context_dump_dir 为每个 node 分配 dump 目录:

  • 目录命名 nl2sql_01, nl2sql_02, ...(按已有目录数递增)
  • 所有 node 共享一个 _context_dump_seq 计数器
  • 每个文件包含 SYSTEM/HUMAN/AI 三段 prompt 快照

7.3 测试关注点

  • 验证:设置 DATAAGENT_CONTEXT_DUMP=1 后,context dump 文件生成
  • 验证:设置 DATAAGENT_CACHE_BREAKPOINT_ANNOTATION=1 后,dump 文件含 标记
  • 验证:NL2SQL 子 Agent 调用后,nl2sql_XX/ 子目录存在

8. LLM Client 架构变更

8.1 litellm 替换为 httpx

变更前llm_client.py 使用 litellm.completion(),依赖 litellm SDK。

变更后:自实现 httpx chat client(LLMClient 类),零三方 SDK 依赖。

变更项 变更前 变更后
HTTP 客户端 litellm httpx.Client(verify=httpx_verify())
重试机制 litellm 内置 自实现 exponential backoff(3 次)
异常映射 litellm 异常 自定义 LLMCallError(含 status_code, retryable)
cache token 提取 不提取 _extract_detail_tokens_from_dict 覆盖 OpenAI/Anthropic/DeepSeek 三格式
依赖 litellm==1.80.0 已从 pyproject.toml 移除

8.2 命名统一化

旧名 新名 说明
_is_qwen_model _supports_explicit_cache_control 覆盖 Qwen/Claude/百炼非 Qwen
_apply_qwen_cache_control_with_anchors _apply_cache_control_with_anchors 厂商无关
_QWEN_CACHE_CONTROL _CACHE_CONTROL_EPHEMERAL 通用化
DATAAGENT_QWEN_CACHE_ANCHOR DATAAGENT_CACHE_ANCHOR 环境变量通用化

8.3 Embedding 也改用 httpx

dataagent/common_utils/knowledge_base/utils_inference.pylitellm.embedding() 改为 httpx POST /embeddings

8.4 测试关注点

  • 验证pip list 中不再有 litellm
  • 验证:LLM 调用失败时抛出 LLMCallError(含 status_code)
  • 验证:retry 行为:429/5xx 自动重试 3 次,exponential backoff

9. 缓存断点策略变更

9.1 五断点策略

断点 位置 作用
bp0 SystemMessage 系统提示词前缀缓存
bp1 history_summary(<history_summary> 标记或 _folded 消息) 折叠摘要后缓存断点
bp2 first-large-tool / tail2 第一个大工具结果或尾部第二消息
bp3 tail_anchor 尾部锚点(倒数第 2 条消息)
bp4 tail2 尾部第二断点

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_control

9.3 IR 摘要冻结(P1 stable IR replacement)

try_replace_with_ir 首次成功渲染的 IR 摘要按 tool_call_id 冻结到 context.ir_summary_cache,后续调用直接复用缓存内容,避免 trajectory 增长导致中段消息内容变化而截断前缀缓存。

9.4 测试关注点

  • 验证:Qwen 模型缓存命中率 ≥ 45%(e2e 实测 90.5%)
  • 验证:Deepseek 模型缓存命中率 ≥ 45%(e2e 实测 94.3%)
  • 验证DATAAGENT_CACHE_ANCHOR=0 关闭 tail anchor 后,bp3/bp4 不再注入

10. Token/Cache 用量追踪变更

10.1 PerformanceCollector 扩展

dataagent/core/utils/performance.pysummarize_llm_usage / build_state_summary / PerformanceCollector 新增三个 token 子字段:

字段 说明
input_cache_read_tokens 缓存命中读取的 token 数
input_cache_creation_tokens 缓存写入的 token 数
output_reasoning_tokens 推理 token 数(DeepSeek reasoning 模型)

10.2 CLI 输出

每轮对话结束后打印一行汇总:

⏱ 耗时: 12.34s | tokens: in=1000 out=50 total=1050 | cache_read=800(80.0%)

10.3 测试关注点

  • 验证performance.jsonllms 字段含 6 个 token 子字段
  • 验证:CLI 每轮输出含 cache_readcache_ratio

11. NL2SQL 节点行为变更

11.1 Selector 节点

变更项 变更前 变更后
空执行结果 崩溃 empty execution_results 时 accept without selection
ref_retries 固定设为 2 min(state["ref_retries"], 1),上限 1
空结果进 Reflector 无防御 合法空结果(无 error)不进 Reflector
sel_retries 递减 在 ref_retries 赋值后 在 ref_retries 赋值前

11.2 Reflector 节点

变更项 变更前 变更后
空 validation_results 崩溃 accept without repair
schema_str 注入到 prompt(<schema>{{ schema }}</schema>
列错误无 schema 无 early-break _is_only_column_errors + schema 为空时 early-break
_fix_sql 参数 (val_res) (val_res, schema_str)

11.3 Generator 节点

变更项 变更前 变更后
空 strategies 崩溃 if self.strategies 守卫,跳过并设 proceed=False
future 异常 崩溃 per-future try/except,跳过失败策略
空结果 崩溃 state["sql"]="", proceed=False, 返回
context dump _dump_llm_context 写入 prompt 快照

11.4 Perceptor 节点

变更项 变更前 变更后
keywords 类型 list[str] list[str] | None(None 时返回空)
column_name_search 解析 next(iter(entry)).split(".") 直接解包 检查 len(parts)==3,malformed 跳过
joinable_tables 解析 直接 j["src"].split(".", 1)[1] try/except 防御
table_list 解析 ((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 测试关注点

  • 验证:Selector 空结果不再崩溃
  • 验证:Reflector prompt 含 <schema>
  • 验证:Generator 空结果时 proceed=False
  • 验证:changping e2e Q2(多表 join 查询)返回正确结果

12. Sub-Agent 行为变更

12.1 Sub-Agent 配置注入

_build_nl2sql_sub_agent_config 新增参数:

  • user_id:注入到子 Agent 配置 USER_ID
  • session_id:注入到子 Agent 配置 SESSION_ID
  • core_config:从 YAML TOOLS.config.core_config deep merge 到子 Agent 配置

12.2 Sub-Agent 日志

变更项 变更前 变更后
日志路径 subagent_{sid}_{sub_id}.log subagent_{sid}.log(无 sub_id 后缀)
日志初始化 依赖 env 注入 sub_agent_entry.py 显式 reconfigure(LoggerConfig(...))
stderr 捕获 丢弃 前 30 行回传主 Agent logger

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 测试关注点

  • 验证:sub-agent 调用后,~/.dataagent/{user_id}/logs/subagent_{session_id}.log 存在
  • 验证:sub-agent 的 stderr 出现在主 Agent 日志
  • 验证:sub-agent 执行完成后 subagent_{sid}_{sub_id} session 目录被清理

13. CLI 输出变更

每轮对话结束后新增一行汇总(_print_turn_summary):

⏱ 耗时: {elapsed_sec}s | tokens: in={in_tok} out={out_tok} total={total_tok} | cache_read={cache_read}({cache_ratio})
  • out_tok 包含 output_reasoning_tokens
  • cache_ratio = cache_read / in_tok * 100,无缓存时显示 -

14. 依赖变更

依赖 变更前 变更后 原因
litellm==1.80.0 在 pyproject.toml 移除 自实现 httpx client 替代
httpx 间接依赖 显式依赖(已在 requirements 中) LLM client + embedding
pytest addopts --verbose --color=yes --verbose --color=yes --ignore=tests/e2e/changping e2e 测试为脚本式,不可 pytest 收集

15. 已知限制与遗留 Issue

限制 原因 Issue
Qwen D6 restart-first-call 0% DashScope prompt cache TTL ~5-10min,测试间隔超 TTL
Deepseek cache_creation=0 Deepseek API 无 cache_creation 概念,缓存隐式构建
HITL 被静默绕过 Deepseek 偶尔用自然语言替代 request_human_feedback tool #44(待解决)
pyvis warning upstream call_context.show() 未安装 pyvis
SQL reformatted warning 部分 SQL 无法美化

16. 测试验证清单

16.1 UT 验证

cd /home/qianlong/workspace/dataagent
source .venv/bin/activate
pytest tests/ -x
# 预期:1201 passed, 14 skipped, 0 failed

14 个 skip 原因:

  • 13 个 ES 不可达(elasticsearch 服务未启动)
  • 1 个 ONTOLOGY_SERVICE_URL 未设置

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 passed

16.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

预期结果:

指标 Qwen Deepseek
Overall hit rate ≥45% (90.5%) ≥45% (94.3%)
Post-creation hit rate ≥73% (94.9%) ≥73% (94.3%)
Q1 实验创建 ✅ HITL 触发 ✅ HITL 触发(第二次运行)
功能校验 7/7 7/7
TC1 PASSED PASSED
TC2 PASSED

16.4 关键变更回归测试项

测试项 验证方法 预期结果
日志目录 运行 CLI chat 后检查 ~/.dataagent/{user_id}/logs/ main_{session_id}.log 存在
日志时区 检查日志文件内时间戳 UTC+8
Plan 强制 复杂任务首轮输出 create_plan tool_call
Plan 告警 DATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD=2,跳过 plan 执行 2 工具 todo 段出现 ⚠️ PLAN REQUIRED
HITL 空反馈 terminal_mode 连续 3 次空输入 sentinel 文本注入
messages.json 检查文件结构 messages + round_summaries
usage_metadata 检查 AIMessage 含 6 个 token 字段
context dump DATAAGENT_CONTEXT_DUMP=1 context_dump/run_N/round_N.txt 生成
cache 断点标注 DATAAGENT_CACHE_BREAKPOINT_ANNOTATION=1 dump 文件含 标记
USER_SQLITE_PATH bash skill 脚本 sqlite3.connect(os.environ['USER_SQLITE_PATH']) 连接成功
sub-agent 日志 NL2SQL sub-agent 调用后 subagent_{session_id}.log 存在,stderr 捕获
CLI 汇总 每轮对话结束 `⏱ 耗时: ...
litellm 移除 pip list | grep litellm 无输出
Selector 空结果 空执行结果 不崩溃,accept without selection
Reflector schema Reflector prompt <schema>
Generator 空结果 空 strategies proceed=False,不崩溃
likedislike
henry成员
7月11日 评论:

放在Planner之前还是之后需要仔细考虑

likedislike
henry成员
7月11日 评论:

DATAAGENT_PLAN_REQUIRED_TOOL_THRESHOLD 之前有好几个调用元数据的步骤,可能会打断

likedislike
henry成员
7月11日 评论:

USER_SQLITE_PATH为空的时候才会设置DATABASE.config.path

likedislike
henry成员
7月11日 评论:

评测框架依赖日志路径

likedislike
henry成员
7月11日 评论:

CCB之后原有的Memory模块中需要Embedding,现在可裁剪掉。

likedislike
henry成员
7月11日 评论:

httpx默认超时时间是6000秒

likedislike
henry成员
7月11日 评论:

llm client在搜推广场景中超时时间会比较大,超时时间要可配置

likedislike
henry成员
7月11日 评论:

llm client重新设计优化

likedislike
henry成员
7月11日 评论:

之前litellm实现的时候都校验支持http实现的client是否对模型支持的齐全

likedislike
henry成员
7月11日 评论:

⏱ 耗时: 12.34s | tokens: in=1000 out=50 total=1050 | cache_read=800(80.0%)

网页中也会加一下

likedislike
henry成员
7月11日 评论:

changping最好不要直接体现

likedislike
xsmqxsmq成员
7月20日 关联了看板:@hwxsmq的看板 20260720