Chapter 04
工程化与全栈落地:把 demo 变成生产系统
上一章造出了能自主决策的 Agent——代价是成本、延迟、失控三笔账同时找上门。这一章是把 demo 变成生产系统要补的工程:让响应可感知(流式)、让账单可控(成本)、让黑盒可查(可观测)、让质量不回退(评估)、让它不被劫持(安全)、让它不雪崩(可靠性),最后落到框架怎么选。
本章你要建立的心智模型
- 流式为什么是一等公民,SSE 与 WebSocket 怎么选,TTFT 买的是哪种延迟
- token 成本的几个杠杆(缓存 / 路由 / 批处理)按什么顺序拉,谁先动收益最大
- 怎么给一个非确定的多步系统做可观测与评估——trace/span 与 offline/online 各管什么
- 生产里最常见的不是答错,而是重试风暴——可靠性与安全(注入、致命三件套)怎么做
判断一个候选人是不是真上过生产,看一道题就够:「你的大模型应用上线后,最常见的线上事故是什么?」初级答「答错 / 幻觉」,中高级答「重试风暴把上下文二次撑大,是一笔成本和延迟的账,不是质量的账」。这一章把工程化拆成七块,每块都落到一个能在白板上算清的取舍。
4.1流式输出:SSE vs WebSocket 与 TTFT
流式不缩短总耗时,只把「首 token 时间(TTFT)」提前——它买的是感知延迟,不是真实延迟。
LLM 逐 token 生成,一段几百字的回答端到端要数秒。非流式接口在全部生成完之前不返回任何字节,用户盯着空白等 5–10 秒——这是体感上最劝退的延迟。流式把「已经生成的 token」边产边推,用户在几百毫秒内就看到第一个字,后面持续滚动。总耗时一字不少,但等待从「白屏焦虑」变成「正在打字」。这就是为什么 OpenAI、Anthropic、Vercel AI SDK 把流式做成默认行为,而不是可选项。
TTFT(Time To First Token)= 分词 + prefill(把 prompt 过一遍算 KV cache)+ 排队等待。它是流式系统的核心指标:用户感知的「快慢」基本由 TTFT 决定,而非总时长。多步 Agent 里延迟会线性累加——每次工具调用 +300ms,串行八步就是 2–4 秒纯编排开销,叠加每步的 LLM 生成,不做流式的多步 Agent 在用户眼里就是「卡死了」。
SSE 与 WebSocket 怎么选。SSE(Server-Sent Events)是纯 HTTP、单向(只有 server→client 推 token),天然走现有的 HTTP 基础设施,代理 / CDN / 鉴权全部复用,浏览器原生 EventSource 自动重连。代价是只能服务端单向推:用户想「中途打断生成」得另开一个 HTTP 请求去取消。WebSocket 是全双工长连接,适合需要在生成途中双向通信的场景——边生成边接受用户中断、人工审批插入、多 Agent 实时转向。代价是它脱离 HTTP 语义,负载均衡、鉴权、断线重连都要自己处理,运维复杂度高一档。
| 方案 | 优势 | 代价 / 为什么不默认选它 |
|---|---|---|
| SSE(默认) | 纯 HTTP 单向、代理/CDN/鉴权全复用、浏览器原生自动重连、实现最简 | 仅 server→client 单向;「停止生成」要另开请求;记得关 Nginx proxy_buffering 否则 token 被缓冲到一起 |
| WebSocket | 全双工,可在生成途中中断 / 插入审批 / 实时转向 | 脱离 HTTP 语义,LB / 鉴权 / 重连全自理,运维复杂度 +1 档;多数对话场景用不上双向 |
| 轮询 polling | 实现无脑、任何栈都能跑 | 延迟高、请求数爆炸、体验差;只在无法长连接的受限环境用 |
流式响应一旦发出 200 OK 和第一个字节,HTTP 状态码就定死了。生成到一半 LLM 报错、超时、触发内容过滤,无法再改 HTTP 状态码——只能在流内发一个自定义错误事件(如 event: error\ndata: {...}),前端解析流内容来识别失败。把错误当成普通 HTTP 异常处理,前端会以为请求成功了却收到半截答案。
# FastAPI 服务端 SSE 流式 —— 演示用,未本机执行
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
async def token_stream(prompt: str):
usage = {"completion_tokens": 0}
try:
async for chunk in llm.astream(prompt): # 上游逐 token 产出
usage["completion_tokens"] += 1
# SSE 帧格式:data: <payload>\n\n
yield f"data: {chunk.text}\n\n"
except Exception as e:
# 200 已发出,状态码改不了 —— 只能走流内错误事件
yield f"event: error\ndata: {str(e)}\n\n"
finally:
# 结束帧顺带回传 token 数,供成本统计
yield f"event: done\ndata: {usage['completion_tokens']}\n\n"
@app.post("/chat")
async def chat(prompt: str):
return StreamingResponse(
token_stream(prompt),
media_type="text/event-stream",
headers={"X-Accel-Buffering": "no"}, # 关掉 Nginx 缓冲,否则 token 会被缓冲攒成一大块
)
SSE 部署在 Nginx 反向代理后面,前端却要等好几秒才一次性收到全部文字,「流式」完全失效。问题最先该查哪里?
展开答案(先停 10 秒再点)
Nginx 默认开启 proxy_buffering on,会把上游的 SSE 数据攒满一个缓冲区再下发,于是逐 token 推送被重新缓冲、合并成一大块再一次性下发。需要在 location 上设 proxy_buffering off;,或让后端发 X-Accel-Buffering: no 响应头。
设计启示:流式是一条贯穿浏览器→CDN→反代→应用的端到端管道,任何一环开了缓冲都会让流式退化成非流式。这就是为什么流式要「一开始就设计进架构」,而不是上线后再加——它牵扯的是整条链路的配置。
4.2成本优化:缓存 / 路由 / 批处理
省钱的杠杆有优先级:先用模型路由砍掉「大材小用」,再用 prompt 缓存吃掉重复前缀,最后用批处理收割可异步的流量。
Agent 一次 turn 扇出 6–12 次 LLM 调用,每次都把完整工具定义 + 系统提示 + 历史重新发一遍。token 成本随轮次和上下文长度线性甚至超线性增长,一个月的账单很容易从「能忽略」涨到「老板要问」。成本优化不是省小钱,而是决定这个应用商业上能不能跑通。关键是杠杆有顺序——拉错顺序会做无用功。
四个杠杆,按收益和改动成本排序。① 模型路由 / 级联:简单任务(分类、抽取、改写)走小模型,难任务才升级到大模型,按置信度分流——据厂商数据可省下可观成本(实际取决于任务分布与降级阈值),这是收益最大、最该先做的杠杆,因为它直接砍掉「大材小用」的结构性浪费。② prompt 前缀缓存:把静态前缀(工具定义→系统提示→固定上下文)缓存住,Anthropic 的缓存读取只算 0.1× 输入价(约 9 折,即省 90%),写入 1.25×(5 分钟 TTL)或 2×(1 小时 TTL);几乎零质量损失,改动也小,是性价比最高的第二步。③ 批处理:OpenAI Batch API 固定5 折、异步 ≤24h 返回、不支持流式,适合离线评估 / 批量摘要这类非实时流量;可与缓存叠加,组合省到约 75%。④ 语义缓存:用 embedding 相似度命中历史问答,命中率约 25–35%,但有「相似但不相同」答错的风险,要配阈值和兜底,放最后。
| 杠杆 | 省多少 / 怎么省 | 代价 |
|---|---|---|
| ① 模型路由 / 级联 | 可省下可观成本(实际看任务分布与降级阈值);简单任务下沉小模型,低置信再升级 | 要建路由逻辑 + 置信度判断;误判会把难题丢给小模型答错 |
| ② Prompt 前缀缓存 | 命中读取 0.1×(9 折);缓存静态前缀 | 写入贵 1.25×/2×、有 TTL;前缀一变(哪怕一个字)缓存全失效,故要把变动内容放末尾 |
| ③ 批处理 Batch | 固定 5 折,可叠加缓存到约 75% | 异步 ≤24h、不支持流式;只适合离线 / 非实时流量 |
| ④ 语义缓存 | 命中率 25–35%,命中即零成本 | 「相似但不同」会答错;需相似度阈值 + 兜底回源 |
缓存命中省的不是一次网络往返,而是 prefill 阶段那段前缀的 KV(注意力 key/value)状态的重算(见 §1.1)——服务端把前缀算好的 KV 状态存下,命中时直接复用、跳过重算。这也解释了它的匹配规则:前缀按「从开头逐字节匹配」生效,一旦某个 token 变了,从那个 token 起 KV 状态就和缓存分叉,后面全部失效。所以 prompt 的拼接顺序必须是「最稳定的放最前,最易变的放最后」:工具定义 → 系统提示 → 长期上下文 → 本轮用户输入。把时间戳、随机 ID、用户名这类每次都变的内容塞在前缀里,等于亲手让缓存命中率归零。
有人为了「让缓存多命中」,每次请求都把整段对话上下文原样缓存一遍。延迟和成本会变好还是变差?
展开答案(先停 10 秒再点)
多半变差。缓存写入比普通输入贵(1.25× 或 2×),而对话上下文每轮都在增长、内容每轮都在变——前缀里只要有变动,缓存就命中不上,等于每轮都在「付高价写入 + 零命中读取」。缓存只对稳定不变的前缀(工具定义、系统提示)才划算;把易变的整段历史拿去缓存,是花写入的钱买不到读取的折扣。
设计启示:缓存收益 = 命中率 × 读取折扣 − 写入溢价。命中率由「前缀稳定性」决定,不是「缓存得越多越好」。
4.3可观测性:trace 与 span
没有 trace,你回答不了「哪一步失败 / 哪一步变贵」——而这正是 LLM 应用排障的全部。
LLM 应用是非确定的,同样的输入两次跑出不同路径;一次用户「turn」会扇出 6–12 次 LLM / 工具调用。出了问题——变慢、变贵、答错——光看一条请求日志根本定位不到是哪一跳出的事。可观测性把一次 turn 还原成一棵可下钻的调用树,让「哪一步」变成可回答的问题。这是把黑盒变成可运维系统的前提。
trace 与 span 的关系。一次完整 turn = 一个 trace;trace 里每一个工作单元(一次检索、一次 LLM 调用、一次工具执行)= 一个 span;span 之间有父子嵌套,组成调用树。生成型 span(LLM 调用)必须记全:prompt、completion、模型 + 参数、token 数、成本、延迟——缺任何一项都会在排障时卡住。落地工具上,Langfuse(基于 OpenTelemetry、可自托管,数据合规友好)和 LangSmith(P50/P99 延迟、成本、错误率看板)是社招里最常被点名的两个。
| 层次 | 记什么 | 回答什么问题 |
|---|---|---|
| 链路追踪 trace/span | 每个 span 的 prompt / 输出 / 模型 / token / 成本 / 延迟 | 哪一跳失败、变慢、变贵 |
| 指标 metrics | P50/P99 延迟、错误率、token 燃烧率、缓存命中率 | 整体健康度、趋势、告警 |
| 审计 audit | 谁在什么时间问了什么、触发了哪些工具 | 合规、安全事件回溯 |
上线那天评估通过 ≠ 永远没问题。模型供应商静默升级、用户提问分布漂移、知识库更新,都会让效果悄悄下滑。可观测性要带上持续监控:把线上输出按采样喂给 online 评估(见 4.4),盯住质量指标的趋势线,而不是只在上线前跑一次。「上线后效果衰减怎么监控」是这道题的高频追问。
4.4评估:offline / online 与 LLM-as-judge
offline 评估是上线前的 gate(防回归),online 评估是上线后的雷达(抓衰减)——两者管的是不同阶段,不可互相替代。
Prompt 改一个字、换一个模型版本、调一处检索参数,任何一处都足以让某类问题悄悄变差。没有评估,你只能靠「线上有人投诉」来发现回归,那时已经晚了。评估把「质量」变成 CI 里能 gate、看板上能盯的数字。评 Agent 比评 LLM 难:路径不唯一、中间步骤难判对错,所以除了最终正确性,还要看过程指标(步数、工具调用准确率、成本)。
offline 与 online 分工。offline 跑在精心策划的 golden dataset 上,进 CI/CD,上线前当 gate——改动合并前先在固定测试集上回归,分数掉了就拦下。online 跑在生产真实流量上,用 canary / A-B 灰度,抓的是离线集覆盖不到的真实分布漂移和效果衰减。
LLM-as-judge 与它的偏见。用一个强模型给输出打分,可扩展、便宜,但有系统性偏见:位置偏见(成对比较时偏向某个固定位置的答案,实测偏好率高达 60–75%)、啰嗦偏好(倾向给更长的答案高分)、自我偏好(倾向给同家族模型的输出高分)。这些偏见是结构性的、不是 bug:judge 自己就是个没有标准答案可对照的自回归模型,给不出"真值",只能用训练里和"好答案"相关的表层特征(更长、排在前、同家族风格)去近似——这正是它系统性偏向长答案 / 前位 / 自家输出的根因。缓解手段:成对比较时交换 A/B 顺序跑两遍取一致结果(能压住位置偏见,却压不掉啰嗦 / 自我偏好),并先用人工标注校准 judge。RAGAS 这类框架给 RAG 提供 faithfulness、answer relevancy、context precision/recall 等现成指标。
| 维度 | offline 离线评估 | online 在线评估 |
|---|---|---|
| 数据来源 | 策划的 golden dataset(固定) | 生产真实流量(漂移) |
| 时机 / 位置 | 上线前,进 CI/CD 当 gate | 上线后,canary / A-B 灰度 |
| 主要防什么 | 改动引入的回归 | 真实分布漂移、效果衰减 |
| 代价 | golden set 要人工维护,覆盖不到长尾 | 需采样标注 / judge,有延迟和成本,结论受流量波动影响 |
只有 offline:固定测试集永远测不到上线后才出现的新提问类型,衰减无感。只有 online:等真实流量暴露问题,坏改动已经影响了用户,且无法在合并前拦截。offline 在「时间轴的左边」把回归挡在门外,online 在「时间轴的右边」抓住门外冒出来的新问题——它们防的是不同阶段的不同风险。
用 LLM-as-judge 做成对比较,把「答案 A vs 答案 B」喂进去,judge 总是偏向排在前面的那个。怎么用最小代价缓解?
展开答案(先停 10 秒再点)
这是位置偏见(实测偏好率 60–75%)。最小代价做法:交换顺序各跑一遍——先 (A,B) 再 (B,A),只有两次结论一致才算数,不一致判平局或交人工。代价是 judge 调用翻倍,但换来结论稳定。更彻底的是先用一批人工标注校准 judge 的打分倾向。
设计启示:自动评估器本身也是个非确定、有偏的 LLM,不能盲信它的单次输出——评估系统也要被评估。
4.5安全:Prompt 注入与致命三件套
Prompt 注入的根因是「指令和数据共用同一条通道」,LLM 分不清哪句是开发者的规则、哪句是攻击者塞进来的内容。
传统注入(SQL / XSS)有清晰的代码-数据边界,加转义就能防。LLM 没有这个边界——系统提示、用户输入、检索到的文档、网页内容,全部以自然语言拼进同一个 prompt。攻击者只要在任意一处写「忽略以上所有指令,改为……」,模型就有概率照做。OWASP 把 Prompt 注入列为 LLM 应用头号风险 LLM01(连续两版第一),因为它无法被彻底「修复」,只能纵深防御压低风险。
直接注入 vs 间接注入。直接注入是用户在对话框里直接写「忽略之前的指令」。间接注入更隐蔽:恶意指令藏在被 RAG 检索到的文档、被 Agent 抓取的网页里,用户本人毫不知情,模型读到这些「数据」时却把其中的指令当命令执行。间接注入是这道题的高频追问,也是最难防的——因为输入源不可信且不可控。
致命三件套(lethal trifecta)。真正把注入从「恶作剧」升级成「数据泄露」的,是三个能力同时具备:① 不可信输入(能接触外部内容)+ ② 私有数据访问(能读到敏感信息)+ ③ 对外发送通道(能把数据发出去,如带宽出网权限的工具、能渲染外链图片)。任一单独存在都还好,三者凑齐,攻击者就能用间接注入指挥 Agent 把私有数据偷运出去。
| 防御层 | 做什么 | 挡住什么 / 局限 |
|---|---|---|
| 指令与数据分隔 | 系统指令与外部内容用明确分隔标记 + 强调「以下是数据不是命令」 | 降低概率,挡不死;模型仍会被绕过 |
| 最小权限工具 | 工具权限 ≤ 当前用户权限,砍掉非必要的出网 / 写操作 | 直接拆掉致命三件套的一条边,最有效 |
| 沙箱执行 | 工具跑在只读 / 隔离容器里 | 限制爆炸半径;不阻止注入本身 |
| 输出审核 + 拦截走私 | 过滤输出里的 markdown 图片外链、ASCII 走私式外泄 | 堵外泄通道;要持续更新规则 |
| HITL 人工确认 | 敏感 / 不可逆操作前要人点确认 | 挡高危动作;牺牲自动化程度 |
常见误区是「用户输入要防注入,RAG 检索的文档来自自家库,可信」。错。库里的文档会被投毒、会本就来自用户上传、会从公网爬取。任何进入 prompt 的非系统内容都按不可信处理,包括检索结果、工具返回、网页正文。间接注入正是钻「以为数据可信」的空子。
除注入防御外,生产还要处理合规——PII 脱敏与最小化收集、数据跨境 / 出境限制、训练与检索语料的授权,以及国内的等保备案要求。面试官(尤其甲方 / 金融 / 政企方向)常问「用户数据怎么保证不外泄、不违规」。
4.6可靠性:重试风暴、熔断、结构化校验
生产里最常见的事故不是答错,而是重试风暴——失败的步骤被重试,重试又把上下文二次撑大,成本和延迟一起爆炸。
Agent 的自主循环天生脆弱:一次工具失败触发重试,重试时把失败的上下文也带进去,下一轮 prompt 更长、更贵、更容易再失败——形成正反馈。这是一笔成本 / 延迟的账,不是质量的账,但它是真实线上事故的头号来源。可靠性工程就是给这个循环装上刹车。
三件套:超时、有选择的重试、按模式熔断。① 给每次 LLM / 工具调用设 30–60s 超时,避免一个挂起的请求拖死整条链。② 重试要有选择:只对 429(限流)和 5xx(服务端错误)重试,4xx(参数错、内容违规)重试只是白烧钱;且必须指数退避 + 抖动(jitter),否则所有客户端同时重试会把上游打垮(惊群)。③ 熔断按「模式」触发,而非只看频率——检测「同样的调用在循环」「成本速率突增」这类模式,比单纯「错误率超阈值」更能抓住重试风暴和死循环。
结构化输出:先用原生保证,再用校验兜底。要让 LLM 稳定吐出能被程序消费的 JSON,首选 §1.5 的原生 strict schema / 约束解码(token 级构造性保证 schema,根本不给"吐出非法结构"留机会);只有当接口不支持原生约束、或要校验 schema 之外的业务语义(取值范围、跨字段一致性)时,才退到下面这条更弱的"事后校验 + 重试"。三层校验:schema 层(字段、类型对不对)→ 结构层(嵌套、必填齐不齐)→ 内容层(取值在合法范围内)。用 Pydantic 解析 + 带错误反馈的重试(上限 3 次)——把校验失败的具体报错回灌给模型让它自我修正——这是工程经验值,例如能把「无法解析」的比例压到千分位级别(具体随模型与 schema 复杂度而变)。对有副作用的工具调用还要加幂等键,保证重试不会重复扣款 / 重复下单。
# Pydantic 校验 + 带错误反馈的重试 —— 演示用,未本机执行
from pydantic import BaseModel, ValidationError, field_validator
class Order(BaseModel):
sku: str
qty: int
@field_validator("qty")
@classmethod
def positive(cls, v):
if v <= 0: # 内容层校验:取值必须合法
raise ValueError("qty 必须为正")
return v
def extract_order(llm, prompt: str, max_retry: int = 3) -> Order:
err = ""
for attempt in range(max_retry):
raw = llm.complete(prompt + err) # 把上一次的报错回灌给模型
try:
return Order.model_validate_json(raw) # schema+结构+内容 三层一次过
except ValidationError as e:
# 关键:不是简单重试,而是带着具体错误让模型自我修正
err = f"\n\n上次输出不合法:{e}。请仅返回符合 schema 的 JSON。"
raise RuntimeError("超过重试上限仍无法解析") # 兜底,不让坏数据流入下游
上游一抖动返回一批 429,如果所有客户端都按「固定 1s 后重试」,它们会在同一时刻再次集体打过去(惊群效应),把刚要恢复的上游再次打挂,循环往复。重试必须叠加随机抖动——退避时间 = 基础退避 × 2^n + random(0, jitter),把重试时刻打散开。只重试 429/5xx、限次数、加抖动,三条缺一不可。
4.7框架选型:LangChain / LangGraph / AutoGen / CrewAI
框架选型的主轴是「你需要多强的控制力」:快速原型用 LangChain,要细粒度控制和持久化就升到 LangGraph,多 Agent 协作看 AutoGen / CrewAI。
选错框架的代价是中途重写。LangChain 的链式抽象让 demo 飞快,但当业务需要循环、条件分支、断点续跑、人工介入时,它的 AgentExecutor 偏黑盒、循环控制弱,会逼你绕一堆 hack。面试官问选型,考的是你能不能说清「从 Chain 升到 Graph 是被什么需求逼的」——这正是 demo 到生产的分水岭。
四个框架的定位。LangChain:链式编排,快速原型首选,生态最大;短板是 AgentExecutor 黑盒、缺细粒度循环 / 状态控制。LangGraph:把流程建模成图(节点 + 边 + 状态),支持 checkpoint 持久化、人工节点(human-in-the-loop)、多 Agent、时间旅行 / 断点调试——生产级控制力。AutoGen(微软):会话式 GroupChat,多个 Agent 像开会一样轮流发言协作。CrewAI:角色化 crew,给每个 Agent 派角色(研究员 / 写手 / 审核)分工协作。
| 框架 | 适合什么 | 代价 / 短板 |
|---|---|---|
| LangChain | 快速原型、线性链、生态丰富 | AgentExecutor 偏黑盒、循环 / 状态控制弱,复杂流程要 hack |
| LangGraph | 生产级单 Agent / 多 Agent:图状细粒度控制、checkpoint 持久化、人工节点、时间旅行调试 | 学习曲线陡、要自己设计状态图,原型阶段偏重 |
| AutoGen | 会话式多 Agent(GroupChat 协作) | 会话编排难精确控制、成本随对话轮次涨 |
| CrewAI | 角色化多 Agent 分工协作,上手快 | 抽象偏高层,细粒度控制和可观测性弱 |
需要这些能力时,就该从 LangChain 升到 LangGraph:① 流程有循环和条件分支(ReAct 式「想-做-看」要反复跑);② 需要断点续跑(长任务挂了能从 checkpoint 恢复,而不是从头来);③ 需要人工介入(敏感步骤暂停等人确认);④ 需要可调试(时间旅行回放每一步状态)。这些恰好都是生产要求而原型不要求的——所以「升级」本质是「从 demo 转生产」。
§面试题检索
先合上教程,把答案在心里过一遍或写下来,再点开对照。直接展开等于把这一章当再读一遍。标「高频」的是社招必问。
- [高频][系统设计] 怎么设计一个大模型应用的后端架构?
参考答案
七层:流式层(SSE/WS,一开始就设计进去)+ 多级缓存(精确 / 语义 / 前缀)+ 多模型路由降级(简单任务小模型、按置信升级)+ 异步编排(队列 / 批处理收割非实时流量)+ 可观测(Langfuse 记 trace/span:prompt/token/成本/延迟)+ token 配额(按用户 / 租户限速防失控)+ 安全(输入输出护栏、最小权限工具)。若用自托管模型,还要有一层推理服务(vLLM 等,靠 continuous batching + PagedAttention 提吞吐)。
追问「哪层先做」:先做可观测(没数据无法优化)和成本路由(收益最大)。追问「流式为什么一开始就设计进去」:它是贯穿浏览器→CDN→反代→应用的端到端管道,后期补要改全链路配置,且多步 Agent 不流式体感就是卡死。
- [高频] 流式输出怎么实现?SSE vs WebSocket 怎么选?
参考答案
生成慢(数秒)就必须流式。SSE:纯 HTTP 单向、实现简单、代理友好(记得关 Nginx
proxy_buffering),默认选它。WebSocket:双向,需要生成途中中断 / 审批 / 转向时才用。HTTP 200 发出后再出的错改不了状态码,要走流内自定义错误事件让前端解析。追问:①统计 token——流式下 usage 不在响应头,要边推边累加或读结束帧;②中途报错——流内 error 事件;③停止生成——SSE 下另开取消请求,WebSocket 下直接发中断消息。
- [高频] token 成本太高怎么优化?
参考答案
按优先级:模型路由(简单任务小模型,据厂商数据可省下可观成本,实际看任务分布与阈值)→ 上下文压缩 → prompt 前缀缓存(读取约 0.1× = 9 折,缓存静态前缀)→ 语义缓存(命中 25–35%)→ 减少工具调用 / 轮次 → 批处理(Batch 5 折,可叠加缓存到约 75%)。
追问「不降质前提下怎么省」:路由 + 前缀缓存 + 批处理这三样几乎不动质量;语义缓存会答错要放最后配阈值。追问「成本看板怎么搭」:在每个生成型 span 上记模型 + token + 成本,按模型 / 用户 / 租户聚合出燃烧率。
- [高频] 自托管推理服务怎么提升吞吐?vLLM 的 PagedAttention 和 continuous batching 解决什么问题?
参考答案
大模型推理的瓶颈是显存(KV cache,见 01 章 §1.1)和 GPU 利用率。PagedAttention 借鉴操作系统虚拟内存分页来管理 KV cache,按需分配、减少碎片,让更多请求共享显存;continuous batching(连续批处理)在每个解码步动态把新到的请求拼进批次,不必等整批跑完,显著提升吞吐。
追问:vLLM / SGLang / TensorRT-LLM 怎么选?为什么长上下文请求会拉低整体吞吐?
- [高频] 怎么做可观测性?
参考答案
三层:链路追踪(Langfuse / LangSmith 记每个 span 的 prompt / 输出 / 模型 / token / 延迟 / 成本,span = 一个工作单元)+ 指标(P99 延迟、错误率、token 燃烧率、缓存命中率)+ 审计(谁何时问了什么、触发哪些工具)。
追问「效果衰减怎么持续监控」:采样线上输出喂 online 评估,盯质量趋势线。追问「怎么定位哪一跳变慢变贵」:看 trace 调用树,每个 span 的延迟和成本一目了然,最慢 / 最贵那一跳即瓶颈。
- [高频] 怎么评估一个 Agent?为什么比评 LLM 难?
参考答案
难在:多步、路径不唯一、中间步骤难判对错。所以除最终正确性还要看过程指标(步数、工具调用准确率、成本)。方法:LLM-as-judge + benchmark,配 offline golden set 和 online 灰度。除自建 golden set + RAGAS 外,还有公开 benchmark 做横向能力评估——SWE-bench(代码任务)、GAIA(通用 agent)、τ-bench(工具调用 / 对话)、AgentBench、MMLU(知识)。
追问「怎么搭离线 eval 防 prompt 改动回归」:固定 golden dataset 进 CI/CD 当 gate,改动合并前跑分,掉分就拦。追问「LLM-as-judge 有哪些偏见」:位置偏见(60–75%)、啰嗦偏好、自我偏好;缓解 = 交换 A/B 顺序跑两遍取一致 + 人工标注校准。
- [高频] 什么是 Prompt 注入?怎么防御?
参考答案
用户 / 外部内容篡改系统指令,根因是指令与数据共用一条通道。直接注入(「忽略之前指令」)vs 间接注入(藏在 RAG 文档 / 网页里)。OWASP LLM01 头号风险。防御(纵深):指令与数据分隔、最小权限工具、沙箱、输出审核、HITL、不信任检索内容。致命三件套 = 不可信输入 + 私有数据 + 外泄通道,凑齐才致命。
追问「间接注入怎么防」:把检索结果 / 工具返回 / 网页正文一律当不可信数据;砍掉致命三件套的一条边(如对能读私有数据的 Agent 关出网通道);拦截 markdown 外链图片这类走私式外泄。
- [高频] LangChain vs LangGraph vs AutoGen vs CrewAI 怎么选?
参考答案
LangChain:链式快速原型(短板:缺循环控制、AgentExecutor 黑盒)。LangGraph:图状细粒度控制 + checkpoint 持久化 + 人工节点 + 多 Agent + 时间旅行调试。AutoGen:会话式 GroupChat 多 Agent 编排。CrewAI:角色化 crew 协作。
追问「为什么从 Chain 升到 Graph」:当需要循环 / 条件分支、断点续跑、人工介入、可调试——这些都是生产要求而原型不要求的,所以「升级」本质就是「转生产」。
- 生产环境的 Agent / LLM 应用踩过哪些失败模式?
参考答案
死循环 / 失控成本、工具选错、上下文爆炸、幻觉调用不存在的工具、错误累积。最常见的生产事故其实是重试风暴导致上下文二次膨胀——是成本 / 延迟 bug,不是质量 bug。
对策:熔断按模式触发(循环 / 成本速率突增)而非只看频率、限制步数、上下文压缩、结构化校验、带抖动的指数退避且只重试 429/5xx。追问「具体某个失败怎么定位解决」:从可观测数据出发——看 trace 找出反复出现的 span,对比成本曲线和上下文长度曲线,锁定是重试 / 缓存未命中 / 上下文膨胀中的哪一个。
账单涨了 3 倍,质量没变,怎么排查到根因?
你的 Agent 月账单突然涨了 3 倍,但抽样看输出质量没有明显变化。给出一个从可观测数据出发、定位到根因的排查顺序——不要凭感觉猜,要说清每一步看哪个指标、能排除什么。
提示(卡住再展开)
质量没变但成本暴涨,说明是「同样的事花了更多 token」——大概率不是质量 bug 而是结构 bug。沿着这条线索想三个嫌疑:① 重试风暴(看错误率 + 单 turn 的 span 数是否变多);② 上下文膨胀(看平均 input token 是否上涨);③ 缓存失效(看缓存命中率是否掉了,可能是前缀里混进了每次都变的内容)。排查顺序:先看 token 燃烧率按「输入 / 输出」拆分 → 看单次 turn 的平均 span 数和重试次数 → 看缓存命中率趋势 → 下钻到具体 trace 看是哪类请求贡献了增量。