AI 应用开发工程师面经
完整信息
本篇目录
来源内容仅部分可用:原文不足
1. 请做一个简单的自我介绍
2. 传统的 Agent 设计里,记忆层应该怎么设计?
Agent 的记忆不能简单理解成把所有历史消息拼接到 Prompt 中,否则会遇到上下文过长、成本过高、历史信息污染以及模型注意力下降等问题。
一般可以分为几层:
- 短期记忆:保存当前任务中的对话、工具调用结果和中间状态。
- 长期记忆:保存用户偏好、历史任务结论、稳定事实等信息。
- 工作记忆:保存当前任务已经完成的步骤、待执行步骤和关键变量。
- 语义记忆:通过向量检索获取与当前问题相关的历史信息。
- 程序记忆:保存固定流程、工具使用规则和业务约束。
实际设计时,短期记忆可以放在 Redis 或状态存储中,长期记忆可以放在关系型数据库和向量数据库中。每次对话结束后,不应该把所有内容都写入长期记忆,而应该经过摘要、去重、重要性判断和敏感信息过滤。
例如可以定义如下结构:
public class AgentMemory {
private String sessionId;
private String userId;
private List<Message> recentMessages;
private Map<String, Object> taskState;
private List<MemoryItem> recalledMemories;
private String conversationSummary;
}
长期记忆还需要考虑失效机制。对于临时偏好,应该设置过期时间;对于用户明确修改过的信息,应该覆盖旧数据;对于可能变化的事实,不能永久当作真实数据使用。
3. 怎么解决大模型的幻觉问题?
幻觉无法通过单一 Prompt 完全消除,只能通过数据、检索、约束和验证多方面降低。
常用手段包括:
- 对知识库内容进行清洗、去重和版本管理。
- 对文档进行合理切分,避免上下文语义被截断。
- 使用混合检索,同时结合关键词检索和向量检索。
- 引入重排序模型,提高候选文档的相关性。
- 要求模型只基于检索内容回答,找不到依据时明确说明。
- 强制输出引用来源,方便用户验证。
- 对模型输出进行规则校验或二次模型审查。
- 对关键业务采用结构化输出和程序侧校验。
- 对高风险结果引入人工审核。
例如,在 Prompt 中可以约束:
你只能使用给定的参考资料回答问题。
如果参考资料中没有足够依据,请直接回答“当前资料无法确认”,
不要根据常识、经验或猜测补充结论。
每个关键结论后面必须标注对应资料编号。
但是 Prompt 约束只是最后一道防线。对于金额、时间、权限、库存等数据,最终结果应该由后端程序或数据库确认,而不是完全信任模型输出。
4. 如何评估 RAG 系统的效果?需要达到什么标准才能上线?
RAG 评估至少要分成检索效果、生成效果和系统效果三部分。
检索层主要关注:
- Recall:正确文档是否被召回。
- Precision:召回结果中是否包含大量无关内容。
- MRR:正确文档在结果列表中的排名。
- NDCG:不同相关程度文档的排序质量。
- Context Recall:回答所需信息是否被检索出来。
生成层主要关注:
- 回答是否正确。
- 是否严格基于检索内容。
- 是否存在无依据扩展。
- 是否完整回答了问题。
- 引用是否准确。
- 语言是否符合业务要求。
系统层还需要关注:
- 平均响应时间和 P95 响应时间。
- Token 消耗和单次请求成本。
- 并发量和限流策略。
- 文档更新后的生效延迟。
- 失败率、超时率和降级情况。
- 敏感信息泄露概率。
上线前不能只准备几条人工测试问题,应该建立一套包含标准答案、标准文档和问题类型的数据集,例如事实查询、跨文档推理、歧义问题、无答案问题和长问题。
一个比较实际的上线门槛是:核心问题的检索召回率达到目标,关键业务问答不能出现严重事实错误,无依据回答比例低于可接受阈值,并且在高峰并发下仍然满足响应时间要求。
5. Spring Boot 中的配置文件有哪些?
Spring Boot 常见配置文件包括:
application.propertiesapplication.ymlapplication-{profile}.yml- 外部配置文件
- 环境变量
- JVM 启动参数
- 命令行参数
- 配置中心中的远程配置
Spring Boot 会根据配置优先级加载属性。一般来说,命令行参数、环境变量和外部配置的优先级会高于项目内部配置文件。
常见环境配置如下:
spring:
profiles:
active: prod
server:
port: 8080
开发和生产环境通常使用不同的 Profile:
# application-dev.yml
llm:
base-url: http://localhost:9000
timeout: 30s
# application-prod.yml
llm:
base-url: https://llm.internal
timeout: 10s
敏感配置,例如 API Key、数据库密码,不应该直接提交到 Git。可以通过环境变量注入:
llm:
api-key: ${LLM_API_KEY}
配置类可以使用 @ConfigurationProperties 统一管理:
@ConfigurationProperties(prefix = "llm")
public class LlmProperties {
private String baseUrl;
private String apiKey;
private Duration timeout;
// getter / setter
}
相比在业务代码中大量使用 <a href="/users/652047" class="js-nc-card" data-card-uid="652047" target="_blank">@Value</a>,@ConfigurationProperties 更适合管理一组具有层次结构的配置。
6. RAG 系统包含哪些核心模块?
一个完整的 RAG 系统通常包括数据接入、文档解析、文本切分、向量化、索引存储、查询改写、召回、重排序、上下文组装、模型生成和效果评估等模块。
离线链路大致如下:
数据源
-> 文档解析
-> 内容清洗
-> 元数据提取
-> 文本切分
-> 向量化
-> 写入索引
在线链路大致如下:
用户问题
-> 问题改写
-> 查询分类
-> 关键词检索 + 向量检索
-> 结果合并
-> 重排序
-> 上下文压缩
-> 大模型生成
-> 引用校验
-> 返回答案
如果只是把文本切成块后做向量检索,只能算基础实现。生产系统通常还要处理:
- 多格式文档解析。
- 表格和图片内容提取。
- 文档权限过滤。
- 文档版本控制。
- 增量更新和删除。
- 召回结果去重。
- 长上下文压缩。
- 无答案检测。
- 引用来源返回。
- 检索质量监控。
7. 关键字召回和向量召回的分数怎么融合?
关键词召回通常基于 BM25 等算法,擅长处理专有名词、编号、错误码和精确匹配;向量召回擅长处理语义相近但字面表达不同的问题。
两者的原始分数通常不在同一个范围内,因此不能直接相加。常见做法是先归一化:
score_normalized =
(score - minScore) / (maxScore - minScore)
然后进行加权:
finalScore = alpha * keywordScore
+ (1 - alpha) * vectorScore
其中 alpha 不能凭经验固定,应该通过验证集调参。
另一种方式是 Reciprocal Rank Fusion,也就是 RRF:
RRFScore(d) = Σ 1 / (k + rank_i(d))
其中:
d表示文档。rank_i(d)表示文档在第i个召回列表中的排名。k通常取一个较大的常数,用来降低高排名差异带来的影响。
RRF 不依赖不同召回器的分数尺度,工程上比较稳定。对于企业知识库,可以采用 BM25、向量召回和规则召回三路结果,先用 RRF 合并,再交给重排序模型处理。
8. 除了 RRF 融合,还有哪些方法?
除了 RRF,还可以使用以下方法:
第一种是分数加权。先对不同召回方式的分数进行归一化,再按照权重求和。它实现简单,但对分数归一化方式比较敏感。
第二种是学习排序。使用人工标注的数据训练排序模型,让模型学习哪些特征更重要,例如关键词匹配程度、向量相似度、文档新鲜度、标题匹配度和用户权限。
第三种是级联召回。先使用低成本方法召回较多候选,再使用更复杂的模型对少量候选重排,以平衡效果和性能。
第四种是基于查询类型选择召回方式。例如错误码、接口名、产品编号适合关键词召回;描述性问题适合向量召回;复杂问题则采用混合召回。
还可以使用查询拆分。对于一个包含多个子问题的长问题,先拆分为多个查询分别召回,再合并结果。
9. Agent 的终止条件怎么设置?如何避免陷入死循环?
Agent 不能只依赖模型自己判断是否结束,应该同时设置程序级终止条件。
常见终止条件包括:
- 模型返回最终答案。
- 达到最大推理步数。
- 达到最大执行时间。
- 达到最大 Token 数。
- 工具调用失败次数超过阈值。
- 连续多轮产生相同的工具调用。
- 任务状态已经满足完成条件。
- 检测到权限、参数或安全风险。
可以使用状态机表示任务状态:
public enum AgentState {
INIT,
PLANNING,
CALLING_TOOL,
OBSERVING,
COMPLETED,
FAILED,
TIMEOUT
}
执行循环需要同时控制次数和时间:
for (int step = 0; step < maxSteps; step++) {
if (System.currentTimeMillis() > deadline) {
return AgentResult.timeout();
}
Action action = planner.next(state);
if (action.isFinalAnswer()) {
return AgentResult.success(action.getContent());
}
if (state.isDuplicate(action)) {
return AgentResult.failed("检测到重复工具调用");
}
ToolResult result = toolExecutor.execute(action);
state.update(action, result);
}
return AgentResult.failed("超过最大执行步数");
此外,还要限制工具参数、工具权限和单个工具的调用次数。对于付款、删除、发布等副作用操作,不能允许 Agent 自动无限重试。
10. Agent 需要调用很多工具时,应该如何设计?
工具数量增加后,主要问题不是调用工具本身,而是工具选择错误、参数生成错误、工具之间依赖复杂以及上下文膨胀。
工具设计应该遵守几个原则:
- 工具名称清晰,避免多个工具语义重叠。
- 参数使用严格的 JSON Schema。
- 工具描述中明确输入、输出、限制和失败场景。
- 将只读工具和有副作用工具分开。
- 对工具设置权限等级。
- 对高风险工具要求人工确认。
- 工具结果尽量结构化。
- 工具调用设置超时、重试和熔断。
- 不要把几十个无关工具一次性暴露给模型。
可以根据用户意图先做工具路由:
用户问题
-> 意图识别
-> 工具域选择
-> 加载该领域工具
-> 参数生成
-> 工具执行
例如客服 Agent 可以先判断问题属于订单、物流、退款还是账户,再只暴露对应领域的工具。
工具返回结果最好统一格式:
{
"success": true,
"data": {
"orderId": "A10086",
"status": "SHIPPED"
},
"errorCode": null,
"message": "查询成功"
}
这样模型不需要解析各种风格不同的文本错误信息。
11. Agent 的规划器和执行器应该如何解耦?
规划器负责决定下一步做什么,执行器负责真正执行动作,两者不应该混在一起。
规划器的输入通常包括:
- 当前任务目标。
- 历史消息。
- 当前任务状态。
- 已执行步骤。
- 可用工具列表。
- 工具执行结果。
- 约束和权限信息。
规划器输出一个结构化动作:
{
"type": "CALL_TOOL
本篇目录
当前问题0111
- 01
请做一个简单的自我介绍
- 02
怎么解决大模型的幻觉问题?
- 03
RAG 系统包含哪些核心模块?
- 04
传统的 Agent 设计里,记忆层应该怎么设计?
- 05
如何评估 RAG 系统的效果?需要达到什么标准才能上线?
- 06
Spring Boot 中的配置文件有哪些?
- 07
关键字召回和向量召回的分数怎么融合?
- 08
除了 RRF 融合,还有哪些方法?
- 09
Agent 的终止条件怎么设置?如何避免陷入死循环?
- 10
Agent 需要调用很多工具时,应该如何设计?
- 11
Agent 的规划器和执行器应该如何解耦?