第 8 课:完整项目实战
本课目标
这一课我们把 resume-generator.ts 的 98 行代码逐行读完,把前面 7 课学到的知识全部串起来。
完整源码(带注释版)
/**
* Resume Generator using Claude Agent SDK
*
* 用 web search 搜索一个人的信息,然后自动生成一份 .docx 简历
*
* 用法: npx tsx resume-generator.ts "人名"
*/
// ========== 第一部分:导入依赖 ==========
import { query } from '@anthropic-ai/claude-agent-sdk';
// query:SDK 的核心函数,"给 AI 一个任务,AI 去完成"(第 3 课)
import * as fs from 'fs';
// fs:Node.js 文件系统模块,用来检查文件是否存在
import * as path from 'path';
// path:路径处理,拼接文件路径用的
// ========== 第二部分:系统提示词 ==========
const SYSTEM_PROMPT = `You are a professional resume writer.
Research a person and create a 1-page .docx resume.
WORKFLOW:
1. WebSearch for the person's background (LinkedIn, GitHub, company pages)
2. Create a .docx file using the docx library
OUTPUT:
- Script: agent/custom_scripts/generate_resume.js
- Resume: agent/custom_scripts/resume.docx
PAGE FIT (must be exactly 1 page):
- 0.5 inch margins, Name 24pt, Headers 12pt, Body 10pt
- 2-3 bullet points per job, ~80-100 chars each
- Max 3 job roles, 2-line summary, 2-line skills`;
// 系统提示词的四大块:角色、流程、输出、约束(第 6 课)
// ========== 第三部分:核心函数 ==========
async function generateResume(personName: string) {
console.log(`\n📝 Generating resume for: ${personName}\n`);
console.log('='.repeat(50));
// 确保输出目录存在
const outputDir = path.join(process.cwd(), 'agent', 'custom_scripts');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// ↑ 提前创建好 agent/custom_scripts/ 目录
// 这样 AI 写代码时可以直接写到这里
// 用户提示词:每次任务不同的具体指令
const prompt = `Research "${personName}" and create a professional
1-page resume as a .docx file. Search for their
professional background, experience, education,
and skills.`;
console.log('\n🔍 Researching and creating resume...\n');
// ====== 关键调用:query() ======
const q = query({
prompt, // 用户提示词
options: {
maxTurns: 30, // 最多 30 轮操作(第 3 课)
cwd: process.cwd(), // 工作目录
model: 'sonnet', // 用 Sonnet 模型
allowedTools: [ // 工具箱(第 4 课)
'Skill', // 读技能文件
'WebSearch', // 搜索
'WebFetch', // 抓取网页
'Bash', // 执行命令
'Write', // 写文件
'Read', // 读文件
'Glob' // 查找文件
],
settingSources: ['project'], // 加载 .claude/skills/(第 5 课)
systemPrompt: SYSTEM_PROMPT, // 系统提示词(第 6 课)
},
});
// ====== 处理消息流 ======
for await (const msg of q) {
// 处理 AI 发来的消息
if (msg.type === 'assistant' && msg.message) {
for (const block of msg.message.content) {
// AI 说了一段话
if (block.type === 'text') {
console.log(block.text);
}
// AI 要用工具
if (block.type === 'tool_use') {
if (block.name === 'WebSearch' &&
block.input &&
typeof block.input === 'object' &&
'query' in block.input) {
// 搜索工具——特殊处理,显示搜索关键词
console.log(`\n🔍 Searching: "${block.input.query}"`);
} else {
// 其他工具——只显示工具名
console.log(`\n🔧 Using tool: ${block.name}`);
}
}
}
}
// 处理工具返回的结果
if (msg.type === 'result') {
if (msg.subtype === 'tool_result') {
const resultStr = JSON.stringify(msg.content).slice(0, 200);
console.log(` ↳ Result: ${resultStr}${resultStr.length >= 200 ? '...' : ''}`);
// ↑ 只显示结果的前 200 个字符,避免刷屏
}
}
}
// ====== 检查结果 ======
const expectedPath = path.join(
process.cwd(), 'agent', 'custom_scripts', 'resume.docx'
);
if (fs.existsSync(expectedPath)) {
console.log('\n' + '='.repeat(50));
console.log(`📄 Resume saved to: ${expectedPath}`);
console.log('='.repeat(50) + '\n');
} else {
console.log('\n❌ Resume file was not created. Check the output above for errors.');
}
}
// ========== 第四部分:入口 ==========
const personName = process.argv[2];
// 从命令行参数获取人名
// npx tsx resume-generator.ts "Elon Musk"
// ↑ 这就是 process.argv[2]
if (!personName) {
console.log('Usage: npx tsx resume-generator.ts "Person Name"');
console.log('Example: npx tsx resume-generator.ts "Jane Doe"');
process.exit(1);
}
generateResume(personName).catch(console.error);
// 启动!
四个部分总览
graph TD
A["第一部分:导入依赖<br>import query + fs + path"] --> B["第二部分:系统提示词<br>SYSTEM_PROMPT = 角色 + 流程 + 输出 + 约束"]
B --> C["第三部分:核心函数 generateResume"]
C --> C1["准备输出目录"]
C1 --> C2["调用 query"]
C2 --> C3["处理消息流(显示进度)"]
C3 --> C4["检查结果文件"]
C4 --> D["第四部分:入口<br>读取命令行参数 - 调用 generateResume"]
消息流处理详解
for await 循环里的消息处理是理解 Agent 行为的关键。让我们用一个具体例子来看 AI 实际发出的消息序列:
时序图:生成 "Elon Musk" 简历
你的程序 AI (Claude)
│ │
│── query(prompt) ──────────> │
│ │
│ <─── assistant/text ─────── │ "我来搜索 Elon Musk 的信息"
│ <─── assistant/tool_use ─── │ WebSearch("Elon Musk LinkedIn")
│ <─── result/tool_result ─── │ 搜索结果...
│ │
│ <─── assistant/tool_use ─── │ WebSearch("Elon Musk education")
│ <─── result/tool_result ─── │ 搜索结果...
│ │
│ <─── assistant/tool_use ─── │ Skill("docx")
│ <─── result/tool_result ─── │ SKILL.md 内容...
│ │
│ <─── assistant/tool_use ─── │ Read("docx-js.md")
│ <─── result/tool_result ─── │ docx-js.md 内容...
│ │
│ <─── assistant/text ─────── │ "信息收集完毕,开始生成简历"
│ <─── assistant/tool_use ─── │ Write("generate_resume.js")
│ <─── result/tool_result ─── │ 文件已创建
│ │
│ <─── assistant/tool_use ─── │ Bash("node generate_resume.js")
│ <─── result/tool_result ─── │ "Resume generated successfully!"
│ │
│ <─── assistant/text ─────── │ "简历已生成,保存在..."
│ │
实际运行一次
让我们真正跑一次,并关注每个阶段:
npx tsx resume-generator.ts "Tim Cook"
你会看到类似的输出:
📝 Generating resume for: Tim Cook
==================================================
🔍 Researching and creating resume...
🔍 Searching: "Tim Cook professional background CEO Apple"
↳ Result: [{"type":"text","text":"Search results for Tim Cook..."}]
🔍 Searching: "Tim Cook education Auburn University Duke MBA"
↳ Result: [{"type":"text","text":"Tim Cook received his Bachelor..."}]
🔧 Using tool: Skill
↳ Result: [{"type":"text","text":"# DOCX creation, editing..."}]
🔧 Using tool: Read
↳ Result: [{"type":"text","text":"# DOCX Library Tutorial..."}]
I'll now create the resume using the docx library.
🔧 Using tool: Write
↳ Result: [{"type":"text","text":"File written successfully"}]
🔧 Using tool: Bash
↳ Result: [{"type":"text","text":"Resume generated successfully!"}]
==================================================
📄 Resume saved to: /path/to/agent/custom_scripts/resume.docx
==================================================
从代码到完整 Agent 的思维转变
写完这个项目之后,你会发现一个重要的思维转变:
传统编程思维:
"我要写代码去搜索 → 写代码处理数据 → 写代码生成文件"
程序员负责每一步的逻辑
Agent 思维:
"我设计好 AI 的角色和规则 → 给它配好工具 → 让它自己搞定"
程序员负责设计框架,AI 负责执行
你的 98 行代码做了什么?
| 你做的 | AI 做的 |
|---|---|
| 设计系统提示词 | 决定搜什么关键词 |
| 选择要用的工具 | 判断搜索结果是否够用 |
| 指定输出路径和格式 | 写出完整的 JS 代码 |
| 处理消息流显示进度 | 执行代码并修复错误 |
| 检查文件是否生成 | 确保排版符合要求 |
你设计了"规则",AI 做了"实事"。
本课小结
- 整个项目只有 98 行代码,分为 4 个部分
- 核心逻辑就是:调用 query() → 处理消息流 → 检查结果
- 消息流是理解 Agent 行为的关键窗口
- 从"写逻辑"到"设计规则"是 Agent 编程的思维转变
课后练习
- 跑三次同一个人名,看看 AI 每次生成的代码和简历是否不同
- 在消息流处理里加一个计数器,看看 AI 一共用了多少轮才完成任务
- 尝试把代码改成接受两个参数:人名和输出文件名(不一定叫 resume.docx)