1. 背景与真正要解决的需求
1.1 先说说这个 Skill 是怎么来的
做 Agent 开发有一段时间了,我越来越发现一个问题:很多智能体看起来什么都能聊,可真到实际干活的时候,连用户给的一个本地视频、一份 PPT 都没法处理。问它能不能提取视频里讲的重点,它只会说"抱歉,我无法访问本地文件";让它把 PPT 梳理成大纲,它能答,但前提是你先把文字贴过去。
这个场景其实特别常见。我接触过的实际需求里,至少有一半是和"从已有材料里提炼信息"相关的:课程回放要转成文字笔记、会议录像要提炼结论、领导扔过来一份几十页的 PPT 要整理成汇报提纲、知识库要批量导入视频和课件内容。用户手里的资料是视频和 PPT,但智能体吃的是文本。这中间缺的,就是把非文本内容"翻译"成智能体能理解的结构化文本的那一层。
市面上其实已经有不少现成的工具,但它们和智能体是割裂的。你需要手动去某个网站转录音频、另开一个工具提取 PPT 文字,再把结果粘贴进对话框。这个流程一旦遇到批量处理,就成了体力活。我想做的,是把整套流程封装成一个 Agent Skill,让智能体自己就能调工具、读文件、做提取,最后把结果整理成一份可供下游使用的结构化内容。
这个标题之所以叫"从 0 到 1 诞生记 + 使用指南",是因为它既是开发记录,也是使用说明。文章会包含:需求分析、技术选型、Skill 目录结构设计、核心代码实现、真实使用中的参数调整和踩坑记录,每一部分都是我实际动手跑过之后总结的。
1.2 哪些人适合参考这个方案
如果你属于下面任何一类,这篇文章应该能直接帮到你:
- 正在做 Agent 开发,想给智能体增加本地文件解析能力的开发者
- 知识管理方向的产品经理或实施人员,需要把视频课程、会议录音、PPT 课件结构化入库
- 被"复制粘贴式"信息整理折磨的运营或助理,想用一个相对自动化的方案解决重复劳动
- 刚接触 Skill 开发,想通过一个完整案例理解"技能包"到底怎么设计的人
我会尽量把每一步的逻辑讲清楚,不只是贴代码,还会解释为什么这么设计、有哪些替代项、各自适合什么场景。这样即使你用的智能体框架和我不同,思路也能直接搬走。
2. 整体设计思路与技术选型
2.1 这个 Skill 的能力边界
动手写代码之前,我先把能力边界定义清楚。Skill 不是万能工具箱,边界越清晰,实现越稳定,后续维护成本越低。我最终确定的处理范围如下:
| 能力项 | 支持内容 | 不支持内容 |
|---|---|---|
| 视频输入 | mp4、mov、mkv 等常见格式 | 直播流、加密视频、超长视频(超过 2 小时建议拆段) |
| 音频提取 | 自动抽取视频音轨并降噪 | 极端嘈杂环境下的高准率识别 |
| 语音识别 | 普通话为主,支持英文、中英混合 | 方言识别受限,多说话人区分能力弱 |
| PPT 输入 | pptx 格式(优先)、ppt 旧格式(需转换) | 加密文件、内嵌 ActiveX 控件的内容 |
| 输出结构 | Markdown 大纲、摘要、要点列表、时间戳引用 | 自动生成插图、复杂表格还原 |
上面有一条特别值得注意:我做的是"内容提取",不是"转写全部内容再总结",而是提取之后还要按结构整理。也就是说,它不是简单地把语音转成文字,而是要把文字再加工成智能体能直接用于回答问题的结构化信息。
为什么这样设计?因为我观察到,智能体最需要的不是一段原始转写稿,而是"这个视频讲了什么、有哪些关键结论、分几个部分、每个部分的要点是什么"。原始转写在需要逐字校对时有价值,但作为知识库条目入库时,反而是整理后的结构更有检索效率。
2.2 技术方案的核心链路
整个处理流程我拆成了五段:
文件接收 → 格式解析 → 内容抽取 → 结构整理 → 结果回传- 文件接收:Skill 通过统一的入口接收文件路径或文件对象,不区分是本地路径还是上传文件
- 格式解析:视频走 FFmpeg 抽音轨,PPT 用解压文件的方式直接读取 XML
- 内容抽取:音频转文字用语音识别接口;PPT 文字从 slide XML 里提取
- 结构整理:把识别出来的文本按时间轴分段、按语义聚类、抽取关键句
- 结果回传:统一输出 Markdown 格式,包含人类可读的内容和结构化的元数据
这个链路里最关键的决策,是把语音识别做成"可替换的接口"。我没有把某一家 ASR 服务写死在代码里,而是做了一个 Provider 抽象层。原因很简单:语音识别的服务商很多,有的免费额度高,有的准确率好,有的对专业术语支持好。不同项目用的服务不一样,如果写死,换一家就要改代码。
2.3 为什么选 FFmpeg 而不是其他工具
视频抽音轨这件事,FFmpeg 几乎是事实标准。它可以命令行处理,资源占用可控,对各种编码格式的支持非常全。我试过用 Python 的 moviepy,接口虽然友好,但底层还是调用 FFmpeg,多包一层反而增加了安装依赖的复杂度。直接在 Skill 里调用 FFmpeg 命令,简单直接,调试也方便。
音频参数方面,我踩过一个小坑:采样率设置得过低会影响识别准确率,设置过高又会让文件体积变大、转写变慢。语音识别服务通常要求音频在 16kHz 左右,我采样时统一设置为 16kHz、单声道、编码为 wav 或 mp3。这个参数是识别率和传输效率之间比较平衡的点。
2.4 PPT 提取为什么不用现成库
PPT 文字提取,常规思路是用 python-pptx。这个库很好用,但它有几个实际问题:一是在某些环境下安装依赖比较重(要装 lxml 等),二是对 pptx 之外的兼容性一般,三是遇到内容嵌入比较复杂的文件时容易报错。
我后来改用了更底层的方式:pptx 文件本质是个压缩包,里面是 XML 结构。直接用 zipfile 解压,然后用 XML 解析提取文字节点,效果反而更稳定。这种方法不依赖任何第三方库,对嵌入对象、表格、形状里的文字也都能拿到。唯一的缺点是要自己处理 XML 命名空间,但这只是写一次的问题。
这算是一个"简单工具做减法"的思路。能用标准库解决的问题,就不额外引入依赖。Agent Skill 的部署环境可能比较多样,少一个依赖,就少一个跑不起来的理由。
3. Skill 的目录结构与工作机制
3.1 Skill 文件目录怎么组织
Skill 说白了就是一个有固定结构的目录,里面包含描述文件、配置文件和实际执行的脚本。我的目录结构如下:
skill/ ├── SKILL.md # Skill 的功能描述,给智能体读取 ├── requirements.txt # Python 依赖清单 ├── config.yaml # 可配置参数(模型、阈值、超时等) ├── run.py # Skill 入口脚本 ├── modules/ │ ├── audio.py # 音频抽取与预处理 │ ├── asr.py # 语音识别(Provider 抽象层) │ ├── pptx_parser.py # PPT 内容提取 │ └── summarizer.py # 文本结构整理与摘要生成 └── output/ # 处理结果的输出目录SKILL.md 是整个 Skill 面向智能体的"说明书",我给它写了两部分:第一部分是"这个 Skill 能做什么、不能做什么",第二部分是"输入参数的 JSON Schema 定义"。智能体拿到这个文件,就能理解什么时候该调用它、传什么参数、返回值是什么结构。这部分写得好不好,直接决定了智能体会不会在错误的时候错误地调用它。
3.2 输入参数与返回值的协议设计
输入参数我定义为 JSON 对象,包含以下字段:
{ "file_type": "video", "file_path": "/data/uploads/xxx.mp4", "output_formats": ["markdown", "summary"], "language": "zh", "detail_level": "standard", "time_range": "0-600" }- file_type:区分 video 和 ppt,这样可以共用同一个入口,内部走不同分支
- file_path:文件位置,支持绝对路径或相对路径
- output_formats:要输出什么形式的结果,markdown 代表完整结构笔记,summary 代表一页纸摘要
- time_range:可选参数,只处理视频的某一段,避免每次都从头跑到尾
返回值统一为一个 JSON:
{ "status": "success", "data": { "title": "自动识别/手动传入的标题", "format_type": "video", "duration": 1234, "segments": [ { "start": 0, "end": 120, "text": "...", "summary": "..." } ], "key_points": ["...", "..."], "markdown": "完整 Markdown 笔记" } }这个协议设计的核心是"分层输出":既有最原始的段落级转写文本,又有提炼好的关键点总结,还有可直接展示的 Markdown 全文。不同下游需求可以按需取用,不必每次重新解析。
3.3 config.yaml 里放了哪些可调参数
配置参数的设计思路是"默认值可用,高级用户可覆盖":
asr: provider: "default" sample_rate: 16000 channel: 1 max_duration_minutes: 120 timeout_seconds: 600 summarization: method: "keyword_entropy" summary_ratio: 0.15 max_points: 5 pptx: extract_notes: true extract_hidden: false min_chars_per_slide: 20 output: language: "zh" include_timestamps: true timestamp_step: 30这里面的两个参数我想特别说下:
- summary_ratio:控制摘要相对原文的长度比例,0.15 表示 1000 字的原文生成约 150 字的摘要。这个值不是越大越好,太大会变成复述,太小会丢掉信息。
- min_chars_per_slide:过滤掉 PPT 里几乎空白的页面,比如只有一张大图、一行字的页面。这是实际使用中很有用的一个细节,能把无效内容挡在外面。
4. 核心模块实现细节
4.1 音频抽取:FFmpeg 命令与参数
音频抽取模块我用 subprocess 调用 FFmpeg,命令如下:
ffmpeg -y -i input.mp4 -vn -ar 16000 -ac 1 -f wav output.wav参数说明:
- -vn:丢弃视频流,只处理音频
- -ar 16000:采样率设为 16kHz。这个值的依据是大多数语音识别服务要求的输入格式,16kHz 对做人声识别来说信息量足够
- -ac 1:转为单声道。双声道转单声道可以让识别服务避免两个通道内容不一致带来的干扰
- -f wav:输出格式设为 wav,兼容性最好
在 run.py 里,我加了一个前置检查:先用 ffprobe 获取视频时长,如果超过 2 小时,就提示用户拆段处理,而不是直接硬跑。这个设计来自一次真实事故——有一次我丢了个 3 小时的会议视频进去,结果整个处理流程跑了很久,最后因为超时失败了。
4.2 语音识别 Provider 抽象层
ASR 模块是一个 Provider 抽象层的骨架结构,不同服务商的实现都遵循同一套接口:
class ASRProvider: def transcribe(self, audio_path: str, language: str) -> TranscribeResult: raise NotImplementedError class DefaultProvider(ASRProvider): def transcribe(self, audio_path, language): # 默认实现:调用某云端通用语音识别服务 ... class LocalProvider(ASRProvider): def transcribe(self, audio_path, language): # 本地模型实现,适合离线环境 ...这个抽象层解决的问题很实际:语音识别的方案迭代很快,你今年用得挺好的接口,明年可能就改了收费策略;你刚换了一个更准的本地模型,结果发现环境里缺推理依赖。把 Provider 做成可替换的,换服务商时只需要新写一个类,不用动其他任何模块。
对绝大多数个人开发者来说,前期先用现成云端服务够用了。等量上来了、成本敏感了,再切换到本地或自部署方案。不要一开始就追求私有化部署,那会拖慢开发节奏。
4.3 PPT 解析:不走库,直接拆 XML
PPT 提取的核心代码如下,用标准库完成:
import zipfile import xml.etree.ElementTree as ET PPTX_NS = { 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' } def extract_text_from_pptx(path: str) -> list: slides = [] with zipfile.ZipFile(path) as z: slide_files = sorted([f for f in z.namelist() if f.startswith('ppt/slides/slide') and f.endswith('.xml')], key=lambda x: int(x.split('slide')[-1].split('.')[0])) for slide_file in slide_files: slide_text = [] with z.open(slide_file) as f: tree = ET.parse(f) root = tree.getroot() for text_elem in root.iter('{http://schemas.openxmlformats.org/drawingml/2006/main}t'): slide_text.append(text_elem.text or '') slides.append(' '.join(slide_text)) return slides提取到的每个幻灯片文本是一个列表项,最后过滤掉长度过短的页。这种方法有两个好处:一是没有第三方依赖,部署到任何 Python 环境都不会因为缺库失败;二是速度非常快,只解析文本节点,不做任何额外的渲染或嵌入媒体处理,一个几十页的 PPT 基本是秒级完成。
4.4 内容整理:把原始文本变成结构化笔记
内容整理是这个 Skill 里最体现"AI 味"的模块,但我的实现刻意不依赖大模型。我用的是一种结合关键词熵和时间连续性规则的轻量级方案,目的是在"快"和"结构可用"之间取一个平衡。
具体逻辑是三步:
| 步骤 | 做什么 | 实现思路 |
|---|---|---|
| 分段 | 将连续转写文本按时长切片 | 默认每 120 秒一个段落,保持基本语义完整性 |
| 关键句抽取 | 从每个段落中找出信息量最大的 1-2 句 | 结合句子长度、关键词密度、位置权重来打分 |
| 摘要合并 | 把分段的摘要合并成全文要点 | 去重 + 按出现顺序排序,控制总长度 |
打分公式的核心是一个关键词权重表。比如"结论是""重点在于""因此"这类提示词会加分,语气词、口头语会减分。句子太短不选、太长不选,因为在口语转写里,又短又频繁出现的句子通常不是核心内容。
这个方案让 Skill 在没有大模型调用成本的情况下,也能给出质量不错的初步整理。如果后续需要更高阶的语义理解,可以在接口处预留大模型调用的位置,把它作为"增强模式"。
4.5 run.py 的流程编排
run.py 是整个 Skill 的编排中枢,每个分支都做了错误处理和中间状态输出:
def main(params): if params['file_type'] == 'video': audio = extract_audio(params['file_path']) transcript = transcribe(audio, params.get('language', 'zh')) result = build_notes(transcript, params) elif params['file_type'] == 'ppt': slides = extract_slides(params['file_path']) result = build_ppt_notes(slides, params) else: raise ValueError('Unsupported file type') save_output(result) return result每个核心步骤我都打印了日志,包含处理耗时和文件大小,方便排查卡在哪个环节。这些日志在第一次部署排查问题时帮了大忙——有一次用户反馈"处理特别慢",我一看日志就知道是音频转写阶段排队耗时太长,而不是代码本身出了问题。
5. 真实环境里的使用指南
5.1 安装与注册步骤
以最常见的部署方式为例,完整步骤如下:
- 把 Skill 目录放到 Agent 框架的技能目录下,一般是 agent/skills/ 这个路径
- 安装依赖:pip install -r requirements.txt,依赖非常轻,主要就是 PyYAML 和 requests
- 检查 FFmpeg 是否可用:执行
ffmpeg -version,没有的话先装好 FFmpeg - 配置 config.yaml 里的语音识别服务接入信息(密钥、端点等)
- 在智能体配置中启用该 Skill,并在测试环境验证一次完整调用
我用了一个 5 分钟的短视频做冒烟测试:视频是一段产品功能介绍,带字幕、背景音乐较轻、语速适中。跑完之后我看输出是否包含正确的分段数、关键点数量和 Markdown 文件是否完整。这个测试能快速暴露八成以上的环境问题。
5.2 不同场景下的调用示例
实际使用中,调用方式取决于你用的智能体框架,但核心参数是一致的。举例说明几个高频场景:
场景一:把课程视频整理成学习笔记
{ "file_type": "video", "file_path": "/data/courses/lesson01.mp4", "output_formats": ["markdown"], "language": "zh", "detail_level": "high" }这个场景我把 detail_level 设为 high,对应的分段粒度会更细,摘要比例提高到 0.2,适合需要保留更多细节的学习场景。
场景二:快速提炼 PPT 汇报大纲
{ "file_type": "ppt", "file_path": "/data/docs/quarter_report.pptx", "output_formats": ["summary"], "detail_level": "low" }汇报场景只需要核心结论,不需要逐页细节。summary 输出会把每一页的要点进一步压缩,最后只生成一页纸的大纲。detail_level 设为 low 时,生成的摘要会更精简,适合快速扫读。
场景三:只处理会议视频的某个部分
{ "file_type": "video", "file_path": "/data/meetings/2025-01-15.mp4", "time_range": "1800-2700", "output_formats": ["markdown", "summary"] }如果一个半小时的会议你只想回看最后十五分钟的结论部分,这个参数能帮你省掉大段前置处理时间。time_range 的单位是秒,这个参数在长视频场景里特别好用。
5.3 参数调优的实操建议
我对参数调优的建议总结成一句话:先跑默认参数,看输出,再按需调整,不要一上来就改配置。
如果发现摘要太啰嗦,把 summary_ratio 往下调,从 0.15 调到 0.1 左右;如果发现关键点漏掉了重要结论,把关键词权重表里的"结论类"提示词权重加大;如果发现某个 PPT 页面对应的内容缺失,检查 min_chars_per_slide 是不是设置得太高,把它降下来再试。
调整参数时一定要做对照测试。改一个参数,用同一个文件跑一遍,对比前后输出的差异,这样才知道是哪个参数在起作用。我曾经一次性改了三个参数,结果出了问题完全不知道是哪个改出来的,后来只能逐项回退排查。
6. 常见问题与排查技巧
6.1 高频问题和解决方案速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| Skill 返回"文件不支持" | 文件格式不在支持列表 | 转成 mp4 或 pptx 后再试 |
| 视频处理特别慢 | 视频时长过长或音频转写排队 | 用 time_range 分段处理,或拆分视频 |
| 转写文本中英文错乱 | 语言参数没有设置或设置错误 | 明确传入 language 参数,中英混合内容用 "zh-en" |
| PPT 提取不出来文字 | 文件是加密状态或旧版 .ppt 格式 | 另存为 pptx 并确认未加密 |
| 输出摘要质量差 | summary_ratio 设置不当 | 调低 ratio,或先检查分段是否合理 |
| 音频文件损坏 | 源视频本身有问题 | 用播放器打开源视频确认视频可正常播放 |
| 路径中含中文报错 | 编码问题 | 路径改为纯英文目录 |
| 命令找不到 FFmpeg | 环境变量没配置 | 重新安装或配置 FFmpeg 可执行文件路径 |
6.2 我踩过的三个真坑
第一个坑是 PPT 提取时遇到的命名空间问题。我最初写的 XML 解析代码没有带命名空间,结果提取出来的文字稀疏不连续。排查半天才发现是命名空间没有声明,root.iter()找不到a:t节点。解决办法就是代码里定义命名空间映射,这个错误写一次就记住了。
第二个坑是语音识别服务的超时设置。默认超时时间设成 120 秒,结果一个半小时的视频在转写阶段直接超时失败。排查后我把超时时间调大到 600 秒,同时加了分段处理的逻辑,避免一次性提交过长的音频。这提醒我,处理长文件时,超时设置一定要考虑最坏情况。
第三个坑是"语言参数显式化"的问题。某次我处理一段中英混合的内容,默认语言参数没传,识别服务自动识别出了一半中文一半英文,但分层乱了。后来我改成显式传入语言,并针对中英混合场景专门做了标签切换,输出质量才稳定下来。
6.3 一些常规文档里不会写的细节
音频降噪这个点很容易被忽略。视频背景音乐、环境噪音、混响都会降低识别准确率。如果对识别质量要求高,可以在 FFmpeg 抽音时串联一个轻量降噪滤波器,比如-af afftdn=nf=-25。对说话声为主的视频,这个滤波器效果很明显。
另一个细节是输出文件的命名规范。我会在文件名里附带原始文件名和日期,比如lesson01_notes_20250120.md。这样多个视频批量处理的时候不会同名覆盖,也能从文件名直接看出处理时间。
还有一个关于批量问题的建议:如果要同时处理几十个文件,强烈建议在 Skill 外层加一个任务队列控制并发。语音识别服务通常有 QPS 限制,并发太高会触发限流,表现为间歇性失败。每批处理 3-5 个文件,间隔几秒再发起下一批,是个稳定且不容易触雷的节奏。
7. 结尾:一点实际的体会
整个 Skill 从需求梳理到可稳定使用,我大概花了两个完整的周末。真正花的功夫不在代码本身,而在想清楚"提取出来之后要变成什么样子"和"哪些环节最不可控"。音频转写是最容易出问题的环节,也是信息质量的天花板,值得优先投入。
另外一个让我印象很深的事是:最初版本我试图把所有能力都塞进去——自动生成标题摘要、多说话人识别、智能配图。后来一个个砍掉了,砍到最后留下的核心功能,反而让这个 Skill 真正变得可靠。这个经验让我在后面的开发里养成了一个习惯:最先做最核心的 20% 功能,跑通之后再加附加值。
如果你也想在自己的智能体上加一个类似的能力,我的建议很简单:先拿 20 个真实文件跑一遍,把你的输出结果给身边人看,问他们"这个内容能直接用吗"。得到的反馈会比你自己调参数快得多。
最后分享一个小技巧。调试 Skill 时,别总盯着代码逻辑找问题。先在命令行手动跑一次完整流程,把每一步的输出文件打开看一眼。这个过程能排除掉大量环境层面的问题,留出更多时间优化真正需要优化的质量环节。