LLM 应用开发
1 LLM 参数与基础原理¶
Temperature 与 Top-P 参数¶
🔹 1. Temperature 和 Top-P 分别控制什么?¶
两者都是在模型输出 logits(未归一化概率)之后,对采样过程施加约束的参数。简单说:
-
Temperature 控制“分布的陡峭程度”——影响所有候选 token 的相对概率。
-
Top-P 控制“候选集的大小”——只从累积概率超过 P 的最可能词中挑。

Temperature 的效果:
-
T=1T=1:保持原始分布,不改变模型对各个词的偏好。
-
T<1T<1(如 0.3):分布变得更尖锐,高概率词的机会更大,输出确定性高、重复多。
-
T>1T>1(如 1.2):分布变平缓,低概率词获得更多机会,输出更多样但可能跑偏。
Top-P(Nucleus Sampling)的效果:
-
比如
Top-P = 0.9:模型将词按概率从高到低排序,只保留累积概率刚好超过 0.9 的最小集合,其余词概率置零后重新归一化。这样动态截断,防止选到特别离谱的词,同时又能适应分布的宽窄。 -
Top-P 小(如 0.3)→ 候选集极小,非常保守;Top-P 大(如 0.95)→ 候选集大,更自由。
一图对比它们的作用域:

在实际调用中,这两个参数常联合使用:先用温度调“口味”,再用 Top-P 切掉“怪味尾巴”。
📐 2. Temperature 和 Top-P 的数学本质是什么?在 Agent 工程实践中如何按任务类型选参?¶
数学本质¶
给定最后一层 logits 向量 zz,标准 softmax 为:

引入温度 TT 后变为:

这相当于在指数函数里对 logits 进行缩放。当 T→0T→0 时,分布趋于 one-hot(argmax);当 T→∞T→∞ 时,趋于均匀分布。
Top-P 则是对这个 p(T)p(T) 进行后处理:设排序后概率递减序列为 p(1),p(2),…p(1),p(2),…,找到最小的 kk 使得 ∑i=1kp(i)≥P∑i=1kp(i)≥P,然后保留前 kk 个,其余概率置零并重新归一化。数学上就是截断再归一化,保证采样集中来自“核”内。
Agent 工程中的选参策略¶
Agent 的行为需要根据不同子任务切换参数。一个实用的决策表:
Agent 工程中的核心原则:
对于需要精确格式和执行的操作(如生成工具调用的 JSON),把温度降到极低(0.0–0.1),甚至可以关闭 Top-P 或设 Top-P=1.0,因为低温度已经保证了足够的确定性。对于需要探索和多样性的步骤(如头脑风暴可能的解决方案),升高温度并配合适中的 Top-P。
一个通用做法是:先固定 Top-P=0.9,只调 Temperature 来找手感,因为 0.9 已经能滤除绝大部分垃圾 token。如果出现重复太多,可以微调频率惩罚或稍加 Top-P 压缩。
🎯 3. 一个 Agent 负责拆解子任务并生成 JSON 工具调用参数,同时也需要做头脑风暴生成多样化方案,应该怎么配置参数?¶
这是典型的“同体双模”需求:解构思维(收敛)+ 发散思维。解决方案是按步骤动态切换采样参数,而不是整个 Agent 使用一套固定配置。
架构设计:

