Henri · 2026-01-25

基于Claude Agent SDK的文档摘要处理工具开发记录

这篇文档记录了我如何一步步构建一个基于 Claude Agent SDK 的文档摘要生成工具。整个过程并非一帆风顺——从最初的 API 选型困惑,到摘要格式的反复调整,再到文件命名规则的多次重构,每一步都伴随着问题的发现与解决。

起点:一个简单的需求

事情的起点很简单:我有一个 Clippings 文件夹,里面存放着从各处收集的文章(接近300篇)。这些文章越积越多,却很少被真正阅读和消化。我希望有一个工具,能够批量处理这些文章,为每篇生成一张摘要卡片——既保留核心信息,又节省阅读时间(最后,我将其上传到自己的网站中,即精选阅读模块)。

最初的代码框架已经存在,使用的是智谱 AI 的 GLM 模型(这是我最初的设想,但还没有完全调通)。但我希望切换到 Claude Agent SDK,因为它提供了更强大的 agent 能力,而且我已经配置好了 Claude Code 环境。接下来的工作是由amp完成的,因为我有免费赠送的10美刀额度,整个thread都使用的smart模型(调用Claude Opus 4.5, 大概花费$7)

第一个挑战:理解 Claude Agent SDK

当我说"使用 Claude Agent SDK"时,虽然我之前用过几次,但其实对它的详细用法印象并不深刻,只有残存于记忆中的模糊印象。我让amp帮我通过查阅最新文档,了解到 Claude Agent SDK 是一个基于 Claude Code 构建的 Python/TypeScript 库,核心 API 是异步的 query() 函数:

from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="你的提示词",
    options=ClaudeAgentOptions(allowed_tools=[])
):
    # 处理响应

与传统的同步 API 调用不同,这里返回的是一个异步迭代器,需要用 async for 来遍历消息流。这意味着整个处理逻辑都需要重构为异步模式。

我们写了一个简单的测试脚本来验证 SDK 是否正常工作:

async def test_basic_query():
    async for message in query(
        prompt="请用中文简单介绍一下你自己",
        options=ClaudeAgentOptions(allowed_tools=[])
    ):
        if hasattr(message, 'result'):
            print(f"结果: {message.result}")

测试通过后,我们才开始正式改造主程序。

设计工作流程

接下来需要设计清晰的工作流程。最终确定的目录结构是:

Clippings/
├── toread/      # 待处理的文章
├── summary/     # 生成的摘要卡片
└── _archived/   # 已处理的原文

工作流程很直观:从 toread 读取文章,生成摘要后保存到 summary,同时将原文移动到 _archived。这样既保留了原文的可追溯性,又让待处理队列保持清晰。

摘要格式的演进

摘要的格式经历了几轮调整。

最初的设计包含"核心观点"、“关键要点”、“金句摘录”、“延伸思考"四个部分,采用分点列举的形式。但这种格式显得有些机械,更像是填空题而非真正的内容理解。

经过讨论,我们将格式调整为三个部分:

  1. 摘要:100字左右的精炼概括
  2. 内容框架与概述:用自然段落描述文章脉络,而非分点罗列
  3. 核心概念及解读:提取关键词并结合文章进行解读

这种格式更贴近人类的阅读习惯——先了解大意,再理解结构,最后掌握核心概念。

关于模板文件,我们发现一个有趣的问题:如果模板中包含具体的示例文字(比如"文章开篇从某个现象切入…"),AI 可能会照搬这些措辞,产生硬编码效果。解决方案是用占位符替代具体示例:

## 内容框架与概述

{用2-4个自然段落概述文章结构与核心论述}

漫长的等待:日志系统的引入

第一次批量处理时,我们遇到了一个实际问题:命令执行后长时间没有任何输出,直到所有文章处理完毕才一次性返回结果。对于几十篇文章的批量处理,这意味着要等待很长时间而不知道进度如何。

解决方案是引入日志系统。在每次批处理开始时,先创建一个日志文件,列出所有待处理的文件:

# 摘要生成任务日志

| 序号 | 文件名 | 状态 |
|------|--------|------|
| 1 | article-1.md | ⏳ pending |
| 2 | article-2.md | ⏳ pending |

