本文讨论 SmartBench 的一次真实架构重构。重点不是"又加了一个 Agent",而是如何让静态分析、图检索和 Agent 共享同一份仓库事实。

问题不是模块少,而是模块彼此看不见
SmartBench 的目标一直是把两类互补能力组合起来:
- LLM 负责理解项目约定、提出开放语义假设;
- 程序负责源码位置、调用关系、控制流和证据唯一性。
在一次架构审计中,我发现代码里其实已经有 SemanticIR、CFG/ICFG、状态规则、GraphRAG、EvidencePack、ProjectReader 和多 Agent。单看每个模块,底座并不差;真正的问题是入口长期演进后形成了三条数据深度不同的路径:
unified使用语言前端生成完整 SemanticIR,执行 linker 和确定性规则;quick从 CodeGraph 包装一份浅层 IR,运行 GraphRAG 和 Agent;- ProjectReader → resolver → validator → CFG analyzer 只存在于实验 runner。
这会产生一个很容易被测试数量掩盖的问题:确定性分析发现的 operation、call edge 和 state witness 到不了 Agent;Agent 提出的项目协议也无法回到默认分析器。三个子系统都"能跑",但没有组成一条产品主链。
"统一"不是把所有算法塞进一个函数
一个常见误区是:既然要合流,就把静态规则、RAG 和 Agent 顺序写进一个巨型 pipeline。这样只是把耦合从入口搬到函数内部。
SmartBench 的做法是统一事实生命周期,而不是统一算法:
rust
Repository
-> ScanPlan
-> Language Frontends
-> SemanticIR
-> SemanticLinker
-> AnalysisSession
|-> deterministic rules
|-> GraphRAG
|-> ProjectReader + resolver + validator + CFG
`-> evidence-constrained Agents
AnalysisSession 持有四类稳定状态:
- 仓库根目录与一次 ProjectFingerprint;
- 合并后的完整 SemanticIR;
- 确定性规则结果与 capability 状态;
- 可选的 ProjectReader 阶段结果。
核心接口刻意很小:
ini
session = AnalysisSession.analyze(project_path, config)
semantic_ir = session.ir
deterministic_report = session.result
evidence_pack = session.build_evidence_pack(query)
project_stage = session.run_project_reader(llm_call)
unified、quick、交互向导、benchmark runner 和 RAG evaluator 现在都从这个入口开始。它们仍然启用不同消费者,但不再重建不同深度的仓库事实。
一次扫描、一次降低、一次链接
合流后,仓库文件先由共享 ScanPlan 发现并按扩展名分派给前端。Python、Go、JavaScript 和 TypeScript 各自负责语法细节,但输出共同的 operation 与 edge contract。多个语言 IR 合并后,SemanticLinker 再保守地解析跨函数关系。
"保守"很重要。遇到重名函数、动态接收者或不唯一类型时,linker 记录 unresolved/ambiguous,而不是猜一条边。后续 analyzer 看到的是明确的覆盖边界,而不是一张看起来完整、实际含有虚构关系的图。
旧的 SemanticIR.from_graph 没有删除。它仍为库兼容和 fallback 服务,但主要 CLI 不再用它替代完整 lowering。这种做法比直接删除旧抽象更适合 Beta 项目:先迁移主链,再让外围调用逐步收敛。
ProjectReader 如何真正进入主链
ProjectReader 不是最终诊断器。它只能在一次确定性 inventory 上提出项目级语义假设,例如:
json
{
"acquire_symbol": "client.Do",
"resource_result_index": 0,
"cleanup_methods": ["Close"],
"resource_member_path": "Body"
}
这份输出不能写入 SemanticIR,也不能直接变成 Finding。主链继续执行:
- resolver 找到唯一真实 CALL、cleanup fact 和 TypeEvidence;
- validator 检查 result binding、member path、可达性与类型;
- ResourceLifecycleAnalyzer 检查 cleanup 是否支配 acquire 后的资源使用;
- 只有带 CFG witness 的结果才进入确定性报告。
在一个同时包含正确清理和缺失清理的 17-operation Go fixture 上,真实 Provider 提出了两个候选:一个通过 gate,另一个因为没有 cleanup fact 被拒绝;通过的协议最终产生一条 cfg_dominance_between_acquire_and_use finding。随后 Proposer、Verifier、Critique 和 Judge 消费的就是包含该 witness 的同一份 EvidencePack。
这个结果只证明主链接通,不是准确率实验。
合流后,输出也必须统一
smartbench unified run 输出确定性 session report。smartbench quick 则把完全相同的报告放在 analysis_report,外层再保存 Agent 建议和 debate log。
因此一次 quick 报告可以同时回答:
- 前端实际生成了多少 operation 和 semantic edge;
- 哪些规则是 full、partial、unsupported 或 unknown;
- ProjectReader 提出了什么、哪些被 gate 拒绝;
- CFG analyzer 是否生成 witness;
- Agent 最终引用了哪些 fact ID。
如果没有 Provider,quick 也会返回确定性报告,而不是只展示图统计。这一点看似只是 CLI 行为,实际决定了"无 LLM"是否还是完整产品路径。
合流没有解决什么
统一 AnalysisSession 解决的是架构断点,不会自动提升所有检测能力。目前仍然存在:
- 大量默认规则属于源码启发式;
- exception、async、alias、dynamic dispatch 和并发语义不完整;
- ProjectReader 资源分析只覆盖规范化的 defer-style cleanup;
- benchmark 规模小,不能给出通用 precision/recall。
但问题的位置已经变化了:以前是"底座算出来也传不到消费者",现在是"消费者共享事实,但事实覆盖仍需扩展"。前者会让任何效果实验失真,后者才是可以逐项测量的研究问题。
一条可复用的架构经验
当一个 AI 工具同时有 parser、规则、RAG 和 Agent 时,优先统一的不是 UI,也不是 prompt,而是下面三件事:
- 仓库快照是否唯一;
- 中间事实模型是否唯一;
- 不同消费者是否共享相同的 unknown/unsupported 边界。
如果这三点没有统一,多 Agent 只是在另一条数据线上"讨论",确定性分析也只能成为报告附件。一个共享 AnalysisSession 不会让系统立刻成熟,却能让之后每一次实验都真正作用在同一套架构上。
项目主线:GitHub - xianyu-sheng/SmartBench Gitee 为单向镜像;文中数据边界以仓库 README 与 benchmark manifest 为准。