跳转至

LLM 应用开发

1 LLM 参数与基础原理

Temperature 与 Top-P 参数

🔹 1. Temperature 和 Top-P 分别控制什么?

两者都是在模型输出 logits(未归一化概率)之后,对采样过程施加约束的参数。简单说:

  • Temperature 控制“分布的陡峭程度”——影响所有候选 token 的相对概率。

  • Top-P 控制“候选集的大小”——只从累积概率超过 P 的最可能词中挑。

image.png

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)→ 候选集大,更自由。

一图对比它们的作用域:

image.png

在实际调用中,这两个参数常联合使用:先用温度调“口味”,再用 Top-P 切掉“怪味尾巴”。


📐 2. Temperature 和 Top-P 的数学本质是什么?在 Agent 工程实践中如何按任务类型选参?

数学本质

给定最后一层 logits 向量 zz,标准 softmax 为:

image.png

引入温度 TT 后变为:

image.png

这相当于在指数函数里对 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 使用一套固定配置。

架构设计:

image.png

在代码实现里,我们在 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 场景特殊性、缓解策略三个层次来回答。

  1. 幻觉的两类成因。内在幻觉(Intrinsic Hallucination)根源在模型本身:训练语料里有错误或过时信息;RLHF 的奖励模型偏向"流畅自信"而非"准确",模型学会了用自信语气说错误的话;以及参数记忆的压缩损失导致事实失真。外在幻觉(Extrinsic Hallucination)是模型无法正确利用上下文中已有的信息——提供了参考文档但回答与文档矛盾,或者指令太复杂导致遵循失败,本质是上下文理解和指令遵循的问题。

  2. Agent 场景的特殊性。在多步 Agent 里,幻觉的危害被放大了——一次幻觉会导致后续多步工具调用都基于错误前提,形成"幻觉雪球"。这是单轮问答里不存在的风险。

  3. 缓解策略。我按层次来做:RAG 提供事实锚点,针对外在幻觉效果最直接,Prompt 里明确要求"只基于提供的文档回答";Self-Consistency 多路径投票,用较高 Temperature 采样 5-10 次再做多数投票,TruthfulQA 的实验证明在事实性问题上有可观提升;工具调用后加 Grounding 验证,让模型检查"我的结论和工具结果是否一致",不一致就触发重规划;置信度提示,要求模型给出置信度等级并明确"不确定比猜测更有价值"。

3️⃣ Key Differences

查看内嵌表格


3、场景题:你的 Agent 在做多步推理时,第二步工具调用返回了正确结果,但第三步的回答却和工具结果矛盾,你怎么排查和处理?

难度级别:⭐⭐(外在幻觉定位、Grounding 验证、Agent 重规划机制)

1️⃣ Common Answer

这应该是幻觉问题,模型没有正确使用工具返回的结果。可以降低 Temperature,或者在 Prompt 里强调要用工具结果回答。

2️⃣ Impressive Answer

这是典型的外在幻觉——工具结果在上下文里,但模型没有正确利用它。我会这样处理:

  1. 先排查根因。检查工具返回结果在上下文中的位置,如果被放在了很前面而当前步骤的指令在后面,可能是 Lost in the Middle 的问题——关键数据被"淹没"在中间位置。同时检查工具返回格式是否解析正常,排除结构化数据解析错误的可能。

  2. 短期修复。在第三步的 Prompt 里,把工具返回结果显式重申一遍,紧放在"现在请基于以上信息"这句话之前,利用近因效应强制模型关注这个数据。

  3. 系统性解决。加入 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

我会从功能设计和性能设计两个维度来展开。

  1. 角色与能力边界要精确,不要模糊。不要只写"你是一个助手",要给出具体身份、具体能力范围和明确的知识边界。边界清晰后模型的行为就更可预测,"自由发挥"的空间更小。

  2. 格式约束用"描述 + 示例"组合,不要只描述。对 Agent 系统来说,工具调用参数解析依赖输出格式的稳定性。只说"用 JSON 输出"不够,要直接给出 schema 加一个正确示例,描述加示例的组合比单纯描述可靠得多。

  3. 边界条件和拒绝策略不能省。明确告诉模型什么时候应该拒绝、拒绝时怎么回答。很多人忽略这一点,结果是模型在超出能力范围时硬撑着给错误答案。加一条"超出能力范围时直接说明,不要猜测或编造"能显著减少这类幻觉。

  4. 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

方向是对的,但具体改造要考虑几个细节:

  1. System Prompt 必须完全固定,包括标点和空格。前缀缓存命中的条件是逐字节完全相同,只改一个字符,从那个位置开始的所有 block 都会 cache miss。所以不只是移出姓名和权限,System Prompt 里任何动态插值都要清除。

  2. 动态信息的放置位置。用户姓名、权限、当前日期这些信息,统一移到对话的第一条 User 消息里,格式固定为"当前用户:{name},权限级别:{role}",让 System Prompt 的 3000 token 在整个会话生命周期内完全不变。

  3. 如果有固定的 Few-shot 示例,也可以跟在 System Prompt 后面一起缓存,按"System Prompt → 固定示例 → 对话历史 → 当前用户输入"的顺序排列,稳定内容在前,动态内容在后。

  4. 效果评估。改造后监控 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 动态选型三个角度来回答。

  1. 质量和多样性比数量更重要。10 个重复覆盖同一种模式的示例,不如 4 个覆盖不同边界情况的示例。好的示例集要覆盖典型场景、关键边界情况,输出格式完全规范,避免示例之间高度相似——相似示例浪费 Token 但不增加信息量。

  2. 位置比数量更容易被忽视。示例放得越靠近实际任务指令,效果越好,这和 LLM 对近端上下文注意力权重更高有关。如果 System Prompt 很长,我会把示例放在 User 消息里紧邻问题的位置,而不是全部堆在 System Prompt 开头。

  3. 静态 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 的标准应用场景,我会这样实现:

  1. 建立分类型的示例库。按合同类型(劳动合同、采购合同、租赁合同等)分类标注高质量解析示例,每类至少覆盖 3-5 种典型格式变体,包含关键的边界情况,比如字段缺失、格式异常的处理方式。

  2. 检索策略。用文档的前 500 字(包含合同标题和开头条款)作为查询,向量化后检索示例库,取 Top-3 最相似的示例。设置相似度阈值(比如 cosine similarity > 0.75),低于阈值的示例不用,宁可给模型说"无相关示例,请基于以下格式要求处理",防止错误类型的示例带来负迁移。

  3. 示例放置顺序。相关性最高的示例放在最靠近当前任务指令的位置,利用近因效应,不要按检索分数降序从上到下堆。

  4. 持续优化。线上把解析结果准确的对话标注入库,并定期做示例去重,避免库里同质化示例过多。

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 三个角度来回答。

  1. 工作单元和优化目标的本质差异。PE 的工作单元是单次 LLM 调用,优化目标是这次调用的输出质量,核心问题是"怎么措辞、怎么提供示例、怎么引导推理链"。CE 的视角完全不同——它把整个上下文窗口看作一个有限的、需要精心编排的资源,工作单元是整个 Agent 的运行生命周期。

  2. CE 要解决的三个核心问题

  3. 信息密度管理:上下文窗口有限,堆砌所有信息不是最优策略,CE 要在有限空间里最大化信息价值密度——把最相关的内容放在注意力最集中的位置,对冗余历史做摘要压缩,对低相关检索结果做截断。
  4. 动态编排:Agent 执行过程中每一步之后上下文的组成都在变化,新工具结果进来了,旧历史要不要保留,CE 需要一套动态决策机制。
  5. 跨步推理一致性:前一步的结论会成为后一步的上下文,CE 要保证关键信息在整个推理链条中不丢失,同时又不让上下文随步骤增加无限膨胀。

  6. 瓶颈转移的本质。当 LLM 的智能已经足够强的时候,瓶颈往往不在于"怎么说",而在于"给模型看什么"。一个上下文编排做得好的 Agent,在相同模型能力下可以比编排混乱的 Agent 有显著更好的表现。这就是为什么 CE 是 Agent 时代更核心的能力。

3️⃣ Key Differences

查看内嵌表格


3、场景题:你的 Agent 在执行 10 步任务时,到第 8 步时上下文窗口快满了,但早期步骤的工具调用结果还要用,怎么设计上下文管理策略?

难度级别:⭐⭐⭐(上下文窗口管理、关键信息保留、历史压缩策略、跨步推理一致性)

1️⃣ Common Answer

上下文窗口快满了就要压缩,可以对早期的对话历史做摘要,把长内容变短,这样就能腾出空间给新内容。

2️⃣ Impressive Answer

这是 CE 的核心场景,我会用分层管理策略来解决:

  1. 关键信息固化,不参与压缩。在 Agent 开始执行时,把任务目标、核心约束、关键中间结论单独维护在一个"状态摘要"结构里,每步执行后更新这个结构,而不是依赖原始的工具返回文本。这部分内容始终保留,不压缩。

  2. 工具调用历史分级处理。对早期步骤的工具返回,区分"结论已被后续步骤引用"和"原始数据还需要直接查阅"两类。前者可以压缩为一句结论("第 3 步确认了用户权限为 admin"),后者如果后续还要用,保留完整内容但做精简格式化。

  3. 动态注入而不是堆积。当某一步需要用到早期数据时,从状态摘要里提取相关部分,临时注入到当前步骤的上下文末尾,而不是让所有历史数据一直留在窗口里。这样每一步的上下文都是"当前步骤最需要的信息"的精选。

  4. 在第 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

