Skip to content

构建 Evaluation + Optimization 的自动回归与提示词优化闭环 - #274

Open
rhljj wants to merge 26 commits into
trpc-group:mainfrom
rhljj:main
Open

构建 Evaluation + Optimization 的自动回归与提示词优化闭环#274
rhljj wants to merge 26 commits into
trpc-group:mainfrom
rhljj:main

Conversation

@rhljj

@rhljj rhljj commented Jul 31, 2026

Copy link
Copy Markdown

已经加入了电商场景的agent,并做出了评估+优化+失败归因的闭环pipeline

rhljj and others added 25 commits July 30, 2026 22:53
…kend

BaselineEvaluator now delegates to an injected EvalBackend instead of
building its own trace-mode executer. This drops the duplicated
_build_report (already in _eval_backend.py) and removes the blanket
`except Exception: pass` that silently turned a missing evalset file
into an all-zero score, which Stage 2 would then misattribute to the
prompt. Also drops the now-unused Path import left in _eval_backend.py.
trace/ holds the three demo-mode datasets (train + val_baseline + val_optimized)
carrying actual_conversation; live/ holds two non-trace datasets (no
actual_conversation, eval_mode=non-trace) for the real-mode optimizer that
calls the agent via call_agent.

Train and val live datasets live at distinct paths so AgentOptimizer does not
trigger its data-leak ValueError. Generator is idempotent and removes the
legacy flat train_baseline/val_baseline.evalset.json files on each run.
Fixes from review round 1:

- Extract _expected_turn_body helper; both _make_turn (trace) and
  generate_live_evalset (live) now build the shared
  user_content + intermediate_data + final_response shape from a single
  source. Trace still adds invocation_id via _with_invocation_id between
  intermediate_data and final_response to preserve key order.

- Drop dead scenario_map parameter from generate_optimized_evalset; the
  function derives scenario from eval_id suffix only.

- Make regression_responses lookup strict: missing key now raises
  KeyError instead of silently producing a non-degraded "optimized"
  case. Likewise the unknown-suffix else branch now raises ValueError
  instead of defaulting to optimization_regression.

- Move `import os` to module level (was inside main()).

Pure refactor: generator output is byte-identical (md5 verified across
all five .evalset.json files).
The plan's runner spec dropped val_candidate_eval_path, so Stage 4 re-scored
the baseline evalset in both modes. Under TraceBackend that replays the same
pre-recorded outputs, so every delta was zero and the gate could never reject
val_003_regression — the behaviour this example exists to demonstrate.

