不用框架,100 行 TypeScript 从零实现一个真正的 AI Agent(附完整可运行代码)
不用 LangChain,不用 CrewAI,纯 TypeScript + Claude API,100 行代码实现一个能自主调用工具、完成复杂任务的 AI Agent。先理解原理,再决定要不要用框架。
Agent 和聊天机器人到底有什么区别?
很多人把"调用大模型 API"等同于"做了一个 Agent"。不是这样的。
聊天机器人是一问一答:你说一句,它回一句,然后等你下一句。
Agent 是自主循环:你给它一个目标,它自己决定要做什么、用什么工具、怎么处理结果,直到任务完成才停下来。
核心区别就一个词:自主决策循环(Agentic Loop)。
text
聊天机器人:用户提问 → 模型回答 → 结束
Agent:用户给目标 → 模型思考 → 调用工具 → 拿到结果 → 继续思考 → 再调工具 → ... → 任务完成
今天我们就来实现这个循环。不用 LangChain,不用 CrewAI,不用任何框架------纯 TypeScript + Claude API,100 行代码搞定。
为什么不用框架?因为框架封装了太多细节。先理解原理,再用框架才知道它帮你做了什么、以及什么时候它在帮倒忙。
核心原理:Tool Use + ReAct 循环
Claude API 原生支持 Tool Use(工具调用)。你在请求时告诉 Claude "你有哪些工具可以用",Claude 会在需要的时候主动选择调用。
整个 Agentic Loop 只有三步:
text
1. 发送消息给 Claude(带上工具定义)
2. 检查 Claude 的响应:
- stop_reason === "end_turn" → 任务完成,退出循环
- stop_reason === "tool_use" → Claude 想用工具,执行后把结果喂回去
3. 回到第 1 步
就这么简单。所有 Agent 框架的底层都是这个循环,区别只在于上面包了多少糖。
实战:构建一个技术周报 Agent
我们来做一个实际有用的东西:一个能自动调研 GitHub 热门项目并生成中文技术周报的 Agent。
你只需要对它说:"帮我分析最近 GitHub 上最火的 AI 项目,生成一份技术周报",它会自己:
- 搜索 GitHub 热门仓库
- 逐个查看感兴趣的项目详情
- 分析整理,生成结构化的中文周报
Step 1:项目搭建
bash
mkdir ai-agent-demo && cd ai-agent-demo
npm init -y
npm install @anthropic-ai/sdk
npm install -D typescript @types/node
tsconfig.json:
json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
Step 2:定义工具
Agent 的能力边界取决于它有什么工具。我们给它两个:
typescript
// src/agent.ts
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // 自动读取 ANTHROPIC_API_KEY 环境变量
// 定义工具列表
const tools: Anthropic.Tool[] = [
{
name: "search_github",
description: "搜索 GitHub 仓库,返回按 Stars 排序的项目列表",
input_schema: {
type: "object" as const,
properties: {
query: {
type: "string",
description: "搜索关键词,如 'AI agent' 或 'LLM framework'",
},
language: {
type: "string",
description: "编程语言筛选,如 typescript、python",
},
},
required: ["query"],
},
},
{
name: "get_repo_readme",
description: "获取指定 GitHub 仓库的 README 内容摘要",
input_schema: {
type: "object" as const,
properties: {
owner: { type: "string", description: "仓库所有者" },
repo: { type: "string", description: "仓库名称" },
},
required: ["owner", "repo"],
},
},
];
注意 description 写得要具体------Claude 靠描述来决定什么时候调用哪个工具。描述模糊,Agent 就会乱调或者不调。这是实际开发中最影响效果的地方。
Step 3:实现工具执行
typescript
// 工具执行器:根据工具名分发到具体实现
async function executeTool(
name: string,
input: Record<string, string>
): Promise<string> {
switch (name) {
case "search_github": {
const params = new URLSearchParams({
q: input.language
? `${input.query} language:${input.language}`
: input.query,
sort: "stars",
order: "desc",
per_page: "5",
});
const res = await fetch(
`https://api.github.com/search/repositories?${params}`,
{ headers: { "User-Agent": "ai-agent-demo" } }
);
if (!res.ok) return `GitHub API 错误: ${res.status}`;
const data = (await res.json()) as {
items: Array<{
full_name: string;
description: string | null;
stargazers_count: number;
language: string | null;
}>;
};
return data.items
.map(
(r) =>
`${r.full_name} (⭐${r.stargazers_count}) - ${r.description || "无描述"} [${r.language || "未知"}]`
)
.join("\n");
}
case "get_repo_readme": {
const res = await fetch(
`https://api.github.com/repos/${input.owner}/${input.repo}/readme`,
{
headers: {
"User-Agent": "ai-agent-demo",
Accept: "application/vnd.github.raw+json",
},
}
);
if (!res.ok) return `无法获取 README: ${res.status}`;
const text = await res.text();
// 截取前 1500 字符,避免 Token 浪费
return text.slice(0, 1500);
}
default:
return `未知工具: ${name}`;
}
}
Step 4:实现 Agentic Loop(核心)
这是整个 Agent 的心脏------20 行代码:
typescript
async function runAgent(task: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: task },
];
let turns = 0;
const MAX_TURNS = 15; // 防止无限循环
while (turns < MAX_TURNS) {
turns++;
console.log(`--- 第 ${turns} 轮 ---`);
const response = await client.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 4096,
system:
"你是一个技术调研助手。请用中文回答。分析项目时关注:项目定位、核心功能、技术亮点、适用场景。",
tools,
messages,
});
// 把 Claude 的回复加入对话历史
messages.push({ role: "assistant", content: response.content });
// 关键判断:Claude 是想用工具,还是已经完成了?
if (response.stop_reason === "end_turn") {
// 任务完成,提取最终文本
return response.content
.filter((b): b is Anthropic.TextBlock => b.type === "text")
.map((b) => b.text)
.join("\n");
}
if (response.stop_reason === "tool_use") {
// Claude 想调用工具,逐个执行
const toolBlocks = response.content.filter(
(b): b is Anthropic.ToolUseBlock => b.type === "tool_use"
);
const results: Anthropic.ToolResultBlockParam[] = [];
for (const tool of toolBlocks) {
console.log(` 调用工具: ${tool.name}`, tool.input);
try {
const result = await executeTool(
tool.name,
tool.input as Record<string, string>
);
results.push({
type: "tool_result",
tool_use_id: tool.id,
content: result,
});
} catch (err) {
// 工具报错也要告诉 Claude,让它自己决定怎么处理
results.push({
type: "tool_result",
tool_use_id: tool.id,
content: `执行出错: ${(err as Error).message}`,
is_error: true,
});
}
}
// 把工具结果喂回去,继续循环
messages.push({ role: "user", content: results });
}
}
return "达到最大轮次限制,任务未完成";
}
几个关键设计决策:
MAX_TURNS防护:没有这个,Agent 可能陷入死循环。生产环境必须加is_error: true:工具出错时不要吞掉异常,告诉 Claude 出错了。Claude 会自己决定是重试、换个方式、还是跳过stop_reason是唯一的判断依据 :"end_turn"表示 Claude 认为任务完成了,"tool_use"表示它还在工作
Step 5:运行
typescript
async function main() {
const report = await runAgent(
"帮我调研最近 GitHub 上最热门的 AI Agent 相关项目(3-5 个)," +
"分析每个项目的核心亮点,最后生成一份中文技术周报。"
);
console.log("\n===== 最终报告 =====\n");
console.log(report);
}
main().catch(console.error);
运行:
bash
ANTHROPIC_API_KEY=your_key npx tsx src/agent.ts
你会看到 Agent 自己一步步地搜索、查看 README、分析总结,最终输出一份结构化的中文技术周报。整个过程你只说了一句话。
生产环境必须考虑的 4 件事
Demo 能跑不等于能上线。以下是实际踩过的坑:
1. 成本控制
每一轮 Agentic Loop 都在消耗 Token。一个 15 轮的 Agent 对话,Token 消耗可能是普通对话的 10 倍以上。
typescript
// 追踪每次调用的 Token 消耗
let totalInputTokens = 0;
let totalOutputTokens = 0;
// 在每次 API 调用后:
totalInputTokens += response.usage.input_tokens;
totalOutputTokens += response.usage.output_tokens;
const cost =
(totalInputTokens / 1_000_000) * 3 + // Sonnet 输入 $3/M
(totalOutputTokens / 1_000_000) * 15; // Sonnet 输出 $15/M
if (cost > 0.5) { // 单次任务成本上限
console.warn(`成本预警: $${cost.toFixed(4)}`);
break;
}
经验法则 :Sonnet 跑 Agent 任务,单次对话成本通常在 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0.01 − 0.01- </math>0.01−0.10。如果超过 $0.50,大概率是 Agent 在做无用功了。
2. 错误恢复
GitHub API 有频率限制(60 次/小时未认证)。你的工具会失败,Agent 必须能处理。
好消息是 Claude 天然擅长这个。只要你把错误信息通过 is_error: true 传回去,Claude 通常会:
- 换一种搜索词重试
- 跳过有问题的项目
- 在最终报告中说明哪些信息获取失败了
不要自己写复杂的重试逻辑------让 Claude 来决定。这正是 Agent 相比脚本的优势。
3. 可观测性
当 Agent 在跑一个复杂任务时,你需要知道它在干什么。最简单的方式:
typescript
// 在 Agentic Loop 中加日志
for (const tool of toolBlocks) {
const start = Date.now();
const result = await executeTool(tool.name, tool.input);
console.log(
`[${tool.name}] ${Date.now() - start}ms | ` +
`输入: ${JSON.stringify(tool.input).slice(0, 100)} | ` +
`输出: ${result.slice(0, 80)}...`
);
}
生产环境建议接入 Langfuse 或 Braintrust 等 LLM Observability 平台,可以看到完整的调用链路和成本明细。
4. Human-in-the-Loop
有些场景,Agent 不应该全自动。比如涉及发送邮件、修改数据库、花钱的操作。
实现方式很简单------在 executeTool 里加一个确认步骤:
typescript
import * as readline from "readline/promises";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// 在高风险工具执行前
if (["send_email", "delete_record"].includes(name)) {
const answer = await rl.question(
`Agent 想执行 ${name}(${JSON.stringify(input)}),确认?(y/n) `
);
if (answer !== "y") {
return "用户拒绝了此操作";
}
}
和框架的关系
写完这个你可能会问:那 LangChain、CrewAI 那些框架到底做了什么?
| 你自己写的 | 框架帮你做的 |
|---|---|
| Agentic Loop(20 行) | 同样的循环,加了更多配置项 |
| 工具定义(手写 JSON Schema) | Zod/Pydantic 自动生成 Schema |
| 错误处理(if/catch) | 自动重试、回退策略 |
| 对话历史(数组 push) | 持久化存储、多会话管理 |
| 日志(console.log) | 结构化追踪、可视化面板 |
什么时候用框架?
- 需要多个 Agent 协作(CrewAI)
- 需要复杂的状态管理和分支逻辑(LangGraph)
- 需要对接 10+ 种工具和数据源(LangChain)
- 团队开发,需要统一的抽象层
什么时候不用框架?
- 学习阶段------先理解原理
- 工具数量少于 5 个的单 Agent 场景
- 需要极致控制每一步行为
- 框架的 magic 让你无法调试问题时
我的建议是:先手写一遍,再决定要不要用框架。 很多时候你会发现,手写反而更简单。
总结
今天我们从零实现了一个 AI Agent,核心收获:
| 概念 | 要点 |
|---|---|
| Agent vs 聊天机器人 | 区别在于自主决策循环,不是 API 调用方式 |
| Agentic Loop | 检查 stop_reason,tool_use 就执行工具继续,end_turn 就结束 |
| 工具设计 | description 决定 Agent 何时调用你的工具,要写得具体 |
| 生产化 | 成本控制 + 错误恢复 + 可观测性 + Human-in-the-Loop |
| 框架选择 | 先手写理解原理,复杂场景再上框架 |
完整代码不到 100 行(不含工具实现),没有任何框架依赖。这就是 Agent 的全部核心逻辑。
你用什么技术栈开发 AI Agent?效果怎么样?欢迎在评论区聊聊。
本文首发于微信公众号「开发者效率局」,欢迎关注获取更多 AI 开发实战内容。