在代码实现里,我们在 Agent 循环的不同阶段调用 LLM 时显式传入不同的参数对象。
代码示例(LangChain 风格的 Agent 内部):
class HybridAgent:
def __init__(self, llm):
self.llm = llm # 基础模型,参数动态覆盖
def run(self, task):
# 第一阶段:头脑风暴 —— 高温度 + 较大 Top-P
brainstorm_prompt = f"针对任务“{task}”,请列出 5 种完全不同的解决方案,要求有创意。"
ideas_raw = self.llm.invoke(
brainstorm_prompt,
temperature=0.9,
top_p=0.95,
max_tokens=500
)
ideas = self.parse_ideas(ideas_raw)
# 第二阶段:选择最优方案并拆解为工具调用 —— 极低温度,确保 JSON 正确
plan_prompt = f"""
从以下方案中选择最可行的一个,并把它拆解为一系列工具调用步骤。
以 JSON 格式输出,每步包含 "tool" 和 "args"。
方案列表:{ideas}
"""
plan_json = self.llm.invoke(
plan_prompt,
temperature=0.0, # 贪婪解码
top_p=1.0, # 无截断
response_format="json" # 强制 JSON
)
steps = json.loads(plan_json)
# 执行步骤
for step in steps:
result = execute_tool(step["tool"], step["args"])
# ...
为什么这样配置有效?
-
头脑风暴阶段:高温度(0.8-1.0)让模型敢于跳出最常见的想法,而 Top-P=0.95 滤掉真的胡言乱语。如果你希望更多样,甚至可以加入少量频率惩罚(
frequency_penalty=0.1),抑制它老在同一句话上打转。 -
拆解与 JSON 生成阶段:温度设为 0,直接就是 greedy decoding,保证每次面对相同的 prompt 都输出一致的、可预测的结构。这里用 Top-P 截断反而可能截掉正确的闭合括号或引号,所以设 Top-P=1.0 保留全部候选。
进阶:在同一阶段内切换 —— “结构化创意”
有时你需要一次调用同时产出“带约束的创意”,比如“生成 3 个产品文案,每个必须包含指定关键词”。这时可以用中等温度 + 低 Top-P 的组合:
-
温度 0.6-0.7 提供足够的变化,让三条文案不重样。
-
Top-P 0.9 保证每个候选都是通顺合理的。
-
最后再用后处理校验关键词是否齐全,不齐就重试。
Agent 的生存法则:没有一套参数能通吃所有任务,但你可以让 Agent 根据自己的“当前意图”来切换模式。 这正是工程设计的魅力所在。
LLM 的幻觉(Hallucination)成因与分类¶
1、基础题:什么是 LLM 幻觉?¶
难度级别:⭐(幻觉定义、基本成因)
LLM 幻觉是指模型生成了与事实不符或与上下文矛盾的内容。根本原因有两类:一是训练数据本身有错误或过时信息;二是 RLHF 阶段奖励模型偏向流畅自信的回答而非准确性,导致模型学会了"自信地说错话"。
2、进阶题:LLM 幻觉的成因和分类是什么?在 AI Agent 系统中有哪些有效的缓解策略?¶
难度级别:⭐⭐(内在/外在幻觉分类、RLHF 偏置、Agent 幻觉雪球效应、缓解策略体系)
1️⃣ Common Answer
幻觉就是模型说了假话。原因是训练数据有错误,或者模型对不熟悉的问题猜了一个听起来合理的答案。可以用 RAG 提供真实知识,或者降低 Temperature 让输出保守一点,还可以在 Prompt 里告诉模型不确定就说不知道。
2️⃣ Impressive Answer
我会从成因分类、Agent 场景特殊性、缓解策略三个层次来回答。
-
幻觉的两类成因。内在幻觉(Intrinsic Hallucination)根源在模型本身:训练语料里有错误或过时信息;RLHF 的奖励模型偏向"流畅自信"而非"准确",模型学会了用自信语气说错误的话;以及参数记忆的压缩损失导致事实失真。外在幻觉(Extrinsic Hallucination)是模型无法正确利用上下文中已有的信息——提供了参考文档但回答与文档矛盾,或者指令太复杂导致遵循失败,本质是上下文理解和指令遵循的问题。
-
Agent 场景的特殊性。在多步 Agent 里,幻觉的危害被放大了——一次幻觉会导致后续多步工具调用都基于错误前提,形成"幻觉雪球"。这是单轮问答里不存在的风险。
-
缓解策略。我按层次来做:RAG 提供事实锚点,针对外在幻觉效果最直接,Prompt 里明确要求"只基于提供的文档回答";Self-Consistency 多路径投票,用较高 Temperature 采样 5-10 次再做多数投票,TruthfulQA 的实验证明在事实性问题上有可观提升;工具调用后加 Grounding 验证,让模型检查"我的结论和工具结果是否一致",不一致就触发重规划;置信度提示,要求模型给出置信度等级并明确"不确定比猜测更有价值"。
3️⃣ Key Differences
3、场景题:你的 Agent 在做多步推理时,第二步工具调用返回了正确结果,但第三步的回答却和工具结果矛盾,你怎么排查和处理?¶
难度级别:⭐⭐(外在幻觉定位、Grounding 验证、Agent 重规划机制)
1️⃣ Common Answer
这应该是幻觉问题,模型没有正确使用工具返回的结果。可以降低 Temperature,或者在 Prompt 里强调要用工具结果回答。
2️⃣ Impressive Answer
这是典型的外在幻觉——工具结果在上下文里,但模型没有正确利用它。我会这样处理:
-
先排查根因。检查工具返回结果在上下文中的位置,如果被放在了很前面而当前步骤的指令在后面,可能是 Lost in the Middle 的问题——关键数据被"淹没"在中间位置。同时检查工具返回格式是否解析正常,排除结构化数据解析错误的可能。
-
短期修复。在第三步的 Prompt 里,把工具返回结果显式重申一遍,紧放在"现在请基于以上信息"这句话之前,利用近因效应强制模型关注这个数据。
-
系统性解决。加入 Grounding 验证节点:每次工具调用后,让 LLM 做一次"我的下一步推断是否和工具结果一致"的自我校验,不一致就记录 conflict 并触发重规划,而不是继续往下走。这样把幻觉消灭在扩散之前。
3️⃣ Key Differences
3.2 Prompt 工程实践¶
System Prompt 的设计最佳实践¶
1、基础题:System Prompt 里应该写什么?¶
难度级别:⭐(System Prompt 基本结构、角色定义)
System Prompt 是 Agent 的行为契约基础,通常包含三部分:角色与能力边界定义(告诉模型它是谁、能做什么、不能做什么)、输出格式约束(格式模板和示例)、以及边界条件与拒绝策略(什么情况下应该拒绝并如何回应)。
2、进阶题:在构建 AI Agent 系统时,System Prompt 的设计有哪些最佳实践?如何兼顾功能性和性能优化?¶
难度级别:⭐⭐(角色边界定义、格式约束、KV Cache 友好设计、优先级排列)
1️⃣ Common Answer
System Prompt 就是告诉模型它是谁、该怎么做。好的 System Prompt 要清晰简洁,包括角色定义、行为规则和输出格式。比如写"你是专业客服助手,只回答产品相关问题,用 JSON 格式输出"。另外 System Prompt 要保持稳定,不要经常改,这样缓存效果更好。
2️⃣ Impressive Answer
我会从功能设计和性能设计两个维度来展开。
-
角色与能力边界要精确,不要模糊。不要只写"你是一个助手",要给出具体身份、具体能力范围和明确的知识边界。边界清晰后模型的行为就更可预测,"自由发挥"的空间更小。
-
格式约束用"描述 + 示例"组合,不要只描述。对 Agent 系统来说,工具调用参数解析依赖输出格式的稳定性。只说"用 JSON 输出"不够,要直接给出 schema 加一个正确示例,描述加示例的组合比单纯描述可靠得多。
-
边界条件和拒绝策略不能省。明确告诉模型什么时候应该拒绝、拒绝时怎么回答。很多人忽略这一点,结果是模型在超出能力范围时硬撑着给错误答案。加一条"超出能力范围时直接说明,不要猜测或编造"能显著减少这类幻觉。
-
KV Cache 友好是性能层面的核心考量。System Prompt 每次请求都相同,API 侧命中前缀缓存能显著降低首 Token 延迟和费用。要求是内容完全固定,包括标点和空格,不能有动态插值。需要动态注入的信息(当前日期、用户信息)统一放到 User 消息里,不要放 System Prompt 里。Anthropic 缓存命中有 90% 费用折扣,高并发场景收益非常可观。
3️⃣ Key Differences
3、场景题:你的 Agent 的 System Prompt 有 3000 个 token,但每次请求都在里面插入当前用户的姓名和权限,导致 Cache 命中率为 0,如何改造?¶
难度级别:⭐⭐(KV Cache 前缀缓存条件、动态内容隔离、Prompt 结构重构)
1️⃣ Common Answer
把用户姓名和权限从 System Prompt 里移出去,放到用户消息里。这样 System Prompt 就固定了,可以命中缓存。
2️⃣ Impressive Answer
方向是对的,但具体改造要考虑几个细节:
-
System Prompt 必须完全固定,包括标点和空格。前缀缓存命中的条件是逐字节完全相同,只改一个字符,从那个位置开始的所有 block 都会 cache miss。所以不只是移出姓名和权限,System Prompt 里任何动态插值都要清除。
-
动态信息的放置位置。用户姓名、权限、当前日期这些信息,统一移到对话的第一条 User 消息里,格式固定为"当前用户:{name},权限级别:{role}",让 System Prompt 的 3000 token 在整个会话生命周期内完全不变。
-
如果有固定的 Few-shot 示例,也可以跟在 System Prompt 后面一起缓存,按"System Prompt → 固定示例 → 对话历史 → 当前用户输入"的顺序排列,稳定内容在前,动态内容在后。
-
效果评估。改造后监控 API 返回的 cache_creation_input_tokens 和 cache_read_input_tokens 比例,确认缓存确实在命中。Anthropic 命中缓存的 input token 只收原价 10%,3000 token 的 System Prompt 在高并发下节省非常可观。
❌ 改造前: [System Prompt + 用户名 + 权限 + 时间戳] → 每次不同 → Cache Miss
✅ 改造后:
① System Prompt (3000 token 纯静态) ← KV Cache 命中
② Fixed Few-shot 示例 ← KV Cache 命中
③ User消息: "当前用户:{name}, 权限:{role}" ← 动态部分,不缓存
④ 用户当前输入
3️⃣ Key Differences
4、容易一起考的题¶
Few-shot Prompting 的示例选择策略¶
1、基础题:Few-shot Prompting 是什么?¶
难度级别:⭐(Few-shot 基本概念、示例数量经验值)
Few-shot Prompting 是在 Prompt 里提供若干输入-输出示例,帮助模型理解任务模式、输出格式和边界情况。通常 3-8 个示例是性价比最高的区间,再多效果提升会递减但 Token 消耗线性增长。质量和多样性比数量更重要。
2、进阶题:Few-shot 示例的选择策略对效果有哪些关键影响?动态 Few-shot 和静态 Few-shot 各自适用什么场景?¶
难度级别:⭐⭐(示例质量 vs 数量、位置影响、动态检索原理、示例池持续优化)
1️⃣ Common Answer
Few-shot 就是给几个例子让模型参考。示例 3 到 8 个比较合适,太少效果不好,太多占太多 Token。动态 Few-shot 是根据问题动态选相似的示例,静态是固定几个。动态效果更好但实现复杂,需要 Embedding 做相似度检索。
2️⃣ Impressive Answer
我会从示例质量原则、位置影响、静态 vs 动态选型三个角度来回答。
-
质量和多样性比数量更重要。10 个重复覆盖同一种模式的示例,不如 4 个覆盖不同边界情况的示例。好的示例集要覆盖典型场景、关键边界情况,输出格式完全规范,避免示例之间高度相似——相似示例浪费 Token 但不增加信息量。
-
位置比数量更容易被忽视。示例放得越靠近实际任务指令,效果越好,这和 LLM 对近端上下文注意力权重更高有关。如果 System Prompt 很长,我会把示例放在 User 消息里紧邻问题的位置,而不是全部堆在 System Prompt 开头。
-
静态 vs 动态的工程选型。静态 Few-shot 适合任务分布集中、边界情况有限的场景(比如固定格式的文档解析),System Prompt 固定,KV Cache 命中率高。动态 Few-shot 适合输入差异大、任务分布宽泛的场景:用 Embedding 模型将示例库向量化,每次请求时检索最相似的 K 个示例注入 Prompt。实现时有两个细节:检索到的示例把相关性最高的放在最靠近任务指令的位置;要设置相似度阈值,过低相似度的示例宁可不用,防止负迁移。
在 Agent 系统里,我还会用"示例池持续优化"机制:人工标注高质量示例入库,同时把线上效果好的对话(通过用户反馈或 LLM 自评筛选)也入库,让示例池随系统运行持续优化。
3️⃣ Key Differences
3、场景题:你的 Agent 需要处理用户提交的各类合同文档,不同类型合同的解析格式差异很大,用静态 Few-shot 效果很差,怎么改造?¶
难度级别:⭐⭐(动态 Few-shot 工程实现、相似度检索、负迁移防御)
1️⃣ Common Answer
可以换成动态 Few-shot,根据当前合同类型选择对应的示例。用 Embedding 检索和当前文档最相似的示例来用,效果应该会好很多。
2️⃣ Impressive Answer
这是动态 Few-shot 的标准应用场景,我会这样实现:
-
建立分类型的示例库。按合同类型(劳动合同、采购合同、租赁合同等)分类标注高质量解析示例,每类至少覆盖 3-5 种典型格式变体,包含关键的边界情况,比如字段缺失、格式异常的处理方式。
-
检索策略。用文档的前 500 字(包含合同标题和开头条款)作为查询,向量化后检索示例库,取 Top-3 最相似的示例。设置相似度阈值(比如 cosine similarity > 0.75),低于阈值的示例不用,宁可给模型说"无相关示例,请基于以下格式要求处理",防止错误类型的示例带来负迁移。
-
示例放置顺序。相关性最高的示例放在最靠近当前任务指令的位置,利用近因效应,不要按检索分数降序从上到下堆。
-
持续优化。线上把解析结果准确的对话标注入库,并定期做示例去重,避免库里同质化示例过多。
3️⃣ Key Differences
4、容易一起考的题¶
3.3 上下文工程¶
Context Engineering 与 Prompt Engineering 的本质区别¶
1、基础题:什么是 Context Engineering?¶
难度级别:⭐(CE 基本定义、与 PE 的基本区分)
Context Engineering 是指对 LLM 整个上下文窗口中所有信息进行动态编排和管理的工程能力,包括 System Prompt、对话历史、RAG 检索结果、工具调用返回、当前任务状态等。区别于 Prompt Engineering 只关注单次调用的指令设计,CE 关注的是"在有限的上下文窗口里,给模型看什么、不看什么、按什么顺序看"。
2、进阶题:Context Engineering 和 Prompt Engineering 的本质区别是什么?为什么说 CE 是 Agent 时代更重要的能力?¶
难度级别:⭐⭐⭐(工作单元差异、信息密度管理、动态编排、跨步推理一致性、瓶颈转移)
1️⃣ Common Answer
Prompt Engineering 是设计和优化输入给 LLM 的提示词。Context Engineering 是更高级的概念,不只是单个 Prompt,而是管理整个上下文窗口里的所有内容。在 Agent 系统里,上下文里有工具返回结果、对话历史、检索到的知识,怎么把这些组织好让模型更好地推理,这就是 CE 要解决的问题。
2️⃣ Impressive Answer
我会从工作单元的本质差异、CE 的核心问题、以及为什么 Agent 时代瓶颈在 CE 三个角度来回答。
-
工作单元和优化目标的本质差异。PE 的工作单元是单次 LLM 调用,优化目标是这次调用的输出质量,核心问题是"怎么措辞、怎么提供示例、怎么引导推理链"。CE 的视角完全不同——它把整个上下文窗口看作一个有限的、需要精心编排的资源,工作单元是整个 Agent 的运行生命周期。
-
CE 要解决的三个核心问题。
- 信息密度管理:上下文窗口有限,堆砌所有信息不是最优策略,CE 要在有限空间里最大化信息价值密度——把最相关的内容放在注意力最集中的位置,对冗余历史做摘要压缩,对低相关检索结果做截断。
- 动态编排:Agent 执行过程中每一步之后上下文的组成都在变化,新工具结果进来了,旧历史要不要保留,CE 需要一套动态决策机制。
-
跨步推理一致性:前一步的结论会成为后一步的上下文,CE 要保证关键信息在整个推理链条中不丢失,同时又不让上下文随步骤增加无限膨胀。
-
瓶颈转移的本质。当 LLM 的智能已经足够强的时候,瓶颈往往不在于"怎么说",而在于"给模型看什么"。一个上下文编排做得好的 Agent,在相同模型能力下可以比编排混乱的 Agent 有显著更好的表现。这就是为什么 CE 是 Agent 时代更核心的能力。
3️⃣ Key Differences
3、场景题:你的 Agent 在执行 10 步任务时,到第 8 步时上下文窗口快满了,但早期步骤的工具调用结果还要用,怎么设计上下文管理策略?¶
难度级别:⭐⭐⭐(上下文窗口管理、关键信息保留、历史压缩策略、跨步推理一致性)
1️⃣ Common Answer
上下文窗口快满了就要压缩,可以对早期的对话历史做摘要,把长内容变短,这样就能腾出空间给新内容。
2️⃣ Impressive Answer
这是 CE 的核心场景,我会用分层管理策略来解决:
-
关键信息固化,不参与压缩。在 Agent 开始执行时,把任务目标、核心约束、关键中间结论单独维护在一个"状态摘要"结构里,每步执行后更新这个结构,而不是依赖原始的工具返回文本。这部分内容始终保留,不压缩。
-
工具调用历史分级处理。对早期步骤的工具返回,区分"结论已被后续步骤引用"和"原始数据还需要直接查阅"两类。前者可以压缩为一句结论("第 3 步确认了用户权限为 admin"),后者如果后续还要用,保留完整内容但做精简格式化。
-
动态注入而不是堆积。当某一步需要用到早期数据时,从状态摘要里提取相关部分,临时注入到当前步骤的上下文末尾,而不是让所有历史数据一直留在窗口里。这样每一步的上下文都是"当前步骤最需要的信息"的精选。
-
在第 6-7 步时提前触发压缩,不要等到快满了才处理,留出余量给最后几步可能更长的工具返回。
3️⃣ Key Differences
4、容易一起考的题¶
Lost in the Middle 问题与上下文内容排列策略¶
1、基础题:什么是 "Lost in the Middle" 现象?¶
难度级别:⭐(LLM 注意力分布、长上下文处理)
LLM 在处理长上下文时,对开头和结尾的信息利用率显著高于中间部分。即使关键信息完整地出现在上下文窗口内,只要它位于中间位置,模型的准确率就会明显下降。这一现象来自 Stanford 2023 年的同名论文,是 Transformer 自回归训练带来的结构性偏置,并非 bug。
2、进阶题:在 RAG 系统和 Agent 工程实践中,如何通过内容排列策略缓解 Lost in the Middle 问题?¶
难度级别:⭐⭐⭐(Reranker 流程、首尾夹击排列法、动态上下文压缩、Agent 多步推理中的近因效应利用)
1️⃣ Common Answer
Lost in the Middle 是说 LLM 对中间内容关注少,所以把重要内容放开头或结尾就行了。RAG 里把检索出来的文档按相关性排序,最相关的放前面。Agent 里的话也差不多,重要信息别放中间就好了。效果不好就多检索几个文档试试。
2️⃣ Impressive Answer
我会从三个层面来回答这个问题:
-
首先,理解现象的根因。Lost in the Middle 本质上是 Transformer 注意力分布不均匀的体现——头尾 token 在自回归训练中接收了更多梯度信号,形成类似"首因效应"和"近因效应"的结构性偏置。这不是偶发问题,是训练方式决定的,需要在工程层面主动应对。
-
其次,RAG 场景用"首尾夹击"排列法。在 Reranker 精排之后,不是直接按分数顺序拼接,而是:相关性最高的文档放首位,次高的放末尾,中间的夹在里面。这样即使模型对中间注意力不足,最关键的信息在两个高注意力位置都有覆盖。如果检索结果太多,还可以用 LLMLingua 等工具对每个片段做压缩摘要,从根本上缩短上下文长度,降低丢失风险。
-
最后,Agent 多步推理中主动利用近因效应。Agent 上下文随步骤增加,早期的关键任务目标会被推到中间而被模型忽略。我的做法是在每一步推理前,把当前任务目标和关键约束重新放置到上下文末尾——紧邻模型即将生成的位置,持续利用近因效应保证核心指令始终在高注意力区域。效果可以用专项测试集(故意把关键信息放中间)来量化验证,把排列策略当工程参数来优化。
3️⃣ Key Differences
3、场景题:Agent 在多步推理中,随着对话轮次增加,早期的任务目标被"推到中间"导致模型偏离,怎么处理?¶
难度级别:⭐⭐(Agent 上下文管理、近因效应、Prompt 重置策略)
1️⃣ Common Answer
可以把任务目标写在 System Prompt 里,这样每次都能看到。或者对话太长了就截断一下,删掉一些早期的对话记录,保留最新的内容。
2️⃣ Impressive Answer
问题的本质是 Lost in the Middle——System Prompt 里的任务目标在对话历史不断增长后,在整体上下文中的位置被"推向中间",注意力权重下降。
核心应对策略是动态近因锚定:在每一步工具调用或推理之前,把当前任务目标和关键约束以 User 消息的形式追加到上下文末尾(而不是只依赖开头的 System Prompt)。这样任务目标始终在近因效应覆盖的区域。
同时配合上下文滑动窗口:保留完整的 System Prompt + 最近 N 轮对话 + 当前步骤的目标重申,中间过于久远的工具调用结果可以压缩成摘要。这两个策略结合,既保证任务不漂移,又控制了 Token 消耗。
3️⃣ Key Differences
4、容易一起考的题¶
KV Cache 友好的 Prompt 设计策略¶
1、基础题:什么是 KV Cache 前缀缓存?¶
难度级别:⭐(Transformer 推理优化、KV Cache 原理)
KV Cache 是将 Transformer Self-Attention 计算产生的 Key 和 Value 矩阵缓存起来,避免重复计算。前缀缓存是其进一步延伸:如果多次请求的输入前缀完全相同,就可以直接复用已缓存的 KV 矩阵,跳过这部分计算,显著降低首 token 延迟和推理成本。
2、进阶题:如何在 Prompt 设计中充分利用 KV Cache 前缀缓存机制来降低延迟和成本?¶
难度级别:⭐⭐⭐(KV Cache block 级匹配原理、Prompt 五层结构布局、反模式识别、平台缓存定价)
1️⃣ Common Answer
KV Cache 就是缓存计算结果,前缀一样就能复用。所以 System Prompt 要保持不变,放在最前面,这样就能命中缓存。动态的用户输入放后面。Anthropic 和 OpenAI 对命中缓存的 token 有价格优惠,能省钱。
2️⃣ Impressive Answer
我会从原理、结构设计和反模式三个角度来回答:
-
首先,理解缓存命中的严格条件。KV Cache 前缀缓存以 block(通常 128 或 256 个 token)为单位匹配,要求前缀逐字节完全相同才能命中。哪怕只改动一个字符、一个空格,从那个位置开始的所有 block 都会 cache miss。这解释了为什么在 System Prompt 里插入动态时间戳或用户 ID 会彻底破坏缓存——即使插值位置在末尾,后续所有 block 都失效。
-
其次,按"稳定在前、动态在后"原则设计五层 Prompt 结构:
[完全固定的 System Prompt] → [固定 Few-shot 示例] → [固定长文档/知识库] → [随对话增长的历史] → [当前用户输入]。越靠前越稳定,缓存命中率越高;越靠后允许越动态。在高并发场景下,前两层几乎可以持续命中缓存,收益最显著。平台侧,Anthropic 对命中缓存的 input token 收费是原价 10%,OpenAI 是 50% 折扣;Anthropic 还支持手动设置cache_control: {"type": "ephemeral"}精确控制缓存边界,对 RAG 固定文档场景很有用。 -
最后,识别并避免常见的破坏缓存反模式:在 System Prompt 里插入动态时间戳或请求 ID;每次请求随机 shuffle 工具列表顺序;把用户姓名、权限注入 System Prompt 开头或中间;使用模板引擎每次生成略有差异的格式。解决方法统一:把所有动态信息移到 User 消息部分。
3️⃣ Key Differences
3、场景题:团队的 Agent 系统在高并发下推理成本居高不下,排查发现 KV Cache 命中率很低,可能的原因有哪些,怎么排查和修复?¶
难度级别:⭐⭐(KV Cache 命中率排查、Prompt 结构问题定位)
1️⃣ Common Answer
可能是 System Prompt 每次都不一样,或者并发请求太多缓存被挤掉了。可以检查一下 System Prompt 是不是固定的,把动态内容移到后面去。
2️⃣ Impressive Answer
排查思路分两步:先定位是"前缀不匹配"还是"缓存容量不足"。
前缀不匹配是最常见原因。具体排查:打印每次请求的 System Prompt,diff 相邻两次是否完全一致(包括空格、换行)。常见元凶:模板引擎每次渲染出略有差异的格式、注入了动态时间戳或请求 ID、工具列表顺序随机化。修复方法:把所有动态字段移到 User 消息,System Prompt 做成纯静态字符串常量,用单元测试断言它在任意参数下渲染结果不变。
缓存容量不足(服务端 LRU 驱逐)一般出现在前缀长度差异很大的场景。可以通过平台的 cache hit token 指标来确认——如果命中率随并发增加而下降,大概率是容量问题,需要联系平台或自建推理服务时调大 KV Cache 分配比例。
3️⃣ Key Differences
4、容易一起考的题¶
3.4 结构化输出与质量保障¶
LLM 结构化输出的演进:JSON Mode vs Function Calling vs Structured Output¶
1、基础题:LLM 结构化输出的 JSON Mode、Function Calling、Structured Output 三种方式分别是什么?¶
难度级别:⭐(LLM 输出格式控制、OpenAI API 基础)
JSON Mode 通过设置 response_format: json_object 保证输出是合法 JSON,但不保证字段符合预期 Schema。Function Calling 通过定义工具的 JSON Schema 引导模型输出特定结构,可靠性更高。Structured Output(Parse API)是最新方案,通过 RLHF 专项训练让模型严格遵循 Pydantic Schema,可靠性最高,三种方案代表了结构化输出能力的三代演进。
2、进阶题:三种结构化输出方案的底层机制和可靠性边界分别是什么?校验失败时应如何设计重试策略?¶
难度级别:⭐⭐(约束解码、RLHF、ValidationError 处理、分层重试策略)
1️⃣ Common Answer
JSON Mode 让模型输出 JSON,Function Calling 通过定义函数让模型填参数,Structured Output 最准确。可靠性是依次递增的。如果输出校验失败,可以重试几次,或者在 Prompt 里加更严格的格式要求,让模型重新生成。
2️⃣ Impressive Answer
我会从三代方案的机制差异和重试策略设计两个角度来回答:
-
首先,三代方案的核心机制和边界。JSON Mode 用约束解码(constrained decoding)保证 token 序列语法合法,但字段名拼错、字段缺失、类型不匹配它一概不管,适合结构简单的场景。Function Calling 把 Schema 放进工具定义,让模型理解字段含义,可靠性显著提升,但嵌套深、枚举多时偶尔还是会漏字段;另外需要显式指定
tool_choice,否则模型可能输出普通文本而不触发工具调用。Structured Output(client.beta.chat.completions.parse())通过 RLHF 专项训练,只要 Schema 合法,几乎可以保证字段完整性和类型正确性,是生产环境的首选。 -
其次,校验失败的分层重试策略。简单粗暴地盲重试效果有限,更有效的方式是"带错误信息回填的重试"——把
ValidationError的具体内容追加到对话里,让模型知道哪里错了再修正。完整策略是:捕获异常 → 把错误原因回填 User 消息 → 指数退避重试(防止打爆限流) → 超过最大次数后降级返回空对象并记录日志。生产上推荐用 Instructor 库,它封装了这套逻辑并支持 Partial 模式(流式边生成边校验),比手写省心很多。
3️⃣ Key Differences
3、场景题:生产环境中 Function Calling 偶尔出现模型输出普通文本而不触发工具调用,怎么处理?¶
难度级别:⭐⭐(Function Calling 配置细节、tool_choice 参数、降级策略)
1️⃣ Common Answer
可能是模型没理解要调用工具,可以在 Prompt 里强调"请使用工具回答",或者把工具描述写得更清楚一些,多试几次应该就好了。
2️⃣ Impressive Answer
这个问题的根本原因是 tool_choice 参数没有显式设置,默认值 auto 允许模型自行决定是否调用工具。当模型"觉得"直接用文本回答更合适时,就会跳过工具调用。
修复方式分两层:
强制调用:将 tool_choice 设为 {"type": "function", "function": {"name": "your_tool"}} 或 required(强制必须调用某个工具),适合工具调用是必须路径的场景。
防御性兜底:即使设置了 required,也要在代码层面检查 response.choices[0].message.tool_calls 是否为空,为空时触发告警并降级处理,而不是让调用方拿到空结果却不知道原因。生产上如果用 Structured Output 替代 Function Calling,可以从根本上绕开这个问题。
3️⃣ Key Differences
4、容易一起考的题¶
Prompt 版本管理与 A/B 测试框架设计¶
1、基础题:为什么 Prompt 需要版本管理?¶
难度级别:⭐(工程化思维、Prompt 生命周期管理)
Prompt 是影响 LLM 输出质量的核心资产,频繁迭代但缺乏追踪会导致"效果莫名变差却不知道是哪次改动引起的"。版本管理让每次 Prompt 变更有据可查、可回滚,结合自动化评估还能在变更前验证不会引起效果退化,是生产环境 Prompt 治理的基础设施。
2、进阶题:在生产环境中,如何对 Prompt 进行版本管理和 A/B 测试?请从工程实践角度设计一套完整的方案。¶
难度级别:⭐⭐⭐(Prompt-as-Code、LangSmith/Langfuse、哈希稳定分组、统计显著性、灰度发布)
1️⃣ Common Answer
可以把 Prompt 存在数据库里,每次修改记录一个版本号。A/B 测试就是把用户随机分两组,一组用旧 Prompt,一组用新 Prompt,然后比较用户满意度或任务完成率。可以用 LangSmith 的 Prompt Hub 来管理,它支持查看版本历史。
2️⃣ Impressive Answer
我会从版本管理、工具选型和 A/B 测试设计三个维度来系统说明:
-
首先,Prompt-as-Code——把 Prompt 纳入 Git。把 Prompt 存成独立的
.yaml或.jinja2文件,文件头带元数据(版本号、适用模型、修改人、变更说明),和业务代码一起走 Code Review 和 PR 流程。每次变更都有 commit message,回滚方便,CI 里可以跑自动化评估集确保没有退化。这是高风险核心 Prompt 的首选管理方式。 -
其次,在线动态场景用 LangSmith 或 Langfuse。如果运营同学需要频繁调整话术,Git 发布周期太重,可以把 Prompt 存在 LangSmith Prompt Hub 或 Langfuse 上,应用启动时 pull 指定版本,改 Prompt 不需要重新发布服务。两者的区别:LangSmith 与 LangChain 生态紧密,Langfuse 开源可自托管,数据合规要求高的场景选后者。
-
最后,A/B 测试的正确姿势是"先定指标再分流"。很多团队先跑测试再想看什么指标,结果数据采集不完整。正确顺序:先确定主指标(任务完成率、结构化成功率、用户显式反馈)→ 按
user_id哈希取模稳定分组(保证同一用户始终落在同一组,避免体验漂移)→ 每次 LLM 调用打上experiment_group标签便于聚合分析 → 跑够足够样本后用 t 检验或 Chi-square 验证差异显著性,避免"感觉好像好一点"就仓促上线。高风险 Prompt 还可以走分阶段灰度:5% → 20% → 50% → 100%,每阶段观察一到两天,有问题立即回滚,和服务部署的金丝雀发布逻辑相同。
3️⃣ Key Differences
3、场景题:线上 Prompt 被某个同学直接在 LangSmith 上改了,导致效果变差,且没有人知道是谁改的、改了什么,怎么从工程上避免这类问题?¶
难度级别:⭐⭐(Prompt 治理、权限控制、变更审计)
1️⃣ Common Answer
可以限制一下 LangSmith 的编辑权限,只让特定人员可以修改。或者改完之后发消息通知一下团队,让大家知道有变更。
2️⃣ Impressive Answer
这个问题的根本是 Prompt 变更缺乏"审计 + 卡点"的工程约束,解决要从三层入手:
变更审计:无论用 LangSmith 还是 Langfuse,所有变更操作都应该有操作人、时间戳和 diff 记录。平台本身支持版本历史,但要在团队规范里明确"Prompt 变更必须填写变更说明",并接入告警(比如 Slack/钉钉通知 Prompt 有新版本发布)。
变更卡点:高风险 Prompt 的变更不应该直接上线,而是先发布到 Staging 环境,跑自动化评估集(预先标注好的测试用例)通过后再推生产。评估集是关键——没有评估集,任何卡点都是形式主义。
权限分层:普通成员只有读权限,变更需要 Review 后由 Prompt Owner 发布,类比代码的 PR 合并流程。对于允许运营直接改的场景,限定可编辑范围(比如只能改话术措辞,不能改结构和变量),用模板约束降低风险。
3️⃣ Key Differences
4、容易一起考的题¶
3.5 长文档处理与成本优化¶
长文档处理策略:分块 vs Map-Reduce vs 递归摘要¶
1、基础题:LLM 的 Context Window 是什么?超出限制时会发生什么?¶
难度级别:⭐(Context Window 定义、超出后的截断或报错行为)
LLM 的 Context Window 是模型单次处理的最大 token 数限制,超出后 API 会报错或截断输入。不同模型限制不同,比如 GPT-4o 支持 128K token,Claude 3 支持 200K token。处理长文档时必须先把文档切分或压缩到 Context Window 以内,才能正常调用。
2、进阶题:处理超出 LLM Context Window 的长文档时,有哪些主流策略?请对比直接分块、Map-Reduce 和递归摘要的适用场景与成本差异。¶
难度级别:⭐⭐(直接分块的跨块信息丢失、Map-Reduce 并行聚合、递归摘要树状压缩、O(N)/O(N log N) 调用次数分析)
1️⃣ Common Answer
当文档太长超过模型的 Context Window 时,可以用几种方法来处理。直接分块就是把文档切成固定长度的片段,分别处理。Map-Reduce 是先对每个分块生成摘要,再把所有摘要汇总成最终结果。递归摘要是对超长文档递归地做摘要,直到长度满足要求。三种方法的选择取决于文档长度和对精度的要求,分块最简单但可能丢失信息,Map-Reduce 效果更好,递归摘要适合非常长的文档。
2️⃣ Impressive Answer
我会从三个策略的核心权衡出发思考这个问题:
-
首先,直接分块(Chunking)是最简单的方案,但有核心缺陷。按固定 token 数切割后,跨块的完整论述会被截断,两边都失去上下文。加 overlap(块间重叠 50 token)只能缓解,不能根治。更大的问题是——如果任务需要整合全文信息(比如"全文的主要矛盾"),单块根本答不了。适用场景:各条款之间彼此独立的信息提取类任务,比如从合同里提取关键日期。
-
其次,Map-Reduce 分两阶段处理,能覆盖全文。Map 阶段对各 chunk 并行生成摘要,Reduce 阶段把所有摘要合并再调一次 LLM 输出最终结果。优点是可并行、信息损失可控;成本是 LLM 调用次数 = chunk 数 + 1,线性增长。适用场景:需要整合全文信息的任务,文档在 100K token 以内。
-
最后,递归摘要解决 Reduce 阶段自身超长的问题。Map-Reduce 在文档极长时,Reduce 收到几十个摘要本身又超长了。递归摘要用树状结构层层合并,调用次数约 O(N log N)。另一个变体是 Refine 模式:顺序处理每个 chunk,把"当前摘要 + 新 chunk"一起精炼,保留线性叙事结构,但严格串行,延迟高(O(N) 次调用)。适用场景:整本书、长篇报告的全文摘要。补充一点:如果任务是 QA,优先考虑 RAG——只检索相关 chunk,成本最低,效果往往更好。
3️⃣ Key Differences
3、场景题:有一份 300 页的法律合同需要做全文风险摘要,该选哪种策略?¶
难度级别:⭐⭐(策略选型决策、长文档全文理解任务的成本与质量权衡)
1️⃣ Common Answer
300 页的合同很长,可以用 Map-Reduce 来处理,先对每段生成摘要,再汇总。或者用递归摘要,把文档层层压缩。
2️⃣ Impressive Answer
300 页合同约 15-20 万 token,超出大多数模型单次 Context Window,且任务是全文风险摘要,需要跨章节整合信息,直接分块不适用。
选型思路:先用 Map-Reduce 对每个章节并行生成风险要点摘要(Map 阶段可并行,速度快)。如果章节数量很多导致 Reduce 阶段的摘要拼合又超长,则在 Reduce 之前再做一轮树状合并——即退化为递归摘要。两者可以组合使用。Refine 模式因为串行延迟太高,300 页文档不推荐。另外,合同各条款之间有强逻辑关联,Map 阶段的 Prompt 里需要明确要求模型关注"与其他条款的关联风险",不然 Reduce 阶段很难发现跨章节的矛盾。
3️⃣ Key Differences
4、容易一起考的题¶
LLM 调用的成本控制策略¶
1、基础题:LLM API 的计费方式是什么?¶
难度级别:⭐(Token 计费、input/output token 的区别)
LLM API 通常按 token 计费,分 input token(你发给模型的内容)和 output token(模型生成的内容)两部分,output token 通常更贵。比如 GPT-4o 的定价是 input $2.5/百万 token、output $10/百万 token。控制成本的核心就是减少不必要的 token 消耗。
2、进阶题:在生产环境中,LLM API 调用成本很高,有哪些有效的成本控制手段?¶
难度级别:⭐⭐(语义缓存命中率、大小模型路由、LLMLingua Prompt 压缩、Batch API 50% 折扣)
1️⃣ Common Answer
LLM 调用成本控制主要有以下几个方法:一是缓存,对相同的请求直接返回缓存结果,避免重复调用;二是使用更便宜的小模型,简单的问题不需要用 GPT-4 这样贵的模型;三是尽量精简 Prompt,减少 Token 数量;四是批量处理,把多个请求合并发送。
2️⃣ Impressive Answer
我会从四个独立的优化维度来回答这个问题:
-
首先是语义缓存(Semantic Cache),命中率最高、收益最直接。普通 exact match 缓存只能命中完全相同的请求,实际用处有限。语义缓存用 embedding 相似度检索历史请求,相似度超过阈值(比如 0.85)直接返回缓存,FAQ 类场景命中率可达 60-70%。工具上可以用 GPTCache 接入 LangChain。上线前要先统计请求分布,估算 ROI,命中率低于 20% 的场景可能不值得投入缓存基础设施。
-
其次是大小模型路由(Model Routing),降低单次调用成本。不是所有请求都需要 GPT-4o。用两层路由:先用关键词规则过滤(翻译、格式化等简单请求直接走 mini 模型),再用小模型做复杂度分类,复杂推理才走大模型。GPT-4o-mini 成本约为 GPT-4o 的 1/30,实践中 60-70% 的请求可以走小模型,整体成本能降 50% 以上。
-
再次是 Prompt 压缩(LLMLingua),专门针对 RAG 场景的冗余 Context。检索回来的 Context 往往包含大量无关内容。LLMLingua 用小语言模型给每个 token 打重要性分,按比例删掉不重要的 token,在信息损失可控的前提下把 Context 压缩 60-80%,大幅降低 input token 数。
-
最后是 Batch API,离线任务的折扣利器。对于非实时任务(数据标注、离线文档处理),OpenAI Batch API 提供 50% 折扣,代价是最长 24 小时内完成。这四种手段组合使用,优先级是:语义缓存(零成本命中)→ 模型路由(降单次成本)→ Prompt 压缩(降 token 数)→ Batch API(离线任务)。项目落地后整体 LLM 成本降了约 65%。
3️⃣ Key Differences
3、场景题:公司的 LLM 调用成本每月 $50,000,老板要求降低 50%,你会怎么做?¶
难度级别:⭐⭐(成本分析诊断、优化优先级决策、量化效果预估)
1️⃣ Common Answer
可以用便宜的小模型替换部分调用,同时加缓存,这样应该能降低成本。
2️⃣ Impressive Answer
降本 50% 是具体目标,我会先诊断再优化,而不是盲目上手段。
第一步,诊断成本结构:按模型、功能模块、用户维度拆分成本,找到前 20% 的高成本来源,通常 80% 的成本集中在少数几个场景。第二步,按 ROI 排优先级:语义缓存实施成本最低,先看 FAQ 类场景的请求重复率,如果超过 30% 就先上缓存(FAQ 场景命中率 60-70%,可能直接省掉 \(15,000-20,000)。然后看请求复杂度分布,如果 60% 的请求是简单任务(翻译、格式化),上模型路由能再省 50% 的这部分成本。如果有 RAG 场景,上 LLMLingua 压缩 Context 可以削减 input token 的 60-80%。离线任务批量化用 Batch API 再拿 50% 折扣。组合下来,\)50,000 降到 $20,000-25,000 是合理预期,但前提是先量化现状,不能凭感觉排优先级。
3️⃣ Key Differences
4、容易一起考的题¶
3.6 模型选型与工程基础设施¶
LLM 网关(LiteLLM)的选型与统一调用接口设计¶
1、基础题:为什么在生产环境中不建议直接调用单一 LLM 提供商的 API?¶
难度级别:⭐(单点依赖风险、限流问题、多模型组合需求)
直接依赖单一提供商存在三个问题:一是单点风险,API 故障或涨价没有备选;二是限流问题,高并发时单个 API Key 的 RPM/TPM 上限会成为瓶颈;三是不同任务适合不同模型,比如代码生成用 Claude、中文理解用 Qwen,单一提供商无法满足组合需求。因此生产环境通常需要一个统一的 LLM 网关层来屏蔽这些差异。
2、进阶题:在需要接入多家 LLM 提供商的场景下,如何设计统一的调用接口?LiteLLM 是怎么解决这个问题的?¶
难度级别:⭐⭐(LiteLLM 统一 OpenAI 格式、多 Key 轮询防限流、模型降级策略、成本追踪回调)
1️⃣ Common Answer
在接入多家 LLM 时,可以封装一个统一的调用类,内部根据配置选择不同的 API。LiteLLM 是一个开源工具,能用 OpenAI 的接口格式调用 Anthropic、Google 等不同的模型,这样切换模型时不需要改太多代码。LiteLLM 还支持负载均衡,可以配置多个 API Key,在它们之间分发请求。如果一个模型出问题了,可以自动切换到备用模型。
2️⃣ Impressive Answer
我会从 LiteLLM 解决的四个核心问题来回答:
-
首先是统一接口,屏蔽各家 API 格式差异。LiteLLM 把 100+ 模型统一封装成 OpenAI Chat Completion 格式,切换模型只改 model 字符串,业务代码完全不动。Claude、Gemini、本地 Ollama 都能用同一套调用方式。
-
其次是多 Key 轮询防限流。单个 API Key 有 RPM 和 TPM 上限,多 Key 轮询是最简单的绕过手段。LiteLLM Router 支持
round-robin、least-busy、latency-based三种策略,生产上推荐least-busy,把请求打到当前负载最低的 Key。 -
再次是按优先级的模型降级(Fallback)。主模型限流或不可用时,自动按顺序切换到备用模型。LiteLLM 还支持
context_window_fallbacks——当请求超过主模型的 Context Window 时,自动切换到支持更长 Context 的备用模型,非常实用。 -
最后是成本追踪。LiteLLM 内置每次调用的 token 消耗和预估成本,通过注册
success_callback可以把成本数据写入自己的数据库或监控系统,按用户、功能模块做成本分摊。如果用 LiteLLM Proxy 独立部署,还能在 Web UI 上查看成本报表。
3️⃣ Key Differences
3、场景题:公司同时使用 GPT-4o 和 Claude,某天 OpenAI 服务出现大规模故障,如何保证业务不中断?¶
难度级别:⭐⭐(故障降级策略、LiteLLM Fallback 配置、业务影响最小化)
1️⃣ Common Answer
可以在代码里加 try-except,OpenAI 报错时换调 Claude 的 API。
2️⃣ Impressive Answer
如果是临时写 try-except 来切换,每个调用点都要改,而且切换逻辑散落在业务代码里,维护成本很高。正确的做法是在 LLM 网关层统一处理降级,业务代码完全感知不到切换。
用 LiteLLM Router 配置 fallbacks,把 OpenAI 的所有调用设置 Claude 为备用。故障发生时,Router 自动在重试超时后切换到 Claude,业务代码的调用接口不变。需要注意的是:Claude 和 GPT-4o 的某些参数行为不一致(比如 system message 的处理方式),Fallback 之前要确认 Prompt 兼容性。另外,降级后要立刻触发告警,通知团队当前在走备用路径,成本可能上升(Claude 和 GPT-4o 定价不同),避免事后账单超支。
# LiteLLM Router:网关层统一降级,业务代码零改动
from litellm import Router
router = Router(
model_list=[
{"model_name": "gpt-4o", "litellm_params": {"model": "openai/gpt-4o"}},
{"model_name": "gpt-4o", "litellm_params": {"model": "anthropic/claude-opus-4-6"}},
],
fallbacks=[{"gpt-4o": ["claude-opus-4-6"]}],
num_retries=2,
)
# 业务代码接口不变,故障自动切换
response = await router.acompletion(model="gpt-4o", messages=[...])
3️⃣ Key Differences
4、容易一起考的题¶
Embedding 模型选型:OpenAI vs BGE vs 本地模型的权衡¶
1、基础题:Embedding 模型在 RAG 系统中的作用是什么?¶
难度级别:⭐(向量化文本、语义检索的基础原理)
Embedding 模型把文本转换成固定维度的向量,语义相近的文本在向量空间中距离更近。在 RAG 系统中,文档在入库时被 Embedding 模型向量化存入向量数据库,用户查询时同样被向量化,然后通过相似度搜索(如余弦相似度)找到最相关的文档块。Embedding 模型的质量直接决定了检索的准确率,是 RAG 系统的核心基础设施。
2、进阶题:在 RAG 系统中,Embedding 模型的选型需要考虑哪些因素?请对比 OpenAI Embedding、BGE 和本地部署模型的核心差异。¶
难度级别:⭐⭐(向量维度与存储成本、BGE-M3 多语言多粒度优势、API 成本 vs 本地 GPU 成本的规模临界点、批量 Embedding 优化)
1️⃣ Common Answer
Embedding 模型选型主要看几个方面:检索质量、成本和部署难度。OpenAI 的 text-embedding-3-small 使用方便,直接调 API 就行;BGE 是国内智源研究院开源的,中文效果好;本地部署可以用 sentence-transformers 跑一些开源模型。如果数据合规有要求,不能调外部 API,就需要本地部署;如果要快速上线,OpenAI API 最省事;如果主要处理中文内容,BGE 效果更好。
2️⃣ Impressive Answer
我会从四个维度系统来回答选型决策:
-
首先是向量维度与存储成本的平衡。维度不是越高越好。text-embedding-3-small 是 1536 维,百万向量约占 6GB;BGE-M3 是 1024 维,约 4GB。OpenAI 的 text-embedding-3 系列支持 Matryoshka 缩维,通过
dimensions参数把 1536 维截断到 256 维,存储成本降 6 倍,检索延迟也更快,在精度损失可接受的前提下很实用。 -
其次是中文场景 BGE-M3 的核心优势。BGE-M3 在中文任务上明显优于 OpenAI 通用 Embedding,有三点关键能力:支持 100+ 语言(中英混合文档不用分语言处理);单模型同时支持 dense、sparse、ColBERT 三种检索模式;最长支持 8192 token 输入,对长文档有优势。
-
再次是 API 成本 vs 本地部署的规模临界点。OpenAI text-embedding-3-small 定价 $0.02/百万 token,适合小规模快速上线。本地一张 A10G 跑 BGE-M3,一次性处理大规模文档库后增量成本极低。经验规则:文档总量 < 5000 万 token 且无合规要求,用 API 最省事;超过这个量级或有数据出境限制,本地部署 ROI 更高。
-
最后是批量 Embedding 优化,无论哪种方案都要做。OpenAI API 单次最多 2048 个文本,本地 BGE-M3 在 24GB 显卡上 batch_size=32-64 比较稳。分批调用避免单次超限,同时最大化吞吐量。
3️⃣ Key Differences
3、场景题:公司要做一个中文法律文档的 RAG 系统,文档总量约 2 亿 token,且有数据不出境的合规要求,Embedding 模型怎么选?¶
难度级别:⭐⭐(合规要求、中文优化、大规模文档的本地部署成本核算)
1️⃣ Common Answer
有数据合规要求就不能用 OpenAI API,只能本地部署,可以用 BGE 模型。
2️⃣ Impressive Answer
这道题有两个硬约束:数据不出境(排除所有云端 API),中文法律文档(需要强中文理解能力)。
模型选 BGE-M3:支持长文本(法律条款动辄上千字),中文效果领先,且支持 dense+sparse 混合检索,对法律文档的精确术语检索有帮助。
部署选本地推理服务(如 Xinference 或 FastEmbed):2 亿 token 的初始建库,用 OpenAI API 需要 $4,但之后每次增量更新都要持续付费,而且合规上直接不可行;一张 A10G 建库完成后,增量成本仅是电费和硬件折旧,ROI 明显更高。工程上需要注意:批量建库时 batch_size 控制在 32-64,显存不够时用 fp16 推理;向量库选 Milvus 或 Qdrant(支持本地部署),不要用 Pinecone 云服务(同样有数据出境问题)。
3️⃣ Key Differences
LLM 可观测性:LangSmith 与 Langfuse 的接入与对比¶
1、基础题:为什么 LLM 应用的可观测性比传统服务更难?¶
难度级别:⭐(LLM 输出不确定性、Prompt 变化的影响、调试手段的差异)
传统服务出问题看日志和指标就够了,但 LLM 应用出问题时,你需要知道的是"模型在第几步拿到了什么 Prompt、输出了什么、为什么跑偏了"。LLM 的输出具有不确定性,同一个 Prompt 不同时间可能输出不同结果,而且 Prompt 的微小改动可能引发大幅性能退化,这些都是传统监控指标无法捕捉的。因此 LLM 应用需要更细粒度的 Trace 体系,能还原完整的调用链路和每步的输入输出。
2、进阶题:如何为 LLM 应用建立可观测性体系?请说明 Trace/Run/Span 的概念,以及 LangSmith 和 Langfuse 的核心差异。¶
难度级别:⭐⭐(Trace/Run/Span 树形结构、LangSmith 零代码接入、Langfuse 自托管、Token 成本统计与业务标签)
1️⃣ Common Answer
LLM 可观测性就是监控 LLM 应用的运行情况,包括每次调用的输入输出、耗时、token 消耗等。LangSmith 是 LangChain 官方的可观测性平台,接入方便,只需要设置几个环境变量就能自动追踪所有 LangChain 调用。Langfuse 是开源的替代方案,可以自托管,数据不需要上传到第三方。Trace 是一次完整的请求记录,Run 是其中某次 LLM 调用,可以通过这些记录来排查问题和分析成本。
2️⃣ Impressive Answer
我会从概念体系、工具接入和选型决策三个层面来回答:
-
首先是 Trace/Span/Run 的树形结构。一次用户请求对应一个 Trace(有唯一 ID),Trace 下是树状嵌套的 Span——AgentExecutor 是顶层 Span,下面挂着多个 LLM Call(Run)和 Tool Call(Span),Tool Call 下面还可以有 HTTP Request Span。这个树形结构还原了 Agent 的实际执行路径,定位问题时能一眼看到哪一步的输入输出出了问题。
-
其次是两个工具的接入方式。LangSmith 对 LangChain/LangGraph 项目是真正的零代码接入,设三个环境变量(
LANGCHAIN_TRACING_V2、LANGCHAIN_API_KEY、LANGCHAIN_PROJECT),所有 Chain 和 Agent 调用自动被 Trace,不需要改业务代码。Langfuse 需要手动用@observe()装饰器标记函数,灵活度更高,非 LangChain 项目也能用,自托管时数据完全不出公司。 -
最后是成本统计和选型建议。两个平台都支持按时间、模型、用户、功能模块维度做 token 和成本聚合。实践中我会给每个 Trace 打业务标签(feature、user_id、experiment_group),这样能精确知道每个功能模块的 LLM 成本,方便做 ROI 分析。选型上:技术栈是 LangChain/LangGraph 且无数据合规要求,直接用 LangSmith;非 LangChain 项目或有合规要求,选 Langfuse 自托管。两者都要做的是在 CI 里跑冒烟测试时也开启 Trace,这样 Prompt 改动引发的性能退化能在上线前就发现。
3️⃣ Key Differences
3、场景题:Agent 在生产环境中偶发性地给出错误答案,但复现不稳定,如何用可观测性工具定位问题?¶
难度级别:⭐⭐(利用 Trace 定位 Agent 执行路径异常、Prompt 版本对比、错误模式归因)
1️⃣ Common Answer
可以在代码里加日志,把每次 LLM 的输入输出打印出来,出错的时候查日志找原因。
2️⃣ Impressive Answer
偶发性错误最难排查,关键是建立"错误留痕"机制,而不是靠运气复现。
首先,确保每次请求都有完整的 Trace,包含 Agent 每步的完整 Prompt 和输出——这是 LangSmith/Langfuse 的核心价值。其次,给每个 Trace 打上用户 ID、请求类型、输入特征等标签,出现错误时在平台上按这些维度过滤,找出错误 Trace 的共同特征(比如是否集中在特定类型的输入、特定时间段、特定工具调用路径上)。
然后,对比错误 Trace 和正确 Trace 的执行路径差异:是哪个 LLM Call 的输出跑偏了?是工具返回的数据格式异常导致后续步骤出错?还是某个 Span 的延迟异常高导致超时?LangSmith 的 Playground 功能还支持直接拿错误 Trace 的 Prompt 做 Replay 测试,能快速验证修复效果。
最后,如果是 Prompt 改动引发的,通过 Trace 里的 Prompt 版本信息能精确定位到哪次发布引入了问题。
3️⃣ Key Differences