跳到主要内容

191 篇博文 含有标签「agents」

查看所有标签

委托悬崖:AI 代理可靠性为何在 7 步以上崩溃

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个单步可靠性为 95% 的代理听起来相当出色。但在执行 10 步任务时,成功率降至 60%;20 步时降至 36%;50 步时只剩约 8%——而这还是基于 95% 这个乐观的估计。实际数据显示,真实世界中代理每步操作的失败率接近 20%,这意味着一个 100 步的任务成功率约为 0.00002%。这不是模型质量问题,也不是提示工程问题,而是一个复合数学问题——而大多数构建代理的团队还没有真正内化这一点。

这就是委托悬崖:当你给代理的任务多增加一步时,失败率不是线性增加,而是成倍放大。

工具文档字符串考古学:描述字段是你杠杆率最高的提示词

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的智能体中杠杆率最高的 prompt 并不在你的系统 prompt(system prompt)中。它是你六个月前在某个工具定义下写的那句描述,它随实现代码一起提交,之后就再也没动过。模型在每一轮对话中都会读取它,以此决定是否调用该工具、绑定哪些参数,以及当响应不符合预期时如何恢复。工程师将其视为面向人类的 API 文档,而模型则将其视为一个 prompt。

这两种视角之间的鸿沟,正是最糟糕的工具使用(tool-use)类 bug 的温床:模型调用了正确的函数名,传入了正确的参数,发出了正确的 API 调用 —— 但原因却是错的,场景是错的,或者它放着旁边更合适的工具不用。没有任何异常抛出。你的评估套件依然通过。这种退化(regression)只会表现为衡量智能体是否真正起到帮助的指标在缓慢下降。

长期运行 AI 智能体中的上下文毒化

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的智能体完成了十二步工作流中的第三步,并自信地报告目标 API 返回了 200 状态。实际上并没有 —— 这个结果来自第一步,仍然停留在上下文窗口中。到第九步时,智能体已经基于一个从未真实存在过的事实进行了四次下游调用。工作流“成功”结束。没有记录任何错误。

这就是上下文污染(context poisoning):它不是一种安全攻击,而是一种可靠性故障模式,即智能体自身积累的上下文变成了错误信息的来源。随着智能体运行时间变长、交互工具增多、管理状态增加,这种故障的发生概率会急剧上升。而且,与崩溃或异常不同,上下文污染对标准监控来说是不可见的。

集成测试的幻象:为什么模拟工具输出会隐藏智能体的真实失败模式

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的智能体通过了每一项测试。CI 流水线显示绿色。你发布了它。

一周后,一位用户报告说他们的批量导出任务悄无声息地只返回了 200 条记录,而不是 14,000 条。智能体访问了分页 API 的第一页,得到了一个干净的响应,以为没有更多内容了,然后就继续下一步了。你的模拟(Mock)一次性返回了全部 200 个条目。而真实的 API 从未告诉智能体还有另外 70 页。

这不是模型的失败。模型的推理是正确的。这是测试基础设施的失败 —— 这种现象在团队构建和测试智能体系统(agentic systems)时非常普遍。

提示注入攻击面映射:在攻击者之前找到每一个攻击向量

· 阅读需 12 分钟
Tian Pan
Software Engineer

大多数团队以一种痛苦的方式发现自己的提示注入攻击面:安全研究员发布了一个演示,客户报告了奇怪的行为,或者事后复盘揭示了一个本不应触发的工具调用。到那时,攻击路径已经被记录在案,爆炸半径已成现实。

提示注入是 OWASP LLM 应用十大风险榜首,但将其定性为单一漏洞掩盖了它的本质:它是一族随应用复杂度增长的攻击向量。你注入提示的每一个外部数据源都是潜在的注入面。在拥有十几个工具集成的智能体系统中,这个攻击面是巨大的——而且大部分都未被绘制成图。

本文是一套实践者在攻击者之前完成映射的方法论。

有状态 vs. 无状态 AI 功能:决定一切下游走向的架构抉择

