Skip to content

构建内容创作智能体 (Build a content builder agent) ​

构建包含品牌记忆、专业技能、子智能体委派与配图生成能力的内容创作智能体

概述 (Overview) ​

本指南演示如何使用 Deep Agents 从零构建一个内容创作智能体。

你所构建的智能体将能够:

  1. 从 AGENTS.md 和技能目录中加载语言风格与工作流规则
  2. 使用 web_search 将网络调研任务委派给专用的研究子智能体
  3. 遵循已加载的技能编写博客文章或社交媒体文案
  4. 使用 Gemini 生成文章封面或社交配图,并将文件保存到项目目录下

本教程中的代码接入了图像生成工具和文件系统后端,使智能体能够在项目目录下读写文章、研究笔记以及配图。有关完整的可运行项目,请参阅 content-builder-agent 示例。

核心概念 (Key concepts) ​

本教程涵盖:

前置准备 (Prerequisites) ​

所需 API 密钥:

  • Anthropic (Claude) 或其他模型提供商的 API 密钥
  • Google (Gemini) API 密钥,用于使用 gemini-2.5-flash-image 生成图像
  • Tavily 用于网络搜索(提供免费额度)
  • LangSmith 用于链路追踪调试(可选)

运行环境要求:Node.js 18 或更高版本。

环境准备 (Setup) ​

1. 创建项目目录

bash
mkdir content-builder-agent
cd content-builder-agent

2. 安装依赖

bash
npm install deepagents @langchain/core @langchain/anthropic @google/generative-ai tavily zod tsx
bash
yarn add deepagents @langchain/core @langchain/anthropic @google/generative-ai tavily zod tsx

安装 tsx 以直接执行 TypeScript 脚本 content_writer.ts。 安装 @langchain/anthropic 以便 LangChain 加载 createDeepAgent 默认使用的 Claude 模型。

3. 配置 API 密钥

bash
export ANTHROPIC_API_KEY="your_anthropic_api_key"
export GOOGLE_API_KEY="your_google_api_key"
export TAVILY_API_KEY="your_tavily_api_key"           # 可选
export LANGSMITH_API_KEY="your_langsmith_api_key"     # 可选

添加配置文件 (Add configuration files) ​

本示例将智能体行为拆分在三类文件中管理:记忆、技能与子智能体定义。

1. 添加 AGENTS.md

在项目根目录下创建 AGENTS.md。 稍后创建智能体并将该文件作为记忆参数(memory)传入时,它会自动载入系统提示词中,使品牌语调和调研规范在每次运行时均生效。

markdown
# Content Writer Agent

You are a content writer for a technology company. Your job is to create engaging, informative content that educates readers about AI, software development, and emerging technologies.

## Brand Voice

- **Professional but approachable**: Write like a knowledgeable colleague, not a textbook
- **Clear and direct**: Avoid jargon unless necessary; explain technical concepts simply
- **Confident but not arrogant**: Share expertise without being condescending
- **Engaging**: Use concrete examples, analogies, and stories to illustrate points

## Writing Standards

1. **Use active voice**: "The agent processes requests" not "Requests are processed by the agent"
2. **Lead with value**: Start with what matters to the reader, not background
3. **One idea per paragraph**: Keep paragraphs focused and scannable
4. **Concrete over abstract**: Use specific examples, numbers, and case studies
5. **End with action**: Every piece should leave the reader knowing what to do next

## Content Pillars

Our content focuses on:
- AI agents and automation
- Developer tools and productivity
- Software architecture and best practices
- Emerging technologies and trends

## Formatting Guidelines

- Use headers (H2, H3) to break up long content
- Include code examples where relevant (with syntax highlighting)
- Add bullet points for lists of 3+ items
- Keep sentences under 25 words when possible
- Include a clear call-to-action at the end

## Research Requirements

Before writing on any topic:
1. Use the `researcher` subagent for in-depth topic research
2. Gather at least 3 credible sources
3. Identify the key points readers need to understand
4. Find concrete examples or case studies to illustrate concepts

如需让智能体符合你自己的品牌调性、选题支柱和排版规则,可直接编辑 AGENTS.md 中的文本。

2. 添加技能 (Add skills)

创建 skills/ 目录。每个技能对应一个文件夹,内部包含一个带有 YAML Frontmatter(包含 name 和 description)以及指令正文的 SKILL.md 文件。

创建 skills/blog-post/SKILL.md,用于编写深度长文、优化 SEO 以及生成封面图像:

md
---
name: blog-post
description: Writes and structures long-form blog posts, creates tutorial outlines, and optimizes content for SEO with cover image generation. Use when the user asks to write a blog post, article, how-to guide, tutorial, technical writeup, thought leadership piece, or long-form content.
---

# Blog Post Writing Skill

## Research First (Required)

**Before writing any blog post, you MUST delegate research:**

1. Use the `task` tool with `subagent_type: "researcher"`
2. In the description, specify BOTH the topic AND where to save:

```
task(
    subagent_type="researcher",
    description="Research [TOPIC]. Save findings to research/[slug].md"
)
```

Example:
```
task(
    subagent_type="researcher",
    description="Research the current state of AI agents in 2025. Save findings to research/ai-agents-2025.md"
)
```

3. After research completes, read the findings file before writing

## Output Structure (Required)

**Every blog post MUST have both a post AND a cover image:**

```
blogs/
└── <slug>/
    ├── post.md        # The blog post content
    └── hero.png       # REQUIRED: Generated cover image
```

Example: A post about "AI Agents in 2025" → `blogs/ai-agents-2025/`

**You MUST complete both steps:**
1. Write the post to `blogs/<slug>/post.md`
2. Generate a cover image using `generate_image` and save to `blogs/<slug>/hero.png`

**A blog post is NOT complete without its cover image.**

## Blog Post Structure

Every blog post should follow this structure:

### 1. Hook (Opening)
- Start with a compelling question, statistic, or statement
- Make the reader want to continue
- Keep it to 2-3 sentences

### 2. Context (The Problem)
- Explain why this topic matters
- Describe the problem or opportunity
- Connect to the reader's experience

### 3. Main Content (The Solution)
- Break into 3-5 main sections with H2 headers
- Each section covers one key point
- Include code examples, diagrams, or screenshots where helpful
- Use bullet points for lists

### 4. Practical Application
- Show how to apply the concepts
- Include step-by-step instructions if applicable
- Provide code snippets or templates

### 5. Conclusion & CTA
- Summarize key takeaways (3 bullets max)
- End with a clear call-to-action
- Link to related resources

## Cover Image Generation

After writing the post, generate a cover image using the `generate_cover` tool:

```
generate_cover(prompt="A detailed description of the image...", slug="your-blog-slug")
```

The tool saves the image to `blogs/<slug>/hero.png`.

### Writing Effective Image Prompts

Structure your prompt with these elements:

1. **Subject**: What is the main focus? Be specific and concrete.
2. **Style**: Art direction (minimalist, isometric, flat design, 3D render, watercolor, etc.)
3. **Composition**: How elements are arranged (centered, rule of thirds, symmetrical)
4. **Color palette**: Specific colors or mood (warm earth tones, cool blues and purples, high contrast)
5. **Lighting/Atmosphere**: Soft diffused light, dramatic shadows, golden hour, neon glow
6. **Technical details**: Aspect ratio considerations, negative space for text overlay

### Example Prompts

**For a technical blog post:**
```
Isometric 3D illustration of interconnected glowing cubes representing AI agents, each cube has subtle circuit patterns. Cubes connected by luminous data streams. Deep navy background (#0a192f) with electric blue (#64ffda) and soft purple (#c792ea) accents. Clean minimal style, lots of negative space at top for title. Professional tech aesthetic.
```

**For a tutorial/how-to:**
```
Clean flat illustration of hands typing on a keyboard with abstract code symbols floating upward, transforming into lightbulbs and gears. Warm gradient background from soft coral to light peach. Friendly, approachable style. Centered composition with space for text overlay.
```

**For thought leadership:**
```
Abstract visualization of a human silhouette profile merging with geometric neural network patterns. Split composition - organic watercolor texture on left transitioning to clean vector lines on right. Muted sage green and warm terracotta color scheme. Contemplative, forward-thinking mood.
```

## SEO Considerations

- Include the main keyword in the title and first paragraph
- Use the keyword naturally 3-5 times throughout
- Keep the title under 60 characters
- Write a meta description (150-160 characters)

## Quality Checklist

Before finishing:
- [ ] Post saved to `blogs/<slug>/post.md`
- [ ] Hero image generated at `blogs/<slug>/hero.png`
- [ ] Hook grabs attention in first 2 sentences
- [ ] Each section has a clear purpose
- [ ] Conclusion summarizes key points
- [ ] CTA tells reader what to do next

接下来,创建 skills/social-media/SKILL.md,用于起草社交媒体文案并生成匹配的视觉图片:

md
---
name: social-media
description: Drafts engaging social media posts, writes hooks, suggests hashtags, creates thread structures, and generates companion images. Use when the user asks to write a LinkedIn post, tweet, Twitter/X thread, social media caption, social post, or repurpose content for social platforms.
---

# Social Media Content Skill

## Research First (Required)

**Before writing any social media content, you MUST delegate research:**

1. Use the `task` tool with `subagent_type: "researcher"`
2. In the description, specify BOTH the topic AND where to save:

```
task(
    subagent_type="researcher",
    description="Research [TOPIC]. Save findings to research/[slug].md"
)
```

Example:
```
task(
    subagent_type="researcher",
    description="Research renewable energy trends in 2025. Save findings to research/renewable-energy.md"
)
```

3. After research completes, read the findings file before writing

## Output Structure (Required)

**Every social media post MUST have both content AND an image:**

**LinkedIn posts:**
```
linkedin/
└── <slug>/
    ├── post.md        # The post content
    └── image.png      # REQUIRED: Generated visual
```

**Twitter/X threads:**
```
tweets/
└── <slug>/
    ├── thread.md      # The thread content
    └── image.png      # REQUIRED: Generated visual
```

Example: A LinkedIn post about "prompt engineering" → `linkedin/prompt-engineering/`

**You MUST complete both steps:**
1. Write the content to the appropriate path
2. Generate an image using `generate_image` and save alongside the post

**A social media post is NOT complete without its image.**

## Platform Guidelines

### LinkedIn

**Format:**
- 1,300 character limit (show more after ~210 chars)
- First line is crucial - make it hook
- Use line breaks for readability
- 3-5 hashtags at the end

**Tone:**
- Professional but personal
- Share insights and learnings
- Ask questions to drive engagement
- Use "I" and share experiences

**Structure:**
```
[Hook - 1 compelling line]

[Empty line]

[Context - why this matters]

[Empty line]

[Main insight - 2-3 short paragraphs]

[Empty line]

[Call to action or question]

#hashtag1 #hashtag2 #hashtag3
```

### Twitter/X

**Format:**
- 280 character limit per tweet
- Threads for longer content (use 1/🧵 format)
- No more than 2 hashtags per tweet

**Thread Structure:**
```
1/🧵 [Hook - the main insight]

2/ [Supporting point 1]

3/ [Supporting point 2]

4/ [Example or evidence]

5/ [Conclusion + CTA]
```

## Image Generation

Every social media post needs an eye-catching image. Use the `generate_social_image` tool:

```
generate_social_image(prompt="A detailed description...", platform="linkedin", slug="your-post-slug")
```

The tool saves the image to `<platform>/<slug>/image.png`.

### Social Image Best Practices

Social images need to work at small sizes in crowded feeds:
- **Bold, simple compositions** - one clear focal point
- **High contrast** - stands out when scrolling
- **No text in image** - too small to read, platforms add their own
- **Square or 4:5 ratio** - works across platforms

### Writing Effective Prompts

Include these elements:

1. **Single focal point**: One clear subject, not a busy scene
2. **Bold style**: Vibrant colors, strong shapes, high contrast
3. **Simple background**: Solid color, gradient, or subtle texture
4. **Mood/energy**: Match the post tone (inspiring, urgent, thoughtful)

### Example Prompts

**For an insight/tip post:**
```
Single glowing lightbulb floating against a deep purple gradient background, lightbulb made of interconnected golden geometric lines, rays of soft light emanating outward. Minimal, striking, high contrast. Square composition.
```

**For announcements/news:**
```
Abstract rocket ship made of colorful geometric shapes launching upward with a trail of particles. Bright coral and teal color scheme against clean white background. Energetic, celebratory mood. Bold flat illustration style.
```

**For thought-provoking content:**
```
Two overlapping translucent circles, one blue one orange, creating a glowing intersection in the center. Represents collaboration or intersection of ideas. Dark charcoal background, soft ethereal glow. Minimalist and contemplative.
```

## Content Types

### Announcement Posts
- Lead with the news
- Explain the impact
- Include link or next step

### Insight Posts
- Share one specific learning
- Explain the context briefly
- Make it actionable

### Question Posts
- Ask a genuine question
- Provide your take first
- Keep it focused on one topic

## Quality Checklist

Before finishing:
- [ ] Post saved to `linkedin/<slug>/post.md` or `tweets/<slug>/thread.md`
- [ ] Image generated alongside the post
- [ ] First line hooks attention
- [ ] Content fits platform limits
- [ ] Tone matches platform norms
- [ ] Has clear CTA or question
- [ ] Hashtags are relevant (not generic)

这些规则指示智能体优先调用 researcher 子智能体,在 blogs/、linkedin/ 或 tweets/ 路径下生成 Markdown 文章,并调用 generate_cover 或 generate_social_image 生成图像。

当创建智能体并指定技能目录时,技能目录下 SKILL.md 的 Frontmatter 将自动注册到系统提示词中,使智能体能够根据用户任务自动匹配并调用相应技能。

编写主脚本 (Build the script) ​

在项目根目录下创建 content_writer.ts。

1. 添加工具

调研子智能体使用 Tavily 搜索;博客和社交工作流使用 Google Generative AI SDK 生成图片:

ts
import { tool } from "@langchain/core/tools";
import * as z from "zod";
import * as fs from "node:fs";
import * as path from "node:path";

const EXAMPLE_DIR = path.dirname(new URL(import.meta.url).pathname);

const webSearch = tool(
  async ({ query, maxResults = 5, topic = "general" }) => {
    const apiKey = process.env.TAVILY_API_KEY;
    if (!apiKey) return { error: "TAVILY_API_KEY not set" };
    try {
      const { TavilyClient } = await import("tavily");
      const client = new TavilyClient({ apiKey });
      return client.search(query, { maxResults, topic });
    } catch (e) {
      return { error: `Search failed: ${e}` };
    }
  },
  {
    name: "web_search",
    description: "Search the web for current information.",
    schema: z.object({
      query: z.string().describe("The search query (be specific and detailed)"),
      maxResults: z
        .number()
        .optional()
        .describe("Number of results to return (default: 5)"),
      topic: z
        .enum(["general", "news"])
        .optional()
        .describe('"general" for most queries, "news" for current events'),
    }),
  },
);

const generateCover = tool(
  async ({ prompt, slug }) => {
    try {
      const { GoogleGenerativeAI } = await import("@google/generative-ai");
      const genai = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY ?? "");
      const model = genai.getGenerativeModel({
        model: "gemini-2.5-flash-image",
      });
      const result = await model.generateContent(prompt);
      const part = result.response.candidates?.[0]?.content?.parts?.find(
        (p) => p.inlineData,
      );
      if (!part?.inlineData) return "No image generated";
      const outputPath = path.join(EXAMPLE_DIR, "blogs", slug, "hero.png");
      fs.mkdirSync(path.dirname(outputPath), { recursive: true });
      fs.writeFileSync(outputPath, Buffer.from(part.inlineData.data, "base64"));
      return `Image saved to ${outputPath}`;
    } catch (e) {
      return `Error: ${e}`;
    }
  },
  {
    name: "generate_cover",
    description: "Generate a cover image for a blog post.",
    schema: z.object({
      prompt: z
        .string()
        .describe("Detailed description of the image to generate."),
      slug: z
        .string()
        .describe("Blog post slug. Image saves to blogs/<slug>/hero.png"),
    }),
  },
);

const generateSocialImage = tool(
  async ({ prompt, platform, slug }) => {
    try {
      const { GoogleGenerativeAI } = await import("@google/generative-ai");
      const genai = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY ?? "");
      const model = genai.getGenerativeModel({
        model: "gemini-2.5-flash-image",
      });
      const result = await model.generateContent(prompt);
      const part = result.response.candidates?.[0]?.content?.parts?.find(
        (p) => p.inlineData,
      );
      if (!part?.inlineData) return "No image generated";
      const outputPath = path.join(EXAMPLE_DIR, platform, slug, "image.png");
      fs.mkdirSync(path.dirname(outputPath), { recursive: true });
      fs.writeFileSync(outputPath, Buffer.from(part.inlineData.data, "base64"));
      return `Image saved to ${outputPath}`;
    } catch (e) {
      return `Error: ${e}`;
    }
  },
  {
    name: "generate_social_image",
    description: "Generate an image for a social media post.",
    schema: z.object({
      prompt: z
        .string()
        .describe("Detailed description of the image to generate."),
      platform: z.string().describe('Either "linkedin" or "tweets"'),
      slug: z
        .string()
        .describe("Post slug. Image saves to <platform>/<slug>/image.png"),
    }),
  },
);

2. 创建智能体 (Create the agent)

调用 createDeepAgent 时,传入记忆路径列表、技能目录、图片生成工具、内联子智能体定义,以及一个以当前示例目录为根的 FilesystemBackend:

ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

function createContentWriter() {
  const researcherSubagent = {
    name: "researcher",
    description:
      "Research subagent with web search capability. Delegate research tasks here.",
    systemPrompt:
      "You are a research assistant. Use the web_search tool to find current, accurate information and return well-organized findings.",
    tools: [webSearch],
  };

  return createDeepAgent({
    model: "google:gemini-3.6-flash",
    memory: ["./AGENTS.md"],
    skills: ["./skills/"],
    tools: [generateCover, generateSocialImage],
    subagents: [researcherSubagent],
    backend: new FilesystemBackend({ rootDir: EXAMPLE_DIR }),
  });
}
ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

function createContentWriter() {
  const researcherSubagent = {
    name: "researcher",
    description:
      "Research subagent with web search capability. Delegate research tasks here.",
    systemPrompt:
      "You are a research assistant. Use the web_search tool to find current, accurate information and return well-organized findings.",
    tools: [webSearch],
  };

  return createDeepAgent({
    model: "openai:gpt-5.5",
    memory: ["./AGENTS.md"],
    skills: ["./skills/"],
    tools: [generateCover, generateSocialImage],
    subagents: [researcherSubagent],
    backend: new FilesystemBackend({ rootDir: EXAMPLE_DIR }),
  });
}
ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

function createContentWriter() {
  const researcherSubagent = {
    name: "researcher",
    description:
      "Research subagent with web search capability. Delegate research tasks here.",
    systemPrompt:
      "You are a research assistant. Use the web_search tool to find current, accurate information and return well-organized findings.",
    tools: [webSearch],
  };

  return createDeepAgent({
    model: "anthropic:claude-sonnet-5",
    memory: ["./AGENTS.md"],
    skills: ["./skills/"],
    tools: [generateCover, generateSocialImage],
    subagents: [researcherSubagent],
    backend: new FilesystemBackend({ rootDir: EXAMPLE_DIR }),
  });
}

3. 添加入口点 (Add an entry point)

ts
const task =
  process.argv.slice(2).join(" ") ||
  "Write a blog post about how AI agents are transforming software development";

const agent = createContentWriter();
const result = await agent.invoke({
  messages: [{ role: "user", content: task }],
  config: { configurable: { threadId: "content-builder-demo" } },
});

const messages = result.messages ?? [];
for (const msg of messages) {
  if (msg.content) console.log(msg.content);
}

运行智能体 (Run the agent) ​

安全提醒

文件系统后端可在配置的 rootDir 下执行读、写以及删除操作。建议仅在专用目录下运行,并在发布前审查生成的内容。

在项目根目录下执行:

bash
npx tsx content_writer.ts

也可以通过附加命令行参数自定义提示词:

bash
npx tsx content_writer.ts Write a blog post about prompt engineering

配置 LANGSMITH_API_KEY 后,可以在 LangSmith 中审查完整的运行链路。

输出成果 (Output) ​

执行成功后,智能体将在项目根目录下生成对应的成果产物:

text
blogs/
└── prompt-engineering/
    ├── post.md
    └── hero.png
research/
└── prompt-engineering.md

完整代码 (Full code) ​

在 GitHub 上查看完整的 content-builder-agent 示例代码。

后续进阶 (Next steps) ​