☰
如何拦截潜在NameError:ATLAS用tree-sitter调用图推理构建结构性否决的完整指南
2026/10/8 22:05:21 网站建设 项目流程

如何拦截潜在NameError:ATLAS用tree-sitter调用图推理构建结构性否决的完整指南

【免费下载链接】ATLASAdaptive Test-time Learning and Autonomous Specialization项目地址: https://gitcode.com/gh_mirrors/atlas112/ATLAS

ATLAS是一个自适应测试时学习(Adaptive Test-time Learning)的 AI 编程代理系统:它会为每个任务生成多个候选代码,再由沙箱执行、几何透镜评分,最后通过**结构性否决(structural veto)**筛掉"能跑但会崩"的候选。这套机制的核心内幕,在于它用tree-sitter 预计算调用图可达性,在代码执行之前就能拦截那些沙箱永远发现不了的潜在NameError。🔍

为什么沙箱抓不住 NameError?

沙箱回答的问题是"这段代码能不能跑",而结构性否决回答的是"这段代码的调用是否真的能解析"。两个问题并不等价:

  • 代码里可能有try/except ImportError兜底、惰性导入、或者根本没执行到的死分支——沙箱全部放行,但换个输入就会在运行时抛出NameError;
  • 真实的事故案例:某次编辑只导入了render_template_string,却直接调用了render_template,编辑路径没发送项目上下文导致整个否决被跳过,带病代码以"已验证"身份落地(见 v3-service/pipeline.py 中的复盘注释)。

于是 ATLAS 引入了一条静态防线:在候选落盘之前,先问它每一个直接调用"你是谁、从哪来"。

第一步:tree-sitter 把源码拆成扁平事实

一切始于解析。v3-service/graph/extract.py 用 tree-sitter 对 Python(及 JavaScript)源码做单次 AST 遍历,把每个文件拆成五种扁平事实元组,统一装入语言无关的CodeGraph数据模型(v3-service/graph/types.py):

事实含义例子
defines定义了哪个函数/类/方法app.py 定义了 run_server (function, 第12行)
calls谁调用了谁main -> render_page
imports导入了什么from pkg.util import helper
exports文件对外暴露的名字顶层非方法定义
contains类包含哪些方法App -> handle_request

这种"扁平事实"设计是刻意的:它既让调用图天然语言无关,又可以直接翻译成 Prolog 事实供可选求解器层消费(docs/reports/CALL_GRAPH_REASONING_V3.md 记录了完整的阶段化设计)。

第二步:跨文件导入解析,把 import 钉到真实文件

知道"导入了helper"还不够,要知道它来自哪个文件。v3-service/graph/resolve.py 做了三档匹配:

  1. from pkg import mod→ 优先指向pkg/mod.py(代码真正所在处,而非包__init__.py);
  2. 模块名精确匹配(import pkg.mod/from pkg.mod import x);
  3. 后缀匹配:裸import mod只要恰好命中一个*/mod文件就解析成功。

解析不了的标准库 / 第三方导入保持None——这不是 bug,而是后面"保守降级"策略的伏笔。

第三步:预计算可达性——O(V+E) 的原生遍历

调用图建好后,v3-service/graph/analyses.py 提供了一组预计算的 O(V+E) 原生查询,全部是朴素 BFS / Tarjan SCC,不依赖任何求解器:

  • reachability(frm, to):从frm出发的 BFS 能否到达to;
  • path(frm, to):最短调用链(修复阶段把"入口→故障函数"的真实路径喂给模型);
  • impact(target):沿反向边 BFS,找出"改它谁都会坏"的传递调用方集合;
  • cycles():迭代版 Tarjan 强连通分量,捕捉函数级循环调用。

一个细节值得一提:图索引用dict 模拟有序集合(analyses.py 的_build_index),因为 Python 的set迭代顺序受哈希种子影响会让path/impact结果不确定——对一个要进代理延迟预算的关键路径工具,确定性就是正确性。

第四步:调用名解析——四层判定顺序

拦截NameError的核心在 v3-service/graph/resolve_calls.py 的unresolved_calls()。对候选代码里的每个直接标识符调用,按四层顺序判定它是否"已解析":

  1. 本地绑定名:任意位置绑定过的名字都算数——函数参数、赋值目标、for/with/海象运算符目标、推导式变量(v3-service/symbols.py 的_extract_python_bound_names专门为此设计,防止把cb()、x = lambda...误杀);
  2. 内置名:PY_BUILTINS从解释器dir(builtins)动态派生而非手工维护——任何手工子集缺一个内置名,就是一次对合法代码的假否决(比如exit(1)曾被拒);
  3. 显式导入名:import a.b.c只绑定顶层包a(你会调用a.b.c.x()而绝不会裸调c());
  4. 通配符导入:from mod import *会解析到该模块的真实导出集合;若模块解析不到批内文件(标准库/第三方),则整轮判定宽松化(lenient=True,什么都不标记)。

⚠️严格模式的深化正在于此:一个名字哪怕在项目的其他文件里确实定义过,只要候选文件没有导入它,严格模式就会标记它——这正是旧版"项目符号任意放行"否决漏掉的真实NameError(测试见 tests/v3-service/test_graph_resolve_calls.py)。

第五步:结构性否决的裁决——保守、加法、永不空集

最后,裁决逻辑接在流水线沙箱之后(v3-service/pipeline.py):候选只要出现 ≥1 个未解析直接调用,就被打上vetoed_by: "structural",附带的 stderr 会写明"将在运行时抛出 NameError 的调用"。

整套机制遵守三条保守红线,宁可漏报、不可误杀:

  • 属性调用永远豁免:obj.foo()的接收者类型静态不可知,os.path.join绝不标记;
  • 不透明通配符 → 宽松模式:from os import *后什么都知道不了,那就什么都不标;
  • fail-open:tree-sitter 缺失或非 UTF-8 时检查视为通过;且否决永远不会清空候选集——全部失败时降级到修复阶段重来,而不是让流水线卡死。

代理侧还通过/internal/call_resolve端点在edit_file/write_file落盘前做同解析器的编辑前门,对原始文件与编辑后文件做未解析名 diff,截断列表会破坏该对比因此返回全量结果(v3-service/main.py)。

一图看懂:从源码到否决的完整链路

候选代码 ──tree-sitter──▶ 扁平事实 (defines/calls/imports/exports/contains) │ 项目文件 ──导入解析────────▶ CodeGraph(按文件哈希缓存增量重建) │ ┌─────────────┼──────────────────┐ ▼ ▼ ▼ 可达性 BFS 最短路径/影响集 未解析调用检查 (repair 上下文) (修复与注入) (结构性否决:≥1 → 拒) │ ▼ 沙箱通过 + 结构解析通过 ⇒ 候选落盘

📦 想深入源码的同学,核心文件都在这里:图引擎 v3-service/graph/、否决内核 v3-service/symbols.py、流水线接线 v3-service/pipeline.py、设计全记录 docs/reports/CALL_GRAPH_REASONING_V3.md。这套"tree-sitter 预计算可达性 + 保守裁决"的组合拳,让 ATLAS 在代码还没跑起来之前,就能精准拦下那些埋着NameError的候选——快、确定、且绝不误伤合法代码。

【免费下载链接】ATLASAdaptive Test-time Learning and Autonomous Specialization项目地址: https://gitcode.com/gh_mirrors/atlas112/ATLAS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询