第二部分:构建你的第一个 AI Agent:LangChain 实战指南
大多数 AI Agent 教程都避而不谈那些棘手的细节。本文将分享我是如何使用 LangChain、tRPC 和 PostgreSQL 构建一个真正可用的 Agent 的全过程——包括我这一路上踩过的那些“坑”。

Title: 拒绝空谈:我是如何从零构建一个真正能用的 AI Agent 的
Excerpt: AI Agent 的炒作很疯狂,但大多数教程只展示“理想路径”。本文刘枫将带你从零构建一个真正能管理博客、操作数据库的生产级 AI Agent,分享代码、架构以及那些踩过的坑。
Content: AI Agent(智能体)的炒作确实很疯狂。每个人都在谈论那些能思考、规划并执行任务的自主系统。但有个事儿大家都没告诉你:大多数教程只展示了“理想路径”(happy path),却对那些真正会出错的地方避而不谈。
上周,我花了两天时间从零开始构建了一个 AI Agent。这可不是什么玩具 Demo,而是一个真正能用的系统——它能管理博客平台、创建用户、撰写文章,而且运行得非常顺畅。我将向你展示我是如何做到的,包括那些第一次尝试时彻底失败的经历。
完整代码: github.com/giftedunicorn/my-ai-agent
我们到底在构建什么
忘了那些抽象的例子吧。我们要构建的是一个能够:
- 在 PostgreSQL 数据库中创建和管理用户
- 按需生成博客文章
- 在使用工具的同时进行对话式回复
- 保持对话历史记录(上下文)
- 真正部署上线(而不仅仅是在本地 localhost 跑跑)
技术栈:Next.js、tRPC、Drizzle ORM、LangChain 和 Google's Gemini。选这些不是为了赶时髦,而是因为它们类型安全、速度快,而且在生产环境中真的好用。
架构(比你想象的更简单)
让我惊讶的是:AI Agent 其实没那么复杂。从核心上讲,它们只是:
- 一个可以调用函数的 LLM(大语言模型)
- 一组 LLM 可以使用的工具
- 一个执行这些工具的循环
- 用于保持上下文的记忆(Memory)
仅此而已。复杂性主要来自于如何让这些部分可靠地协同工作。
数据库 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,你需要定义:
- 工具是做什么的(描述)
- 它需要什么输入(使用 Zod 定义 schema)
- 它实际执行什么(函数)
这是创建用户的工具:
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,也没有复杂的链(chains)。只有 createAgent 和 invoke。
系统提示词(你的 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 做什么、如何回应以及包含哪些细节。模糊的提示词只会导致模糊的行为。
处理对话历史
大多数示例都跳过了这一点,但对于良好的用户体验来说,它是至关重要的。我是这样处理的:
// 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 次交流。这既保留了足够的上下文,又不会因为它太长而导致混乱或烧钱。
那些糟糕的部分(真正崩溃的地方)
循环依赖:我的第一次尝试失败了,因为 agent.ts 导入了 appRouter,而 appRouter 又导入了 agentRouter,造成了循环依赖。解决方案?创建一个临时的内联 router,只包含工具所需的 routers。
工具响应提取:LangChain 的响应格式在 v1.2 中变了。结果现在位于 result.messages[result.messages.length - 1].content,而不是 result.output。这个问题花了我一个小时才搞定。
类型安全:工具的 func 参数需要显式类型定义。你不能直接解构——你需要先对 input 进行类型断言(cast)。TypeScript 在这里救不了你。
亲手搭建
这是你实际需要的步骤:
- 安装依赖:
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 建好后才测试工具。先用测试数据直接调用它们。
监控工具使用情况。我添加了日志来查看 Agent 调用了哪些工具以及原因。这让我发现我的工具描述需要改进。
使用流式传输(Streaming)。目前,用户需要等待完整的响应。流式传输会让它感觉更快,即使总耗时是一样的。
现实检验
构建 AI Agent 不是魔法,但也绝非易事。你会发现大部分时间花在:
- 工具设计(每个工具应该做什么?)
- 提示词工程(Prompt Engineering,如何让 Agent 行为正确?)
- 错误处理(如果数据库挂了怎么办?如果 LLM 产生幻觉怎么办?)
- 类型安全(让 TypeScript 对动态的 LLM 响应感到满意)
而不是花在实际的“AI”部分。
亲自试试
本教程的代码是真实的——我是一边写这篇文章一边构建的。你可以:
- 测试:“create 3 mock users”(创建 3 个模拟用户)
- 尝试:“create 2 blog posts for user 1”(为用户 1 创建 2 篇博客文章)
- 询问:“how many users do we have?”(我们有多少用户?)
Agent 会通过决定调用哪些工具、执行它们并进行对话式回复来处理所有这些请求。
下一步
这只是基础。从这里开始,你可以:
- 添加身份验证(谁可以创建什么?)
- 实现流式响应
- 添加更复杂的工具(搜索、分析、集成)
- 构建反馈循环(工具调用成功了吗?)
- 添加速率限制(别让用户创建 10,000 篇文章)
但要从简单开始。在添加十个平庸的工具之前,先让一个工具运行良好。
最棒的部分是什么?一旦你理解了这个模式——工具 + LLM + 记忆——你就可以为任何事情构建 Agent。数据库管理、客户支持、内容生成,随你便。
难点不在代码,而在于设计出真正能解决实际问题的工具。
资源:
- 完整源代码: github.com/giftedunicorn/my-ai-agent
- 构建于 Create T3 Turbo
- LangChain 文档: js.langchain.com
- 获取 Gemini API key: aistudio.google.com
分享

Feng Liu
shenjian8628@gmail.com