# 只用 100+ 行扣寫一個超迷你 HackMD Agent [English](/@EastSun5566/building-a-tiny-hackmd-agent) **AI Agent** 大概是這陣子最常聽到的名詞了,看了一些相關介紹還是很模糊,到底是什麼鬼,竟然可以做到理解、決策並自主完成任務?感覺又很複雜,像是背後有什麼神秘的黑魔法。 其實不然。 AI 底層是個黑盒子,但 Agent 其實比想像中還明確。它就是==一個強大的 LLM 加上重複執行的迴圈以及一組與外部世界互動的工具==,工具可以直接想像成 **function / API call**,迴圈用來維持對話,讓 LLM 理解使用者意圖後並決定呼叫某個工具完成目標。所以我可以很簡單暴力的定義: :::info AI Agent = LLM + Loop + Tools ::: 對,其實就這樣!讓我們僅僅用 **100+** 行程式碼就可以實作一個迷你管理 HackMD 筆記的 Agent。~~沒有框架、MCP 或是 A2A 等太複雜的東西~~,完全從零開始。 也可以直接看完成的 repo: {%preview https://github.com/EastSun5566/tiny-hackmd-agent %} ## 準備實作 我們使用: - [Deno](https://deno.com/) v2+ (TypeScript) - [HackMD API](https://hackmd.io/settings#api) 來管理筆記 - [Anthropic API](https://console.anthropic.com/account/keys) 做為 LLM 接下來初始化專案: ```bash deno init tiny-hackmd-agent && cd tiny-hackmd-agent ``` 安裝相依: ```bash deno add npm:@anthropic-ai/sdk npm:@hackmd/api ``` ## Agent 主體 首先是 Agent 主體函式,他接收兩個參數 `ai` 跟 `tools`,裡面直接是一個暴力的 `while (true)` 迴圈: ```ts function runAgent(ai: Anthropic, tools: Tool[]) { while (true) { // ... } } ``` `ai` 不特別說明,就是 Anthropic 的 API client: ```ts const ai = new Anthropic({ apiKey: '<YOUR_KEY>' }); ``` ## 工具定義 接下來比較有趣的是 `tools`,`tools` 是我們自己定義的工具列表,用來告訴 LLM 有哪些工具可以使用: ```ts interface Tool { name: string; description: string; input_schema: ...; call(input: ...): Promise<string>; } ``` - `name`:工具名稱 - `description`:工具描述,告訴 LLM 何時使用這個工具 - `input_schema`:[JSON Schema](https://json-schema.org/overview/what-is-jsonschema#what-is-json-schema) 用來告訴 LLM 使用這個工具時需要傳入參數的「形狀」 - `call`:==實際呼叫工具時的執行邏輯== 因為我們這邊要管理筆記,所以 `call` 中當然就是呼叫 HackMD API,如讀取單一筆記: ```ts const api = new API('<YOUR_TOKEN>'); const tools: Tool[] = [ { name: "read_note", description: "Read a note content by ID", input_schema: { type: "object", properties: { noteId: { type: "string", description: "The ID of the note", }, }, required: ["noteId"], }, call({ noteId }) { return api.getNote(noteId); }, ] ``` 稍微包裝一下,我這邊不列出所有細節,但大概可以理解會有 `list_notes`、`read_note`、`create_note`、`update_note`、`delete_note` 等工具: ```ts function createTools(apiToken: string): Tool[] { const api = new API(apiToken); return [ { name: "list_notes", // ... }, { name: "read_note", // ... }, { name: "create_note", // ... }, // ... ] } ``` ## 對話迴圈 OK 接下來回到原本的 while 迴圈,來實作讀取使用者輸入,我們需要一個 `conversation` 陣列記錄完整對話,並把 `tools` 與對話傳給 LLM: ```ts const conversation = []; let shouldReadInput = true while (true) { if (shouldReadInput) { // 讀取使用輸入 const input = prompt("😂: "); if (!input) break; conversation.push({ role: "user", content: input }); } // 與 LLM 對話 const message = await ai.messages.create({ model: "claude-3-5-haiku-latest", messages: conversation, // 完整對話歷史 tools, // 剛剛定義的 tools system: "You are a helpful agent for managing HackMD notes.", }); conversation.push({ role: "assistant", content: message.content }); // 處理回應 const toolResults = []; for (const content of message.content) { // 處理文字回應和工具使用 } } ``` 接下來 AI 回應的 `message.content` 會是一個陣列,陣列中每項目我們只關注兩種形態:文字回應 `text` 或是工具使用 `tool_use`,==如果是純文字,我們就直接顯示;如果要用工具,我們就需要手動執行工具==: ```ts const toolResults = [] for (const content of message.content) { if (content.type === "text") { console.log(`🤖: ${content.text}`); } if (content.type === "tool_use") { const tool = tools.find(({ name }) => name === content.name); console.log(`🔧 Using: ${content.name}...`); // 執行工具 const result = await tool.call(content.input) toolResults.push({ type: "tool_result", tool_use_id: content.id, content: result, }); } } const hasToolResults = toolResults.length > 0; if (hasToolResults) { conversation.push({ role: "user", content: toolResults }); } shouldReadInput = !hasToolResults ``` 執行完工具後,將結果加入對話讓 LLM 知道執行結果,然後回到迴圈起始並給予回應。 ## 完整程式碼 沒錯!就這樣,我們已經完成了完整的 Agent 功能。最後加上進入點: ```ts async function main() { const ai = new Anthropic({ apiKey: '<YOUR_KEY>' }); const tools = createTools('<YOUR_TOKEN>'); await runAgent(ai, tools); } main() ``` 把 Agent 跑起來看結果: ```bash deno run --allow-net --allow-env main.ts ``` :::spoiler Screenshots ![Screenshot 2025-08-14 at 3.28.54 AM](https://hackmd.io/_uploads/By2FDDcuxl.png) ![Screenshot 2025-08-14 at 3.31.24 AM](https://hackmd.io/_uploads/rJhEuwq_gx.png) ::: 上述展現了 **Agent** 基於使用者輸入,使用了列表與建立筆記工具的能力。比想像中簡單很多吧!完整 repo: {%preview https://github.com/EastSun5566/tiny-hackmd-agent %} ## 總結 透過這個實作,我們可以看到 AI Agent 的核心其實很直觀: - **LLM** 負責理解和決策 - **Loop** 維持持續的對話 - **Tools** 提供與外部世界互動的能力 以上,你也可以寫出自己的 Agent :smiley: --- #### Credits - [A practical guide to building agents](https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf) - [How to Build an Agent](https://ampcode.com/how-to-build-an-agent) - [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) {%hackmd @EastSun5566/license %}