Demo now passes empty best_prompts so applied_prompts degrades to a no-op:
trace replay ignores the prompt, and writing it back only pollutes system.md.
run_pipeline.py was the only place demo_mode should appear in the
codebase. Now all demo/real divergence collapses into one
if/else block: datasets, backend (Trace vs Live), agent factory,
demo result path, and real-mode train path. The 40-entry
SCENARIO_MAP dict is replaced by derive_scenario(eval_id), which
maps eval_id suffix to scenario and is importable at module
level for downstream tests. The PipelineRunner(...) call matches
the current _runner.py signature (incl. val_candidate_eval_path
and gate_metrics_config_path). scenario_map is passed via the
public constructor kwarg, not by private-attribute mutation. The
final summary now reports args.output_dir as the parent
(the runner's UTC-timestamp subdir holds the actual artifacts)
so the printed path is not misleading.

Verify:
  python -c "import run_pipeline; print(run_pipeline.derive_scenario('val_003_regression'))"
  -> optimization_regression
  python run_pipeline.py --help exits 0 with all flags
  python run_pipeline.py --output-dir /tmp/eo_task14
  -> full demo pipeline runs to REJECTED (5 deliberate regressions
     in trace/val_optimized.evalset.json), baseline 0.25 / candidate 0.50
  system.md hash and mtime unchanged after demo run
…cstring

Three review-driven cleanups:
- run_pipeline.py: drop _agent_factory_lazy(). LiveBackend's default
  agent_factory=create_agent already does the right thing, and agent.agent
  is imported at module load via pipeline/_eval_backend.py regardless of
  mode, so the wrapper was misleading dead code.
- pipeline/_runner.py: PipelineRunner.run_dir attribute, assigned right
  after the UTC-timestamp mkdir. run_pipeline.py now prints the actual
  run directory, not the parent.
- pipeline/_stage_validation.py: replace stale `scenario_map=SCENARIO_MAP`
  in the module docstring example with the derive_scenario() approach.
  test_pipeline_fake.py's local SCENARIO_MAP intentionally left alone.

Verify: python run_pipeline.py --output-dir /tmp/eo_task14_fix prints
"报告已写入: /tmp/eo_task14_fix/20260731T024326Z" and that directory
contains both optimization_report.{json,md}. Verdict still REJECTED
(baseline 0.25, candidate 0.50, 5 deliberate regressions). system.md
hash unchanged.
- Replace 40-line SCENARIO_MAP with derive_scenario import + fixture
- Update all paths: train_baseline → trace/train, val_baseline → trace/val_baseline, test_config → gate_metrics
- Add backend=TraceBackend() and gate_metrics_config_path params
- Use runner.run_dir for report path assertions

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace test_config.json references with gate_metrics.json and document
the two-layer metric strategy: gate metrics ⊇ optimizer metrics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- test_live_datasets_no_trace: guard live datasets against trace cases
- test_datasets_complete: guard all 5 dataset files exist and non-empty
- test_call_agent_rebuild: guard agent rebuild per invocation
- test_applied_prompts: guard baseline restore on exception/success
- test_gate_superset_of_optimizer: guard gate ⊇ optimizer metrics
- test_scenario_derivation: guard suffix-based scenario derivation
- test_eval_backend: guard EvalBackend protocol conformance

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@1153e7f). Learn more about missing BASE report.

Files with missing lines Patch % Lines
trpc_agent_sdk/evaluation/_agent_evaluator.py 50.00000% 1 Missing ⚠️
trpc_agent_sdk/evaluation/_local_eval_service.py 0.00000% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main        #274   +/-   ##
==========================================
  Coverage        ?   88.43514%           
==========================================
  Files           ?         491           
  Lines           ?       46036           
  Branches        ?           0           
==========================================
  Hits            ?       40712           
  Misses          ?        5324           
  Partials        ?           0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

基于我对 pr.diff(SDK 评估变更 + 新的 eval_optimize_loop 示例流水线)的审查,以下是我的审查结论。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/pipeline/_stage_acceptance_gate.py:139-156:回归检查的分支优先级使 max_regressions_allowed--max-regressions 失效
    • _check_regressions 先判断 no_new_hard_failures and num_regressions > 0,命中即直接判失败,永远到不了 elif num_regressions > max_regressions_allowed。而 _models.py 字段说明明确写着“当 > 0 时覆盖 no_new_hard_failures 的严格限制”,run_pipeline.py--max-regressions 也只改 max_regressions_allowed、保持 no_new_hard_failures=True,因此 --max-regressions N 单独使用时对回归完全无效,只有配合 --allow-regressions 才生效。
    • 修复方向:先比较 num_regressions > max_regressions_allowed,再在 no_new_hard_failures 为真且 max_regressions_allowed == 0 时才走严格拒绝;或在 CLI 层当 --max-regressions > 0 时自动把 no_new_hard_failures 置 False。并补一条 no_new_hard=True, max_regressions_allowed=2 且存在 1 个回归应通过的测试(当前 test_reject_when_regressions_exceed_limitno_new_hard=False 规避了该路径,掩盖了 bug)。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/_eval_backend.py:79-81TraceBackend.evaluate 吞掉 AssertionError 后未校验评测是否真正跑完
    • try: await executer.evaluate() except AssertionError: pass 会把 SDK 在评测过程中抛出的任意断言失败(含数据集/配置非法)静默当“基线本该有失败 case”处理,随后 get_result() 若返回 None 才报错,否则可能返回一个半成品/空结果被 _build_report 当成真实报告,导致门控基于错误数据决策。建议仅吞“结果中断言”,或至少在捕获后记录日志并区分“无结果”与“有断言但结果存在”两种情况。
  • examples/optimization/eval_optimize_loop/pipeline/_eval_backend.py:50-53TraceBackend 临时 metrics 文件在 get_executer 抛异常时可能残留
    • tempfile.NamedTemporaryFile(delete=False) 创建的临时文件靠 finally: os.unlink 清理;但 EvalConfig(**config_data) 校验发生在 try 之前,若该行抛错(配置非法)则不会进入 try/finally,临时文件已写出但不会被删除。建议把临时文件创建与 EvalConfig 校验都纳入同一 try/finally,或先校验再写临时文件。

💡 Suggestion

总结

整体风险中等:SDK 侧(_eval_criterion.pyre.IGNORECASE 修正 [\s\S] 被小写化、_agent_evaluator.py/_local_eval_service.py 放开 runner 入参)改动正确且有测试覆盖;示例流水线存在 1 个必须修复的门控逻辑错误(--max-regressions/max_regressions_allowed 失效),以及评测异常处理与临时文件清理两处稳健性隐患。

测试建议

  • 补充 AcceptanceGateno_new_hard_failures=True, max_regressions_allowed=2 且存在 1 个回归时应“通过”的测试,固化覆盖优先级修复。
  • 补充 TraceBackendeval_set_path/metrics 配置非法(EvalConfig 校验失败、get_result() 返回 None)时的行为测试,确认不产生误导性报告。

"""
num_regressions = len(regressed_ids)

if self._config.no_new_hard_failures and num_regressions > 0:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

回归检查分支优先级使 max_regressions_allowed / --max-regressions 失效

_check_regressions 先判断 no_new_hard_failures and num_regressions > 0 即直接失败,永远到不了 elif num_regressions > max_regressions_allowed 分支。导致 --max-regressions N 单独使用时对回归完全无效,只有配合 --allow-regressions 才生效。建议先比较 num_regressions > max_regressions_allowed,再在 no_new_hard_failures 为真且 max_regressions_allowed == 0 时才走严格拒绝,并补充 no_new_hard=True、max_regressions_allowed=2 且存在 1 个回归应通过的测试。

@rhljj

rhljj commented Jul 31, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@rhljj
rhljj requested a review from helloopenworld July 31, 2026 10:47
@rhljj

rhljj commented Jul 31, 2026

Copy link
Copy Markdown
Author

have read the CLA Document and I hereby sign the CLA

Rook1ex added a commit to trpc-group/cla-database that referenced this pull request Jul 31, 2026
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经获取了所有行号。现在我来编写审查报告。基于我对 pr.diff 的审查,以下是我的发现。

发现的问题

🚨 Critical

未发现 Critical 问题。核心 SDK 改动(_eval_criterion.py 的 regex IGNORECASE 修复、_agent_evaluator.py / _local_eval_service.py 的 runner 兼容)逻辑正确且与上下游调用方一致。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/_stage_acceptance_gate.py:139-145--max-regressions 在默认配置下静默失效

    • _check_regressions 先判 no_new_hard_failures and num_regressions > 0 → 失败,再 elif 才判 max_regressions_allowed。当 no_new_hard_failures=True(默认)时,任何回归都直接失败,max_regressions_allowed 永远不会被检查。但 _models.py 字段描述与 docstring 都声称 max_regressions_allowed > 0 会"覆盖 no_new_hard_failures 的严格限制"。run_pipeline.py:193-194 的默认分支 no_new_hard=True, max_reg=args.max_regressions 正好命中此路径——--max-regressions 3 完全无效,仅 --allow-regressions(显式置 no_new_hard=False)才生效。建议当 max_regressions_allowed > 0 时优先走数量上限判断,或在文档/CLI 中明确二者互斥。
  • examples/optimization/eval_optimize_loop/pipeline/_eval_backend.py:178-186num_runs>1 时只取 runs[0],多轮评测被丢弃

    • _build_report 对每个 eval_idcase_result = runs[0],pass/fail 与 metric 分数均只看第 1 轮,num_runs 的其余轮次被忽略。而 _runner.py/_stage_validation.py:49 对 Stage 1/4 都传 num_runs=2,docstring 称其为"抵消 LLM 非确定性"。real 模式下这会让门控决策只依赖单次采样,与设计意图不符。建议聚合多轮(如多数表决或取最差)或在 demo/trace 之外明确单轮语义。
  • examples/optimization/eval_optimize_loop/agent/tools.py:4918-4923,5036-5058:real 模式下并发 case 共享 CSV 存在写竞争

    • cart.csv / orders.csv 是包目录下的共享文件,add_to_cart / checkout 走 read-modify-write 全量覆写。SDK get_executer 默认 case_parallelism=4,LiveBackend 会并发执行多个 case,同一 cart.csv 的并发覆写会互相覆盖、丢数据。demo/trace 模式不调 agent 故无此问题;real 模式建议给每个 session 用独立 cart 文件或加锁。

💡 Suggestion

总结

整体风险可控:SDK 三处改动是正确的 bugfix/兼容性扩展,且有对应测试。主要问题集中在示例流水线的门控回归逻辑(--max-regressions 静默失效)与多轮评测聚合缺失,均为非阻塞性的正确性/稳定性隐患,无必须立即修复的 Critical 项。

测试建议

  • 补一条 AcceptanceGate 用例:no_new_hard_failures=True, max_regressions_allowed=2 且存在 1 个回归,断言实际行为(当前会拒绝),以锁定 --max-regressions 语义,避免与字段文档描述冲突被忽视。
  • _build_report 补一条 num_runs>1 的用例,明确多轮结果的聚合规则(当前取 runs[0])。

…on swallow

- _stage_acceptance_gate: max_regressions_allowed > 0 now overrides the
  no_new_hard_failures strict check (was shadowed by branch priority, so
  --max-regressions alone had no effect); strict-mode rejection keeps the
  no_new_hard_failures report name
- _eval_backend: swallow only _EvaluationCasesFailed instead of all
  AssertionError, so unrelated assertion failures propagate instead of
  feeding a half-finished report into the gate; guard temp metrics file
  cleanup across all exception paths
- _stage_validation: merge write-back/non-write-back branches into a single
  evaluate call via contextlib.nullcontext
- add tests for gate override semantics and backend error paths

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

多个用例修改了共享购物车。现在我来完成审查报告。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/agent/tools.py:91-113:实时模式下 agent 工具通过全局共享 CSV(cart.csv/orders.csv)维护状态,且 _read_csv→修改→_write_csv 无文件锁,伴随 eval_case_parallelism=2num_runs=2 的并发评测会触发读写竞争,导致购物车/订单文件互相覆盖或损坏。
    • 影响范围:real 模式(本示例主线)下 add_to_cart/get_cart/checkout 类 case 的工具返回不确定甚至文件错乱,候选评测结果不可复现,门控结论失真。建议按 session_id 隔离状态(如内存 dict 或 per-session 文件),或在 _perform_inference 前后重置数据,并对写操作加锁。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/agent/tools.py:105-113reset_data() 已定义但流水线与评测流程从未调用,real 模式下 cart.csv/orders.csv 会跨 case 累积(前一个 add_to_cart/checkout 改变后续 get_cart case 的输入),使 get_cart/checkout 类期望与实际长期不匹配。

    • 建议在每个 eval case 推理前(或 LiveBackend.evaluate 入口)调用 reset_data(),保证每个 case 起点一致;这也与 trace 数据中 "购物车有 3 件商品" 的期望对齐。
  • examples/optimization/eval_optimize_loop/pipeline/_eval_backend.py:186-189_build_report 固定取 runs[0],而 gate_metrics.json/_runner.py 均用 num_runs=2。第二 run 的结果被丢弃,metric_breakdownpass_rate 只反映第一次运行,与 "重复评测抵消 LLM 非确定性"(README §7)的设计意图相悖。

    • 建议对多次 run 聚合(如取平均分、按多数票决定 overall_status),或在 EvalSetReport 中显式记录仅采用 run0 的策略,避免 num_runs>1 时静默丢数据。
  • examples/optimization/eval_optimize_loop/tests/test_pipeline_fake.py:231-247test_gate_accepts_when_allow_regressions 注释称 "整体仍可能被拒绝",但 demo 数据中候选 pass_rate=0.5、baseline=0.25,提升>0 且回归检查通过,实际会被 ACCEPTED;注释与断言不一致会误导后续维护者。

    • 建议要么把注释改为反映真实结果(候选被接受),要么显式断言 decision.accepted 的实际值以消除歧义。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/_stage_optimization.py:155-168_metric_calls 依次尝试 total_metric_callsextras['total_metric_calls']total_judge_model_calls,但当前 SDK 的 OptimizeResult 既无顶层字段、extras 也由 gepa 回填,而 total_judge_model_calls 始终为 0(字段说明已注明)。demo 数据 extras={} 时该字段恒为 0,审计意义有限;可考虑直接读 rounds[].budget_used 末值或显式标注 "当前 SDK 版本不可用"。

总结

整体架构清晰、SDK 改动(regex IGNORECASE、runner= 校验放宽)正确且配套测试到位;核心风险集中在 real 模式下 agent 工具基于无锁全局 CSV 维护可变状态,并发评测下会导致数据竞争与跨 case 状态泄漏,属必须修复的 Critical 问题,其余为非阻塞性的稳定性与测试表述问题。

测试建议

  • 补充 real 模式下并发评测 add_to_cart/get_cart/checkout case 的测试,验证共享状态隔离(或 reset_data 钩子)后结果稳定可复现。
  • 补充 num_runs>1_build_report 聚合策略的单元测试,明确多次 run 如何合并为单 EvalSetReport

writer.writerows(rows)


def _init_data() -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

real 模式共享 CSV 状态无锁,并发评测触发读写竞争

实时模式下 agent 工具通过全局共享 cart.csv/orders.csv 维护状态,且 _read_csv→修改→_write_csv 无文件锁。eval_case_parallelism=2 与 num_runs=2 的并发评测会触发读写竞争,导致购物车/订单文件互相覆盖或损坏,候选评测结果不可复现。建议按 session_id 隔离状态或对写操作加锁。

@rhljj

rhljj commented Jul 31, 2026

Copy link
Copy Markdown
Author

我按照你的意见进行了修改: 🚨 Critical — 门控分支优先级(pipeline/_stage_acceptance_gate.py:139)

  • _check_regressions 重构为:先比较 num_regressions > max_regressions_allowed,再判断严格模式
  • max_regressions_allowed > 0 现在正确覆盖 no_new_hard_failures(符合 _models.py:81 契约),--max-regressions N 单独使用生效
  • 实现中发现并修正了评审建议的一个缺陷:flat 版 if/elif 会让"严格拒绝"分支成为死代码且报告名退化——改为嵌套版,严格模式(max=0 + no_new_hard=True)仍报告
    no_new_hard_failures,超限报告 regression_limit

⚠️ Warning — AssertionError 被误吞(pipeline/_eval_backend.py)

  • TraceBackend 和 LiveBackend 改为 except _EvaluationCasesFailed(SDK 专用子类,trpc_agent_sdk/evaluation/_agent_evaluator.py:78),与 SDK
    内部优化器包装器(_optimize_evaluator_call.py:119)同一模式
  • 无关断言(numpy 等)现在向上传播,半成品结果不会送入门控

⚠️ Warning — 临时文件生命周期(pipeline/_eval_backend.py:56)

  • 核实发现:评审声称的"EvalConfig 抛错泄漏"路径在当前代码中不存在(校验在临时文件创建之前)
  • 仍加固了生命周期:tmp_metrics_path 初始化 + json.dump 前赋值 + 条件 unlink,覆盖所有异常路径

💡 Suggestion — 重复调用合并(pipeline/_stage_validation.py:70)

  • 写回/非写回两条分支合并为单次 backend.evaluate,用 contextlib.nullcontext() 处理 demo 模式

@rhljj

rhljj commented Jul 31, 2026

Copy link
Copy Markdown
Author

所有测试已经通过,不过按照建议我还是增加了tools 会话并发隔离机制

@rhljj rhljj closed this Jul 31, 2026
@rhljj rhljj reopened this Jul 31, 2026
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已完成审查。结合 diff 与仓库上下文(SDK 源码、evalset 数据、README 设计意图、trace/live 数据集),以下是结论。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/agent/tools.py:132search_products 用整串子串匹配,与 live 模式 evalset 期望不一致,导致多个 *_optimizable case 在 live 模式必然失败
    • 匹配逻辑为 query_lower in p["name"].lower(),要求查询整串连续出现在商品名内。但 live 数据集期望非连续多词与分类级查询命中:val_004_optimizable/train_001"wireless headphones" 期望命中 2 款耳机("Wireless Bluetooth Headphones" 和 "Sony WH-1000XM6 Headphones",整串都不连续出现);val_012"electronics" 期望命中 P001(无任何商品名含 "electronics");val_017"phone" 期望同时命中 iPhone 和 Samsung Galaxy S25(后者名中不含 "phone")。trace 模式用预录制 actual_conversation 不调工具故能通过,但 live 模式真正调用工具时返回 0 结果 → final_response 不匹配 → 即使 agent 行为完美这些 case 也失败,优化器会据此得出错误结论,破坏 live 流水线核心功能。修复:按 token 拆分后全部命中即匹配,并扩展到 description/category 字段。
    • ...
      query_lower = query.lower()
      for p in products:
          if query_lower in p["name"].lower():   # 多词非连续/分类查询永远进不来
      ...

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/_eval_backend.py:188_build_report 只取 runs[0],丢弃 num_runs=2 的其余运行,削弱"消除 LLM 输出方差"的设计意图

    • README §7 明确门控侧 num_runs=2 用于"消除 LLM 输出方差,避免污染 delta",但 _build_reportcase_result = runs[0] 只采用第 1 次运行的 final_eval_status 与 metric 分数,第 2 次运行被完全忽略;metric_scores 也只追加 runs[0] 的单次分数。trace 模式重放同一轨迹两次结果相同无影响,但 live 模式下 pass/fail 与 pass_rate 仅反映单次运行,未做跨 run 聚合(如多数表决/均值),delta 与门控决策仍受单次随机性污染。修复:对多次运行按 case 聚合状态与平均分数后再构建报告。
  • examples/optimization/eval_optimize_loop/agent/tools.py:362apply_coupon 从不校验 min_purchase,绕过最低消费业务规则

    • _COUPONS 为 SAVE50/NEWUSER 配置了 min_purchase(200/100),但函数只 code.upper() 查表后无条件返回 valid: True,从不读取 CART_CSV 或比对总额。空购物车也能"满 200 减 50"通过,min_purchase 形同死配置。修复:读取购物车计算总额,低于 min_purchase 时返回 valid: False
  • examples/optimization/eval_optimize_loop/agent/tools.py:194add_to_cart 不校验零/负 quantity,可写入负数购物车

    • 库存检查为 if quantity > stock,负数(如 -1 > 50 为 False)直接通过,随后把负数量量与小计写入 cart.csv,污染购物车状态。修复:在库存检查前加 if quantity <= 0: 返回错误。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/agent/tools.py:303-306checkout 跨两文件写入非原子且 CSV 读写无并发保护
    • 先追加 orders.csv 再清空 cart.csv,中途失败会留下"已下单但购物车未清"的不一致状态,可能触发重复结算;add_to_cart/checkout 的读-改-写也无文件锁,并发工具调用会丢失更新。单线程 demo 无影响,但作为可复用示例建议先清购物车或加锁/原子替换。CSV 字段转换(float(p["price"]) 等)对脏行也无兜底,单行损坏会整体抛错。

总结

存在一个必须修复的问题:search_products 的整串子串匹配与 live 模式 evalset 期望的多词/分类查询不兼容,会使 live 流水线多个核心 *_optimizable case 即使 agent 完美也失败;其余为门控多运行聚合缺失、优惠券最低消费与负数量校验缺失等建议修复项。

测试建议

  • 补充 search_products 的 live 行为单测:断言 search_products("wireless headphones") 命中 P001 与 P006、search_products("electronics") 命中电子产品、search_products("phone") 命中 P002 与 P007,与 evalset 期望对齐。
  • 补充 num_runs=2_build_report 的聚合行为测试(当前 test_eval_backend.py 仅覆盖单结果路径),以及 apply_coupon 低于 min_purchaseadd_to_cart(quantity<=0) 的负向用例。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants