LLM Agent 概念:從基礎問答到多角色協作

五個層級的 pseudo code,展示從「單純問答」到「會做事的 agent」、「多個角色互相協作」、「固定流程的 workflow」,再到「開票分工的 workflows」的演進。第三、四層有真的可以跑的程式(team.jsworkflow.js),第五層純概念示意,實際實作參考 Skill(agent-claude-bot)

1. 基礎:單純問答

一來一回,沒有記憶、沒有動作,純粹「問 → 答」。

const response = await claude("hi 你是誰")
console.log(response)
// "我是AI"

對應這個資料夾裡的 llm.jsnode llm.js "hi"

2. Agent:不只回答,還會做事

多了「工具」(tools)——claude 不是只吐文字,還能實際呼叫函式(寫檔案、跑指令…),自己決定要不要用、用幾次。

const result = await claude("寫一篇文章並存檔", {
  tools: [writeFile, readFile /* ... */],
})
// claude 內部自己決定:先想文章內容 → 判斷需要存檔 → 呼叫 writeFile 工具 → 回報完成
console.log(result)
// "已將文章存到 article.md"

跟第 1 層的差別:第 1 層的輸出只是文字;第 2 層的輸出可能是「文字+一連串工具呼叫造成的副作用」。

3. Agent Teams:多角色輪流協作

單一 agent 換成兩個各自扮演角色、輪流發言的 agent,每輪都把目前為止的對話 history 整包帶給下一個角色——這樣後面發言的角色才看得到前面說過什麼。

let history = ""

while (true) {
  const writerReply = await claude(`歷史對話:${history}\n你是作家,說出你的想法`)
  history += `作家說:${writerReply}`

  const editorReply = await claude(`歷史對話:${history}\n你是編輯,要從讀者角度說要怎麼改`)
  history += `編輯說:${editorReply}`
}

真正可執行的版本在 team.jswhile(true) 換成有限輪數,避免無止盡打 API 燒 quota):

node team.js "主題" [輪數]

每輪印出當前角色(作家/編輯)的發言,並把它累加進 history 傳給下一輪。

4. Workflow:定義一連串的動作

跟第 3 層不一樣的地方:Agent Teams 是「角色輪流講話、講到你手動停下來」,走向是即興的;Workflow 是先把步驟定死(像 pipeline),每一步做什麼、順序是什麼都寫在流程定義裡,跑起來是固定路徑,不是角色間的自由對話。上一步的輸出直接餵給下一步當輸入。

const workflow = [
  { name: "大綱", prompt: (topic) => `幫「${topic}」列一個大綱` },
  { name: "草稿", prompt: (topic, prev) => `依照這個大綱寫一段草稿:\n${prev}` },
  { name: "潤稿", prompt: (topic, prev) => `潤飾這篇草稿,讓它更好讀:\n${prev}` },
]

let result = topic
for (const step of workflow) {
  result = await claude(step.prompt(topic, result))
  console.log(`【${step.name}】`, result)
}

真正可執行的版本在 workflow.js

node workflow.js "主題"

固定跑「大綱 → 草稿 → 潤稿」三步,每步的輸出接下一步的輸入,最後印出潤稿完成的成品。

5. Workflows:開票、多條 workflow 平行跑

規模再大一點的 workflow 常常會先「開 ticket」——把工作拆成一張一張票,每張票各自走「規劃 → 閱讀 code → implement → test → review → commit」,可以多個 worker 平行處理不同票。這層只給概念示意,沒有真的可執行檔案。

const tickets = await pm.listTickets({ status: "todo" })

for (const ticket of tickets) {
  await pm.setStatus(ticket.id, "in_progress")

  const plan = await claude(`規劃怎麼做這張票:${ticket.doc}`)
  const context = await claude(`讀這些檔案,理解現有寫法:${plan.filesToRead}`)
  const diff = await claude(
    `依照規劃實作:${plan}\n\n現有程式碼:${context}`,
    { tools: [readFile, writeFile, runShell] }
  )

  await pm.setStatus(ticket.id, "testing")
  const testResult = await runTests()
  if (!testResult.pass) {
    await pm.setStatus(ticket.id, "debugging")
    continue // 換下一張票,這張之後再處理
  }

  await claude(`review 這個 diff,format + lint 過再定稿:${diff}`, { tools: [runShell] })
  await claude(`把改動 commit 起來,寫一句符合這張票的 commit message:${diff}`, { tools: [runShell] })
  await pm.setStatus(ticket.id, "review") // 交給人審
}

這層沒有自己寫的可執行檔案——真實世界已經有現成的兩種對應: