☰
从零构建AI工程:代码库问答助手的完整落地指南
2026/9/29 11:13:43 网站建设 项目流程

直接上手一个真实项目,比看十篇教程有用得多。我这次想聊的,就是“ai-engineering-from-scratch”这件事——从零开始,把一个AI工程完整落地,而不是停留在“调用大模型API”或者“套个聊天框”的层面。这个项目的核心目标是:不依赖别人封装好的成品,自己动手把模型能力、工具编排、上下文管理、效果评估串成一条可运行的工程链路。如果你是刚转AI方向的开发者,或者已经在写Prompt但总觉得不成体系,这篇内容会给你一套可复用的思考方式和操作路径。

市面上关于AI工程的讨论很多,但真正能落到代码里的不多。我这次选择了一个非常具体的场景:做一个能读懂代码仓库的AI问答助手。它要能根据仓库里的代码、文档、配置文件,回答“这个项目怎么启动”“这个函数在哪里定义”“这个模块的依赖关系是什么”这类问题。听起来不算复杂,但真正做的时候会遇到一串经典问题:知识怎么灌进模型?上下文窗口不够怎么办?答案不准确怎么修正?Agent调用工具失败了怎么兜底?这些恰恰就是AI工程的日常。

1. 先想清楚:AI工程和调API到底差在哪

很多朋友第一次接触AI开发,是从“调用一个聊天接口”开始的。输入一段文字,拿到一个回答,任务就完了。这当然也算AI应用,但它不叫工程。工程意味着一件事:系统里存在不确定因素,而你通过设计让不确定性可控。放在AI里面,这个不确定因素就是模型本身——它有时聪明,有时犯低级错误,有时一本正经地胡说八道。AI工程的核心工作,就是把这种不稳定的能力嵌进一个稳定运行的系统里。

有人会觉得,那我选最强的模型不就行了?实际上不行。模型能力是基础,但不是全部。我见过团队用顶级模型做出一个体验很差的Demo,也见过用普通模型配合精心设计的链路做出非常顺手的工具。差别就在于:前者把模型当成了全部,后者把模型当成了一个零部件。真正决定项目上限的,是你怎么设计任务拆解、怎么管理上下文、怎么让Agent有效地调用工具、怎么评估和修正输出。

1.1 大多数失败项目倒在同一个坑里

我观察到很多AI项目的失败,并不是模型不够聪明,而是工程链路没打通。最常见的现象是:Demo阶段效果惊艳,一接真实场景就露馅。比如做个“企业知识库助手”,小范围试的时候还挺好,一上线就发现用户问法一变,答案质量立刻崩掉。原因是你没有去梳理:问题来了之后,是走检索?走直接问答?还是走多轮澄清?每种策略的触发条件是什么?答错了之后用户反馈怎么回收?

再一个常见问题是没有评测体系。很多人做AI应用,判断效果的方式是“我自己觉得好不好”,这在小范围没问题,但工程化必须量化。一个Prompt改完,到底是变好了还是变差了?没有评测数据,你就是抓瞎。我在这个项目里一开始就搭了一套最小可用的批量评测脚本,后面每个改动都跑一遍,效果好了再合入。这套东西才是AI工程和AI Demo的分水岭。

1.2 工程化的三个关键环节:任务拆解、上下文设计、结果评估

这三个环节基本决定了项目的成败。任务拆解是第一步,也是最被低估的一步。很多人拿到需求第一反应是“我要写一个超长的Prompt让模型一步到位”,结果Prompt写到两千字,模型依然执行得稀烂。正确做法是把一个大任务拆成几个小任务,每个小任务由一个独立的Prompts或函数完成,再通过编排把它们串起来。一个模块只干一件事,出错了也方便定位。

上下文设计是第二环。模型不是万事通,它只知道自己“看到”的内容。你要决定什么信息进上下文、什么信息不进、信息量超出窗口之后怎么压缩。这一步直接决定了回答的准确度。结果评估则是工程闭环,没有评估,前面两步都是盲人摸象。好在这三环都有成熟的方法论可以套,并不需要从零发明。

2. 项目立项:把“代码库AI助手”变成可执行方案

先交代一下这个项目的完整需求:用户提供一个Git仓库地址或本地目录,AI助手可以回答仓库相关的技术问题。它不是通用聊天机器人,而是限定在“代码和工程文档”这个垂直领域。我给它设计了几个核心使用场景,包括仓库结构讲解、函数定位与解释、启动步骤梳理、依赖关系说明,以及根据现有代码风格生成新模块代码。定义好场景之后,很多事情就明朗了。

2.1 明确边界:能做什么,不做什么

刚开始做的时候我犯过一个错误:想让这个助手什么都干。既能讲业务逻辑,又能解释算法细节,还能做Code Review,甚至想让它直接帮你重构整个项目。结果呢,它什么都干不好。后来我把边界收了回来:当前版本只处理“文本层面的代码理解问题”。也就是说,它回答的是“这段代码做了什么”“这个函数在哪里”“项目该怎么启动”这类可以通过阅读源码和文档得出答案的问题。运行时动态分析、真实环境验证这类事情,V1版本不做。

这个边界听起来很保守,但它有一个巨大的好处:你知道模型在什么情况下会成功,在什么情况下会失败。工程最怕的就是不可预期的失败。边界越清晰,越容易做质量控制。等基础链路稳定了,再慢慢扩展到“代码生成”“CI集成”这些方向,稳步迭代,一次只加一个功能点。

2.2 技术选型:模型、框架、接口的取舍

这个项目我主要考虑了三个选型问题:模型用哪家,Agent框架要不要用,检索索引怎么建。

模型方面,我优先考虑兼顾中文能力和代码理解能力的通用大模型,同时必须支持Function Calling。Function Calling非常关键,它是Agent能调用外部工具的前提。如果模型不支持这个能力,后面要自己硬写JSON解析来模拟工具调度,又麻烦又脆弱。

框架方面,我做过两种尝试:一种是直接用LangChain之类的现成编排框架,另一种是纯手写一套调度逻辑。结论是:V1阶段手写更扎实。现成框架封装了很多概念,学起来也需要成本。不过我确实不是全套框架都否定,只是对于这个规模的项目,手写一个基于函数循环的Agent调度器反而更清晰。自己写的调度逻辑,每一步都能控制,出了Bug也好修。框架的抽象在项目变大之后才有价值。

索引方面,我用的是最基本的“目录树+关键文件全文检索”方案。先把仓库里Markdown、TXT、PY、TS、GO等文本类的文件读进来,做一个简单的关键词倒排索引,再加上一层语义向量检索。两个通道的结果合并去重之后,再交给模型分析。这套方案不需要上重型分布式向量数据库,单机跑足够。

3. 核心机制拆解:Prompt、上下文与Agent怎么协同

既然标题是“from scratch”,那核心机制就不能一笔带过。这一部分我会讲清楚三个东西:Prompt到底在解决什么问题、Agent的循环是如何转起来的、以及上下文和Token预算怎么算。这三个概念你搞明白了,AI工程的地基就算打好了。

3.1 Prompt Engineering不是咒语,是任务说明书

很多人把Prompt Engineering当成某种魔法咒语,觉得一个“魔法Prompt”就能让模型变聪明。我不太认同这个思路。我理解的Prompt Engineering,是把一个任务描述清楚,让模型能稳定地执行。它更像写需求说明书:你要让模型扮演什么角色,拿到什么输入,遵守哪些约束,输出什么格式,不确定的时候怎么处理。

我有个非常实用的经验:一个通用的任务Prompt,至少要包含五个部分——角色定义、任务目标、输入数据说明、输出格式要求、兜底行为。拿“函数定位”这个任务举例,我会这么写:

你是一名资深的代码检索专家。你的任务是根据用户的问题,在给定的项目文件中查找最可能相关的函数或方法,并解释它的作用。 输入是一批代码文件的内容片段,每段前面有一个文件名标记。 输出必须遵循以下格式:文件名 + 函数名 + 三行以内的解释。 如果给定的文件内容中没有找到相关信息,必须输出“未找到相关信息”,严禁编造。

