Skip to content

面试中如何讲解:我做过的AI智能体应用开发项目

我之前做过一个企业内部知识库AI智能体项目,主要目标是帮助员工、客服和开发人员快速查询公司内部资料。这个系统接入了公司制度文档、接口文档、FAQ、故障排查手册和部分业务接口。用户可以直接用自然语言提问,系统会先检索内部知识库,再调用大模型生成回答,并且返回引用来源。

这个项目不是简单调一个大模型接口。真正开发时,我把它当成一个完整的后端系统来做,重点考虑了需求边界、权限控制、RAG检索、提示词工程、响应时间、Token成本、模型输出不稳定、异常兜底和上线后的监控。

一、项目背景和业务目标

这个项目最开始是为了解决内部重复咨询的问题。公司里很多制度、接口说明、排障文档都散落在不同平台上,新员工或者客服人员经常不知道去哪里查,开发和运维排查问题时也需要翻很多文档。

所以我们做了一个企业知识库AI助手,让用户可以直接提问,比如:

  • 出差住宿费报销标准是多少?
  • 接口返回401一般是什么原因?
  • 支付回调失败应该怎么排查?
  • 某个错误码代表什么意思?
  • 根据接口文档生成一组测试用例。

我负责的部分主要是Java后端智能体流程,包括会话接口、RAG检索编排、提示词组装、大模型调用封装、权限校验、Token统计、日志追踪和异常兜底。

二、需求阶段我做了哪些拆解

需求阶段我没有直接开始接模型,而是先和产品、业务同事把边界梳理清楚。因为AI项目最怕范围太宽,最后做成一个什么都能问、但回答不稳定的聊天框。

我当时主要拆了四件事。

第一,明确用户群体。

这个智能体主要给三类人用:

  • 内部员工:查询制度、流程、FAQ。
  • 客服人员:查询标准话术、业务规则、常见问题。
  • 开发和运维人员:查询接口文档、错误码、排障手册。

不同用户看到的资料范围不一样,所以后端必须做权限控制。

第二,明确能回答什么。

我们把第一版范围控制在知识库问答和简单排障建议上,不做复杂自动执行。比如可以回答“支付回调失败怎么排查”,但不会自动去改数据、重发回调或者执行生产操作。

第三,明确数据来源。

第一版接入了制度文档、接口文档、FAQ和排障手册。业务数据库只开放了少量只读查询接口,并且对敏感字段做了脱敏。

第四,明确不能做什么。

知识库没有依据时,系统必须明确提示“没有找到明确依据”,不能让模型自己编。用户无权限访问的文档,不能进入检索结果,更不能传给大模型。

三、整体架构设计

后端用的是 Java + Spring Boot。整体链路是:

text
用户提问

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回答。这样即使模型调用中间失败,也能看到用户问过什么,以及失败原因。

用户下次打开系统时,前端会先调用会话列表接口,比如:

text
GET /api/conversations

后端按user_id查询这个用户自己的会话列表,返回标题、最后更新时间和最近一条消息。用户点击某个会话后,再调用详情接口:

text
GET /api/conversations/{conversationId}/messages

后端根据conversationId查询消息表,把历史对话按时间顺序返回给前端展示。这里必须校验conversationId是不是属于当前登录用户,不能只凭会话ID查询,否则会有越权风险。

多轮上下文传给模型时,我没有把全部历史消息都塞进去。因为历史越长,Token成本越高,响应也越慢。我的处理方式是:

  • 只取最近几轮关键对话。
  • 对更早的历史做摘要。
  • 当前问题永远优先级最高。
  • RAG检索结果优先级高于历史闲聊内容。
  • 超过Token限制时,优先丢弃低价值历史。

比如用户第一轮问“支付回调失败怎么排查”,第二轮继续问“那401这种情况呢”,第二轮的问题本身不完整。后端会加载上一轮上下文,知道这里的“401”还是在支付回调排查场景下,然后再去检索接口鉴权和支付回调相关文档。

这里我遇到过一个问题:历史上下文带太多时,模型容易被前面的问题带偏。后来我做了上下文选择规则,只保留和当前问题相关的最近几轮,同时提示词里明确要求“优先回答当前用户问题”。这样多轮对话既能连续,又不容易跑题。

五、核心开发流程

用户输入问题后,后端不是直接把问题丢给大模型,而是走了一套完整流程。

第一步,做基础校验。

包括用户是否登录、问题是否为空、长度是否超限、是否包含明显敏感信息。空问题和超长问题不会调用模型,直接返回提示,避免浪费Token。

第二步,做权限过滤。

系统会根据用户角色、部门和知识库权限,确定他能访问哪些文档。RAG检索时只在有权限的文档范围内查,避免把敏感文档传给模型。

第三步,做RAG检索。

用户问题会先转成向量,然后到向量库里检索相关文档片段。检索结果会再根据相似度、文档权限、文档状态做过滤。

第四步,组装提示词。

Prompt里会包含用户问题、检索到的知识片段、回答规则和输出格式要求。重点要求模型只能基于资料回答,没有依据就明确说明。

第五步,调用大模型。

模型调用统一通过ModelClient封装,里面处理超时、错误码、响应解析和Token统计。

第六步,做结果校验。

模型返回后,后端会检查输出格式、敏感词、引用来源和是否为空。如果JSON格式错误,会做一次修复重试;如果仍然失败,就走兜底返回。

第七步,保存日志。

每次调用都会保存用户问题、命中文档、模型名称、输入Token、输出Token、耗时、是否成功、失败原因。这样线上出问题可以追踪。

六、提示词工程我是怎么做的

提示词这块我没有追求复杂,而是重点控制模型行为。

第一版提示词大概是这种结构:

text
你是企业内部知识库助手。

任务:
根据提供的知识库内容回答用户问题。

要求:
1. 只能基于知识库内容回答。
2. 知识库没有明确依据时,必须说明没有找到明确依据。
3. 不能编造制度、金额、流程、接口字段和错误码。
4. 回答要通俗,适合普通员工理解。
5. 涉及流程时按步骤回答。
6. 最后给出引用来源。

知识库内容:
{retrieved_context}

用户问题:
{user_question}

后面遇到一个问题:前端需要展示答案、引用来源、是否需要人工介入,但模型有时会输出一段自然语言,后端不好解析。

我后来把输出改成了固定JSON格式:

text
请严格输出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后端来保证。这样项目才不是一个简单聊天框,而是一个能落地的智能体应用。