概述

本指南演示了如何使用 Deep Agents 从头构建一个内容写作 Agent。 您构建的 Agent 将:
  1. AGENTS.md 和技能文件夹加载语音和工作流规则
  2. 使用配备 web_search 的专用子代理委托网络研究
  3. 按照加载的技能起草博客或社交内容
  4. 使用 Gemini 生成封面或社交图像,并将文件保存到项目目录下
本教程中的代码连接了图像生成工具和文件系统后端,以便 Agent 可以在项目目录下读取和写入帖子、研究笔记和图像。获取完整的可运行项目,请参阅 content-builder-agent 示例。

关键概念

本教程涵盖:

前置条件

API 密钥:
  • Anthropic(Claude)
  • Google(Gemini)用于使用 gemini-2.5-flash-image 进行图像生成
  • Tavily 用于网络搜索(免费套餐)
  • LangSmith 用于跟踪(可选)
Python 3.11 或更高版本。

设置

1

创建项目目录

mkdir content-builder-agent
cd content-builder-agent
2

安装依赖

pip install deepagents google-genai pillow pyyaml rich tavily-python langchain
在您自己的项目中将 deepagents 固定到支持的范围(例如 >=0.3.5,<0.4.0)以匹配上游示例。
3

设置 API 密钥

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"     # 可选

添加配置文件

示例将行为保存在三种文件中:记忆、技能和子代理定义。
1

添加 AGENTS.md

在项目根目录创建 AGENTS.md。 稍后当您创建 Agent 并将此文件指定为记忆参数的一部分时,它会被加载到系统提示中,这样品牌语音和研究期望就会应用到每次运行。
# 内容写作 Agent

您是一家科技公司的内容作家。您的工作是创作引人入胜、内容丰富的文章,帮助读者了解人工智能、软件开发和新晋技术。

## 品牌语音

- **专业但平易近人**:像一位知识渊博的同事一样写作,而不是教科书
- **清晰直接**:除非必要,否则避免使用术语;用简单的方式解释技术概念
- **自信但不傲慢**:分享专业知识,但不盛气凌人
- **引人入胜**:使用具体的例子、类比和故事来说明要点

## 写作标准

1. **使用主动语态**:"Agent 处理请求"而不是"请求由 Agent 处理"
2. **价值前置**:从对读者重要的内容开始,而不是背景
3. **一段一意**:保持段落集中且易于浏览
4. **具体胜于抽象**:使用具体的例子、数字和案例研究
5. **结尾有行动号召**:每篇文章都应该让读者知道下一步该做什么

## 内容支柱

我们的内容聚焦于:
- AI Agent 和自动化
- 开发者工具和生产力
- 软件架构和最佳实践
- 新晋技术和趋势

## 格式指南

- 使用标题(H2、H3)来分割长内容
- 在相关的地方包含代码示例(带语法高亮)
- 列表项目超过3个时使用项目符号
- 尽可能保持句子在25个单词以内
- 结尾包含明确的行动号召

## 研究要求

在撰写任何主题之前:
1. 使用 `researcher` 子代理进行深入的主题研究
2. 收集至少3个可信来源
3. 确定读者需要理解的关键要点
4. 找到具体的例子或案例研究来说明概念
为了让这个 Agent 符合您自己的语气、支柱和格式规则,请更新 AGENTS.md 中的文本。
2

添加 subagents.yaml

创建一个名为 subagents.yaml 的文件。 然后添加以下文本,其中描述了一个带有 Tavily 支持的 web_search 工具、Haiku 模型 ID 的 researcher 子代理,以及在从主 Agent 委托时指定保存路径的说明:
# 子代理定义
# 这些由 content_writer.py 加载并与工具连接

researcher:
  description: >
    在撰写任何内容之前,始终首先使用此工具研究任何主题。
    在网络上搜索当前信息、统计数据和来源。
    委托时,告诉它主题以及保存结果的文件路径
    (例如,"研究可再生能源并保存到 research/renewable-energy.md")。
  model: anthropic:claude-haiku-4-5-20251001
  system_prompt: |
    您是一个研究助手。您可以访问 web_search 和 write_file 工具。

    ## 您的工具
    - web_search(query, max_results=5, topic="general") - 在网络上搜索
    - write_file(file_path, content) - 保存您的发现

    ## 您的流程
    1. 使用 web_search 查找主题信息
    2. 使用具体查询进行2-3次有针对性的搜索
    3. 收集关键统计数据、引用和例子
    4. 将发现保存到任务中指定的文件路径

    ## 重要提示
    - 用户会告诉您在哪里保存文件 - 使用该确切路径
    - 始终在发现中包含来源 URL
    - 保持发现简洁但信息丰富
  tools:
    - web_search
该文件稍后在创建深度 Agent 时作为参数传递。
3

添加技能

创建一个 skills/ 目录。每个技能是一个文件夹,包含一个带有 YAML frontmatter(namedescription)和技能说明的 SKILL.md 文件。创建 skills/blog-post/SKILL.md 并将以下文本复制到其中,其中包含有关创建长篇文章、优化内容以进行 SEO 以及生成封面图像的信息。
---
name: blog-post
description: 撰写和构建长篇博客文章,创建教程大纲,并通过封面图像生成优化内容的 SEO。当用户要求写博客文章、文章、操作指南、教程、技术文章、思想领袖文章或长篇内容时使用。
---

# 博客文章写作技能

## 首先进行研究(必需)

**在撰写任何博客文章之前,您必须委托研究:**

1. 使用 `task` 工具,指定 `subagent_type: "researcher"`
2. 在描述中,同时指定主题和保存位置:

```
task(
    subagent_type="researcher",
    description="研究 [主题]。将发现保存到 research/[slug].md"
)
```

示例:
```
task(
    subagent_type="researcher",
    description="研究 2025 年 AI Agent 的现状。将发现保存到 research/ai-agents-2025.md"
)
```

3. 研究完成后,在写作之前阅读发现文件

## 输出结构(必需)

**每篇博客文章必须同时有文章和封面图像:**

```
blogs/
└── <slug>/
    ├── post.md        # 博客文章内容
    └── hero.png       # 必需:生成的封面图像
```

示例:关于"2025 年 AI Agent"的文章 → `blogs/ai-agents-2025/`

**您必须完成两个步骤:**
1. 将文章写入 `blogs/<slug>/post.md`
2. 使用 `generate_image` 生成封面图像并保存到 `blogs/<slug>/hero.png`

**没有封面图像的博客文章是不完整的。**

## 博客文章结构

每篇博客文章都应遵循以下结构:

### 1. 钩子(开场)
- 以引人注目的问题、统计数据或陈述开始
- 让读者想要继续阅读
- 保持2-3句话

### 2. 背景(问题)
- 解释为什么这个主题很重要
- 描述问题或机会
- 与读者的经历联系起来

### 3. 主要内容(解决方案)
- 分成3-5个带 H2 标题的主要部分
- 每个部分涵盖一个关键点
- 在有帮助的地方包含代码示例、图表或截图
- 使用项目符号列出列表

### 4. 实际应用
- 展示如何应用概念
- 如适用,包含分步说明
- 提供代码片段或模板

### 5. 结论和 CTA
- 总结关键要点(最多3个项目符号)
- 以明确的行动号召结尾
- 链接到相关资源

## 封面图像生成

写完文章后,使用 `generate_cover` 工具生成封面图像:

```
generate_cover(prompt="图像的详细描述...", slug="your-blog-slug")
```

该工具将图像保存到 `blogs/<slug>/hero.png`

### 编写有效的图像提示

使用以下元素构建您的提示:

1. **主题**:主要焦点是什么?要具体和明确。
2. **风格**:艺术方向(极简、等距、扁平设计、3D 渲染、水彩等)
3. **构图**:元素如何排列(居中、三分法、对称)
4. **调色板**:具体颜色或情绪(暖大地色调、冷蓝和紫、高对比度)
5. **灯光/氛围**:柔和漫射光、戏剧性阴影、黄金时刻、霓虹灯辉光
6. **技术细节**:宽高比注意事项、用于文本叠加的负空间

### 提示示例

**技术博客文章:**
```
等距 3D 插图,互联发光的立方体代表 AI Agent,每个立方体都有微妙的电路图案。立方体由发光的数据流连接。深海军蓝背景(#0a192f)配以电蓝色(#64ffda)和柔和紫色(#c792ea)点缀。干净简约的风格,顶部有大量负空间用于标题。专业科技美学。
```

**教程/操作指南:**
```
干净扁平插图,描述手在键盘上打字,抽象代码符号向上飘浮,转变为灯泡和齿轮。暖色渐变背景,从柔和珊瑚色到浅桃色。友好、平易近人的风格。居中构图,有文本叠加空间。
```

**思想领袖文章:**
```
人类轮廓侧脸与几何神经网络图案融合的抽象可视化。分割构图——左侧有机水彩纹理过渡到右侧干净矢量线条。柔和鼠尾草绿和暖赤陶色配色方案。沉思的、前瞻性的情绪。
```

## SEO 注意事项

- 在标题和第一段中包含主要关键词
- 在整个文章中自然使用关键词3-5次
- 标题保持在60个字符以内
- 撰写元描述(150-160个字符)

## 质量检查清单

完成前:
- [ ] 文章保存到 `blogs/<slug>/post.md`
- [ ] 英雄图像生成在 `blogs/<slug>/hero.png`
- [ ] 钩子在最初2句话内吸引注意力
- [ ] 每个部分都有明确的目的
- [ ] 结论总结了关键要点
- [ ] CTA 告诉读者下一步做什么
接下来,创建 skills/social-media/SKILL.md 并将以下文本复制到其中,其中包含有关起草社交媒体帖子和生成配套图像的信息:
---
name: social-media
description: 起草引人入胜的社交媒体帖子,撰写钩子、建议标签、创建推文串结构,并生成配套图像。当用户要求写 LinkedIn 帖子、推文、Twitter/X 推文串、社交媒体标题、社交帖子或为社交平台重新调整内容时使用。
---

# 社交媒体内容技能

## 首先进行研究(必需)

**在撰写任何社交媒体内容之前,您必须委托研究:**

1. 使用 `task` 工具,指定 `subagent_type: "researcher"`
2. 在描述中,同时指定主题和保存位置:

```
task(
    subagent_type="researcher",
    description="研究 [主题]。将发现保存到 research/[slug].md"
)
```

示例:
```
task(
    subagent_type="researcher",
    description="研究 2025 年可再生能源趋势。将发现保存到 research/renewable-energy.md"
)
```

3. 研究完成后,在写作之前阅读发现文件

## 输出结构(必需)

**每个社交媒体帖子必须同时有内容和图像:**

**LinkedIn 帖子:**
```
linkedin/
└── <slug>/
    ├── post.md        # 帖子内容
    └── image.png      # 必需:生成的视觉内容
```

**Twitter/X 推文串:**
```
tweets/
└── <slug>/
    ├── thread.md      # 推文串内容
    └── image.png      # 必需:生成的视觉内容
```

示例:关于"提示工程"的 LinkedIn 帖子 → `linkedin/prompt-engineering/`

**您必须完成两个步骤:**
1. 将内容写入适当的路径
2. 使用 `generate_image` 生成图像并与帖子一起保存

**没有图像的社交媒体帖子是不完整的。**

## 平台指南

### LinkedIn

**格式:**
- 1,300 字符限制(~210 个字符后显示更多)
- 第一行至关重要 - 使其成为钩子
- 使用换行以提高可读性
- 末尾使用 3-5 个标签

**语气:**
- 专业但个人化
- 分享见解和学习
- 提问以推动参与
- 使用"我"并分享经历

**结构:**
```
[钩子 - 1 句引人注目的话]

[空行]

[背景 - 为什么这很重要]

[空行]

[主要见解 - 2-3 个短段落]

[空行]

[行动号召或问题]

#标签1 #标签2 #标签3
```

### Twitter/X

**格式:**
- 每条推文 280 字符限制
- 推文串用于较长内容(使用 1/🧵 格式)
- 每条推文不超过 2 个标签

**推文串结构:**
```
1/🧵 [钩子 - 主要见解]

2/ [支持点 1]

3/ [支持点 2]

4/ [示例或证据]

5/ [结论 + CTA]
```

## 图像生成

每个社交媒体帖子都需要一张引人注目的图像。使用 `generate_social_image` 工具:

```
generate_social_image(prompt="详细描述...", platform="linkedin", slug="your-post-slug")
```

该工具将图像保存到 `<platform>/<slug>/image.png`

### 社交图像最佳实践

社交图像需要在拥挤的信息流中以小尺寸醒目:
- **大胆、简单的构图** - 一个清晰的焦点
- **高对比度** - 滚动时脱颖而出
- **图像中无文字** - 太小无法阅读,平台会添加自己的
- **方形或 4:5 比例** - 跨平台适用

### 编写有效的提示

包含以下元素:

1. **单一焦点**:一个清晰的主题,而不是繁忙场景
2. **大胆风格**:鲜艳的色彩、强烈的形状、高对比度
3. **简单背景**:纯色、渐变或微妙纹理
4. **情绪/能量**:匹配帖子语气(励志、紧迫、深思)

### 提示示例

**见解/提示帖子:**
```
单个发光灯泡漂浮在深紫色渐变背景上,灯泡由互联的金色几何线条构成,光线向外柔和散发。极简、醒目、高对比度。方形构图。
```

**公告/新闻:**
```
由彩色几何形状构成的抽象火箭飞船向上发射,粒子尾迹。明亮珊瑚色和蓝绿色配色方案,干净白色背景。充满活力、庆祝的情绪。大胆扁平插画风格。
```

**发人深省的内容:**
```
两个重叠的半透明圆形,一个蓝色一个橙色,在中心形成发光交集。代表协作或思想交叉。深炭灰背景,柔和空灵的辉光。极简而沉思。
```

## 内容类型

### 公告帖子
- 以新闻为首
- 解释影响
- 包含链接或下一步

### 见解帖子
- 分享一个具体的学习
- 简要解释背景
- 使其可操作

### 问题帖子
- 提出一个真正的问题
- 先提供您的看法
- 保持聚焦于一个主题

## 质量检查清单

完成前:
- [ ] 帖子保存到 `linkedin/<slug>/post.md``tweets/<slug>/thread.md`
- [ ] 图像与帖子一起生成
- [ ] 第一行吸引注意力
- [ ] 内容符合平台限制
- [ ] 语气符合平台规范
- [ ] 有明确的 CTA 或问题
- [ ] 标签相关(不是通用的)
它们指示 Agent 首先调用 researcher 子代理,在 blogs/linkedin/tweets/ 下写入 markdown,并调用 generate_covergenerate_social_image 生成图像。当您稍后创建 Agent 并指定技能文件夹时,这些技能文件夹中的 SKILL.md 文件的 frontmatter 会被加载到系统提示中,这样 Agent 就可以在任务与技能描述匹配时使用该技能。

构建脚本

在项目根目录创建 content_writer.py。以下部分按顺序放在一个文件中。
1

添加工具

researcher 子代理使用 Tavily 搜索。 博客和社交工作流使用 Gemini 图像生成。 稍后创建 Agent 时,load_subagents 函数会读取 subagents.yaml 并将工具名称解析为这些装饰函数。
import os
from pathlib import Path
from typing import Literal

import yaml
from langchain.tools import tool

EXAMPLE_DIR = Path(__file__).parent


@tool
def web_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news"] = "general",
) -> dict:
    """Search the web for current information.

    Args:
        query: The search query (be specific and detailed)
        max_results: Number of results to return (default: 5)
        topic: "general" for most queries, "news" for current events

    Returns:
        Search results with titles, URLs, and content excerpts.
    """
    try:
        from tavily import TavilyClient

        api_key = os.environ.get("TAVILY_API_KEY")
        if not api_key:
            return {"error": "TAVILY_API_KEY not set"}

        client = TavilyClient(api_key=api_key)
        return client.search(query, max_results=max_results, topic=topic)
    except Exception as e:
        return {"error": f"Search failed: {e}"}


@tool
def generate_cover(prompt: str, slug: str) -> str:
    """Generate a cover image for a blog post.

    Args:
        prompt: Detailed description of the image to generate.
        slug: Blog post slug. Image saves to blogs/<slug>/hero.png
    """
    try:
        from google import genai

        client = genai.Client()
        response = client.models.generate_content(
            model="gemini-2.5-flash-image",
            contents=[prompt],
        )

        for part in response.parts:
            if part.inline_data is not None:
                image = part.as_image()
                output_path = EXAMPLE_DIR / "blogs" / slug / "hero.png"
                output_path.parent.mkdir(parents=True, exist_ok=True)
                image.save(str(output_path))
                return f"Image saved to {output_path}"

        return "No image generated"
    except Exception as e:
        return f"Error: {e}"


@tool
def generate_social_image(prompt: str, platform: str, slug: str) -> str:
    """Generate an image for a social media post.

    Args:
        prompt: Detailed description of the image to generate.
        platform: Either "linkedin" or "tweets"
        slug: Post slug. Image saves to <platform>/<slug>/image.png
    """
    try:
        from google import genai

        client = genai.Client()
        response = client.models.generate_content(
            model="gemini-2.5-flash-image",
            contents=[prompt],
        )

        for part in response.parts:
            if part.inline_data is not None:
                image = part.as_image()
                output_path = EXAMPLE_DIR / platform / slug / "image.png"
                output_path.parent.mkdir(parents=True, exist_ok=True)
                image.save(str(output_path))
                return f"Image saved to {output_path}"

        return "No image generated"
    except Exception as e:
        return f"Error: {e}"


def load_subagents(config_path: Path) -> list:
    """Load subagent definitions from YAML and wire up tools.

    Unlike `memory` and `skills`, Deep Agents does not load subagents from files by default.
    This helper externalizes configuration so you can edit YAML without changing Python code.
    """
    available_tools = {
        "web_search": web_search,
    }

    with open(config_path) as f:
        config = yaml.safe_load(f)

    subagents = []
    for name, spec in config.items():
        subagent = {
            "name": name,
            "description": spec["description"],
            "system_prompt": spec["system_prompt"],
        }
        if "model" in spec:
            subagent["model"] = spec["model"]
        if "tools" in spec:
            subagent["tools"] = [available_tools[t] for t in spec["tools"]]
        subagents.append(subagent)

    return subagents
2

创建 Agent

使用 create_deep_agent 创建深度 Agent 时,传递记忆路径、技能目录、图像工具、YAML 中的子代理以及以示例目录为根的文件系统后端,以便 ./AGENTS.md./skills/ 等路径能够正确解析。
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend


def create_content_writer():
    """Create a content writer agent configured by filesystem files."""
    return create_deep_agent(
        memory=["./AGENTS.md"],
        skills=["./skills/"],
        tools=[generate_cover, generate_social_image],
        subagents=load_subagents(EXAMPLE_DIR / "subagents.yaml"),
        backend=FilesystemBackend(root_dir=EXAMPLE_DIR),
    )
3

添加入口点

使用用户消息调用 Agent 以验证 Agent 是否正常工作:
import sys

from langchain.messages import HumanMessage

if __name__ == "__main__":
    task = (
        " ".join(sys.argv[1:])
        if len(sys.argv) > 1
        else "Write a blog post about how AI agents are transforming software development"
    )

    agent = create_content_writer()
    result = agent.invoke(
        {"messages": [HumanMessage(content=task)]},
        config={"configurable": {"thread_id": "content-builder-demo"}},
    )

    for msg in result.get("messages", []):
        if hasattr(msg, "content") and msg.content:
            print(msg.content)

运行 Agent

文件系统后端可以在 root_dir 下读取、写入和删除文件。只在专用目录中运行,并在发布前审查生成的内容。
从项目目录中,您可以不带参数调用 Agent 或将提示作为参数传递:
python content_writer.py
设置 LANGSMITH_API_KEY 后,您可以在 LangSmith 中检查运行情况。

输出

成功时,生成的产物会写入系统临时目录(在 macOS 和 Linux 上,通常在 /tmp/ 下),而不是项目文件旁边。
blogs/
└── prompt-engineering/
    ├── post.md
    └── hero.png
research/
└── prompt-engineering.md
路径遵循 SKILL.md 中的技能说明。

完整代码

在 GitHub 上浏览完整的 content-builder-agent 示例,包括基于 Rich 的流式传输 UI。