Part 2:打造你的第一個 AI Agent:LangChain 實戰指南
大多數 AI Agent 教學往往略過了開發中最棘手的細節。這是我如何使用 LangChain、tRPC 和 PostgreSQL 構建一個真正可運作 Agent 的實戰紀錄——包含我這一路上踩過的所有坑。

AI Agent(智慧代理人)的熱潮是貨真價實的。每個人都在談論那些能思考、規劃並執行任務的自主系統。但有個沒人告訴你的真相:大部分的教學只展示了順利運作的「快樂路徑」(happy path),卻跳過了那些會出錯的環節。
上週,我花了兩天時間從頭開始打造一個 AI Agent。這不是什麼玩具範例,而是一個真實的系統——它能管理部落格平台、建立使用者、撰寫文章,而且真的能運作。我將向你展示我究竟是如何做到的,包括那些第一次嘗試時失敗的部分。
完整程式碼:github.com/giftedunicorn/my-ai-agent
我們實際上在打造什麼
忘了那些抽象的範例吧。我們正在打造一個能夠做到以下幾點的 Agent:
- 在 PostgreSQL 資料庫中建立和管理使用者
- 根據需求生成部落格文章
- 在使用工具的同時進行對話式回應
- 維護對話歷史記錄
- 真正部署上線(而不只是在本機端 localhost 跑跑 demo)
技術堆疊 (Stack):Next.js、tRPC、Drizzle ORM、LangChain 以及 Google's Gemini。選用這些不是因為趕流行,而是因為它們具備型別安全 (type-safe)、速度快,且真的能在生產環境運作。
架構(比你想像的更簡單)
這點讓我蠻驚訝的:AI Agent 其實沒那麼複雜。就核心而言,它們只是:
- 一個可以呼叫函式的 LLM(大型語言模型)
- 一組 LLM 可以使用的工具
- 一個執行這些工具的迴圈
- 用於維持上下文 (Context) 的記憶體
就這樣。複雜度主要來自於如何讓這些組件可靠地協同運作。
資料庫架構 (Database Schema)
首先是基礎。我們需要使用者、文章和訊息的資料表:
export const User = pgTable("user", (t) => ({
id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
name: t.varchar({ length: 255 }).notNull(),
email: t.varchar({ length: 255 }).notNull().unique(),
bio: t.text(),
createdAt: t.timestamp().defaultNow().notNull(),
updatedAt: t.timestamp().defaultNow().notNull(),
}));
export const Post = pgTable("post", (t) => ({
id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
userId: t
.integer()
.notNull()
.references(() => User.id, { onDelete: "cascade" }),
title: t.varchar({ length: 500 }).notNull(),
content: t.text().notNull(),
published: t.boolean().default(false).notNull(),
createdAt: t.timestamp().defaultNow().notNull(),
updatedAt: t.timestamp().defaultNow().notNull(),
}));
沒什麼花俏的。就是用 PostgreSQL 建立乾淨的關聯式資料。Message 資料表用於儲存對話歷史——這對於維持請求之間的上下文至關重要。
打造工具(魔法發生的地方)
這是大多數教學講得很含糊的地方。他們會說:「就建立一些工具啊。」讓我展示這實際看起來是什麼樣子。
工具就是你的 AI 可以呼叫的函式。透過 LangChain 的 DynamicStructuredTool,你需要定義:
- 這個工具做什麼(描述 description)
- 它需要什麼輸入(使用 Zod 定義 schema)
- 它實際執行什麼(函式 function)
這是建立使用者的工具:
const createUserTool = new DynamicStructuredTool({
name: "create_user",
description:
"Create a new user in the database. Use this when asked to add, create, or register a user.",
schema: z.object({
name: z.string().describe("The user's full name"),
email: z.string().email().describe("The user's email address"),
bio: z.string().optional().describe("Optional biography"),
}),
func: async (input) => {
const { name, email, bio } = input as {
name: string;
email: string;
bio?: string;
};
const user = await caller.user.create({ name, email, bio });
return `Successfully created user: ${user.name} (ID: ${user.id}, Email: ${user.email})`;
},
});
描述 (Description) 的重要性比你想像的還大。 LLM 會根據它來決定何時呼叫這個工具。關於何時使用它,請務必具體說明。
回傳值呢?那是 LLM 看到的內容。我回傳包含所有相關細節(ID、名稱、確認訊息)的結構化文字。這有助於 LLM 給予使用者更好的回應。
Agent:將一切整合
這裡開始變得有趣了。新的 LangChain API (v1.2+) 簡化了一切:
const agent = createAgent({
model: new ChatGoogleGenerativeAI({
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
model: "gemini-2.0-flash-exp",
temperature: 0.7,
}),
tools: [...createUserTools(caller), ...createPostTools(caller)],
systemPrompt: AGENT_SYSTEM_PROMPT,
});
const result = await agent.invoke({
messages: conversationMessages,
});
就這樣。沒有 ChatPromptTemplate,沒有 AgentExecutor,也不需要複雜的 Chain。只需要 createAgent 和 invoke。
系統提示詞 (System Prompt):Agent 的個性
這是你教導 Agent 該如何表現的地方:
const AGENT_SYSTEM_PROMPT = `You are an AI assistant that helps manage a blog platform.
You have access to tools for:
- User management (create, read, list, count)
- Post management (create, list)
When users ask you to perform actions:
1. Use the appropriate tools to complete the task
2. Be conversational and friendly
3. Provide clear confirmation with specific details
4. When creating mock data, use realistic names and content
Always confirm successful operations with relevant details.`;
這是我的慘痛教訓:要明確。確切地告訴 Agent 該做什麼、如何回應,以及要包含哪些細節。模糊的提示會導致模糊的行為。
處理對話歷史 (Conversation History)
大多數範例都跳過這部分,但這對於良好的使用者體驗至關重要。這是我處理的方式:
// Get last 10 messages from database
const history = await ctx.db
.select()
.from(Message)
.orderBy(desc(Message.createdAt))
.limit(10);
// Convert to LangChain format
const conversationMessages = [
...history.reverse().map((msg) => ({
role: msg.role === "user" ? "user" : "assistant",
content: msg.content,
})),
{ role: "user", content: input.message },
];
簡單,但有效。Agent 現在記得最後 10 次的交流。這足以提供上下文,又不會多到讓它感到困惑或造成成本過高。
混亂的部分(實際崩潰的地方)
循環依賴 (Circular Dependencies):我第一次嘗試時失敗了,因為 agent.ts 引入了 appRouter,而 appRouter 又引入了 agentRouter,造成了循環依賴。解決方案?建立一個臨時的 router,裡面只包含工具所需的 routers。
工具回應的提取:LangChain 的回應格式在 v1.2 中改變了。結果現在位於 result.messages[result.messages.length - 1].content,而不是 result.output。這花了我一個小時才搞清楚。
型別安全 (Type Safety):工具的 func 參數需要明確的型別定義。你不能直接解構——你需要先對 input 進行型別轉換 (cast)。TypeScript 在這裡救不了你。
建立你自己的 Agent
這是你實際需要的步驟:
- 安裝依賴套件:
pnpm add @langchain/core @langchain/google-genai langchain drizzle-orm
- 環境變數:
POSTGRES_URL="your-database-url" # 試試 Vercel Postgres, Supabase, 或本地 PostgreSQL
GOOGLE_GENERATIVE_AI_API_KEY="your-gemini-key" # 從 https://aistudio.google.com/app/apikey 獲取
- 資料庫設定:
pnpm db:push # 根據 schema 建立資料表
- 開始構建:
- 定義你的資料庫 schema
- 為 CRUD 操作建立 tRPC procedures
- 建立包裝這些 procedures 的 LangChain 工具
- 使用你的工具建立 Agent
- 將其連接到你的前端
如果重來一次,我會做什麼改變
如果明天讓我重新開始:
從更少的工具開始。我最初建立了 7 個工具。先專注於 3-4 個核心工具。讓它們完美運作,然後再擴展。
獨立測試工具。不要等到 Agent 建好才測試你的工具。先用測試資料直接呼叫它們。
監控工具使用情況。我加入了 log 記錄來查看 Agent 呼叫了哪些工具以及原因。這讓我發現我的工具描述 (description) 需要改進。
使用串流 (Streaming)。目前,使用者需要等待完整的回應。串流會讓感覺變快,即使實際花費的時間是一樣的。
現實檢測 (Reality Check)
打造 AI Agent 不是魔法,但也絕非瑣事。你會花更多的時間在:
- 工具設計(每個工具該做什麼?)
- 提示工程(Prompt Engineering,我該如何讓 Agent 行為正確?)
- 錯誤處理(如果資料庫掛了怎麼辦?如果 LLM 產生幻覺怎麼辦?)
- 型別安全(讓 TypeScript 對動態的 LLM 回應感到滿意)
這些時間會遠多於處理實際 AI 部分的時間。
自己試試看
本教學的程式碼是真實的——我是在寫這篇文章的同時構建它的。你可以:
- 測試指令:「建立 3 個模擬使用者」
- 嘗試:「為使用者 1 建立 2 篇部落格文章」
- 詢問:「我們有多少使用者?」
Agent 會透過決定呼叫哪些工具、執行它們,並以對話方式回應來處理所有這些請求。
下一步是什麼
這只是基礎。從這裡開始,你可以:
- 加入身份驗證(誰可以建立什麼?)
- 實作串流回應 (Streaming responses)
- 加入更複雜的工具(搜尋、分析、第三方整合)
- 建立回饋迴圈(工具呼叫成功了嗎?)
- 加入速率限制(不要讓使用者一次建立 10,000 篇文章)
但請從簡單開始。在加入十個平庸的工具之前,先讓一個工具運作良好。
最棒的部分是什麼?一旦你理解了這個模式——工具 + LLM + 記憶——你就可以為任何事物打造 Agent。資料庫管理、客戶支援、內容生成,隨你便。
最難的不是寫程式,而是設計出真正能解決問題的工具。
資源:
- 完整原始碼:github.com/giftedunicorn/my-ai-agent
- 使用 Create T3 Turbo 構建
- LangChain 文件:js.langchain.com
- 獲取 Gemini API 金鑰:aistudio.google.com
分享

Feng Liu
shenjian8628@gmail.com