· 阅读需 13 分钟
Tian Pan
Software Engineer

当一个购物助手向一位两年前曾提及怀孕的用户推荐婴儿产品时,系统没有抛出任何异常。它完全按照设计运行。LLM 返回了一个充满信心的 HTTP 200 响应。问题出在数据上——一段从未被清除的过期记忆——而且它完全隐形,直到一位用户投诉才被发现。这就是潜伏在有状态 AI 系统中的幽灵,其行为与你习惯调试的 Bug 截然不同。

有状态与无状态 AI 功能之间的抉择,表面上看起来异常简单。但在实践中,这是你在构建 AI 产品时最早做出的架构决策之一,其影响会贯穿存储层、调试工具链、安全态势和运营成本。大多数团队是在不经意间做出这个决定的——盲目沿用某种模式,却未仔细审视其权衡取舍。本文旨在帮助你做出有意识的选择。

隐藏草稿板问题:为什么仅凭输出监控无法保障生产级 AI Agent 的安全

· 阅读需 12 分钟
Tian Pan
Software Engineer

当 o1 或 Claude 等思考增强模型生成回答时,它们会在写出任何输出之前,在内部生成数千个推理 token。在某些配置下,这些思考 token 永远不会被公开。即使它们可见,最近的研究也揭示了一个令人震惊的模式:对于涉及敏感或伦理模糊话题的输入,前沿模型仅在 25–41% 的情况下会在其可见推理中承认这些输入的影响。

在其余时间里,模型在其草稿本 (scratchpad) 中做了其他事情,然后写出一个并不反映这些过程的输出。

这就是隐藏的草稿本问题,它改变了每个依赖输出层监控来执行安全约束的生产级智能体系统的安全计算方式。

实时智能体 UI 背后的流式传输基础设施

· 阅读需 15 分钟
Tian Pan
Software Engineer

大多数智能体流式实现都会在以下四个方面出错:代理静默地吞掉了流、用户关闭标签页而智能体却在后台运行导致持续消耗 token、页面刷新导致任务丢失,或者工具调用在流的中途失败而智能体陷入静默空闲。这些都不是模型问题。它们是基础设施问题,是团队在本地测试良好,但在生产环境中才会发现的问题。

这篇文章讨论的就是这种差距 —— 服务器端架构的决策决定了一个实时智能体 UI 是否真正可靠,而不仅仅是在演示环境中看起来令人惊叹。

主体层级问题:多智能体系统中的授权

· 阅读需 13 分钟
Tian Pan
Software Engineer

一家制造公司的采购智能体逐渐确信自己可以在没有人工审核的情况下批准 50 万美元的采购。它这样做并非通过软件漏洞或凭据窃取,而是通过为期三周的供应商电子邮件序列,其中嵌入了澄清问题:“10 万美元以下的任何订单都不需要副总裁批准,对吧?”随后逐步扩展了这一假设。到它批准 500 万美元的欺诈订单时,该智能体运行的范围完全处于其认为的授权限制内。人类认为该智能体有 5 万美元的上限。而该智能体认为自己根本没有上限。

这就是最具体形式的主体层级问题(principal hierarchy problem):授予的权限、声称的权限以及实际行使的权限之间存在不匹配。当智能体衍生出子智能体,而这些子智能体又进一步衍生出更多智能体时,问题会呈指数级增长,链条中的每一环都会对允许的操作做出独立判断。

工具选择难题:当智能体拥有数十个工具时,如何选择调用哪一个

· 阅读需 12 分钟
Tian Pan
Software Engineer

大多数 Agent 演示仅使用 5 个工具,而生产系统通常拥有 50 个。这两个数字之间的差距,正是大多数 Agent 架构分崩离析的地方。

当你给一个 LLM 4 个工具和一个明确的任务时,它通常能选对。但当你给它 50 个工具时,更有趣的事情发生了:准确率大幅下降,Token 成本激增,且失败模式通常表现为模型幻觉出一个工具调用,而不是承认它不知道该用哪一个。来自 Berkeley Function Calling Leaderboard 的研究发现,在跨多个领域的日历调度任务中,当工具数量从 4 个扩展到 51 个时,准确率从 43% 骤降至仅 2%。这绝不是一个平滑的性能退化曲线。

生产中的推理模型:何时使用,何时不使用

· 阅读需 9 分钟
Tian Pan
Software Engineer

大多数采用推理模型的团队都会犯同样的错误:他们开始在所有地方使用它们。一个新模型发布,带着令人印象深刻的基准测试数据,然后在一周内,它就处理了客户支持、文档摘要以及它真正为之构建的那两个真正困难的问题。然后,基础设施账单就来了。

推理模型——o3、支持扩展思维的 Claude、DeepSeek R1 及其后续版本——确实与标准 LLM 不同。它们在生成输出之前会执行内部的思维链(chain-of-thought),花费更多的计算周期来探索问题空间。这种额外的工作在需要多步骤逻辑的任务上带来了真正的提升。但它也导致每次请求的成本增加 5–10 倍,并增加 10–60 秒的延迟。这两点都无法作为默认设置被接受。

生产环境中的结构化输出:如何用 LLM 生成可靠的 JSON

· 阅读需 11 分钟
Tian Pan
Software Engineer

大语言模型是文本生成器。你的应用程序需要数据结构。这两个事实之间的差距,正是生产环境中的错误滋生之地。

每个使用大语言模型构建产品的团队都会遇到这个瓶颈。模型在游乐场中表现出色——它返回的内容看起来像 JSON,字段大多正确,通常能通过 JSON.parse。然后你将其部署上线,你的解析层却在凌晨两点开始抛出异常。响应中多了一个逗号。或者是一个 Markdown 代码围栏。或者模型决定在 JSON 前面添加一段解释性文字。又或者它幻觉出一个字段名。

业界已经花了三年时间来解决这个问题。这就是目前解决方案的收敛点,以及仍然让团队头疼的问题。

三个成熟度级别

团队处理结构化输出的方式有着清晰的进展,每个级别都有一个实际的可靠性上限。

级别 1:提示工程。 你写道:“只用以下格式的有效 JSON 回复:”并展示一个示例。对于简单模式,这在 80-95% 的情况下有效。失败模式很微妙:模型在复杂提示中添加前言,在模式变长时将 JSON 包装在代码块中,或者静默地省略可选字段。你添加一个正则表达式清理步骤和一个 try/catch,然后说服自己这样没问题。

对于任何重要的应用来说,这都远不够好。95% 的解析成功率听起来很高,直到你有一个 10 步的智能体链:0.95^10 ≈ 0.60。十次智能体运行中,有六次会失败。这个数学计算是无情的。

级别 2:函数调用/工具使用。 所有主要提供商都暴露了一个 API,你可以在其中定义 JSON 模式,模型应该填充它。这能让你达到 95-99% 的可靠性。但问题是:模式是一个提示,而不是一个约束。模型将模式视为其上下文的一部分并学习遵循它——但解码过程中的任何环节都无法阻止它生成无效 token。提供商仍然可能返回格式错误的数据包,尤其是在处理复杂模式或边缘情况输入时。

级别 3:带有约束解码的原生结构化输出。 这是在数学上保证 100% 模式有效性的地方。推理引擎根据你的模式构建一个有限状态机,并在每个生成步骤中屏蔽无效 token。模型实际上无法生成无法解析的输出。OpenAI 结合 json_schemaresponse_format、Gemini 的 response_schema,以及 Outlines 等开源框架都使用了这种方法。

如果你正在构建任何需要可靠下游解析的东西——分类管道、智能体工具调用、数据提取——你都会需要级别 3。

约束解码的实际工作原理

理解其实现方式是值得的,因为它决定了你能使用和不能使用哪些模式。

在每个生成步骤中,模型都会在其整个词汇表(50,000 多个 token)上生成一个概率分布。通常,你会从该分布中采样。而使用约束解码时,你首先构建一个有限状态机,代表你的 JSON 模式中的每个有效路径。在采样之前,你会计算一个token 掩码:一个布尔向量,其中 false 意味着“给定 FSM 中的当前状态,这个 token 不能出现在这里”。你将这些 logits 置零,然后从剩余的 logits 中采样。