这个Prompt看起来朴素,但它每一句都有用。角色定义限制模型的知识边界,任务目标明确动作,输出格式方便后续程序解析,兜底行为禁止幻觉。写Prompt的时候我习惯把“不允许做什么”写得非常具体,这个比“请认真回答”有效得多。

3.2 Agent循环:模型推理与工具执行怎么配合

严格来说,Agent不是某一个具体的模型,而是一种循环执行的架构。流程是这样的:模型接收用户消息,然后决定是要直接回答,还是要调用某个工具。如果决定调用工具,它会生成一个结构化的工具调用请求,系统根据这个请求去执行真正的函数,拿到结果之后再喂回给模型,模型继续推理,直到它决定直接回答为止。这个循环是整个Agent架构最核心的运转方式。

我在实现的时候写了一个很简陋但够用的调度器。核心逻辑就一个while循环,里面做三件事:把历史消息发给模型,判断返回内容是要继续调工具还是直接回复,如果要调工具就执行对应的Python函数并把结果追加到消息序列。每条工具调用的结果我都加上了实际函数名和耗时标记,方便后面排查性能问题。

这里有一个重要细节:每轮循环都会额外消耗Token,因为工具结果也都进入上下文。如果Agent陷入死循环,Token消耗会非常可怕。所以我设了“最大循环次数上限”,默认是5次。5次还没把问题处理完,就会要求模型放弃并返回一个部分结果,至少用户能看到中间发生了什么。

3.3 上下文与Token预算:算清楚你的窗口够不够用

上下文管理是整个AI工程里最容易被忽视、但影响最大的环节。大模型的注意力窗口是有限的,比如32K、128K,而一份真实代码仓库可能有几万甚至几十万个Token。你不可能全塞进去。解决这个问题有两个方案:一是检索,二是压缩。我的项目两个都用。

检索负责把“可能相关的片段”从海量文件中捞出来。哪些文件可能有用户问到的函数?哪里提到了这个名字?关键词倒排索引负责精准匹配函数名和变量名,向量检索负责语义匹配“项目如何启动”这类模糊问题。匹配到的片段拼接之后,我再设置一个“最大进入上下文的字符数”,比如8000字。超出部分就做取舍,只能优先放得分最高的几段。

压缩则是为了减少噪音。注释、空行、无意义的样板代码在进入上下文之前都会被去掉。源码本身信息密度低的部分,比如重复的getter/setter,我会直接丢弃。这步做完之后,Token消耗能省下大概一半左右,回答的准确率不会明显下降。Token预算这件事我建议在一开始就算好:假设窗口是32K,那么系统Prompt占2K,检索片段占8K,聊天历史占10K,模型输出预留8K,这样还剩4K浮动。把预算写进代码里,做成硬限制,比写“尽量压缩控制”靠谱得多。

4. 实操:从零搭建一个能读代码库的AI助手

讲完原理,开始动手。我这里的代码示例做了大量简化,但保留了完整的技术脉络。如果照这个搭,你也能得到一个可运行的骨架,后续想丰富功能也只是往里头加函数的问题。

4.1 环境准备:依赖、密钥、模型接入

我先说环境,一个最小可跑的清单:

Python 3.10+ openai SDK(或你使用的大模型厂商对应SDK) 一个支持Function Calling的模型API(如gpt-4o-mini、qwen-plus、deepseek-chat等) 纯Python实现的向量库:numpy + 一个Embedding接口就够

依赖这块我不建议一开始就引入重型框架。项目还在原型阶段,能省则省。我在V1版本里连数据库都没用,索引直接跑在内存里,重启就重新构建。数据量大了之后,再换ES或专门的向量数据库也不迟,反正上层接口可以抽象成一个“搜索”函数。

模型接入方式很简单,关键是Function Calling的定义。我定义了一个叫“search_code”的工具,它的作用就是在仓库索引里搜索与关键词相关的文件片段。这个工具函数的定义必须严格写清楚参数名和用途,模型才能正确生成调用请求。我记得第一次调试的时候,参数名写得太随意,模型经常把参数传错,后来改成语义明确的命名就好多了。

4.2 三步实现:索引构建、检索召回、答案生成

整个系统就三条主线:建立索引,检索片段,让模型总结回答。

第一步是索引构建。我写了一个脚本遍历目录,把所有文本类文件读进来。对每个文件做三件事:记录路径、生成关键词倒排表、调用Embedding接口把文件内容变成向量存进内存列表。一个小细节是,我只做粗粒度的文件级别切块,先用简单粗暴的方式跑通,再回来优化切块策略。实测下来,代码文件其实不需要切得太碎,因为函数之间的引用关系经常跨文件,切碎了反而丢线索。

第二步是检索召回。用户提问之后,先做一次关键词倒排表查询,再做一次向量相似度查询,然后把两路结果合并去重。代码大概长这样:

def search_relevant_files(query, top_k=5): # 1. 关键词倒排:提取query中的标识符,比如函数名、变量名 keyword_hits = [hit for token in extract_identifiers(query) for hit in inverted_index.get(token, [])] # 2. 语义向量召回:用embedding算相似度 query_vector = embed_fn(query) vector_hits = rank_by_similarity(query_vector, file_vectors, top_k=top_k) # 3. 合并去重,按得分排序,本质上是用多路召回补全单一搜索的盲区 merged = merge_and_dedup(keyword_hits, vector_hits) return trim_to_budget(merged, max_chars=8000)

这里我要解释一下为什么要做两路召回。关键词倒排擅长精确匹配,比如“find_user_by_id”这个函数名,向量检索往往想不明白。而向量检索擅长语义匹配,比如用户问“怎么登录”,代码里没有“登录”这个词,但有“authenticate”函数,关键词倒排就挂了。两路召回取并集,准确率会高很多。

第三步是答案生成。把检索到的文件片段拼接起来,丢给模型,让它按照用户的问题组织答案。我这里的生成Prompt也很固定,里面必须强调“只能根据给定的片段回答,片段没有的信息就承认不知道”。这是对付幻觉枷锁最有效的方法,比说一万遍“别乱编”都好使。

4.3 评测体系:没有评测就没有工程化

我见过太多的AI项目死在“自我感觉良好”上,所以我从这个项目第一天开始就搭了评测。评测方法不复杂,写一个JSON文件,里面存20条左右的测试用例,每条包含“问题、期望的答案关键词、期望引用的文件路径”。跑完一轮生成评测结果后,有两个指标:召回率指标,即“回答中是否包含期望关键词”;定位准确率指标,即“回答是否关联了期望文件”。

看似简单,但实际作用巨大。有一次我调整了切块大小,从512字符改成1024字符,测试集上的定位准确率从85%降到了70%,说明切块太粗导致检索到了错误的文件。如果没有评测,我可能根本不会发现这个变化,还沉浸在“上下文变长了应该更好”的错觉里。所以我强烈建议:任何AI工程,上线前先花两天时间搭一个粗糙的评测集,后面每一笔改动都有据可依。

每次Prompt调整、切换模型、修改检索策略,都先在测试集上跑一遍,对比分数再决定去留。这个习惯能帮你避免大量无效试错,是成本最低的质量保障手段。不要一开始就追求完美评测集,粗糙的20条用例就已经能把回退风险挡掉大半。

4.4 把AI能力接回日常开发流

项目跑通之后,我开始盘算怎么把它接入我的日常工作流,而不是让它孤零零地躺在脚本库。我做了两个很实用的动作。

第一个是做了一个命令行工具。平时开发时遇到不懂的代码,我不用切窗口去问通用AI,而是直接在当前目录下执行命令,工具会自动扫描仓库并回答。单论便捷性,这比复制粘贴代码片段高效太多。第二个是接入了当前主流的AI编程工具和IDE插件。这些工具本质上也是走“检索代码片段 + 模型推理”的路线,区别在于它们做了一层更深入的IDE交互。如果我对这套工程链路理解到位了,再去使用这类插件,就能清楚猜出它们背后的工作原理,遇到插件返回错误时也能快速定位。

这条路径的价值在于:你掌握的是底层能力,而不是某个产品按钮。“AI工程”四个字听起来很大,拆开了其实就是模型调用、提示词、检索、函数调度、评测这五件事。把它们组合好,你会发现不只是做“代码助手”,任何垂直领域的AI应用都能套上这套架构。

5. 踩坑实录:输出不稳定、工具卡死、成本失控

最后分享几个我在反复调试中踩过的坑。这些东西不会出现在官方文档里,但每一个都真实影响过项目进度。

5.1 Prompt输出飘了怎么办

项目初期遇到过一种情况:同一个问题,问两次,答案差异非常大。第一次回答很全面,第二次答案像是换了一个人。排查之后发现,问题出在我把聊天历史全部丢给了模型。历史消息里含有用户之前某个不明确的表达,模型被带偏了。解决办法是:回答代码问题时,不把整段闲聊历史传给模型,只传“检索结果 + 当前问题”。这能大幅提升输出稳定性。其实这就是上下文隔离的思路——关键任务只给它最必要的信息,不给它解读错误的机会。

5.2 回答太泛、缺少信息量

另一个常见的毛病是模型总觉得问题太简单,会给出“这一步需要根据项目具体情况来调整”这类废话。原因在于系统Prompt里缺少强制要求。我在系统Prompt里加了一句:“回答中必须引用具体的文件名、函数名或代码行。”自此之后,输出的信息密度高了很多。想让模型认真干活,你得告诉他“拿什么证明你在认真干活”,具体的引用就是一种证明。

5.3 Agent陷入循环、工具调用停不下来

调度器上线第一次测试,就出现了Agent连续8次调用同一个搜索函数、参数几乎没变的情况。关键原因是循环里没有“去重记忆”。我修了两处:一是在调度逻辑里,如果同样的工具调用参数已经出现过一次,就不再允许调用;二是设置最大循环上限为5次,触顶后强制让模型基于现有信息回答,禁止再发起新调用。这个限制看起来很粗暴,但确实管用。

5.4 Token成本失控的排查思路

有一段时间我发现账单涨得很快,查了日志发现是Embedding调用次数太多了。原因是我把整个文件的每个段落都单独调了一次Embedding接口,文件多的时候光这一点就积少成多。优化方案是:对文件做合并切块,每块保持512个字符左右,并且缓存已经算好的向量,第二次构建索引直接加载缓存。这项优化让Embedding调用量降低了75%。另外,在Agent循环里面,每一次函数调用的结果都重新进入模型,这块Token消耗也要盯紧。我每次都会打印每条消息的Token数字段,丢了心里有数。

5.5 一个小技巧:日志里永远保留“模型原始返回”

这条是我最想强调的经验之一。不管你在上层把逻辑包装得多好,日志里一定要保留每个环节模型返回的原始内容,尤其是工具调用参数。因为当系统行为诡异的时候,绝大多数原因不是逻辑写错了,而是模型生成了你意料之外的输出格式。没有原始返回日志,你只能猜;有了原始返回日志,你能一眼看出问题出在哪一层。我在项目里就用这个思路解决了好几个诡异问题,算是真正的“低成本排查高收益”技巧。

6. 写在最后的一点体会

这个项目从头到尾大概花了三周业余时间,从零到可用的过程比我想象中顺利,也比我想象中琐碎。顺利是因为技术链路已经非常成熟,ETS一步到位;琐碎是因为每一个环节都需要反复打磨——Prompt改一句,评测数据跳几个点,又来调检索参数。AI工程其实没有多少神秘之处,它就是把“模型能力”这团火用“工程管道”稳下来,让火光不至于忽明忽暗。

我个人感触最深的一点是:不要指望一个全能Prompt解决所有问题,也别指望一个最强模型兜住所有场景。AI工程的进步不是靠某一个魔法组件,而是靠一个一个小决策——这个任务拆不拆,这段上下文怎么给,这个工具调用要不要限制,这条评测用例该不该加。把这些小决策做对了,项目自然就立住了。如果你正在从零做自己的AI项目,希望这篇能给你一张足够实的地图,少走几段弯路。

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

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

立即咨询