面试中如何讲解:我做过的AI智能体应用开发项目
我之前做过一个企业内部知识库AI智能体项目,主要目标是帮助员工、客服和开发人员快速查询公司内部资料。这个系统接入了公司制度文档、接口文档、FAQ、故障排查手册和部分业务接口。用户可以直接用自然语言提问,系统会先检索内部知识库,再调用大模型生成回答,并且返回引用来源。
这个项目不是简单调一个大模型接口。真正开发时,我把它当成一个完整的后端系统来做,重点考虑了需求边界、权限控制、RAG检索、提示词工程、响应时间、Token成本、模型输出不稳定、异常兜底和上线后的监控。
一、项目背景和业务目标
这个项目最开始是为了解决内部重复咨询的问题。公司里很多制度、接口说明、排障文档都散落在不同平台上,新员工或者客服人员经常不知道去哪里查,开发和运维排查问题时也需要翻很多文档。
所以我们做了一个企业知识库AI助手,让用户可以直接提问,比如:
- 出差住宿费报销标准是多少?
- 接口返回401一般是什么原因?
- 支付回调失败应该怎么排查?
- 某个错误码代表什么意思?
- 根据接口文档生成一组测试用例。
我负责的部分主要是Java后端智能体流程,包括会话接口、RAG检索编排、提示词组装、大模型调用封装、权限校验、Token统计、日志追踪和异常兜底。
二、需求阶段我做了哪些拆解
需求阶段我没有直接开始接模型,而是先和产品、业务同事把边界梳理清楚。因为AI项目最怕范围太宽,最后做成一个什么都能问、但回答不稳定的聊天框。
我当时主要拆了四件事。
第一,明确用户群体。
这个智能体主要给三类人用:
- 内部员工:查询制度、流程、FAQ。
- 客服人员:查询标准话术、业务规则、常见问题。
- 开发和运维人员:查询接口文档、错误码、排障手册。
不同用户看到的资料范围不一样,所以后端必须做权限控制。
第二,明确能回答什么。
我们把第一版范围控制在知识库问答和简单排障建议上,不做复杂自动执行。比如可以回答“支付回调失败怎么排查”,但不会自动去改数据、重发回调或者执行生产操作。
第三,明确数据来源。
第一版接入了制度文档、接口文档、FAQ和排障手册。业务数据库只开放了少量只读查询接口,并且对敏感字段做了脱敏。
第四,明确不能做什么。
知识库没有依据时,系统必须明确提示“没有找到明确依据”,不能让模型自己编。用户无权限访问的文档,不能进入检索结果,更不能传给大模型。
三、整体架构设计
后端用的是 Java + Spring Boot。整体链路是:
用户提问
↓
ChatController 接收请求
↓
参数校验、登录态校验、权限校验
↓
AgentService 编排智能体流程
↓
RagService 检索知识库
↓
ToolService 调用必要的业务只读接口
↓
PromptService 组装提示词
↓
ModelClient 调用大模型
↓
GuardrailService 校验和脱敏
↓
保存会话、Token、耗时和引用来源
↓
返回结果给前端我没有把逻辑全部写在一个Service里,而是拆成了几个比较清晰的模块:
| 模块 | 作用 |
|---|---|
| ChatController | 提供对话接口,接收用户问题 |
| AgentService | 编排完整智能体流程 |
| RagService | 负责知识库召回和结果过滤 |
| PromptService | 负责提示词模板和上下文拼接 |
| ModelClient | 统一封装大模型API调用 |
| ToolService | 封装业务只读工具调用 |
| ConversationService | 保存多轮会话上下文 |
| GuardrailService | 做权限、安全、敏感信息处理 |
| CostService | 统计Token、耗时和调用成本 |
这样拆的好处是后面方便调优。比如RAG召回不准,就主要看RagService;模型输出格式乱,就看PromptService和ModelClient;成本太高,就看CostService和上下文拼接逻辑。
四、多轮对话存储我是怎么做的
这个项目里,多轮对话是单独设计的,不是简单把所有聊天内容都临时放在内存里。因为用户下次进入系统时,需要能看到之前的会话记录;同一个会话里继续提问时,模型也需要知道前面聊过什么。
我把会话存储拆成两张核心表。
第一张是会话表,主要记录一次对话主题:
| 字段 | 说明 |
|---|---|
| id | 会话ID |
| user_id | 用户ID |
| title | 会话标题 |
| status | 会话状态 |
| created_at | 创建时间 |
| updated_at | 更新时间 |
第二张是消息表,记录每一轮用户和AI的消息:
| 字段 | 说明 |
|---|---|
| id | 消息ID |
| conversation_id | 会话ID |
| user_id | 用户ID |
| role | 消息角色,比如user、assistant、system |
| content | 消息内容 |
| references | 引用的知识库来源 |
| input_tokens | 输入Token |
| output_tokens | 输出Token |
| latency_ms | 本轮耗时 |
| created_at | 创建时间 |
前端每次新建聊天时,后端会先创建一条会话记录,返回conversationId。后续用户继续在这个聊天窗口里提问,前端都会带上这个conversationId,后端就能知道这次问题属于哪一段会话。
每轮对话处理时,我会先保存用户问题,再调用RAG和大模型,模型返回后再保存AI回答。这样即使模型调用中间失败,也能看到用户问过什么,以及失败原因。
用户下次打开系统时,前端会先调用会话列表接口,比如:
GET /api/conversations后端按user_id查询这个用户自己的会话列表,返回标题、最后更新时间和最近一条消息。用户点击某个会话后,再调用详情接口:
GET /api/conversations/{conversationId}/messages后端根据conversationId查询消息表,把历史对话按时间顺序返回给前端展示。这里必须校验conversationId是不是属于当前登录用户,不能只凭会话ID查询,否则会有越权风险。
多轮上下文传给模型时,我没有把全部历史消息都塞进去。因为历史越长,Token成本越高,响应也越慢。我的处理方式是:
- 只取最近几轮关键对话。
- 对更早的历史做摘要。
- 当前问题永远优先级最高。
- RAG检索结果优先级高于历史闲聊内容。
- 超过Token限制时,优先丢弃低价值历史。
比如用户第一轮问“支付回调失败怎么排查”,第二轮继续问“那401这种情况呢”,第二轮的问题本身不完整。后端会加载上一轮上下文,知道这里的“401”还是在支付回调排查场景下,然后再去检索接口鉴权和支付回调相关文档。
这里我遇到过一个问题:历史上下文带太多时,模型容易被前面的问题带偏。后来我做了上下文选择规则,只保留和当前问题相关的最近几轮,同时提示词里明确要求“优先回答当前用户问题”。这样多轮对话既能连续,又不容易跑题。
五、核心开发流程
用户输入问题后,后端不是直接把问题丢给大模型,而是走了一套完整流程。
第一步,做基础校验。
包括用户是否登录、问题是否为空、长度是否超限、是否包含明显敏感信息。空问题和超长问题不会调用模型,直接返回提示,避免浪费Token。
第二步,做权限过滤。
系统会根据用户角色、部门和知识库权限,确定他能访问哪些文档。RAG检索时只在有权限的文档范围内查,避免把敏感文档传给模型。
第三步,做RAG检索。
用户问题会先转成向量,然后到向量库里检索相关文档片段。检索结果会再根据相似度、文档权限、文档状态做过滤。
第四步,组装提示词。
Prompt里会包含用户问题、检索到的知识片段、回答规则和输出格式要求。重点要求模型只能基于资料回答,没有依据就明确说明。
第五步,调用大模型。
模型调用统一通过ModelClient封装,里面处理超时、错误码、响应解析和Token统计。
第六步,做结果校验。
模型返回后,后端会检查输出格式、敏感词、引用来源和是否为空。如果JSON格式错误,会做一次修复重试;如果仍然失败,就走兜底返回。
第七步,保存日志。
每次调用都会保存用户问题、命中文档、模型名称、输入Token、输出Token、耗时、是否成功、失败原因。这样线上出问题可以追踪。
六、提示词工程我是怎么做的
提示词这块我没有追求复杂,而是重点控制模型行为。
第一版提示词大概是这种结构:
你是企业内部知识库助手。
任务:
根据提供的知识库内容回答用户问题。
要求:
1. 只能基于知识库内容回答。
2. 知识库没有明确依据时,必须说明没有找到明确依据。
3. 不能编造制度、金额、流程、接口字段和错误码。
4. 回答要通俗,适合普通员工理解。
5. 涉及流程时按步骤回答。
6. 最后给出引用来源。
知识库内容:
{retrieved_context}
用户问题:
{user_question}后面遇到一个问题:前端需要展示答案、引用来源、是否需要人工介入,但模型有时会输出一段自然语言,后端不好解析。
我后来把输出改成了固定JSON格式:
请严格输出JSON,不要输出多余解释:
{
"answer": "回答内容",
"confidence": "high|medium|low",
"references": ["引用文档"],
"needHumanHelp": true
}同时Java后端做了JSON解析校验。模型没有按JSON返回时,不会直接报错给用户,而是先尝试修复一次;修复失败再返回兜底提示。
这个地方我学到的一点是:提示词只能提高模型按规则输出的概率,不能代替后端校验。
七、RAG知识库我是怎么做的
RAG这块主要分成知识入库和在线检索两部分。
知识入库时,我们先把文档做清洗,比如去掉页眉页脚、无意义空行、重复标题。然后按章节和段落切分,生成向量后写入向量数据库,同时保存文档标题、章节、权限标签、更新时间等元数据。
在线检索时,用户问题会先生成向量,然后在用户有权限的文档范围内检索Top K片段。检索结果会根据相似度阈值过滤,低于阈值就认为知识库没有可靠依据。
RAG调优过程中遇到过几个问题。
第一个问题是文档切片太大。
早期一个片段接近两三千字,模型输入Token很多,响应慢,而且回答经常抓不到重点。后来改成按300到800字左右切片,并保留一小段上下文重叠,效果明显好一些。
第二个问题是切片太碎。
有些制度条款被切开后,模型只看到半句话,回答不完整。后来我们按标题层级和段落边界切分,尽量保证一个片段表达完整含义。
第三个问题是只用向量检索时,错误码和接口名命中不好。
比如用户问某个错误码,向量相似度不一定高。后来加了关键词检索和标签过滤,把向量检索和关键词检索结合起来,错误码、接口名、订单状态这类问题命中率提升比较明显。
第四个问题是检索结果太多会干扰模型。
一开始为了怕漏资料,会塞很多片段给模型,结果模型反而容易混淆。后来控制Top K数量,并设置相似度阈值,只传最相关的几段。
八、模型响应时间我是怎么优化的
上线测试时,最明显的问题是响应慢。慢的地方主要有三个:RAG检索、业务工具调用、大模型生成。
我做了几类优化。
第一,接入流式响应。
前端使用SSE接收模型输出,后端边拿到模型内容边推给前端。虽然总耗时没有完全消失,但用户能更快看到首字,体验提升比较明显。
第二,控制提示词长度。
RAG只传最相关的几个片段,不再把整篇文档塞进去。多轮会话也不直接拼接全部历史,而是只保留最近几轮和必要摘要。
第三,控制输出长度。
普通知识问答要求简洁回答,复杂排障问题才允许输出更长步骤。这样既降低耗时,也减少Token消耗。
第四,高频问题做缓存。
像报销标准、请假流程、接口鉴权说明这类高频问题,会把相似问题和答案缓存起来。命中缓存时不再调用模型。
第五,模型分层。
简单意图识别、问题分类用成本更低的小模型或规则判断;真正需要总结、生成、归纳时再调用主模型。
九、成本我是怎么控制的
AI应用成本主要来自Token。输入越长、输出越长、调用次数越多,费用越高。
我当时做了几个控制点。
第一,限制输入长度。
用户问题超过长度会提示用户缩短,避免一次请求带入大量无关内容。
第二,限制输出长度。
提示词里要求普通回答控制在一定长度内,排障类问题才允许分步骤详细说明。
第三,减少上下文。
多轮对话不会无限拼接历史记录,而是保留最近对话和摘要。
第四,缓存高频问题。
缓存命中后可以直接返回,不走模型调用。
第五,记录成本明细。
每次调用都会记录用户、会话、模型、输入Token、输出Token、耗时和是否成功。后面可以按用户、部门、功能统计消耗。
第六,确定性逻辑不用模型。
权限判断、参数校验、路由判断、是否为空、是否超长,这些全部用Java代码处理。模型只负责理解、总结和生成。
十、边界状态和兜底我是怎么处理的
AI项目最怕模型一本正经地答错,所以我对边界状态做了很多兜底。
用户问题为空时,直接返回“请输入要咨询的问题”,不调用模型。
用户问题过长时,提示用户缩短问题,避免浪费Token。
知识库没有命中可靠资料时,返回“当前知识库中没有找到明确依据”,不让模型自由发挥。
检索结果相似度低时,不进入大模型回答流程,或者提示用户换个问法。
模型接口超时时,返回友好提示,并记录超时日志。
模型输出JSON格式错误时,先做一次格式修复重试,失败后走兜底返回。
用户访问无权限知识库时,直接拦截,相关文档不会进入检索范围。
工具调用失败时,会明确提示“业务数据查询失败”,不会假装已经查询成功。
返回内容里出现敏感字段时,后端会做脱敏处理,比如手机号、身份证号、Token、密钥等。
十一、开发中遇到的主要问题
这个项目中我印象比较深的问题有几个。
第一个是模型编造答案。
早期只把用户问题直接发给模型,模型回答很流畅,但有些内容公司制度里根本没有。后来接入RAG,并在提示词里强制要求“只能基于知识库回答”,同时低相似度时直接拒答,这个问题明显减少。
第二个是RAG命中不准。
有些问题语义上相关,但关键词不明显;有些错误码、接口名又必须靠精确匹配。最后用了向量检索加关键词检索的混合方式,并且加了文档标签和权限过滤。
第三个是响应时间偏长。
一开始模型要等完整回答生成后才返回,用户感觉很慢。后来改成SSE流式输出,并减少上下文长度,体验改善比较明显。
第四个是模型输出格式不稳定。
明明提示词要求JSON,它有时还是会输出解释文本。后端后来增加了JSON解析校验和一次修复重试,不能解析时走兜底。
第五个是成本不可控。
测试阶段有些用户会连续问很长的问题,导致Token消耗很高。后来加了输入长度限制、输出长度限制、缓存和成本统计。
第六个是权限边界问题。
一开始只在接口入口校验用户身份,但RAG检索时没有把文档权限作为强过滤条件。后来把权限标签写入文档元数据,检索阶段就按权限过滤,避免无权限文档进入模型上下文。
十二、测试和验证
测试时我没有只看模型能不能返回内容,而是重点验证它有没有按业务规则回答。
我做了几类测试场景:
- 正常问题能命中正确知识库文档。
- 知识库没有答案时不会编造。
- 用户无权限时查不到受限文档。
- 空问题、超长问题能直接拦截。
- 模型超时时能返回兜底提示。
- JSON格式错误时能修复或兜底。
- 高频问题能命中缓存。
- Token、耗时、引用来源能正常记录。
RAG测试里还准备了一批固定问题和期望命中文档,比如:
| 问题 | 期望命中文档 |
|---|---|
| 报销住宿标准是多少 | 差旅报销制度 |
| 接口返回401怎么办 | 接口鉴权说明 |
| 支付回调失败怎么排查 | 支付系统排障手册 |
| 某错误码是什么意思 | 错误码说明文档 |
这样可以比较稳定地评估知识库检索质量。
十三、上线后监控
上线后我重点看几类指标。
第一类是性能指标,包括平均响应时间、首字响应时间、P95响应时间。
第二类是稳定性指标,包括模型调用成功率、超时率、工具调用失败率。
第三类是成本指标,包括每日Token消耗、按用户统计的调用次数、按功能统计的模型费用。
第四类是效果指标,包括RAG命中率、低相似度拒答次数、用户反馈、人工介入率。
这些数据对后续优化帮助很大。比如响应慢,就看是RAG慢、业务接口慢还是模型生成慢;成本高,就看是不是上下文太长或者某些高频问题没有缓存;回答不准,就回看命中文档和提示词。
十四、面试现场可以这样完整表述
我之前做过一个企业内部知识库AI智能体项目,主要用于员工制度问答、客服业务规则查询,以及开发运维排障辅助。这个项目不是简单调用大模型接口,而是一个完整的Java后端系统。用户提问以后,系统会先做权限校验和问题预处理,然后通过RAG从知识库中检索相关资料,再组装提示词调用大模型,最后对模型结果做校验、脱敏、记录日志并返回给用户。
我在项目中主要负责后端智能体流程开发,包括ChatController对话接口、AgentService流程编排、RagService知识库检索、PromptService提示词组装、ModelClient模型调用封装、GuardrailService安全校验,以及Token成本统计和调用日志。
需求阶段我先把边界定清楚。第一版只做知识库问答和简单排障建议,不做自动修改数据这类高风险操作。数据源接入制度文档、接口文档、FAQ和排障手册,业务接口只开放只读查询,并且敏感字段做脱敏。知识库没有依据时,系统必须明确提示没有找到依据,不能让模型自己编。
开发过程中,我把核心链路拆成几步:先校验用户和问题,再按用户权限过滤可访问文档,然后进行RAG检索,拿到相关片段后组装提示词,调用模型生成回答,再做格式校验、敏感信息过滤、引用来源记录,最后保存Token、耗时和命中文档。
提示词方面,我主要控制模型行为,要求它只能基于知识库回答,没有依据就拒答,不能编造制度、金额、流程、接口字段和错误码。为了方便前端展示,我让模型输出固定JSON结构,但后端不会完全相信模型,所以增加了JSON解析校验和一次修复重试。
RAG方面,我做了文档清洗、切片、向量化和元数据存储。调优时遇到过切片太大、切片太碎、错误码命中不准的问题。后来把文档按章节和段落切成300到800字左右,并保留少量上下文重叠;同时把向量检索和关键词检索结合起来,对错误码、接口名这类问题效果更好。
性能方面,早期响应比较慢,因为要等模型完整生成后再返回。后来我接入了SSE流式响应,用户可以先看到模型首字输出。同时减少无效上下文,只传最相关的知识片段;高频问题加缓存;简单任务用规则或小模型处理,复杂总结再用主模型。
成本方面,我主要控制Token。做了输入长度限制、输出长度限制、多轮上下文压缩、高频问题缓存和调用成本统计。每次调用都会记录模型名称、输入Token、输出Token、耗时、用户和会话ID,后面可以按用户、部门和功能分析成本。
项目里比较典型的问题有几个。第一个是模型编造答案,解决方式是接入RAG,并在低相似度时拒答。第二个是RAG命中不准,解决方式是混合检索和文档标签过滤。第三个是模型输出格式不稳定,解决方式是JSON解析校验和修复重试。第四个是权限边界问题,解决方式是在文档入库时写入权限标签,检索阶段就按权限过滤,避免无权限内容进入模型上下文。
测试时我重点验证业务意图,而不是只看有没有回答。比如知识库没有答案时不能编造,用户无权限时不能命中文档,模型超时时要有兜底,RAG要命中正确文档,Token和耗时要记录准确。上线后主要监控响应时间、模型成功率、超时率、Token消耗、RAG命中率和用户反馈。
这个项目给我的感受是,AI智能体真正难的不是调模型API,而是让它在真实业务里稳定、准确、可控地运行。模型负责理解和生成,权限、校验、路由、成本控制、异常兜底这些确定性逻辑必须由Java后端来保证。这样项目才不是一个简单聊天框,而是一个能落地的智能体应用。