我会从三个层面来回答这个问题:

  1. 首先,理解现象的根因。Lost in the Middle 本质上是 Transformer 注意力分布不均匀的体现——头尾 token 在自回归训练中接收了更多梯度信号,形成类似"首因效应"和"近因效应"的结构性偏置。这不是偶发问题,是训练方式决定的,需要在工程层面主动应对。

  2. 其次,RAG 场景用"首尾夹击"排列法。在 Reranker 精排之后,不是直接按分数顺序拼接,而是:相关性最高的文档放首位,次高的放末尾,中间的夹在里面。这样即使模型对中间注意力不足,最关键的信息在两个高注意力位置都有覆盖。如果检索结果太多,还可以用 LLMLingua 等工具对每个片段做压缩摘要,从根本上缩短上下文长度,降低丢失风险。

  3. 最后,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

我会从原理、结构设计和反模式三个角度来回答:

  1. 首先,理解缓存命中的严格条件。KV Cache 前缀缓存以 block(通常 128 或 256 个 token)为单位匹配,要求前缀逐字节完全相同才能命中。哪怕只改动一个字符、一个空格,从那个位置开始的所有 block 都会 cache miss。这解释了为什么在 System Prompt 里插入动态时间戳或用户 ID 会彻底破坏缓存——即使插值位置在末尾,后续所有 block 都失效。

  2. 其次,按"稳定在前、动态在后"原则设计五层 Prompt 结构[完全固定的 System Prompt] → [固定 Few-shot 示例] → [固定长文档/知识库] → [随对话增长的历史] → [当前用户输入]。越靠前越稳定,缓存命中率越高;越靠后允许越动态。在高并发场景下,前两层几乎可以持续命中缓存,收益最显著。平台侧,Anthropic 对命中缓存的 input token 收费是原价 10%,OpenAI 是 50% 折扣;Anthropic 还支持手动设置 cache_control: {"type": "ephemeral"} 精确控制缓存边界,对 RAG 固定文档场景很有用。

  3. 最后,识别并避免常见的破坏缓存反模式:在 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

我会从三代方案的机制差异和重试策略设计两个角度来回答:

  1. 首先,三代方案的核心机制和边界。JSON Mode 用约束解码(constrained decoding)保证 token 序列语法合法,但字段名拼错、字段缺失、类型不匹配它一概不管,适合结构简单的场景。Function Calling 把 Schema 放进工具定义,让模型理解字段含义,可靠性显著提升,但嵌套深、枚举多时偶尔还是会漏字段;另外需要显式指定 tool_choice,否则模型可能输出普通文本而不触发工具调用。Structured Output(client.beta.chat.completions.parse())通过 RLHF 专项训练,只要 Schema 合法,几乎可以保证字段完整性和类型正确性,是生产环境的首选。

  2. 其次,校验失败的分层重试策略。简单粗暴地盲重试效果有限,更有效的方式是"带错误信息回填的重试"——把 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 测试设计三个维度来系统说明:

  1. 首先,Prompt-as-Code——把 Prompt 纳入 Git。把 Prompt 存成独立的 .yaml.jinja2 文件,文件头带元数据(版本号、适用模型、修改人、变更说明),和业务代码一起走 Code Review 和 PR 流程。每次变更都有 commit message,回滚方便,CI 里可以跑自动化评估集确保没有退化。这是高风险核心 Prompt 的首选管理方式。

  2. 其次,在线动态场景用 LangSmith 或 Langfuse。如果运营同学需要频繁调整话术,Git 发布周期太重,可以把 Prompt 存在 LangSmith Prompt Hub 或 Langfuse 上,应用启动时 pull 指定版本,改 Prompt 不需要重新发布服务。两者的区别:LangSmith 与 LangChain 生态紧密,Langfuse 开源可自托管,数据合规要求高的场景选后者。

  3. 最后,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

我会从三个策略的核心权衡出发思考这个问题:

  1. 首先,直接分块(Chunking)是最简单的方案,但有核心缺陷。按固定 token 数切割后,跨块的完整论述会被截断,两边都失去上下文。加 overlap(块间重叠 50 token)只能缓解,不能根治。更大的问题是——如果任务需要整合全文信息(比如"全文的主要矛盾"),单块根本答不了。适用场景:各条款之间彼此独立的信息提取类任务,比如从合同里提取关键日期。

  2. 其次,Map-Reduce 分两阶段处理,能覆盖全文。Map 阶段对各 chunk 并行生成摘要,Reduce 阶段把所有摘要合并再调一次 LLM 输出最终结果。优点是可并行、信息损失可控;成本是 LLM 调用次数 = chunk 数 + 1,线性增长。适用场景:需要整合全文信息的任务,文档在 100K token 以内。

  3. 最后,递归摘要解决 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

我会从四个独立的优化维度来回答这个问题:

  1. 首先是语义缓存(Semantic Cache),命中率最高、收益最直接。普通 exact match 缓存只能命中完全相同的请求,实际用处有限。语义缓存用 embedding 相似度检索历史请求,相似度超过阈值(比如 0.85)直接返回缓存,FAQ 类场景命中率可达 60-70%。工具上可以用 GPTCache 接入 LangChain。上线前要先统计请求分布,估算 ROI,命中率低于 20% 的场景可能不值得投入缓存基础设施。

  2. 其次是大小模型路由(Model Routing),降低单次调用成本。不是所有请求都需要 GPT-4o。用两层路由:先用关键词规则过滤(翻译、格式化等简单请求直接走 mini 模型),再用小模型做复杂度分类,复杂推理才走大模型。GPT-4o-mini 成本约为 GPT-4o 的 1/30,实践中 60-70% 的请求可以走小模型,整体成本能降 50% 以上。

  3. 再次是 Prompt 压缩(LLMLingua),专门针对 RAG 场景的冗余 Context。检索回来的 Context 往往包含大量无关内容。LLMLingua 用小语言模型给每个 token 打重要性分,按比例删掉不重要的 token,在信息损失可控的前提下把 Context 压缩 60-80%,大幅降低 input token 数。

  4. 最后是 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 解决的四个核心问题来回答:

  1. 首先是统一接口,屏蔽各家 API 格式差异。LiteLLM 把 100+ 模型统一封装成 OpenAI Chat Completion 格式,切换模型只改 model 字符串,业务代码完全不动。Claude、Gemini、本地 Ollama 都能用同一套调用方式。

  2. 其次是多 Key 轮询防限流。单个 API Key 有 RPM 和 TPM 上限,多 Key 轮询是最简单的绕过手段。LiteLLM Router 支持 round-robinleast-busylatency-based 三种策略,生产上推荐 least-busy,把请求打到当前负载最低的 Key。

  3. 再次是按优先级的模型降级(Fallback)。主模型限流或不可用时,自动按顺序切换到备用模型。LiteLLM 还支持 context_window_fallbacks——当请求超过主模型的 Context Window 时,自动切换到支持更长 Context 的备用模型,非常实用。

  4. 最后是成本追踪。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

我会从四个维度系统来回答选型决策:

  1. 首先是向量维度与存储成本的平衡。维度不是越高越好。text-embedding-3-small 是 1536 维,百万向量约占 6GB;BGE-M3 是 1024 维,约 4GB。OpenAI 的 text-embedding-3 系列支持 Matryoshka 缩维,通过 dimensions 参数把 1536 维截断到 256 维,存储成本降 6 倍,检索延迟也更快,在精度损失可接受的前提下很实用。

  2. 其次是中文场景 BGE-M3 的核心优势。BGE-M3 在中文任务上明显优于 OpenAI 通用 Embedding,有三点关键能力:支持 100+ 语言(中英混合文档不用分语言处理);单模型同时支持 dense、sparse、ColBERT 三种检索模式;最长支持 8192 token 输入,对长文档有优势。

  3. 再次是 API 成本 vs 本地部署的规模临界点。OpenAI text-embedding-3-small 定价 $0.02/百万 token,适合小规模快速上线。本地一张 A10G 跑 BGE-M3,一次性处理大规模文档库后增量成本极低。经验规则:文档总量 < 5000 万 token 且无合规要求,用 API 最省事;超过这个量级或有数据出境限制,本地部署 ROI 更高。

  4. 最后是批量 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

我会从概念体系、工具接入和选型决策三个层面来回答:

  1. 首先是 Trace/Span/Run 的树形结构。一次用户请求对应一个 Trace(有唯一 ID),Trace 下是树状嵌套的 Span——AgentExecutor 是顶层 Span,下面挂着多个 LLM Call(Run)和 Tool Call(Span),Tool Call 下面还可以有 HTTP Request Span。这个树形结构还原了 Agent 的实际执行路径,定位问题时能一眼看到哪一步的输入输出出了问题。

  2. 其次是两个工具的接入方式。LangSmith 对 LangChain/LangGraph 项目是真正的零代码接入,设三个环境变量(LANGCHAIN_TRACING_V2LANGCHAIN_API_KEYLANGCHAIN_PROJECT),所有 Chain 和 Agent 调用自动被 Trace,不需要改业务代码。Langfuse 需要手动用 @observe() 装饰器标记函数,灵活度更高,非 LangChain 项目也能用,自托管时数据完全不出公司。

  3. 最后是成本统计和选型建议。两个平台都支持按时间、模型、用户、功能模块维度做 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

查看内嵌表格