在迷宫中找针:Glob 空间索引与大模型上下文装填
(第 57 篇:Agent 动力学之导航引擎)
当你把一个 Agent 扔进一个拥有 5000 个文件的企业级单体大仓(Monorepo)中,然后让它:“去查一下报错的 Auth 模块在哪”。如果你没有为它提供极客级别的文件索引感官系统,它会像一个瞎子一样去调用 ls -R,瞬间塞爆十几万 Token 然后崩溃。
在这一章,我们将探讨如何利用 Glob 模式匹配与受限搜索空间,为大模型构建一套“可伸缩的高清雷达”。
1. 致命的愚蠢:让大模型自己去 find .
很多初级开发者会简单地丢给大模型一个 run_shell 工具,任由它去摸索。
悲剧现场:
大模型往往会输出:run_shell("find . -name '*auth*'")。这在物理上是可以执行的,但在 node_modules 或 .git 存在的情况下,控制台会立刻向大模型喷射上万行的垃圾路径。如果设置了超时,任务挂死;如果没设置超时,你的 Token 费用会让你当场破产。
教训:大模型对物理文件系统的“空间深度”是没有抽象概念的。必须由宿主机 (Agent Runtime) 利用专门的 Glob 解析器来代劳,并强制屏蔽一切噪音。
2. 宿主机降维:Glob Pattern 系统探针
我们不在工具箱里提供自由度过高的 run_shell 搜文件,而是提供一个严格封锁的 list_files_glob(pattern) 专用函数。
2.1 【核心源码】具备隔离能力的 Workspace 探测器
import fnmatch
import os
from typing import List
class WorkspaceNavigator:
"""
Agent 的“回声定位器”:
在千万级文件深渊中,只让合法的文件名浮出水面。
"""
# 物理层拦截:永远屏蔽那些会让 LLM 关注力瞬间瓦解的黑洞目录
HARD_IGNORE = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**", ".venv/**"]
def __init__(self, workspace_root: str):
self.root = workspace_root
def search_files(self, glob_pattern: str, limit: int = 50) -> List[str]:
"""
根据 Agent 提供的 Glob 表达式进行多级过滤检索。
"""
results = []
# 使用 os.walk 手工实现,以获得最细粒度的 Ignore 过滤权
for root, dirs, files in os.walk(self.root):
# 剪枝:如果目录本身在忽略列表,直接跳过整个子树
dirs[:] = [d for d in dirs if not self._is_ignored(os.path.join(root, d))]
for file in files:
rel_path = os.path.relpath(os.path.join(root, file), self.root)
if fnmatch.fnmatch(rel_path, glob_pattern):
if not self._is_ignored(rel_path):
results.append(rel_path)
# 熔断机制:防止匹配到上万个文件导致的上下文溢出
if len(results) >= limit:
break
if len(results) >= limit: break
return results
def _is_ignored(self, path: str) -> bool:
return any(fnmatch.fnmatch(path, ig) for ig in self.HARD_IGNORE)
3. 增强:Workspace Map (项目指路明灯)
如果你想做到如 Cursor 或 Devin 的水平(在开启项目的前两秒,大模型就初步掌握了项目结构):
极客做法 (Lighthouse Strategy):
在用户启动 Agent 后,不要急着让大模型推理。底层索引器首先会做一次 “浅层深搜”:列出根目录下的所有文件以及 src/ 等核心目录的一级子文件。
```text [系统投喂:工程微缩地图] 当前项目核心骨架 (Depth=2): / (Root) ├── package.json (Entry) ├── src/ │ ├── auth/ (Auth Module) │ ├── modules/ (Logic) │ └── index.ts (Main) └── docker-compose.yml ```
只有这种通过宿主机强算力生成的“导盲地图”,配合受限的 Glob 搜索探针,才能让 Agent 在几十万行代码中如入无人之境,而不是像只无头苍蝇一样在缓存文件里乱撞。
4. 语义感知:搜索结果的排序优先级
并不是所有的搜寻结果都具有同样的价值。当 Agent 搜索 auth 时,我们应该在返回给它的列表中,将 代码文件 (.ts, .py) 排在 资源文件 (.svg, .css) 之上,并根据它当前正在编辑的文件关联度进行加权。
排序算法核心:
- 扩展名正向加权:关键逻辑文件 (.go, .java) 权重 +100。
- 深度加权:浅层目录文件由于更可能是模块入口,权重 +50。
- 最近修改时间:刚被编辑过的文件更有可能是当前任务的上下文。
本章精粹
- 屏蔽是第一生产力:Agent 出错 80% 是因为读入了无关的垃圾数据。
- ** Glob 是最佳中间语**:它比自然语言更精确,比复杂的正则表达式更易于大模型生成。
- 结果熔断:宁可漏掉,不可塞满保护。绝不返回超过 100 个文件,强迫 Agent 在报错后反思其搜索策略。
5. Glob 只解决“在哪里”,rg 解决“是什么”:两套工具别混
很多 Agent 失败的根因是: 把“找文件名”和“找内容”混成一个工具。
建议明确分工:
- Glob/索引工具:只返回路径列表,输出可控。
- 内容检索工具:用
rg,并且必须携带范围约束(文件类型、glob、最大结果数)。
rg 的优势不只是快:
它默认尊重 .gitignore、默认跳过隐藏文件与二进制文件,
并且提供 file type alias、glob、smart-case 等可组合的“结构化开关”。 citeturn0search5turn0search7
6. 把 rg 变成“可控观测”:不要直接给模型一把无限火力机枪
给 Agent 一个 run_shell("rg ...") 看似简单,但风险很大:
- 输出爆炸:一次匹配命中 3 万行,直接塞爆上下文。
- 范围漂移:没有
--glob或-t,会扫到生成目录和依赖目录。 - 漏检误判:不知道 ignore 在生效,模型会以为“没找到=不存在”。
因此你应该把 rg 包装成“受限工具”:
- 强制结果上限(例如
--max-count)与长度上限(例如--max-columns),避免单文件刷屏。 - 强制 deny glob(例如
--glob '!**/.git/**'与--glob '!**/node_modules/**')。 - 强制摘要:只回传前 N 条,并标记“已截断”。
保留一个 debug 模式: 当用户说“为什么找不到”,允许额外输出过滤原因(不要默认喂给模型)。
7. 空间索引:从“每次 walk”升级为“可缓存候选集”
当工程大到一定规模,
每次 os.walk 都是在浪费时间。
你需要一个索引层,哪怕是最简陋的:
- 冷启动:遍历一次目录树,产出路径清单。
- 增量更新:监听文件变化(或定期刷新)。
- 查询:glob 在索引上跑,而不是在磁盘上跑。
这会把“文件发现”从 I/O 密集变成内存计算, 并且更容易做上限控制(例如只返回 top-K)。
8. 工程风险清单(必须写进系统设计)
- 资源耗尽:walk/rg 触发大量 I/O,导致 CPU 飙升或磁盘抖动。
- 信息泄露:索引把敏感路径暴露给模型(例如
.env、密钥文件)。 - 误删误改:如果把“搜索工具”和“写入工具”混在一起,模型可能在不理解的情况下写回文件。
- 观测污染:路径列表过长、构建产物过多,会污染模型注意力并诱发幻觉。
治理点:
- deny-by-default:敏感文件类型与目录默认不可见。
- 只读优先:索引与检索工具默认只读,写入必须走更强的确认与事务。
- 审计:记录每次检索的范围、过滤规则、截断策略,保证可复盘。
9. 最小可测:给索引器一个“可回归”的测试夹具
索引与检索工具经常被当成“工具层”,没人写测试。 但它一旦出错,会直接把模型导向错误方向(比普通 bug 更难定位)。
建议最小测试夹具包含:
- 一个小型目录树(含
.gitignore、node_modules/、dist/、隐藏文件)。 - 一个“敏感文件”(例如
.env)用于验证 deny-by-default 生效。 - 一个包含大量匹配行的文件,用于验证
rg包装器的截断策略。
断言不应该是“能跑”,而应该是:
- 返回数量不超过上限(limit)。
- 忽略目录不会出现在结果里。
- 截断时必须显式标记“已截断”,避免模型误以为“只有这些”。
扫清了文件系统的迷雾,你的 Agent 已经具备了“翻箱倒柜”的能力。但在下一章,我们将讨论如何利用比文件系统更高维度的“全能先知”——【LSP 协议对接:如何让 Agent 直接调取 IDE 的类型检查与定义跳转能力?】。我们要让 Agent 插上飞翔的翅膀了。
(本文完 - 深度解析系列 23 / 全文约 1600 字)
(注:建议将 ripgrep (rg) 也并入你的工具链,它是处理万级文件正则搜索的最快路径。)
参考与延伸(写作核验)
- ripgrep 用户指南与 GUIDE(类型过滤、glob、ignore 规则、smart-case)。 citeturn0search5turn0search7