会议纪要整理
执行步骤
检查输入是否包含可识别的会议内容。 区分讨论意见、最终结论和未决问题。 提取行动项、负责人和截止时间。 无法确认的信息标记为“待确认”。 按规定模板输出,并与原始记录复核。
其中,`name`和`description`是规范中的必填字段。`name`是给当前Skill起个名字,`description`则给当前Skill进行任务描述,那Skill是怎样进入并完成一次任务的呢?
安装Skill以后,系统不会把所有文件一股脑交给大模型。常见的运行过程分为三步:
1. 系统先提供已安装Skill的名称和描述。大模型根据用户任务判断哪些能力可能相关。
2. 某个Skill被选中后,系统再加载它的`SKILL.md`,大模型据此检查输入并执行核心步骤。
3. 执行到特定环节时,才按说明读取参考资料、套用模板或运行脚本。
例如,用户提出把一份会议记录整理成纪要,大模型或Agent工具会从多个Skill中匹配会议纪要能力,再读取其完整规则。若任务还要求生成Excel行动项表,还可能同时选用表格处理Skill。大模型负责结合当前任务作判断,Skill负责提供稳定的方法,平台或Agent框架负责加载文件和调用工具。
相较于维护的Skill,与Prompt、工作流、MCP有什么区别呢?接下来做一些梳理。
<a id="c28-s2"></a>
## <strong>Skill与Prompt的区别</strong>
Prompt是你在当前任务中交给大模型或Agent的输入,通常包括目标、材料、限制条件和输出要求。它回答的是这一次要做什么。例如:
```bash
请把这段会议记录整理成会议纪要,列出结论和待办事项。
这条Prompt已经说明本次目标,但没有完整规定什么算最终结论、负责人缺失时怎么办、能否补全原文没有的信息,以及输出采用什么结构。Skill把这些相对稳定的规则提前保存下来。下一次再处理会议记录时,你只需要提供本次材料和具体要求,不必重新复制整套方法。从作用、载体、加载方式以及版本管理等角度,对Prompt与Skill做个对比:
维度 | Prompt | Skill |
主要作用 | 描述当前任务 | 定义一类任务通常怎样完成 |
使用周期 | 多数只用于当前请求 | 可以被多次调用和持续维护 |
载体 | 对话中的文字、文件和上下文 | 包含 |
内容 | 目标、材料、约束和输出要求 | 触发条件、步骤、工具、模板、异常和验收 |
加载方式 | 通常直接进入当前上下文 | 可以按任务需要逐层加载 |
版本管理 | 容易散落在聊天记录中 | 可以纳入Git并保留变更记录 |
当然,Prompt和Skill并不是二选一,Prompt负责告诉大模型或Agent这一次要处理哪份材料、有什么临时要求,而Skill则负责提供这类任务已经沉淀好的做法。同一个Skill面对不同输入时,仍然需要Prompt说明本次目标。不是每条Prompt都值得做成Skill。一个只做一次、目标还在变化的任务,先用Prompt跑通更合适。等同类任务反复出现、处理方法逐渐稳定,再把它沉淀成Skill。
工作流是一套用于组织多个任务节点的运行安排,通常会规定先后顺序、条件分支、数据传递和失败后的去向。它回答的是多个环节怎样衔接。
例如,一条周报工作流可以是:
读取原始数据 → 清洗字段 → 生成图表 → 撰写分析 → 导出周报
Skill关注的是某个能力单元应该怎样完成。上面的每一步都可以由一个Skill提供方法,也可以由脚本、人工操作或其他工具完成。两者的区别不在于谁固定、谁灵活,而在于关注对象不同:
一个Skill内部可以包含固定步骤,也可以包含条件判断。一个工作流也可以在某个节点调用Agent,让Agent根据现场情况决定下一步。例如,会议纪要Skill可以根据输入作出不同处理:
登录 参与讨论
在真实系统中,Skill和工作流经常组合使用:工作流负责把多个环节串起来,Skill负责让其中某个环节按照稳定方法完成。
MCP是模型上下文协议,用于以统一方式向Agent提供外部工具、资源和提示模板。它回答的是Agent怎样连接和调用外部能力。通过MCP,Agent可以连接数据库、知识库、项目管理系统、地图服务或其他业务系统。但连接成功只代表Agent可以使用某项能力,不代表它已经知道应该怎样完成业务任务。假设Agent已经能读取项目管理系统,它仍然需要知道:
这些任务规则更适合写进Skill。可以把两者的关系概括为:MCP提供连接外部工具和数据的标准方式,Skill描述怎样使用这些能力完成一类任务。
一个Skill可以指导Agent调用MCP工具,也可以完全不使用MCP。一个MCP服务也可以被多个Skill采用。涉及发送消息、修改记录、删除数据、产生费用或公开发布时,Skill应明确规定确认点,不能因为工具已经连接就默认允许执行。
概念 | 主要回答的问题 | 典型内容 |
Prompt | 这一次要做什么 | 当前目标、材料和临时要求 |
Skill | 这类任务通常怎样做 | 方法、规则、资源、工具和验收 |
工作流 | 多个环节怎样衔接 | 节点、顺序、分支和结果传递 |
MCP | Agent怎样连接外部能力 | 工具、资源、参数和调用接口 |
一个完整的Skill,既要有说明任务方法的内容,也要有承载这些内容的文件。就任务本身来说,需要写清触发条件、输入、执行步骤、工具和资源、输出、异常处理;就文件组织来说,至少要有入口文件SKILL.md,复杂一些的Skill还会配上参考资料、模板、脚本和测试案例。以会议纪要Skill为例,完整目录可以这样组织:
meeting-notes/
├── SKILL.md # 入口:名称、描述、输入、步骤和验收
├── references/
│ ├── terms.md # 术语表和内容分类规则
│ └── privacy-rules.md # 敏感信息与脱敏要求
├── assets/
│ └── meeting-template.md # 纪要输出模板
├── scripts/
│ └── clean-transcript.py # 清理时间戳和重复口头语
└── tests/
├── normal-case.md # 信息完整的测试案例
├── boundary-case.md # 信息缺失或存在冲突的案例
└── failure-case.md # 无法继续处理的案例
其中,SKILL.md是整个目录的入口。Agent先从这里了解Skill适合处理什么任务、需要什么输入、按什么步骤执行。进一步地,根据其他约束,可以补充其他工具或代码,如上述目录组织中,如果遇到专业术语或隐私要求时,可以读取references/ 目录获取相关信息;再生成结果阶段,可以套用assets/中的模板;需要稳定完成文本清洗等机械操作时,再运行scripts/中的程序。tests/主要用于开发和改版后的验证,不是每次执行任务都要加载的内容。在构建阶段,目录不要求一次完全建设完毕。规则较少时,一个SKILL.md就能组成可运行的Skill;当主文件越来越长,或者某项资料、模板和脚本需要反复使用时,可以进一步进行拆分。无论文件多少,Agent实际执行的仍然是下面这条任务链:
用户提出任务
↓
大模型根据名称和description筛选相关Skill
↓
加载选中Skill的SKILL.md
↓
检查输入 → 执行步骤 → 使用工具和资源 → 生成并检查输出
↓
遇到异常时按规则停止、降级或请求补充
当l同时存在多个Skil时,大模型通常先比较每个Skill的名称和描述,再选择与当前意图最相关的最小集合。例如,把会议记录整理成纪要并导出行动项表,可能同时需要会议纪要Skill和表格处理Skill;如果只要求整理文字,则没有必要加载表格能力。具体筛选和调用方式由所用平台或Agent框架实现,但Skill作者必须把适用场景和边界写清楚,才能帮助大模型作出正确选择。
下面从触发条件、输入检查、执行步骤、工具与资源以及输出控制等方面进行介绍
所谓触发条件,是指Skill提供大模型在众多Skill中找到当前需要的能力,主要体现在SKILL.md开头的name和description中。以会议纪要Skill为例。name是Skill的标识名称,通常使用小写字母、数字和连字符,并与目录名称保持一致。它要简短、明确,让人和系统都能区分这项能力。例如:
name: meeting-notes
description用来说明这项Skill能做什么、遇到什么任务时应该使用。大模型读取用户任务后,会把任务意图与已安装Skill的名称和描述进行比较;匹配成功,才继续加载完整的SKILL.md。会议纪要Skill的description可以这样写:
description: 将会议转写稿、聊天记录或散乱笔记整理为结构化会议纪要。当用户要求生成会议摘要、提取结论、行动项或待确认问题时使用。
不要为了增加触发概率塞入大量无关关键词。描述太宽,Agent会在不相关的任务中误用;描述太窄,又可能在真正需要时找不到它。因为,最终完成Skill的编写,还需要依赖测试,测试时需要同时验证应该触发和不应该触发的任务,例如有一个会议录音ASR识别的Skill,就不能在会议纪要相关任务时触发。
所谓的输入检查,是指在开始执行某个Skill时,Agent或大模型需要知道,在完成任务时,需要什么、哪些材料必需、哪些可以缺省,以及缺少关键信息时怎样处理。因此选中Skill后,下一步不是立刻生成结果,而是检查材料是否足够。以会议纪要Skill为例,对于输入部分,可以这样约定:
输入检查通过后,Agent才开始正式处理任务。执行步骤要写清楚从读取材料到交付结果的完整顺序:每一步处理什么,得到什么中间结果,遇到不同情况时转到哪里。只写分析内容、提取重点,还不足以让Agent稳定执行。会议纪要Skill可以按照下面的顺序处理:
1. 读取全部会议材料;文件为空或无法读取时,转入异常处理。
2. 识别会议主题、时间和参与人,缺失字段暂时保留为空。
3. 清理不影响原意的口头语和重复内容,同时保留原始材料不变。
4. 按议题整理内容,把发言分别归入观点、决定和待确认问题。
5. 从会议内容中提取已经明确分派的行动项,并记录任务、负责人和截止时间。
6. 负责人或截止时间在原文中没有出现时,将对应字段标记为“待确认”。
7. 把整理结果填入会议纪要模板。
8. 逐项回查原文;无法找到依据的结论和行动项不得交付。
按照上述举例的这组步骤,有明确的入口、处理顺序和结束条件。第1步决定任务能否继续,第2至第6步形成结构化内容,第7步生成交付结果,第8步负责验收。执行步骤写到这个程度,后面的工具、模板和异常规则才知道应该接在哪个环节。
执行步骤说明要做什么,工具和资源说明具体依靠什么完成。工具负责执行动作,例如读取文件、运行脚本;资源负责提供规则和模板,例如术语表、隐私要求和纪要格式。两者都要写出具体名称和使用时机。会议纪要Skill可以使用下面这些内容:
类型 | 具体工具或资源 | 使用时机 | 作用 |
文件读取工具 | Python的 | 执行第1步时 | 读取UTF-8编码的TXT或Markdown会议记录 |
Word读取工具 |
| 输入为DOCX时 | 读取Word文档中的段落和表格文字 |
清洗脚本 |
| 划分议题前 | 清理时间戳、连续重复句和不影响原意的口头语 |
术语资源 |
| 遇到简称或专业名词时 | 统一项目名、产品名和人员称谓 |
隐私资源 |
| 输出前复核时 | 判断个人信息和业务敏感内容是否需要脱敏 |
输出模板 |
| 生成纪要时 | 规定会议信息、结论、行动项和未决问题的排列方式 |
SKILL.md不能只列出这些名称,还要说明什么时候调用。例如,普通TXT直接使用pathlib读取;DOCX改用python-docx;文本中出现项目简称时才读取术语表;需要对外发送时再检查隐私规则。脚本还应说明输入、输出和依赖,运行失败后进入哪条异常处理。访问令牌和密码不能写进Skill文件或脚本。
完成处理后,Agent需要知道怎样交付。输出控制阶段要求不能只写生成会议纪要,还要说明结构、格式和完成标准。根据要求,可以约定输出格式,例如:
会议纪要
一、会议信息
会议主题:
会议时间:
参与人员:
二、关键结论
……
三、行动项
四、未决问题
如果结果需要保存为文件,还应说明文件类型、命名规则和保存位置。
异常处理贯穿输入、执行、工具调用和输出检查,不是最后才补上的说明。它决定Agent遇到问题时是继续、降级、停止,还是请求用户确认。
常见异常包括输入为空、文件损坏、依赖缺失、外部服务不可用、工具权限不足,以及不同材料中的信息互相矛盾。可以使用下面四条统一原则:
前面已经说明Skill怎样被发现、怎样执行,以及一项完整能力需要包含哪些部分。现在把这些内容真正落到会议纪要案例中。
设计Skill的核心,不是先建目录或写一段很长的Prompt,而是把人的工作经验整理成Agent能够执行和检查的方法。这个过程可以分成六步:选择适合封装的任务,确定目标和边界,复盘人工方法,把经验改写成规则,写出可以执行的第一版,最后跑通最短闭环。每一步都会产生下一步需要的内容:任务范围决定输入输出,人工方法形成执行步骤,初稿把这些内容组织起来,测试结果再推动下一版修改。
下面继续以会议纪要Skill为例,从一项模糊需求逐步形成可以在魔搭中运行的能力。
适合做成Skill的任务通常有几个共同点:
整理会议纪要符合这些条件:每次会议内容不同,但检查材料、识别结论、提取行动项和组织输出的方法基本一致;结果还可以回到原文逐项复核。因此,本章选择它作为贯穿案例。
如果任务目标本身还没有说清楚,或者每次都要完全依赖临场判断,先不要急着做Skill。可以先用Prompt完成几次,等方法稳定后再沉淀。
选定任务后,要把宽泛愿望缩小成能够交付和验收的目标。做一个办公助手范围太大,无法成为一个稳定的Skill。
会议纪要Skill的第一版目标可以写成:
接收一份原始会议记录,输出包含会议信息、关键结论、行动项和未决问题的结构化纪要;不根据常识补写原文中没有的负责人、日期和结论。
这个目标说清了输入、输出和禁止事项。第一版的输入是已经形成文字的会议记录,不负责录音转写;结果先保存为Markdown,不自动发邮件,也不写入项目管理系统。边界越清楚,后面的步骤、异常和测试越容易定义。
目标明确后,可以先把人工完成任务时真正采用的方法找出来,再判断哪些动作可以固定,哪些地方仍然需要结合上下文判断。整理的逻辑可以按照法:先进行三层过滤,再完成四维提取。
所谓三层过滤,可以按顺序处理会议转写稿:
完成清理后,再从四个维度整理信息:
1. 议题:会议讨论了哪些问题;
2. 观点:围绕每个议题,各方提出了哪些关键意见;
3. 决策:哪些事项已经形成明确共识;
4. 待办:谁需要在什么时间之前完成什么任务。
这套方法非常适合用来复盘会议纪要任务,因为它还原了人工整理时的真实顺序:先降低原始材料中的噪声,再识别内容之间的关系,最后提取需要执行的任务。复盘时还要继续追问几个细节:删减内容是否会改变原意,什么表达才能算作正式决策,负责人或截止时间缺失时怎样处理,材料前后矛盾时是否需要请用户确认。经过这一步,得到的不是一段泛泛的经验总结,而是一条可以继续拆解的处理路线。下一小节再把其中的过滤、分类、提取和复核,改写成Agent可以执行的动作、条件和检查点。
接下来把上述记录中的经验,改写成Agent可以执行的三类语句:
三类语句要连起来使用。先执行动作,遇到不同情况时按条件分支,完成后用检查点验收。例如,先提取行动项;若负责人或截止时间缺失,则保留行动项并标记待确认;输出前再核对每项任务是否都能在原文中找到依据。如果一句要求无法判断是否完成,就继续把它拆小。
经过前面的梳理,第一版SKILL.md已经有了材料。写的时候可以按照Agent处理任务的顺序组织,比如在会议纪要任务中,需要先总结正常会议的结论,在进一步整理出下一步的任务以及待确认的问题等,以此顺序来输出Skill的描述信息。例如:
---
name: meeting-notes
description: 将会议记录整理为包含结论、行动项和待确认问题的结构化纪要。当用户要求整理会议内容或提取会议任务时使用。
---
# 会议纪要整理
## 输入要求
- 必须提供可读取的会议正文。
- 会议名称、时间和参与人可以缺少,但不得自行补写。
## 执行步骤
1. 检查会议正文是否可读。
2. 区分讨论意见、已经确认的结论和待确认问题。
3. 提取行动项、负责人和截止时间。
4. 对照原文复核结论和行动项。
## 输出要求
- 输出会议信息、关键结论、行动项和待确认问题。
- 原文缺少的负责人或截止时间标记为待确认。
## 异常处理
- 没有会议正文时停止,并请用户补充材料。
- 不同内容互相冲突时保留冲突,不自行选择答案。
为了进一步考虑实际使用的底座模型、Agent工具等,需要对当前整理的Skill进行验证。需要先验证整理时Skill能否从输入走到合格输出。第一版可以从以下几个方面进行观察:识别任务情况、核心步骤执行情况、输出结果是否满足条件等方面进行观察,进一步地,可以按下面的方法完成第一次验证:
SKILL.md、一份信息完整的会议记录和一份期望的纪要结构。meeting.txt,使用会议纪要Skill生成Markdown纪要,不要补写原文没有的信息。当Skill能够被稳定选中、正确读取输入、按规定结构输出,并且面对缺失信息时不编造内容,整体执行过程、输出内容,基本符合Skill中的预定,才算跑通。此后再根据真实需求增加其他要求,例如数据清洗工具、脱敏规则、长文本分段或外部系统连接。下一节就在魔搭的环境中完成这条最短路线。
前面已经完成了会议纪要Skill的设计。下面在魔搭 Notebook 中实现这项能力,以差旅报销和客户接待的办公例会为例,将会议记录整理为已确认事项、待办事项和待确认事项,并检查Skill的调用过程。下面按实验步骤介绍主要操作和关键代码,涉及的辅助函数和变量需提前定义。
本实验使用Qwen3-4B完成推理。这里主要补充Skill所需的组件,ms-swift和模型部署的基本用法可参考前面章节。
首先,运行以下代码,查看当前Python和依赖包的版本:
import sys
import importlib.metadata as metadata
print("Python:", sys.version)
for package in ["ms-agent", "modelscope", "omegaconf"]:
try:
print(package, metadata.version(package)
except metadata.PackageNotFoundError:
print(package, "未安装")
本次实验使用Python 3.12.13、ms-agent 1.6.0、modelscope 1.39.0和omegaconf 2.3.0。其中,ms-agent负责加载Skill和读取参考文件,modelscope用于访问社区资源,ms-swift和vLLM提供模型推理服务。如果依赖尚未准备好,先安装ms-agent==1.6.0等实验组件,保留已有训练包和基础数值计算包的版本。安装完成后重启内核,再继续后续操作。在modelscope_skill_lab下创建带有时间标识的目录,分别保存每次实验的Skill、案例和报告。代码如下:
WORK_DIR = (Path.cwd() / "modelscope_skill_lab").resolve()
RUN_DIR = WORK_DIR / (
datetime.now().strftime("%Y%m%d_%H%M%S") + "_" + uuid.uuid4().hex[:6]
)
for name in ["releases", "tests", "reports", "downloads", "config"]:
(RUN_DIR / name).mkdir(parents=True, exist_ok=True)
其中,releases保存Skill版本,tests保存测试输入,reports保存结果,config保存模型配置,便于后续核对和比较。
前面已经说明了Skill的组成,这里直接将会议纪要规则写入文件。本实验只需要一个入口文件和一份输出约定,目录形式如下:
releases/
└── 1.0.0/
└── meeting-notes/
├── SKILL.md
└── references/
└── output-contract.md
SKILL.md保存适用范围、处理步骤和异常规则,开头的名称、描述和版本用于标识这项能力。头部内容如下:
---
name: meeting-notes
description: 将会议转写稿、群聊会议记录或会议笔记整理为中文会议纪要,提取已确认的结论、行动项和待确认问题。用户要求会议纪要或会议行动项时使用;一般知识问答、模型训练原理解释不使用。
version: "1.0.0"
---
在执行步骤中,本实验特别保留了三类约束:没有会议正文时请求补充材料;负责人和截止时间没有明确出现时标为“待确认”;结论和行动项必须引用原文作为依据。这些规则会在17.8的测试中逐项检查。
为了便于检查,模型先输出JSON,再由程序整理成会议纪要。output-contract.md规定输出字段,并增加处理状态和原文依据,主要内容如下:
字段 | 内容 |
|
|
| 会议主题 |
| 已确认的结论及原文依据 |
| 任务、负责人、截止时间和原文依据 |
| 待确认事项或存在的冲突 |
| 需要补充的材料或无法处理的原因 |
其中,decisions和actions中的evidence字段要求引用原文的连续片段。模型可以概括任务内容,但用于核对的证据不能改写。当status不为ok时,结论和行动项应为空数组,同时在message中说明原因。
将入口内容和输出约定分别保存在SKILL_V1、OUTPUT_CONTRACT两个变量中,再写入对应文件:
V1_DIR = RUN_DIR / "releases" / "1.0.0" / SKILL_NAME
(V1_DIR / "references").mkdir(parents=True, exist_ok=True)
(V1_DIR / "SKILL.md").write_text(SKILL_V1, encoding="utf-8")
(V1_DIR / "references" / "output-contract.md").write_text(
OUTPUT_CONTRACT, encoding="utf-8"
)
运行以后,可以在文件浏览器中找到上述目录,打开SKILL.md确认内容已经写入。相关内容如下图所示。
当然,魔搭Skills中心(https://modelscope.cn/skills)也提供了相关资源,读者可以直接从魔搭Skills中心选择已有资源,将页面中的安装标识填入COMMUNITY_SKILL_ID,通过下面的接口下载:
COMMUNITY_SKILL_ID = "" # 填写从社区页面复制的安装标识。
if COMMUNITY_SKILL_ID:
COMMUNITY_DIR = Path(HubApi().download_skill(
skill_id=COMMUNITY_SKILL_ID,
local_dir=str(RUN_DIR / "downloads"),
).resolve()
print((COMMUNITY_DIR / "SKILL.md").read_text(encoding="utf-8")
文件准备好后,使用ms-agent的SkillLoader加载本地目录,读取名称、描述、版本和资源路径。代码如下:
from ms_agent.skill.loader import SkillLoader
from ms_agent.skill.schema import SkillContext
loader = SkillLoader()
registered = loader.load_skills(str(V1_DIR.resolve()
print(loader.list_skills()
本次加载记录为['meeting-notes@1.0.0']。其中,meeting-notes是Skill的ID,1.0.0是规则版本,与ms-agent软件包的版本不同。加载目录以后,可以通过SkillContext读取参考文件。下面的代码只加载名为output-contract.md的参考资料:
skill = next(iter(registered.values()
context = SkillContext(skill=skill, root_path=V1_DIR)
references = context.load_references(names=["output-contract.md"])
print(references[0]["content"])
为了让模型请求读取文件,本实验通过自定义的NotebookSkillTools封装了两个工具函数:
工具函数 | 作用 | 主要参数 |
| 列出已加载Skill的ID、版本和用途 | 无 |
| 读取Skill入口或指定参考文件 |
|
读取入口时,调用skill_view并传入{"skill_id": "meeting-notes"};读取参考文件时,再指定file_path。其中,meeting-notes是读取对象,skill_view是执行读取的工具。
加载与读取检查如下:
ACTIVE_DIR = V1_DIR
runtime = make_runtime(ACTIVE_DIR)
viewed = runtime.tools.call_tool(
tool_name="skill_view",
tool_args={"skill_id": SKILL_NAME},
)
reference = runtime.tools.call_tool(
tool_name="skill_view",
tool_args={
"skill_id": SKILL_NAME,
"file_path": "references/output-contract.md",
},
)
print("入口读取成功:", "content" in json.loads(viewed)
print("参考资料读取成功:", "content" in json.loads(reference)
make_runtime()负责校验目录、加载版本和准备工具。本次入口和参考资料的读取结果均为True,说明文件能够正常访问,接下来就可以让模型发起读取请求。Skill加载与资源读取结果如下。
本实验使用魔搭社区的Qwen/Qwen3-4B,通过ms-swift部署为OpenAI兼容接口。在已安装ms-swift和vLLM的GPU环境终端中运行:
CUDA_VISIBLE_DEVICES=0 swift deploy \
--model Qwen/Qwen3-4B \
--load_args false \
--infer_backend vllm \
--enable_thinking false \
--host 0.0.0.0 \
--port 18002 \
--api_key 123 \
--vllm_gpu_memory_utilization 0.8 \
--vllm_max_model_len 8000 \
--max_new_tokens 2000
在魔搭中,模型启动阶段的结果如下:
服务日志,当输出如下内容表示服务已启动完毕:
INFO:Application startup complete.
INFO:Uvicorn running on http://0.0.0.0:18002 (Press CTRL+C to quit)
魔搭端启动成功的日志如下:
下面使用一份行政例会记录进行调用。材料中包含两项已经确认的安排、两名负责人的具体任务,以及一项仍需确认的信息:
会议主题:本周行政工作安排。会议时间:2026-09-04。
主管:今天确认两件事。第一,本月差旅报销材料统一在下周二前收齐;
第二,客户接待会定在下周三下午三点,地点是三楼会议室。
李明:报销材料我来收集,截止时间为2026-09-08。
王敏:我负责预订三楼会议室并发送会议邀请,截止时间为2026-09-07。
小周:客户可能有两个人线上参加,不过还没确认。
主管:线上参会名单先记为待确认,其他事项按刚才的安排推进。
模型需要整理会议中已经确认的安排,列出任务、负责人和截止时间,并保留尚未确定的信息。这里生成的是纪要,不实际办理报销或发送邀请。先使用明确指定Skill的请求,再使用日常办公中常见的自然语言请求,对比两种表达能否调用同一项能力。核心调用代码如下:
explicit_result = await run_skill_task(
"用 meeting-notes 帮我整理一下刚才的办公例会,"
"把报销和客户接待的安排、负责人、截止时间列清楚。"
+ "\n\n会议记录:\n" + DEMO_TEXT,
ACTIVE_DIR,
)
natural_result = await run_skill_task(
"这是刚才例会的记录,帮我整理成一份简短纪要,"
"方便发到工作群。还没确定的事情单独列出来。"
+ "\n\n会议记录:\n" + DEMO_TEXT,
ACTIVE_DIR,
)
其中,run_skill_task()为每次任务新建对话,并将Skill索引和工具定义发送给模型。模型提出读取请求后,由程序执行文件读取,再把内容返回给模型。为了避免模型反复读取已经获得的资料,程序会检查入口和输出约定是否都已读取成功。两者都具备后,进入最终答案生成阶段。关键判断如下:
ready_to_answer = (
read_skill_successfully(result)
and read_skill_successfully(result, "references/output-contract.md")
)
if ready_to_answer:
messages[0]["content"] = (
system + "\n技能入口和输出规则已读取完成。"
"现在根据原始会议材料生成最终结果,"
"只输出符合规则的 JSON,不再调用工具。"
)
message, finish_reason, usage = await asyncio.to_thread(
model_call, messages, [] if ready_to_answer else api_tools
)
资料读取完成后,请求使用tool_choice="none",让模型生成最终答案。提示更新在已有的system消息中,工具结果后仍由模型回复,保持角色顺序。本次两种请求都读取了Skill和输出约定,生成了办公纪要。结果保留了报销和客户接待安排,列出李明、王敏的任务,并将线上参会人数记为待确认。不同调用的措辞略有差异,可对照原文复核。
页面展示的是整理后的纪要,保存的JSON仍包含evidence等字段。原文引用和异常输入的处理,将在后面的测试中继续检查。
调用完成后,检查入口、输出约定是否读取,以及读取失败时能否报告原因。trace保存了每轮模型回复、工具参数和返回内容,可用于定位问题。例如,检查前面两次调用的读取情况,可以使用下面的代码:
rows = []
for label, result in [("显式调用", explicit_result), ("自然语言调用", natural_result)]:
rows.append({
"调用方式": label,
"请求状态": result["run_status"],
"入口已读取": read_skill_successfully(result),
"规则已读取": read_skill_successfully(
result, "references/output-contract.md"
),
})
display(pd.DataFrame(rows)
本次两种调用均为completed,入口和规则读取均为True,说明请求结束且资料已读取。纪要内容是否正确,还需要进一步检查。为了检查工具的异常处理,还可以故意传入不存在的Skill ID和不存在的参考文件。例如:
missing_resource = runtime.tools.call_tool(
tool_name="skill_view",
tool_args={
"skill_id": SKILL_NAME,
"file_path": "references/does-not-exist.md",
},
)
print("缺失文件处理是否符合预期:", "error" in json.loads(missing_resource)
这里故意使用不存在的文件,返回error表示正确识别了问题。表示本次所传入的内容将其标为“处理是否符合预期”,可以避免与实际运行故障混淆。
常规展示保留检查结果即可。需要排查时,再从reports中查看对应轮次的完整回复和工具记录。
一次调用完成后,还需要用不同材料检查Skill是否可靠。下面准备固定案例,验证信息提取和异常处理,并将结果与Skill版本一起保存。
本节继续使用同一份会议纪要Skill。先建立测试矩阵和检查条件,再运行第一版测试,最后修改规则、比较两个版本并演示回退。
案例需要同时记录输入和预期行为。正常案例检查信息提取,边界案例检查不确定信息的处理,失败案例检查能否停止并说明原因,另设不相关任务检查误触发。
测试矩阵如下:
编号 | 类别 | 输入特征 | 预期行为 |
N01 | 正常 | 包含报销和客户接待安排的完整办公例会 | 保留结论、任务、负责人和截止时间 |
N02 | 正常 | 已经写明行动项的简短会议笔记 | 提取明确的任务、负责人和日期 |
B01 | 边界 | 已经安排任务,但未确定截止时间 | 日期填写“待确认” |
B02 | 边界 | 有人提出建议,会议尚未批准 | 不生成已确认决定,保留待确认事项 |
B03 | 边界 | 两份记录对同一事项的时间表述冲突 | 保留冲突,不自行选择结论 |
B04 | 边界 | 有人提出需要完成一项工作,但未指定负责人 | 不把发言人自动作为负责人 |
F01 | 失败 | 没有会议正文 | 返回 |
F02 | 失败 | 提供的是产品说明书 | 返回 |
T01 | 不应触发 | 一般知识问答,与会议纪要无关 | 直接回答,不读取会议纪要Skill |
每个案例用一个字典保存输入和检查条件。下面分别选取一个正常案例、边界案例和失败案例,展示其写法:
# 办公例会中的两项截止日期应与原文保持一致。
normal_case = {
"id": "N01", "kind": "正常", "text": DEMO_TEXT, "status": "ok",
"owners": ["李明", "王敏"],
"due_values": ["2026-09-08", "2026-09-07"],
"min_actions": 2, "min_decisions": 1,
}
boundary_case = {
"id": "B01", "kind": "边界",
"text": "测试会议记录:已安排陈晨整理测试用例,会上没有确定截止时间。",
"status": "ok", "owners": ["陈晨"],
"due_values": ["待确认"], "min_actions": 1,
}
failure_case = {
"id": "F01", "kind": "失败", "text": "", "status": "needs_input",
}
status规定预期状态,owners和due_values检查负责人和日期,min_actions规定最少行动项数量。这些条件只用于验收,模型接收的仍是用户请求和案例正文。
更换材料后,预期日期和负责人也要同步更新。上面的N01已按办公例会填写日期;已保存的测试记录仍有旧日期预期,后面会说明其影响。
案例准备好以后,需要把“正确完成”的要求写成可以执行的检查。使用check_output()对模型返回结果进行验收,主要包括下面几个方面:
检查内容 | 判断方法 |
请求是否结束 | 检查 |
资料是否读取 | 检查工具记录中入口和输出约定的实际返回 |
输出格式是否正确 | 检查JSON能否解析,以及字段名称和类型是否符合约定 |
关键信息是否符合案例 | 检查状态、负责人、截止时间和条目数量 |
原文依据是否存在 | 检查 |
失败输入是否停止生成 | 检查异常状态下结论和行动项为空,并说明原因 |
是否误用Skill | 对不相关任务检查是否发生了 |
以原文依据为例,模型生成的结论可以是概括性的文字,但evidence需要保留原始表述。下面是验收函数中的关键判断:
if item["evidence"] not in case["text"]:
problems.append(field + " 的证据不在原文中")
item表示当前条目,field表示正在检查的字段。该判断可发现引用被改写或引用来源错误,但证据能否支持结论,仍需人工复核。对于没有正文或材料不适用的案例,还需要检查模型有没有停止生成纪要内容。关键代码如下:
if data["status"] != case["status"]:
problems.append("status 不符合预期")
if case["status"] != "ok":
if data["decisions"] or data["actions"] or not data["message"].strip():
problems.append("失败情形应停止生成纪要内容并说明原因")
例如,F01应返回needs_input。如果仍返回ok,或者说明材料不足的同时生成了会议决定,都不符合要求。还可以人为加入错误日期、删除读取记录,检查验收函数能否识别问题。这是检查代码的自测,不能代替真实模型测试。文件缺失、模拟服务失败和输入过长用于检查程序能否报告异常;F01、F02则检查模型面对异常材料时的回答。两类结果分别记录。
run_suite()依次运行案例,每次新建对话,保存结果和工具记录,再调用check_output()生成问题清单。主要代码如下:
CASE_IDS = None # None表示运行全部案例。
REPEATS = 1 # 本次每个案例运行一次。
v1_results, v1_report_dir = await run_suite(
V1_DIR, case_ids=CASE_IDS, repeats=REPEATS
)
display(v1_results)
print("原始输出和轨迹:", v1_report_dir)
PASS_AUTO表示自动检查通过,FAIL表示结果不符合要求,ERROR表示运行未正常完成。human_review记录人工复核状态,本次仍为pending。本次1.0.0版本的实际记录中,9个案例有6个自动通过、3个失败,未出现标记为ERROR的案例。结果如下:
案例 | 自动检查结果 | 记录中的主要问题 |
N01 | FAIL | 结论的证据未匹配原文;旧的截止日期预期未满足 |
N02 | PASS_AUTO | 未触发当前检查条件中的问题 |
B01 | PASS_AUTO | 未触发当前检查条件中的问题 |
B02 | PASS_AUTO | 未触发当前检查条件中的问题 |
B03 | PASS_AUTO | 未触发当前检查条件中的问题 |
B04 | PASS_AUTO | 未触发当前检查条件中的问题 |
F01 | FAIL | 处理状态不符合预期,未满足异常输入的停止生成要求 |
F02 | FAIL | 处理状态不符合预期,未满足异常输入的停止生成要求 |
T01 | PASS_AUTO | 未发生会议纪要Skill的读取请求 |
N01首先存在测试预期不一致的问题:正文中王敏的截止日期为2026-09-07,测试代码却仍要求2026-09-09。因此,这项日期报错不能直接归为模型错误,应先修正due_values再重跑。N01还存在证据未匹配原文的问题。同一办公示例的调用轨迹中,模型将原文“第二,客户接待会定在下周三下午三点,地点是三楼会议室。”改成了“主管:客户接待会定在下周三下午三点,地点是三楼会议室。”。含义虽然接近,但增加了该片段中没有的称谓,不符合evidence逐字摘录的要求。
F01没有正文,应请求补充材料;F02提供产品说明书,应说明材料不适用。本次两项均未满足状态和停止生成要求,需要结合案例报告中的最终文本和工具轨迹进一步分析。后续应先校准测试预期,再完善证据引用和异常处理规则。自动通过的案例也要对照原文复核。
保留1.0.0,在新目录中创建候选版本1.0.1,并补充负责人判定规则。两个版本分别加载和测试,便于比较。下面是版本创建代码的主要部分:
V2_DIR = RUN_DIR / "releases" / "1.0.1" / SKILL_NAME
if not V2_DIR.exists():
shutil.copytree(V1_DIR, V2_DIR)
updated = (V2_DIR / "SKILL.md").read_text(encoding="utf-8")
updated = updated.replace('version: "1.0.0"', 'version: "1.0.1"', 1)
updated += (
"\n## 负责人判定补充\n"
"发言人不自动等于负责人;提出安排、建议或疑问的人,"
"没有明确接受任务时,负责人仍为“待确认”。\n"
)
(V2_DIR / "SKILL.md").write_text(updated, encoding="utf-8")
这项规则针对B04:提出工作的人不一定是负责人。由于输入输出结构未变,本实验将其记为修订版本,并在CHANGELOG.md中记录规则、关联案例和候选状态。
接下来使用相同的模型、案例和重复次数测试候选版本,再按案例编号进行合并比较。核心代码如下:
v2_results, v2_report_dir = await run_suite(
V2_DIR, case_ids=CASE_IDS, repeats=REPEATS
)
comparison = v1_results.merge(
v2_results,
on=["case_id", "repeat", "kind"],
suffixes=("_v1", "_v2"),
)
display(comparison[[
"case_id", "automatic_result_v1", "automatic_result_v2",
"problems_v1", "problems_v2",
]])
相关过程结果如下:
在本次保存的记录中,1.0.0有6项自动通过,1.0.1有7项自动通过。逐项对比以后,可以看到变化集中在F01:
本次只观察到F01由失败变为自动通过,尚不能证明新增规则带来了稳定改善。修改针对负责人判定,变化却发生在空输入案例;每例也只运行一次,N01预期仍需校准。采用候选版本前,应修正测试条件,让两版重跑相同案例,并提高REPEATS检查稳定性。人工复核后,再决定是否采用;F02等未通过的情况继续完善规则。
每个版本都需要保存完整文件和测试记录。本实验在release-manifest.json中记录文件清单和哈希,同时保存变更说明、配置和报告。主要产物如下:
文件或目录 | 保存内容 |
| 两个版本的Skill目录和版本清单 |
| 规则变化及关联案例 |
| 固定的测试输入与预期条件 |
| 模型输出、工具轨迹、自动检查结果和版本比较表 |
| 当前使用的版本、路径和文件哈希 |
| 本次运行环境记录 |
activate_version()检查目录和文件哈希,再创建新的运行器,保证版本清单与实际规则一致。
ACTIVE_DIR, runtime = activate_version("1.0.1")
print("切到候选版:", ACTIVE_DIR)
ACTIVE_DIR, runtime = activate_version("1.0.0")
print("回退到旧版:", ACTIVE_DIR)
本次已完成从1.0.1切回1.0.0的文件校验和加载,但回退后没有再次调用模型,尚未验证回退后的测试结果。
如果需要确认回退后的行为,应继续对当前目录执行测试:
rollback_results, rollback_report_dir = await run_suite(
ACTIVE_DIR, case_ids=CASE_IDS, repeats=REPEATS
)
display(rollback_results)
保存规则、案例、结果和环境信息后,就能追溯每个版本的表现。后续修改应对应具体案例,经过测试和复核,再决定采用新版本还是恢复原版。
本章节所有实验数据和代码,可参考:
https://modelscope.cn/gallery/liucong/0e182f07-3330-40cc-ba59-173b7cc609b7