每处理完一篇文章,立即更新日志状态(✅ done / ❌ failed / ⏭️ skipped),并追加一条带时间戳的日志条目。这样即使命令超时中断,我们也能从日志中看到哪些文章已经处理完成。

class ProcessLogger:
    def update_status(self, filename: str, status: str, message: str = ""):
        # 更新状态表格
        # 追加日志条目
        # 写入文件(实时持久化)

文件命名的困境

文件命名是我们遇到的最棘手的问题之一。

最初的方案很简单:从标题中提取英文字符,转小写,用连字符连接。对于英文标题,这工作得很好:

  • “The Next Two Years of Software Engineering” → the-next-two-years-of-software-engineering.md

但对于中文标题,问题就来了:

  • “鲍勃·迪伦:我们这个时代的诗人” → 鲍勃迪伦我们这个时代的诗人.md(中文字符没有被过滤)

修复后又出现新问题:

  • “硅谷的尤达大师” → 20260124-232655.md(纯中文标题变成了时间戳)

时间戳虽然能保证唯一性,但完全失去了语义。更糟糕的是,有些文章的原标题本身就没有意义,比如 “Thread by @AztecaAlpaca”——即使正确提取也是无用的 thread-by-aztecaalpaca.md

最终的解决方案是让 Claude 自己来生成文件名。在 prompt 中增加一个要求:

首先,在第一行输出一个适合做文件名的英文短语(3-5个单词),格式:
SLUG: your-english-slug-here
规则:
- 必须是纯英文,单词用连字符连接,全小写
- 如果原标题是中文,翻译成英文并提取关键词
- 如果原标题无意义,根据文章内容提炼核心主题

这样:

  • “鲍勃·迪伦——欧美音乐的灵魂” → bob-dylan-soul-of-rock.md
  • “Thread by @AztecaAlpaca” → 根据文章内容生成有意义的名称

AI 理解文章内容,自然能生成更合适的文件名。

原文信息的位置调整

一个小但重要的细节:原文信息(来源链接、作者、发表日期)应该放在哪里?

最初我们把它放在摘要开头,紧跟在标题之后。但这样阅读体验不好——读者点开一篇摘要,首先想看的是内容概括,而不是元数据。

调整后,原文信息移到了文章末尾,并且将原文标题和来源 URL 合并为一个链接:

## 原文信息

| 字段 | 内容 |
|------|------|
| 原文 | [文章标题](https://source-url.com) |
| 作者 | 作者名 |
| 发表日期 | 2025-01-20 |

这样既保留了可追溯性,又不干扰主要内容的呈现。

最终的代码结构

经过这些迭代,最终的代码结构如下:

tools/
├── process.py           # 主处理脚本
├── test_agent_sdk.py    # SDK 测试脚本
├── summary_template.md  # 摘要模板参考
├── README.md           # 使用说明
├── logs/               # 处理日志
└── docs/               # 开发文档

核心处理流程:

  1. 创建日志文件,列出待处理文件
  2. 逐个读取文章,解析 frontmatter 和正文
  3. 调用 Claude 生成摘要(包含 SLUG 和 DESCRIPTION)
  4. 从响应中提取 slug 作为文件名
  5. 写入摘要文件,移动原文到归档目录
  6. 更新日志状态

反思与总结

回顾整个开发过程,有几点值得记录:

异步编程的必要性:Claude Agent SDK 基于异步设计,这不是可选的,而是必须适应的。好在 Python 的 asyncio 已经足够成熟,改造成本并不高。

实时反馈很重要:长时间无响应的命令会让人焦虑。日志系统虽然简单,但极大改善了使用体验。下次设计批处理工具时,这应该是标配而非事后补救。

让 AI 做它擅长的事:文件命名这个问题,我们最初试图用正则表达式和规则来解决,结果越改越复杂。最终让 Claude 来生成文件名,反而是最简洁有效的方案。AI 理解语义,规则处理语法——各司其职。

格式设计需要迭代:摘要的格式不是一次设计好的,而是在实际使用中逐步调整的。过于结构化的分点列举会显得机械,适度的自然段落更易阅读。

这个工具还有改进空间,但作为一个解决实际问题的工具,它已经可以正常工作了。剩下的优化,留待实际使用中发现新需求时再说。


最后更新:2026-01-25