Originally published on tamiz.pro.

围绕大语言模型(LLM)的最初狂热,很大程度上源于它们生成代码的能力。我们看到开发者要求 LLM 编写 React 组件、Python 数据管道或 SQL 查询,然后即时得到结果的演示。这种能力虽然令人印象深刻,但与 AI 智能体所带来的工程挑战有着本质区别。代码生成器是无状态工具;而 AI 智能体是一个有状态、自主的实体,会随时间与外部系统交互、做出决策并执行操作。

将 AI 智能体当作代码生成器对待,是大多数 AI 项目无法进入生产的主要原因。当你从生成静态产物转向编排动态、多步骤的工作流时,复杂性就从语法正确性转移到了系统可靠性。在这篇深入探讨中,我们将讨论为什么生产级 AI 智能体需要工程范式的转变,重点关注三大支柱:严谨的状态管理、全面的观测性以及确定性控制流。

根本转变:无状态生成 vs. 有状态执行

要理解工程差距,我们首先必须区分代码生成器与智能体的工作方式。

代码生成器运行在 Request -> Response 循环中。输入是提示,输出是代码片段。LLM 不会在调用之间保留记忆,不会修改外部状态(如数据库),也不会根据运行时错误决定下一步。它是一个高熵的函数。

AI 智能体是一个循环。它观察环境、推理下一个最佳行动、执行该行动(通常通过工具或 API),然后观察结果。这形成了一个反馈循环:

Observe -> Think -> Act -> Observe -> Think -> ...

这个循环引入了静态代码生成所没有的几种工程复杂性:

  1. 非确定性:智能体在工作流中的路径并不固定。它依赖于 LLM 的推理,即使输入相同,不同运行之间也可能不同。
  2. 副作用:智能体经常与外部世界交互(发送邮件、更新数据库、调用 API)。这些操作不可逆,必须谨慎处理。
  3. 状态累积:随着智能体完成复杂任务,它会累积上下文。如果不严格管理状态,智能体将面临上下文窗口溢出或基于过时信息产生幻觉。
  4. 失败模式:与在语法错误时失败的脚本不同,智能体可能通过选择错误的工具、使用无效参数调用 API 或陷入重试循环而静默失败。

支柱一:严谨的状态管理

在传统软件工程中,状态通过变量、数据库记录或会话存储进行管理。在 AI 智能体中,状态分散在 LLM 的上下文窗口和外部系统之间。这种碎片化是主要 bug 来源。

隐式状态的问题

考虑一个被分配“研究一个主题并撰写报告”的简单智能体。一种朴素实现可能会在每一步都将整个对话历史传递给 LLM。随着对话增长,上下文窗口会填满,导致:

  • 成本爆炸:更多 token = 更高的 API 成本。
  • 性能下降:更长的提示需要更长时间处理。
  • 注意力稀释:LLM 可能会忘记早期指令或埋在上下文深处的关键事实。

解决方案:显式状态机

生产级智能体不应仅依赖 LLM 的记忆。相反,它们应使用显式状态机或结构化数据存储来跟踪进度。

1. 结构化状态存储

不要将原始文本转储到上下文中,而是维护一个表示任务当前状态的结构化 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;
  };
}

Enter fullscreen mode Exit fullscreen mode

2. 状态持久化与恢复

如果智能体崩溃或超时,你需要从最后一个已知良好状态恢复,而不是重新开始。这需要在关键检查点将 AgentState 持久化到数据库(如 PostgreSQL、Redis)。

当智能体恢复时,它会加载状态,从关键事实和进度重建上下文,并从上次中断的地方继续。这对长时间运行的任务至关重要。

3. 上下文窗口管理

使用上下文摘要向量检索等技术来管理 LLM 的上下文窗口。不要传递整个历史,而应传递:

  • 当前的 AgentState
  • 先前交互的摘要。
  • 从向量数据库中检索的相关块。

这减少了 token 使用,并使 LLM 专注于相关信息。

支柱二:观测性与追踪

你无法改进无法测量的东西。在传统软件中,我们使用日志、指标和追踪。对于 AI 智能体,这些必须适应非确定性、多步骤工作流。

传统日志的局限

传统日志是线性的、基于事件的。AI 智能体的执行是一个节点和边的图。像 "Tool called: search_api" 这样的简单日志行是不够的,因为它无法捕获:

  • 提示:发送给 LLM 的是什么?
  • 响应:LLM 做出了什么决定?
  • 延迟:每一步花费了多长时间?
  • 成本:消耗了多少 token?
  • 置信度:LLM 是否表达了不确定性?

解决方案:面向 LLM 的分布式追踪

采用 LLM 感知的分布式追踪框架(如 OpenTelemetry)。智能体工作流中的每一步都应是追踪中的一个跨度

智能体跨度的关键属性

  1. 输入/输出提示:存储每次 LLM 调用的完整提示和响应。这对于调试幻觉和优化提示至关重要。
  2. 工具调用详情:如果智能体使用工具,请记录工具名称、输入参数和输出结果。
  3. Token 使用:跟踪输入和输出 token 以进行成本监控。
  4. 延迟:将延迟分解为 LLM 处理时间、工具执行时间和网络开销。

示例:OpenTelemetry 集成

以下是如何使用 OpenTelemetry 检测智能体步骤的方法:

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();
    }
  });
}

Enter fullscreen mode Exit fullscreen mode

可视化与调试

使用仪表板(如 LangSmith、Phoenix 或 Arize)来可视化追踪。这允许你:

  • 识别瓶颈:查看哪些步骤较慢。
  • 调试失败:了解智能体为何选择了错误的操作。
  • 监控成本:跟踪每次智能体运行的 token 使用情况。
  • 比较运行:通过并排比较追踪来 A/B 测试不同的提示或模型。

支柱三:确定性控制流

LLM 是概率性的。它们擅长创造性任务,但不擅长确定性逻辑。生产级智能体必须将“思考”部分(由 LLM 处理)与“行动”部分(由确定性代码处理)分开。

混合方法

不要让 LLM 控制整个工作流。相反,使用 LLM 进行:

  • 意图识别:用户试图实现什么?
  • 信息提取:需要什么数据?
  • 决策制定:接下来应该使用哪个工具?

使用确定性代码进行:

  • 编排:管理状态机。
  • 验证:确保工具输入正确。
  • 错误处理:重试失败的步骤。
  • 安全:清理输入和输出。

示例:护栏与验证

永远不要盲目信任 LLM 的输出。始终根据模式验证它。

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;
  }
}

Enter fullscreen mode Exit fullscreen mode

处理非确定性

由于 LLM 输出是非确定性的,你需要策略来处理可变性:

  1. 降低温度重试:如果步骤失败,请以较低温度重试以获得更确定的结果。
  2. 自我纠正:允许智能体批评自己的输出并进行改进。例如,如果工具调用失败,请将错误消息传递回 LLM 并要求它更正参数。
  3. 回退:为关键步骤准备确定性回退。例如,如果 LLM 未能提取数据,则使用基于规则的解析器作为备份。

生产最佳实践

1. 从小处开始,快速迭代

不要从第一天起就构建复杂的多智能体系统。从解决特定问题的单智能体工作流开始。先为该智能体正确处理状态管理、观测性和控制流。然后逐步增加复杂性。

2. 安全第一

AI 智能体容易受到注入攻击、提示注入和数据泄露的影响。始终:

  • 清理用户输入。
  • 验证工具输出。
  • 根据用户权限限制工具访问。
  • 在状态存储中加密敏感数据。

3. 监控与迭代

AI 不是“设置后即忘”。持续监控智能体的性能。关注:

  • 高错误率:某些步骤是否频繁失败?
  • 高延迟:某些步骤是否缓慢?
  • 高成本:某些提示是否低效?
  • 用户反馈:用户是否对结果满意?

使用这些数据来改进提示、优化状态管理并提升观测性。

结论

从代码生成器转向生产级 AI 智能体不仅仅是一个扩展问题;它是一个根本性的工程挑战。它需要一种从编写静态代码到编排动态、有状态工作流的心态转变。

通过实施严谨的状态管理、全面的观测性和确定性控制流,你可以构建可靠、成本效益高且安全的 AI 智能体。这些支柱不是可选的附加功能;它们是任何生产级 AI 系统的基础。

AI 工程的未来不仅仅关乎更好的模型;而是关乎更好的系统。掌握这些工程原则,你将有能力构建下一代智能应用。

常见问题

问:我可以使用 LangChain 或 LlamaIndex 等现有框架来构建生产级智能体吗?

答:可以,但需谨慎。这些框架非常适合原型设计,并提供状态和观测性的抽象。然而,对于生产环境,你通常需要定制或扩展它们,以满足安全、性能和成本的具体要求。不要将它们视为“黑盒”;要理解其底层机制。

问:如何处理运行数小时或数天的长时间智能体?

答:使用持久状态存储(如数据库)定期保存智能体的状态。实现从最后一个检查点恢复智能体的机制。考虑使用事件驱动架构(如 AWS Lambda 或 Azure Functions)异步触发智能体步骤。

问:调试行为不正确的 AI 智能体的最佳方法是什么?

答:使用分布式追踪来可视化智能体的执行流。查找 LLM 做出意外决策或工具调用失败的步骤。分析这些步骤的提示和响应以识别问题。使用 A/B 测试来比较不同的提示或模型。

要获取构建生产就绪 AI 系统的更多见解,请访问 Tamiz's Insights