Originally published on tamiz.pro.

大型語言模型(LLM)最初帶來的熱潮,主要來自它們生成程式碼的能力。我們看到開發者要求 LLM 撰寫 React 元件、Python 資料管線或 SQL 查詢,程式碼便即時出現。雖然這項能力令人印象深刻,但與 AI Agent 所帶來的工程挑戰有本質上的不同。程式碼生成器是無狀態的工具;而 AI Agent 則是具狀態、自主的實體,能與外部系統互動、做出決策,並隨時間執行動作。

把 AI Agent 當成程式碼生成器,是大多數 AI 專案無法上線的主要原因。當你從產生靜態產物轉向編排動態、多步驟工作流程時,複雜度便從語法正確性轉移到系統可靠性。本文將深入探討為什麼生產級 AI Agent 需要工程思維的轉變,聚焦三大關鍵支柱:嚴謹的狀態管理、全面的觀測性,以及確定性的控制流程。

根本轉變:無狀態生成 vs. 具狀態執行

要理解工程落差,我們必須先區分程式碼生成器與 Agent 的差異。

程式碼生成器(Code Generator) 運行於 Request -> Response 迴圈。輸入是提示詞,輸出是一段程式碼。LLM 不保留呼叫之間的記憶、不修改外部狀態(如資料庫),也不會根據執行時錯誤自行決定下一步。它是一個高熵值的函式。

AI Agent 則是一個迴圈。它觀察環境、推論下一個最佳動作、執行該動作(通常透過工具或 API),再觀察結果。這形成了一個回饋迴圈:

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

這個迴圈引入了靜態程式碼生成所沒有的幾項工程複雜度:

  1. 非確定性: Agent 在工作流程中的路徑並非固定。它取決於 LLM 的推論,即使輸入相同,每次運行的結果也可能不同。
  2. 副作用: Agent 經常與外部世界互動(寄送郵件、更新資料庫、呼叫 API)。這些動作是不可逆的,必須謹慎處理。
  3. 狀態累積: 當 Agent 處理複雜任務時,會累積上下文。若狀態管理不嚴謹,Agent 將面臨上下文視窗溢位,或根據過時資訊產生幻覺。
  4. 失敗模式: 與在語法錯誤時失敗的腳本不同,Agent 可能因選擇錯誤的工具、以無效參數呼叫 API,或陷入重試迴圈而靜默失敗。

支柱一:嚴謹的狀態管理

在傳統軟體工程中,狀態透過變數、資料庫紀錄或 session store 管理。在 AI Agent 中,狀態分散於 LLM 的上下文視窗與外部系統之間。這種碎片化是 Bug 的主要來源。

隱式狀態的問題

考慮一個被指派「研究主題並撰寫報告」的簡單 Agent。天真的實作可能在每一步都把整個對話歷史傳給 LLM。隨著對話增長,上下文視窗會填滿,導致:

  • 成本爆炸: 更多 token = 更高的 API 成本。
  • 效能下降: 更長的提示詞需要更長的處理時間。
  • 注意力稀釋: LLM 可能忘記較早的指示,或忽略深埋在上下文中的關鍵事實。

解決方案:顯式狀態機

生產級 Agent 不應僅依賴 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. 狀態持久化與恢復

如果 Agent 當機或逾時,你需要從最後一個已知良好狀態恢復,而不是重新開始。這需要在關鍵檢查點將 AgentState 持久化到資料庫(如 PostgreSQL、Redis)。

當 Agent 恢復時,它會載入狀態、從關鍵事實與進度重建上下文,然後從中斷處繼續。這對於長時間運行的任務至關重要。

3. 上下文視窗管理

使用 上下文摘要向量檢索 等技術來管理 LLM 的上下文視窗。不要傳遞整個歷史,而是傳遞:

  • 目前的 AgentState
  • 先前互動的摘要。
  • 從向量資料庫檢索的相關區塊。

這可以減少 token 使用量,並讓 LLM 專注於相關資訊。

支柱二:觀測性與追蹤

無法衡量的事物就無法改善。在傳統軟體中,我們使用日誌、指標與追蹤。對於 AI Agent,這些必須適應非確定性、多步驟的工作流程。

傳統日誌的極限

傳統日誌是線性且基於事件的。AI Agent 的執行是一個由節點與邊組成的圖形。一個簡單的日誌行如 "Tool called: search_api" 是不夠的,因為它無法捕捉:

  • 提示詞: 傳送給 LLM 的是什麼?
  • 回應: LLM 做了什麼決定?
  • 延遲: 每個步驟花了多少時間?
  • 成本: 消耗了多少 token?
  • 信心度: LLM 是否表達了不確定性?

解決方案:LLM 的分散式追蹤

採用 LLM 感知的分散式追蹤框架(如 OpenTelemetry)。Agent 工作流程中的每一步都應該是追蹤中的一個 span

Agent Span 的關鍵屬性

  1. 輸入/輸出提示詞: 儲存每次 LLM 呼叫的完整提示詞與回應。這對於除錯幻覺與優化提示詞至關重要。
  2. 工具呼叫細節: 如果 Agent 使用工具,請記錄工具名稱、輸入參數與輸出結果。
  3. Token 使用量: 追蹤輸入與輸出 token,以進行成本監控。
  4. 延遲: 將延遲細分為 LLM 處理時間、工具執行時間與網路開銷。

範例:OpenTelemetry 整合

以下是如何使用 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();
    }
  });
}

Enter fullscreen mode Exit fullscreen mode

視覺化與除錯

使用儀表板(如 LangSmith、Phoenix 或 Arize)來視覺化追蹤。這允許你:

  • 識別瓶頸: 查看哪些步驟較慢。
  • 除錯失敗: 了解為什麼 Agent 選擇了錯誤的動作。
  • 監控成本: 追蹤每次 Agent 運行的 token 使用量。
  • 比較運行: 透過並排比較追蹤,A/B 測試不同的提示詞或模型。

支柱三:確定性的控制流程

LLM 是機率的。它們擅長創意任務,但不擅長確定性邏輯。生產級 Agent 必須將「思考」部分(由 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. 自我修正: 允許 Agent 批評自己的輸出並加以改進。例如,如果工具呼叫失敗,請將錯誤訊息傳回 LLM 並要求其修正參數。
  3. 備援: 為關鍵步驟提供確定性備援。例如,如果 LLM 無法擷取資料,請使用基於規則的解析器作為備份。

生產最佳實踐

1. 從小處開始,快速迭代

不要從一開始就建立複雜的多 Agent 系統。從解決特定問題的單一 Agent 工作流程開始。為該 Agent 建立正確的狀態管理、觀測性與控制流程。然後逐步增加複雜度。

2. 安全性優先

AI Agent 可能容易受到注入攻擊、提示詞注入與資料外洩。務必:

  • 清理使用者輸入。
  • 驗證工具輸出。
  • 根據使用者權限限制工具存取。
  • 將狀態儲存中的敏感資料加密。

3. 監控與迭代

AI 不是「設定後就忘記」。持續監控 Agent 的效能。注意:

  • 高錯誤率: 某些步驟是否經常失敗?
  • 高延遲: 某些步驟是否緩慢?
  • 高成本: 某些提示詞是否效率不佳?
  • 使用者回饋: 使用者對結果滿意嗎?

使用這些資料來改進提示詞、優化狀態管理並改善觀測性。

結論

從程式碼生成器轉向生產級 AI Agent 不僅是規模擴大的問題;這是一項根本的工程挑戰。它需要思維轉變,從撰寫靜態程式碼轉向編排動態、具狀態的工作流程。

透過實作嚴謹的狀態管理、全面的觀測性與確定性的控制流程,你可以建立可靠、具成本效益且安全的 AI Agent。這些支柱不是可有可無的附加功能;它們是任何生產級 AI 系統的基礎。

AI 工程的未來不僅在於更好的模型;更在於更好的系統。掌握這些工程原則,你將能妥善打造下一代智慧應用程式。

常見問題

問:我可以使用 LangChain 或 LlamaIndex 等現有框架來建立生產級 Agent 嗎?

答: 可以,但需謹慎。這些框架非常適合原型設計,並提供狀態與觀測性的抽象。然而,對於生產環境,你通常需要自訂或擴充它們,以滿足安全性、效能與成本的特定需求。避免將它們視為「黑箱」;請了解其底層機制。

問:如何處理需要數小時或數天才能完成的長時間運行 Agent?

答: 使用持久化狀態儲存(如資料庫)定期儲存 Agent 的狀態。實作機制以從最後的檢查點恢復 Agent。考慮使用事件驅動架構(如 AWS Lambda 或 Azure Functions)來非同步觸發 Agent 步驟。

問:除錯行為不正確的 AI Agent 的最佳方法是什麼?

答: 使用分散式追蹤來視覺化 Agent 的執行流程。尋找 LLM 做出意外決策或工具呼叫失敗的步驟。分析這些步驟的提示詞與回應以找出問題。使用 A/B 測試來比較不同的提示詞或模型。

欲了解更多關於建立生產就緒 AI 系統的見解,請造訪 Tamiz's Insights