深度分析LLM代码生成与生产级Agent的根本差异,强调状态管理、可观察性、控制流确定性的必要性。揭示多数AI项目失败的工程根因。
最初发布于 tamiz.pro。
围绕大型语言模型(LLM)的第一波狂热,很大程度上源于它们生成代码的能力。我们看过这样的演示:开发者要求 LLM 编写一个 React 组件、一条 Python 数据管道或一条 SQL 查询,然后代码瞬间出现在眼前。这项能力固然令人印象深刻,但它与 AI Agent 带来的工程挑战有着本质区别。代码生成器是一种无状态工具;AI Agent 则是一个有状态的自主实体,会与外部系统交互、做出决策,并随着时间推移执行操作。
把 AI Agent 当作代码生成器,是大多数 AI 项目无法进入生产环境的首要原因。当你从生成静态产物转向编排动态的多步骤工作流时,复杂性的重心也会从语法正确性转移到系统可靠性上。在这篇深度解析中,我们将探讨为什么生产级 AI Agent 需要一场工程范式的转变,并重点关注三个关键支柱:严谨的状态管理、全面的可观测性,以及确定性的控制流。
要理解其中的工程鸿沟,首先必须区分代码生成器与 Agent 分别在做什么。
代码生成器运行在 Request -> Response 循环中。输入是 prompt,输出是一段代码。LLM 不会在多次调用之间保留记忆,不会修改外部状态(例如数据库),也不会根据运行时错误自行决定下一步该做什么。它是一个高熵函数。
AI Agent 本身就是一个循环。它观察环境,推理下一步的最佳行动,执行该行动(通常通过工具或 API),然后观察执行结果。由此形成一个反馈循环:
Observe -> Think -> Act -> Observe -> Think -> ...
这个循环引入了静态代码生成所不具备的多种工程复杂性:
非确定性:Agent 在工作流中采用的路径并不固定。它取决于 LLM 的推理,而即使输入相同,这种推理在不同运行之间也可能发生变化。
副作用:Agent 经常需要与外部世界交互,例如发送电子邮件、更新数据库和调用 API。这些操作不可逆,必须谨慎处理。
状态累积:Agent 在处理复杂任务的过程中会不断累积上下文。如果不对这些状态进行严格管理,Agent 就可能遭遇上下文窗口溢出,或根据过时的信息产生幻觉。
失败模式:脚本会因为语法错误而明确失败,但 Agent 可能以静默方式失败,例如选错工具、使用无效参数调用 API,或者陷入重试循环。
在传统软件工程中,状态通过变量、数据库记录或 session store 管理。在 AI Agent 中,状态分散在 LLM 的上下文窗口和外部系统之间。这种碎片化是 bug 的一个主要来源。
设想一个简单的 Agent,它的任务是“研究一个主题并撰写报告”。一种朴素的实现方式,可能是在每一步都把完整对话历史传递给 LLM。随着对话越来越长,上下文窗口会逐渐被填满,进而导致:
成本爆炸:token 越多,API 成本越高。
性能下降:prompt 越长,处理所需的时间越久。
注意力稀释:LLM 可能忘记早期指令,或者忽略深埋在上下文中的关键事实。
生产环境中的 Agent 不应该只依赖 LLM 的记忆。相反,它们应该使用显式状态机或结构化数据存储来跟踪进度。
不要把原始文本一股脑塞进上下文,而应维护一个表示任务当前状态的结构化 JSON 对象。该状态应该包括:
任务进度:哪些步骤已经完成、仍在等待或已经失败。
关键事实:提取出的实体、数据点或已经做出的决策。
工具输出:缓存之前工具调用的结果,避免重复调用 API。
interface AgentState {
taskId: string;
status: 'initializing' | 'researching' | 'drafting' | 'reviewing' | 'completed' | 'failed';
progress: {
researchSteps: number;
totalResearchSteps: number;
sourcesConsulted: string[];
};
context: {
keyFacts: string[];
userPreferences: Record<string, any>;
};
metadata: {
createdAt: Date;
updatedAt: Date;
lastError?: string;
};
}
如果 Agent 崩溃或超时,你需要让它从上一个已知的正常状态继续执行,而不是从头开始。这就要求在关键检查点将 AgentState 持久化到数据库中,例如 PostgreSQL 或 Redis。
Agent 恢复运行时,会加载状态,根据关键事实和进度重新构建上下文,然后从中断的位置继续执行。这对于长时间运行的任务至关重要。
可以使用上下文摘要或向量检索等技术来管理 LLM 的上下文窗口。不要传递完整历史,而是传递:
当前的 AgentState。
此前交互的摘要。
从向量数据库中检索到的相关片段。
这样既能减少 token 用量,也能让 LLM 专注于相关信息。
无法度量,就无法改进。在传统软件中,我们使用日志、指标和 trace。面对 AI Agent 时,这些手段必须进行调整,以适应非确定性的多步骤工作流。
传统日志是线性的,以事件为基础。AI Agent 的执行过程则是一张由节点和边组成的图。像 "Tool called: search_api" 这样简单的日志行远远不够,因为它无法捕获:
Prompt:向 LLM 发送了什么?
响应:LLM 做出了什么决定?
延迟:每个步骤花了多长时间?
成本:消耗了多少 token?
置信度:LLM 是否表达了不确定性?
采用能够感知 LLM 的分布式追踪框架,例如 OpenTelemetry。Agent 工作流中的每个步骤都应该成为 trace 中的一个 span。
输入/输出 Prompt:存储每次 LLM 调用的完整 prompt 和响应。这对于调试幻觉以及优化 prompt 至关重要。
工具调用详情:如果 Agent 使用工具,应记录工具名称、输入参数和输出结果。
Token 用量:跟踪输入和输出 token,用于监控成本。
延迟:把延迟拆分为 LLM 处理时间、工具执行时间和网络开销。
下面展示了如何使用 OpenTelemetry 为 Agent 的一个步骤添加监测:
const tracer = opentelemetry.trace.getTracer('ai-agent-tracer');
async function executeResearchStep(agentState, toolClient) {
return await tracer.startActiveSpan('research.step', async (span) => {
try {
// Log the prompt sent to the LLM
span.setAttribute('llm.prompt', agentState.researchPrompt);
// Call the LLM
const response = await llmClient.generate({
prompt: agentState.researchPrompt,
model: 'gpt-4-turbo'
});
// Log token usage
span.setAttribute('llm.usage.total_tokens', response.usage.total_tokens);
span.setAttribute('llm.usage.prompt_tokens', response.usage.prompt_tokens);
span.setAttribute('llm.usage.completion_tokens', response.usage.completion_tokens);
// Parse the response to extract tool calls
const toolCalls = parseToolCalls(response.text);
// Execute tool calls
for (const call of toolCalls) {
const toolResult = await toolClient.execute(call);
span.setAttribute(`tool.${call.name}.result`, toolResult);
}
span.setStatus({ code: opentelemetry.SpanStatusCode.OK });
return response;
} catch (error) {
span.recordException(error);
span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR, message: error.message });
throw error;
} finally {
span.end();
}
});
}
使用 LangSmith、Phoenix 或 Arize 等仪表盘将 trace 可视化。这样你就可以:
识别瓶颈:查看哪些步骤执行缓慢。
调试故障:理解 Agent 为什么选择了错误的行动。
监控成本:跟踪每次 Agent 运行所消耗的 token。
比较运行结果:通过并排比较 trace,对不同 prompt 或模型进行 A/B 测试。
LLM 是概率性的。它们擅长创造性任务,却很不擅长确定性逻辑。生产环境中的 Agent 必须把“思考”部分(由 LLM 负责)与“行动”部分(由确定性代码负责)分离开来。
不要让 LLM 控制整个工作流。应该把 LLM 用于:
意图识别:用户想要实现什么目标?
信息提取:需要哪些数据?
决策:下一步应该使用哪个工具?
把确定性代码用于:
编排:管理状态机。
验证:确保工具输入正确。
错误处理:重试失败的步骤。
安全:清理输入和输出。
绝不要盲目信任 LLM 的输出。始终要根据 schema 对其进行验证。
import { z } from 'zod';
const ToolCallSchema = z.object({
tool: z.enum(['search', 'extract', 'summarize']),
params: z.object({
query: z.string(),
filters: z.object({ dateRange: z.string().optional() }).optional()
})
});
async function safeToolCall(llmResponse: string) {
try {
// Parse the LLM's response
const parsed = JSON.parse(llmResponse);
// Validate against the schema
const validated = ToolCallSchema.parse(parsed);
// Execute the tool
return await executeTool(validated.tool, validated.params);
} catch (error) {
// Handle validation errors gracefully
if (error instanceof z.ZodError) {
logger.warn('Invalid tool call structure', { error: error.errors });
return { error: 'Invalid tool call structure' };
}
throw error;
}
}
由于 LLM 的输出具有非确定性,因此你需要采用相应策略来应对变化:
降低 Temperature 后重试:如果某个步骤失败,以更低的 temperature 重试,从而获得更加确定的结果。
自我纠正:允许 Agent 批判并完善自己的输出。例如,如果工具调用失败,可以把错误信息传回 LLM,并要求它修正参数。
Fallback:为关键步骤准备确定性的 fallback。例如,如果 LLM 无法提取数据,就使用基于规则的解析器作为备用方案。
不要从第一天起就构建复杂的多 Agent 系统。先从一个解决特定问题的单 Agent 工作流开始。首先为这个 Agent 做好状态管理、可观测性和控制流,然后再逐步增加复杂性。
AI Agent 可能遭受注入攻击、prompt injection 和数据泄漏。务必做到:
清理用户输入。
验证工具输出。
根据用户权限限制工具访问。
对状态存储中的敏感数据进行加密。
AI 并不是设置好之后就可以置之不理的系统。要持续监控 Agent 的表现,重点关注:
高错误率:某些步骤是否频繁失败?
高延迟:某些步骤是否执行缓慢?
高成本:某些 prompt 是否效率低下?
用户反馈:用户对结果是否满意?
利用这些数据优化 prompt、改进状态管理并增强可观测性。
从代码生成器迈向生产级 AI Agent,并不仅仅是一个扩展规模的问题,更是一项根本性的工程挑战。它要求我们转变思维方式:从编写静态代码,转向编排动态的、有状态的工作流。
通过实施严谨的状态管理、全面的可观测性和确定性的控制流,你可以构建出可靠、经济且安全的 AI Agent。这些支柱并非可有可无的附加项,而是所有生产级 AI 系统的根基。
AI 工程的未来不仅取决于更好的模型,也取决于更好的系统。掌握这些工程原则,你就能为构建下一代智能应用做好充分准备。
Q:我可以使用 LangChain 或 LlamaIndex 等现有框架来构建生产环境中的 Agent 吗?
A:可以,但需要谨慎。这些框架非常适合制作原型,也提供了状态和可观测性相关的抽象。不过,在生产环境中,你通常需要对它们进行定制或扩展,才能满足特定的安全、性能和成本要求。不要把它们当作“黑盒”;你需要理解其底层机制。
Q:如何处理需要运行数小时或数天的 Agent?
A:使用持久化状态存储(例如数据库),定期保存 Agent 的状态。实现一种从上一个检查点恢复 Agent 的机制。可以考虑使用事件驱动架构,例如 AWS Lambda 或 Azure Functions,以异步方式触发 Agent 的各个步骤。
Q:调试行为异常的 AI Agent,最好的方法是什么?
A:使用分布式追踪将 Agent 的执行流程可视化。寻找 LLM 做出意外决策或工具调用失败的步骤。分析这些步骤的 prompt 和响应,找出问题所在。使用 A/B 测试比较不同的 prompt 或模型。
若想了解更多关于构建生产就绪 AI 系统的见解,请访问 Tamiz's Insights。
如果需要采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。