跳到內容

進階★★★★10 分鐘閱讀

從零寫一個 agent loop(不用任何框架)

把抽象層拿掉,實際的 loop 大概 50 行。在你拿 LangGraph 之前先自己寫一次。

登入以收藏

新手剛開始用 LLM 蓋東西時,通常一上來就抓 agent 框架 —— LangGraph、CrewAI、Mastra、AutoGen —— 然後立刻被 nodes、edges、state schema、各種抽象層搞到迷路。半年後才發現,框架在解的是它自己的問題,而你真正的問題它沒解。

從零寫一次 agent loop 會讓你真的搞懂「agent」是什麼。寫完一次以後,你才會知道你需不需要框架。劇透:80% 的產品不需要。

agent 真實的樣子

單一 LLM 呼叫回答一個問題就停。agent 就是把 LLM 放在一個 loop 裡,給它工具。就這樣。虛擬碼:

while not done:
  response = llm.generate(messages, tools)
  if response.is_final:
    return response.text
  for tool_call in response.tool_calls:
    result = execute(tool_call)
    messages.append(tool_result(result))

其他東西 —— 多 agent 編排、routing、supervisor —— 都是這個 loop 外面包一層。把 loop 寫對,其他都是裝飾。

一個能跑的範例:研究型 agent

來蓋一個小 agent,能用搜尋回答事實問題。兩個工具:search(query)fetch(url)。我們用 Anthropic 的 TypeScript SDK,但 OpenAI / Gemini 形狀基本上一樣。

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const tools = [
  {
    name: "search",
    description: "搜尋網路。回傳前 5 筆結果,含標題、URL、片段。",
    input_schema: {
      type: "object",
      properties: { query: { type: "string" } },
      required: ["query"],
    },
  },
  {
    name: "fetch",
    description: "抓一個網頁的全文,最多 5000 字。",
    input_schema: {
      type: "object",
      properties: { url: { type: "string" } },
      required: ["url"],
    },
  },
];

async function executeTool(name: string, input: any): Promise<string> {
  if (name === "search") return await webSearch(input.query);
  if (name === "fetch") return await fetchPage(input.url);
  return `Unknown tool: ${name}`;
}

async function runAgent(question: string, maxSteps = 10) {
  const messages: any[] = [{ role: "user", content: question }];

  for (let step = 0; step < maxSteps; step++) {
    const response = await client.messages.create({
      model: "claude-sonnet-4-7",
      max_tokens: 4096,
      tools,
      messages,
    });

    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason === "end_turn") {
      const textBlock = response.content.find((b) => b.type === "text");
      return textBlock?.text ?? "";
    }

    if (response.stop_reason === "tool_use") {
      const toolResults = [];
      for (const block of response.content) {
        if (block.type !== "tool_use") continue;
        const result = await executeTool(block.name, block.input);
        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: result,
        });
      }
      messages.push({ role: "user", content: toolResults });
      continue;
    }

    throw new Error(`Unexpected stop_reason: ${response.stop_reason}`);
  }

  throw new Error(`Agent exceeded ${maxSteps} steps`);
}

這就是整個 agent。42 行。執行 runAgent("Anthropic 在 2026 年三月公佈了什麼?"),看模型自己搜尋、讀網頁、整合答案。

loop 一步一步在幹嘛

  1. 把使用者問題加上工具 schema 送給模型。
  2. 模型決定:資訊夠了直接回答,還是要呼叫工具?
  3. 如果 stop_reason === "end_turn" 就是答完了,return。
  4. 如果 stop_reason === "tool_use" 就是它要呼叫一個或多個工具。執行每一個,結果 append 進 message 歷史。
  5. 繼續迴圈。下一輪模型會看到自己之前的呼叫跟結果。

模型沒有「呼叫」工具 —— 是你的程式在呼叫。模型只是吐出一個結構化請求,你跑真正的函式,把結果 append 回去讓它下一輪看到。

五件會出包的事

自己寫一次會體驗到所有框架在抽象掉的失敗模式:

  1. 無限迴圈。 沒有 maxSteps,一個一直要呼叫工具的 agent(或一直撞同一個失敗工具)會把你的預算燒光。永遠要有上限。
  2. token 膨脹。 每一輪 message 歷史都會長,跑久了會撞 context window。解法:摘要舊輪次、把不需要的工具結果丟掉、或用 prompt caching。
  3. 工具錯誤不能拋例外。 fetch(url) 404 的時候,要把錯誤訊息當 tool result 回傳,不能 throw。throw 會殺掉整個 loop;模型其實能從錯誤訊息復原。
  4. JSON schema 漂移。 工具的 input schema 寫得鬆,模型會傳怪輸入進來。schema 寫嚴格、用 enum、server 端再驗一次。
  5. 停止條件。 「答完了」比想像中難。模型有時候會想問澄清問題(沒 tool_use 但也沒答完)。仔細讀 stop_reason

真的需要再加上的東西

基本 loop 跑起來之後,你會慢慢想要:

  • Streaming。 邊跑邊把模型的思考顯示出來。Anthropic 的 stream API 直接給你,buffer text delta 就好。
  • System prompt。 角色設定、輸出格式、拒答規則。放在 system 參數,不是 messages 裡。
  • 記憶。 長期對話需要摘要或 vector recall。第一天不需要拿記憶函式庫 —— 把 messages 存到檔案再讀回來就夠了。
  • 追蹤 / log。 印出每次模型輸出、每次工具呼叫、每次結果。你會讀這些 log 幾百次。
  • 成本追蹤。 每次 response.usage 有 token 數,加總起來。

什麼時候不要自己寫

  • 多 agent 協同共享狀態。 五個 agent 要透過 graph 協同,有重試、平行分支、human-in-the-loop,LangGraph 真的在解真問題。先自己寫一次,等真的痛了再升級。
  • 你想要現成的 observability。 LangSmith / Langfuse 直接給你 UI。如果你不打算自己蓋 dashboard,值得用。
  • >5 人團隊一起寫 agent 程式碼。 共用框架可以給你新人 onboarding 文件、type hint、共同詞彙。

單一產品有一兩個專業 agent,從零寫的 loop 出貨快、debug 容易、換模型不用重寫整個堆疊。

你真正學到的東西

寫完這個 loop 之後,「agent」這整件事就不神祕了。多 agent 系統?就是一個 loop 把另一個 loop 當工具呼叫。Planning agent?同一個 loop 加 planner_stepexecutor_step 工具。自我修正?同一個 loop 加一個叫 critic 的工具。

框架不是錯的,只是比你以為的早。先寫 50 行版本。

下一步

  • Anthropic 官方 tool use 文件。
  • Building effective agents —— Anthropic 部落格,2024 年 12 月。讀兩遍。
  • 查這些詞:ReAct prompting、function calling、MCP for tool sharing、agent observability。

最後更新: 2026-04-29

We use cookies

Anonymous analytics help us improve the site. You can opt out anytime. Learn more