公司动态

把LLM记忆变成程序分析:用AST与调用图构建代码库问答

📅 2026/8/31 10:39:06
把LLM记忆变成程序分析:用AST与调用图构建代码库问答
把 LLM memory 变成 program analysis这个说法听起来像是一次工程翻车但我在实现一个代码库问答机器人时真的遇到了。最初的需求很朴素让大语言模型记住项目里有哪些类、函数、依赖关系这样它能回答“这个函数被谁调用了”“修改这段代码会影响哪些模块”这类问题。为了做到这一点我先尝试把代码文件简单切块后塞进向量库效果很差。后来改用代码结构来生成“记忆”结果发现要为 LLM 构造一份可检索的代码记忆就必须做 AST 解析、符号表构建、调用图提取、摘要生成这些正是程序分析的核心工作。这篇文章把这个意外过程完整拆开先解释 LLM 记忆和程序分析在技术上是如何重叠的再给一个可复现的最小案例最后附上排查方法和生产环境建议。适合读者正在做 RAG 代码问答、想把 LLM 接入代码库、或者对程序分析与 LLM 结合感兴趣的开发者。读完你可以复现一套代码记忆系统也能理解为什么不能直接用普通文档切块来处理代码类问题。1. 先理解 LLM memory 的分类再看它和 program analysis 的重叠LLM 领域说的 memory 并不只有“对话历史”。在很多工程实现里memory 是一个宽泛概念凡是能帮助模型在单轮请求之外继续利用信息的东西都可以叫记忆。代码库场景尤其容易混淆因为文档记忆、代码结构记忆、对话记忆的构建方法完全不同。1.1 LLM 记忆不是只有“上下文聊天”常见的 LLM memory 可以分成三类记忆类型实现方式典型问题代码库场景是否够用对话上下文记忆把多轮消息拼接后重复发送占用 token超出窗口后旧信息丢失不够无法覆盖整个代码库外部语义记忆把文本切块后做向量化用相似度检索代码是结构化文本纯文本切块会切断语法边界不够容易检索到无关片段结构化知识记忆用 AST、符号表、调用图、索引等方式生成需要额外分析管线但结果稳定基本够也是本文重点从表格能看出普通聊天应用最常用的“外部语义记忆”一到代码库场景就会失效。原因在于代码不是自然语言它的语义大部分体现在函数调用、类型约束、模块依赖、作用域规则上而不是体现在相邻几行文字里。把函数签名从函数体里拆开或者把 import 语句和调用点切到不同块检索出来的“相关片段”往往缺少推理所需的结构信息。于是自然会产生一个想法为什么不把代码结构本身作为记忆这里的“记忆”不再是一段文本而是一份结构化索引。构建这份索引的过程已经进入程序分析领域。1.2 程序分析的核心工作流程序分析的经典流程大致是解析源代码生成 AST 或更底层的中间表示构建符号表记录变量、函数、类型、模块的定义位置和可见范围构建调用图和依赖图表达函数之间、模块之间的引用关系执行特定分析比如数据流分析、污点分析、可达性分析产出报告说明问题发生在哪个文件哪一行并给出触发路径。静态分析器和 IDE 的“查找引用”“重构预览”“调用层次”功能底层都是这套流程。它之所以可靠是因为每个环节都使用确定的语法和规则而不是凭概率猜测。但在 LLM 承担“记忆”职责时很多人会跳过这些步骤直接把代码文本喂给模型期望模型自己完成结构理解。模型确实能理解一部分代价是不可控、不可检索、不保证覆盖全库。1.3 重叠点把代码变成可检索的结构化知识LLM memory 和 program analysis 的重叠点在于两者都回答同一个问题一份代码库包含哪些实体它们之间有什么关系某个问题应该参考哪些部分。程序分析给出的是精确答案register_user在app/service.py第 42 行定义被app/controller.py第 18 行调用。LLM memory 给出的是可读答案模型可以把这个调用链翻译成“如果修改注册函数需要检查管理端和定时任务两条入口”。如果你用 AST 生成记忆用符号表做实体消歧用调用图做检索路由那么记忆构建过程本身就是迷你版程序分析器。反过来程序分析器得到的精确结构也是 LLM 最缺少的那部分“事实记忆”。我在实践中的体会是不要把“给 LLM 加记忆”当成一个检索问题而要当成一个代码理解问题。记忆只是结果理解才是过程。2. 一个真实转折从“给 LLM 加记忆”变成“做程序分析”这个转折不是理论推演而是我调了两版方案之后才意识到的。2.1 第一版方案文档切块加向量检索为什么失败第一版实现很直接把每个 Python 文件按 300 到 500 字符切块用嵌入模型向量化后写入向量库用户提问时做相似度检索把 top-k 块拼进 prompt。现象是单文件内部的小问题还能答对一旦涉及跨文件调用、继承关系、模块依赖回答就开始混乱。最典型的表现是模型会把import语句提到过的模块当成“被调用方”而实际上根本没有发生调用。它还会把两个同名函数混淆因为纯文本切块没有保留“这个函数属于哪个类、定义在哪个文件”的上下文。问题出在两个地方代码切块经常从函数体中间或参数列表中间断开语法边界被破坏向量相似度擅长匹配“看起来像”的文本不擅长匹配“结构上确实相关”的代码。也就是说普通 RAG 保留的是“词汇记忆”代码分析需要的是“结构记忆”。2.2 第二版方案用 AST 生成代码记忆第二版我改用 Python 标准库ast解析整个代码库只保留结构化信息模块名、import 依赖、函数签名、函数调用列表、类定义、行号。这段结构让人想到符号表和调用图。关键收获是