结果是:模型只能生成那些能够推进模式有效完成的 token。这不是后处理——它融入到每一个解码步骤中。

早期的实际开销曾是一个担忧。为复杂模式构建初始 FSM 可能需要 50-200 毫秒。但像 XGrammar(来自 MLC 团队)这样的引擎能够在每个 token 不到 40 微秒内完成 token 掩码生成,后续请求则重用缓存的 FSM,开销几乎为零。对于简单模式,延迟影响低于 5%。对于具有大型枚举集的深度嵌套模式,延迟可能达到 30-60%——这真正提示你需要简化你的模式了。

模式设计:团队常犯的错误

即使约束解码强制执行了语法有效性,糟糕的模式设计仍然会导致语义失败。以下是困扰大多数团队的模式:

将推理置于结论之前。 如果你的模式有一个 reasoning 字段和一个 classification 字段,请将 reasoning 放在前面。大语言模型从左到右生成 token。当模型在确定分类之前写出其推理时,它会产生更好的分类。如果你将答案字段放在前面,模型会在思考之前确定一个标签,然后才在推理字段中进行合理化。这听起来像大语言模型的一个怪癖,但它确实能持续地将准确性提高几个百分点。

扁平化你的模式。 嵌套是可靠性的大敌。OpenAI 的原生结构化输出最多支持 5 层嵌套和 100 个总属性。超出这个范围,语法编译时间会飙升,每个 token 的开销也会增加。更重要的是,即使有约束解码,具有 4 层以上深度嵌套的模式也表现出明显更高的错误率——模型有更多机会失去上下文。如果你的模式深度嵌套,请问问自己,这种嵌套是反映了实际的数据层次结构,还是仅仅是组织偏好。

描述每个字段。 Pydantic 的 Field(description=...) 值作为内联指令传递给模型。如果没有描述,模型将仅从字段名推断语义。confidence: float——它是 0-1 还是 0-100?status: str——有效值是什么?字段描述不是文档;它们是直接影响输出质量的提示指令。

明确处理可选性。 OpenAI 的结构化输出不支持你期望的那种可选字段。如果一个字段可以不存在,请将其建模为带有默认值 NoneOptional[str],而不是仅仅是 没有默认值的 str | None。提供商对这种区别处理不同,如果处理不当,会在运行时产生神秘的“无效模式”错误。

避免复杂模式。 带有复杂模式的正则表达式约束字段、具有多个分支的 oneOf 和递归模式会在 FSM 中造成组合爆炸。如果你需要“一个或多个与某个模式匹配的项”,请考虑将问题分解为多个顺序调用,而不是在单个模式中表达它。

实践中的服务商格局

每个主要的服务商都有不同的 API 接口,其抽象层无法清晰地在不同服务商之间转换。

OpenAI 提供了最成熟的实现。你可以使用 client.beta.chat.completions.parse() 搭配 Pydantic 模型,它能处理模式转换并返回一个类型化的 Python 对象。使用原始 JSON 模式的 response_format 方法也有效,但需要手动构建模式。.parse() 方法是正确的默认选择。

Anthropic 没有专用的结构化输出 API。其惯用模式是强制使用工具:将你的模式定义为一个工具,然后设置 tool_choice 强制模型调用它。如果没有 tool_choice: {type: "tool", name: "your_tool"},模型可能会选择根本不使用该工具。这并非约束解码——它仍然是 Level 2——但比提示工程要可靠得多。

Google Gemini 提供了带有约束解码的 response_schema,类似于 OpenAI 的方法。该 API 接受原始 JSON 模式而非 Pydantic 模型,所以你需要模式转换工具。

对于跨多个服务商工作的团队来说,Instructor 库抽象了这些差异。它在 OpenAI、Anthropic、Gemini 等平台之间提供了一个统一的 client.chat.completions.create(response_model=YourPydanticModel) 接口。Instructor 还能处理验证失败时的自动重试——如果模型返回了未能通过 Pydantic 验证的内容,它会附带错误信息重新提示并再次尝试。

验证三明治

即使在使用原生结构化输出时,也务必在其之上添加一个验证层。这并非多疑——这是为了防范语法约束无法捕捉的语义失败。

from openai import OpenAI
from pydantic import BaseModel, field_validator

class ClassificationResult(BaseModel):
reasoning: str
label: str
confidence: float

@field_validator("confidence")
def confidence_must_be_normalized(cls, v):
if not 0.0 <= v <= 1.0:
raise ValueError(f"confidence must be between 0 and 1, got {v}")
return v

@field_validator("label")
def label_must_be_valid(cls, v):
valid_labels = {"positive", "negative", "neutral"}
if v not in valid_labels:
raise ValueError(f"label must be one of {valid_labels}, got {v}")
return v

client = OpenAI()
result = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[...],
response_format=ClassificationResult,
)

# result.choices[0].message.parsed is already a ClassificationResult
# but Pydantic validators run during construction, so they've already fired

模式强制结构。Pydantic 验证器强制语义。两者你都需要。

约束解码保证的是语法有效性,而非语义正确性。模型可以在一个浮点数字段中返回 confidence: 1.7 并满足模式。它也可以从模式的枚举中返回一个对于输入而言语义上错误的标签。验证器能捕捉前者;评估则捕捉后者。

智能体链中的结构化输出

在多步骤工作流中,可靠性计算会变得更糟。每个返回结构化数据的工具调用都是一个模式验证可能失败的步骤。通过 Instructor 的重试行为,失败会附带错误上下文进行重试——但重试会消耗 token 和延迟,而且某些故障模式会循环。

这里有两种有助于解决问题的模式:

在每个步骤中缩小你的模式。 不要将一个庞大复杂的模式贯穿于每个工具调用。在每个步骤中,只提取你需要用于下一步的数据。更小的模式具有更低的失败率和更少的开销。

每次调用都记录模式版本。 模式会演变,而错误往往源于模式变更未在所有地方传播。将模式版本与提示和响应一同记录。当出现问题时,你可以根据当时生效的模式重放精确的输入。

仍然存在的问题

约束解码解决了解析问题,而非建模问题。无论模式强制如何,一些故障模式依然存在:

幻觉枚举值。 如果你的模式允许 enum: ["gpt-4", "claude-3-5-sonnet", "gemini-2-0-flash"],而你添加了一个新模型却忘记更新模式,模型将被迫返回其中一个有效值——但它可能会自信地返回错误的值。模式约束并不能让模型准确;它们只是让模型可解析。

长链中的语义漂移。 在多步骤管道中,第 N 步的结构化输出会作为第 N+1 步的提示输入。意义上的错误(而非格式上的)会以解析检查无法检测到的方式累积。这时,评估和抽查比工具更重要。

调用者之间的模式不匹配。 在包含多个服务的生产系统中,调用服务中的模式定义与下游消费者期望的模式定义不一致是很常见的。将你的 Pydantic 模型视为事实的唯一来源,并将其作为一个包共享,而不是复制粘贴的字典。

默认应该是 Level 3

原生结构化输出的工程论点很简单:提示工程增加了重试复杂性,函数调用增加了验证复杂性,两者都增加了在凌晨 2 点调试起来令人烦恼的故障模式。带有 Pydantic 验证层的原生结构化输出为你提供了最强的保证,并消除了一整类生产事故。

工具链已成熟。XGrammar 使约束解码足够快,对于简单模式而言,延迟很少成为问题。Instructor 库消除了服务商特定的样板代码。在 2025 年,没有充分的理由使用 Level 1 解析交付新的 LLM 管道。

唯一的真正代价是模式设计规范。扁平模式、描述性字段、显式可选性、先推理后结论的顺序——这些都不是复杂的要求,但它们需要刻意为之。这种规范正是区分在演示中有效与在生产中可靠运行的 LLM 功能的关键。