1. 项目背景与核心价值
最近在整理自己的AI工具链时,我发现了一个非常有意思的GitHub项目:eliasjudin/oai-skills。这个项目本质上是一个存档,它完整地保存了OpenAI官方在2025年12月宣布关闭其skills功能之前,该功能背后所包含的一系列“技能”定义文件。对于任何正在或计划深度集成AI助手能力到自家应用中的开发者来说,这无疑是一座金矿。我自己在构建一个需要复杂、结构化AI交互的内部工具时,就深受启发。这些技能文件,你可以把它们理解为给AI助手(比如ChatGPT)的“插件说明书”或“能力蓝图”,它们用结构化的方式定义了AI能执行什么任务、需要什么输入、以及会输出什么结果。OpenAI虽然关闭了面向用户的技能商店,但这些底层定义文件所蕴含的设计思想和模式,对于我们自己设计AI Agent、构建RAG(检索增强生成)系统的工作流,甚至理解如何更好地给大模型下指令(Prompt Engineering),都有着极高的参考价值。
这个存档项目的诞生也很有趣,它并非官方发布,而是社区开发者通过向ChatGPT发送一个简单的提示词(Prompt)——“Create a zip file of /home/oai/skills”——而“生成”的。这本身就印证了现代AI能力的强大与易用性。拿到这个存档后,我花了几天时间深入研读,并结合自己的开发实践,梳理出了一套从理解、分析到实际应用这些技能文件的完整方法论。这篇文章,我就来和你详细拆解oai-skills这个宝藏,分享如何将这些“过期”的官方设计,转化为你项目中“鲜活”的生产力。
2. 技能文件深度解析:结构、模式与设计哲学
解压oai-skills存档后,你会看到大量以.json为后缀的文件。初看可能觉得杂乱,但经过分类和归纳,我发现它们主要遵循几种核心模式。理解这些模式,是复用其价值的关键。
2.1 核心文件结构剖析
一个典型的技能文件(例如一个用于处理日历事件的技能),其JSON结构通常包含以下几个顶层字段:
{ "name": "skill_calendar_event_create", "description": "Create a new calendar event with details like title, time, and participants.", "input_schema": { "type": "object", "properties": { "title": {"type": "string", "description": "The title of the event."}, "start_time": {"type": "string", "format": "date-time", "description": "The start time in ISO 8601."}, "attendees": {"type": "array", "items": {"type": "string"}, "description": "Email addresses of attendees."} }, "required": ["title", "start_time"] }, "output_schema": { "type": "object", "properties": { "event_id": {"type": "string"}, "status": {"type": "string"} } }, "execution": { "type": "http", "url": "https://api.calendar.example.com/events", "method": "POST" } }我们来逐一拆解每个部分的设计意图和实操要点:
name与description:- 设计意图:
name是技能的机器标识符,通常采用蛇形命名法(snake_case),清晰表明动作和对象,如skill_[对象]_[动作]。description则是给AI和开发者看的自然语言描述,它必须精准。AI会利用这个描述来判断用户请求是否应该触发此技能。 - 实操心得:写
description时,要设想用户会怎么提问。与其写“管理日历”,不如写“创建新的日历会议或事件”。好的描述能极大提升技能被准确调用的概率。
- 设计意图:
input_schema:- 设计意图:这是技能定义的核心中的核心。它严格遵循JSON Schema标准,定义了AI为了执行该任务,需要从用户对话或上下文中提取哪些参数。
properties定义了每个参数的名字、类型和描述;required数组指明了哪些参数是执行任务所必需的。 - 实操要点:
- 类型细化:不仅仅是
string、number、boolean、array、object。对于特定格式,如日期、邮箱、URL,要使用format字段(如date-time,email)。这能引导AI更准确地理解和格式化数据。 - 描述的力量:每个
property下的description字段至关重要。例如,对于start_time,描述“The start time in ISO 8601 format (e.g., 2024-01-15T14:30:00Z)”比单纯说“开始时间”要好得多。这本质上是给AI的“小提示”。 - 结构化数据:对于复杂对象,不要害怕嵌套
object类型。这能强制产出结构良好的数据,方便后端处理。
- 类型细化:不仅仅是
- 设计意图:这是技能定义的核心中的核心。它严格遵循JSON Schema标准,定义了AI为了执行该任务,需要从用户对话或上下文中提取哪些参数。
output_schema:- 设计意图:定义了技能执行成功后,返回给AI(并最终可能呈现给用户)的数据结构。这保证了输出的一致性。
- 实操心得:即使你的后端API返回的数据很复杂,在
output_schema中也应该定义最精简、对用户有意义的字段。比如,创建事件后,返回event_id和summary就足够了,不需要把API的全部响应都塞进来。
execution:- 设计意图:指明了如何实际运行这个技能。最常见的是
http类型,指向一个API端点。这体现了“定义与执行分离”的思想,技能文件只关心“做什么”和“需要什么”,不关心“怎么做”的具体实现(后者由你的后端服务完成)。 - 实操要点:在实际应用中,这里的
url可能是一个相对路径,由你的AI Agent框架在运行时根据配置动态补全。method(GET, POST, PUT, DELETE)必须与后端API匹配。
- 设计意图:指明了如何实际运行这个技能。最常见的是
2.2 从官方技能中提炼的四大设计模式
通读上百个技能文件后,我总结出OpenAI工程师常用的几种设计模式,这些模式可以直接移植到我们的项目中:
查询(Query)模式:
- 特征:
execution.method通常为GET。input_schema包含搜索、过滤参数(如query,filter_by,max_results)。output_schema是一个对象数组。 - 案例:搜索邮件、查询数据库、查找文档。例如,一个
skill_search_emails的技能,输入可能是query(关键词)和label(标签),输出是邮件列表。 - 应用建议:在设计RAG系统的检索技能时,这几乎是标准模板。你可以定义一个
skill_search_knowledge_base的技能,输入是用户问题,输出是相关的文档片段列表。
- 特征:
操作(Action)模式:
- 特征:
execution.method为POST、PUT、DELETE。input_schema包含创建或修改一个实体所需的全部字段。output_schema通常包含操作状态和生成的ID。 - 案例:创建日历事件、发送邮件、在CRM中新建客户。这就是上面日历例子的典型。
- 应用建议:当你需要AI驱动的工作流改变外部系统状态时,就使用这个模式。务必在
input_schema中明确required字段,防止AI尝试用不完整的数据调用你的API。
- 特征:
计算/转换(Computation/Transformation)模式:
- 特征:可能没有
execution块,或者指向一个内部计算函数。input_schema接收原始数据,output_schema返回处理后的结果。 - 案例:格式化日期、计算税费、将文本翻译成另一种语言、提取摘要。
- 应用建议:这类技能非常适合封装那些轻量级、无副作用的数据处理逻辑。它们可以作为更复杂工作流中的一环。例如,在总结一篇长文章前,先调用一个
skill_extract_main_text的技能来净化HTML。
- 特征:可能没有
复合(Orchestration)模式:
- 特征:一个技能的
execution可能并不直接调用HTTP API,而是触发一系列其他技能的执行。这通常在更上层的Agent框架中实现。 - 设计思想:这是构建复杂AI Agent的关键。官方技能文件本身是原子性的,但它们的价值在于可以被像编排器一样的“大脑”组合使用。例如,一个“安排团队周会”的用户请求,可能被分解为:1)
skill_find_team_members, 2)skill_find_free_slots, 3)skill_create_calendar_event。 - 应用建议:在设计你的技能库时,要有意识地追求技能的“原子性”和“可组合性”。一个技能只做好一件事,这样它们就能像乐高积木一样被灵活组装。
- 特征:一个技能的
注意:在分析这些技能文件时,切记它们来自一个特定的历史版本和环境(
/home/oai/skills)。直接复制粘贴其中的API端点(URL)是无效的。我们汲取的是其模式、结构和设计思想,而非具体的实现内容。
3. 实战应用:将官方模式融入你的AI项目
理解了技能的设计模式后,关键在于如何将其应用到我们自己的项目中。下面我以构建一个“智能研发助手”为例,分享从零开始定义、实现和集成自定义技能的完整流程。这个助手能帮开发者查询Bug、提交代码、生成周报。
3.1 第一步:定义你的技能清单(Skill Inventory)
不要一上来就写代码。先像产品经理一样,列出你的AI助手需要具备哪些能力。参考oai-skills的分类方式,我为“研发助手”规划了以下技能:
| 技能名称 (name) | 描述 (description) | 模式 | 核心输入参数 | 预期输出 |
|---|---|---|---|---|
skill_bugzilla_search | 在Bugzilla系统中根据条件搜索Bug报告。 | 查询 | query(关键词),status(状态),assignee(负责人) | Bug列表(ID, 摘要, 状态) |
skill_gitlab_create_mr | 在GitLab仓库中创建合并请求(Merge Request)。 | 操作 | source_branch,target_branch,title,description | MR的ID和Web URL |
skill_jira_get_sprint_tasks | 获取当前Jira迭代中分配给当前用户的任务。 | 查询 | sprint_id(可选,默认为当前迭代) | 任务列表(Key, 摘要, 状态) |
skill_generate_weekly_report | 基于本周的代码提交、解决的Bug和任务记录,生成周报草稿。 | 计算 | start_date,end_date | 周报文本(Markdown格式) |
这个表格就是你开发的“地图”。它迫使你思考技能的边界、输入输出的契约。
3.2 第二步:编写技能定义文件
接下来,为每个技能创建对应的JSON定义文件。我们以skill_gitlab_create_mr为例,展示一个高度仿真的定义:
// skills/gitlab_create_mr.json { "name": "skill_gitlab_create_mr", "description": "Create a new Merge Request in the specified GitLab project. Useful for submitting code changes for review.", "input_schema": { "type": "object", "properties": { "source_branch": { "type": "string", "description": "The name of the branch where the changes are implemented. E.g., 'feature/add-user-auth'." }, "target_branch": { "type": "string", "description": "The name of the branch you want to merge into. Usually 'main', 'master', or 'develop'.", "default": "main" }, "title": { "type": "string", "description": "A concise title for the Merge Request that summarizes the change." }, "description": { "type": "string", "description": "Detailed explanation of the changes, including motivation, implementation details, and any testing performed. Markdown is supported." }, "project_id": { "type": "string", "description": "The numeric ID or path of the GitLab project. If not provided, the default project from configuration will be used." } }, "required": ["source_branch", "title"] }, "output_schema": { "type": "object", "properties": { "mr_id": { "type": "number", "description": "The internal ID of the created Merge Request." }, "mr_iid": { "type": "number", "description": "The project-specific IID (instance ID) of the Merge Request." }, "web_url": { "type": "string", "description": "The direct URL to view the Merge Request in the GitLab web interface." }, "status": { "type": "string", "description": "Creation status, e.g., 'created', 'failed'." } } }, "execution": { "type": "http", "url": "{{GITLAB_API_BASE}}/projects/{{project_id}}/merge_requests", "method": "POST", "headers": { "PRIVATE-TOKEN": "{{GITLAB_TOKEN}}" } } }关键实现细节与避坑指南:
- 默认值(
default):注意target_branch设置了"default": "main"。这是一个非常实用的技巧。这意味着当用户只说“为分支feature/xxx创建MR”时,AI可以自动将target_branch补全为main,而无需反复追问用户。合理使用默认值能极大提升对话流畅度。 - 动态变量(
{{...}}):execution块中的{{GITLAB_API_BASE}}、{{project_id}}、{{GITLAB_TOKEN}}不是硬编码的。在实际的AI Agent框架(如LangChain、AutoGen或自定义框架)中,这些变量会在运行时被替换为真实的值。这实现了技能定义的可移植性和安全性(令牌不暴露在定义文件中)。 - 描述即文档:
description字段不仅给AI看,也是给未来维护此技能的开发者看的。写得越清晰,后续集成和调试成本越低。
3.3 第三步:实现技能的执行器(Executor)
技能定义只说了“做什么”,真正的“怎么做”需要执行器来完成。执行器是一个后端服务,它接收AI Agent解析好的参数,调用真实的第三方API或内部服务。
以下是一个用Python FastAPI实现的skill_gitlab_create_mr执行器示例:
# skill_executors/gitlab.py import os import requests from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter() # 从环境变量获取配置,安全且灵活 GITLAB_API_BASE = os.getenv("GITLAB_API_BASE", "https://gitlab.com/api/v4") GITLAB_TOKEN = os.getenv("GITLAB_TOKEN") DEFAULT_PROJECT_ID = os.getenv("DEFAULT_GITLAB_PROJECT_ID") class CreateMRRequest(BaseModel): source_branch: str target_branch: str = "main" # 与服务端默认值保持一致 title: str description: str = "" project_id: str = None @router.post("/gitlab/create_mr") async def execute_create_mr(request: CreateMRRequest): """执行创建GitLab MR的技能""" if not GITLAB_TOKEN: raise HTTPException(status_code=500, detail="GitLab token not configured.") # 确定项目ID:优先使用传入的,否则用默认的 project_id = request.project_id or DEFAULT_PROJECT_ID if not project_id: raise HTTPException(status_code=400, detail="Project ID must be specified.") # 准备调用GitLab API url = f"{GITLAB_API_BASE}/projects/{project_id}/merge_requests" headers = {"PRIVATE-TOKEN": GITLAB_TOKEN} data = { "source_branch": request.source_branch, "target_branch": request.target_branch, "title": request.title, "description": request.description, "remove_source_branch": True # 可以根据需要添加更多选项 } try: response = requests.post(url, headers=headers, json=data, timeout=30) response.raise_for_status() mr_data = response.json() # 格式化输出,严格匹配技能定义中的output_schema return { "mr_id": mr_data.get("id"), "mr_iid": mr_data.get("iid"), "web_url": mr_data.get("web_url"), "status": "created" } except requests.exceptions.RequestException as e: # 详细的错误处理,便于AI向用户解释 error_msg = f"Failed to create MR: {str(e)}" if hasattr(e.response, 'text'): error_msg += f" Response: {e.response.text[:200]}" raise HTTPException(status_code=500, detail=error_msg)实操心得:执行器设计的黄金法则
- 契约优先:执行器的输入和输出必须与技能定义文件中的
input_schema和output_schema严格一致。这是AI Agent和你后端服务之间的“合同”,任何偏差都会导致调用失败或数据混乱。 - 错误处理要友好:不要只是抛出晦涩的HTTP 500错误。像上面代码一样,捕获异常并返回对AI和最终用户都有意义的错误信息。AI可以利用这些信息向用户做出更友好的解释,比如“创建MR失败,可能是因为源分支不存在”。
- 配置外置:API令牌、基础URL等敏感或可变信息一定要通过环境变量或配置中心管理,绝对不要硬编码。
3.4 第四步:集成到AI Agent框架
这是最后一步,也是让技能“活”起来的一步。你需要一个AI Agent框架(或自己构建一个调度中心)来:
- 加载你定义的所有技能JSON文件。
- 将这些技能的描述和输入模式“告知”大语言模型(如GPT-4、Claude或本地模型)。
- 当用户发出指令时,框架让LLM判断是否需要调用技能,以及需要传入哪些参数。
- 框架调用对应的执行器,并将结果返回给LLM,由LLM组织成自然语言回复给用户。
这里以使用LangChain的Tool概念为例,展示如何包装我们的技能:
# agent_core.py import json from langchain.tools import BaseTool from langchain.agents import initialize_agent from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field from typing import Type # 1. 动态加载技能定义 def load_skill_definitions(skills_dir): skills = [] for file in os.listdir(skills_dir): if file.endswith('.json'): with open(os.path.join(skills_dir, file), 'r') as f: skill_def = json.load(f) skills.append(skill_def) return skills # 2. 为每个技能创建一个动态的LangChain Tool class DynamicSkillTool(BaseTool): name: str description: str args_schema: Type[BaseModel] executor_url: str # 对应执行器的端点 def _run(self, **kwargs): # 这里简化了,实际应调用对应的执行器HTTP服务 # 例如:requests.post(self.executor_url, json=kwargs) response = call_executor_service(self.name, kwargs) return json.dumps(response) # 返回字符串供LLM读取 async def _arun(self, **kwargs): # 异步支持 pass # 3. 根据技能定义,动态创建Pydantic模型用于参数验证 def create_args_model_from_schema(schema): # 这是一个简化示例,实际需要递归处理JSON Schema fields = {} for prop_name, prop_def in schema['properties'].items(): field_type = map_json_type_to_python(prop_def['type']) description = prop_def.get('description', '') default = ... if prop_name in schema.get('required', []) else prop_def.get('default', None) fields[prop_name] = (field_type, Field(default=default, description=description)) return type('DynamicArgs', (BaseModel,), {'__annotations__': fields}) # 4. 组装Agent def create_agent(): llm = ChatOpenAI(model="gpt-4", temperature=0) skill_defs = load_skill_definitions("./skills") tools = [] for def_ in skill_defs: ArgsModel = create_args_model_from_schema(def_['input_schema']) tool = DynamicSkillTool( name=def_['name'], description=def_['description'], args_schema=ArgsModel, executor_url=f"http://localhost:8000/execute/{def_['name']}" ) tools.append(tool) agent = initialize_agent(tools, llm, agent="structured-chat-zero-shot-react-description", verbose=True) return agent # 5. 运行Agent if __name__ == "__main__": agent = create_agent() result = agent.run("帮我基于feature/auth-branch创建一个合并请求到develop分支,标题是'用户认证模块上线'") print(result)这个流程将官方技能的定义模式与现代AI开发框架结合了起来。你的AI助手现在就能理解“创建MR”这样的复杂指令,并自动完成它了。
4. 进阶技巧与避坑实录
在实际集成和使用的过程中,我踩过不少坑,也总结出一些能让技能系统更稳健、更智能的经验。
4.1 技能描述的优化艺术
技能的description字段是AI决定是否调用它的主要依据。写得好坏,天差地别。
- 反面例子:
“处理邮件。” - 正面例子:
“搜索用户的收件箱中的电子邮件。可以根据发件人、主题关键词、接收时间范围进行过滤,并返回匹配的邮件列表。”
优化技巧:
- 使用动词开头:以“Search for...”, “Create a new...”, “Calculate the...”开头,明确动作。
- 包含关键参数:在描述中自然提及主要的输入参数,如上例中的“发件人、主题关键词、接收时间范围”。
- 说明适用场景:可以加一句“当用户想查找特定邮件时使用此功能。”
- 避免歧义:不要用“处理”、“管理”这种泛义词,要具体。
4.2 处理模糊的用户输入
用户不会总是说“请调用skill_search_emails,参数query=‘预算’, label=‘重要’”。他们更可能说“找我上周收到的关于预算的那封重要邮件。”
这就需要你的AI Agent具备参数推断和补全的能力。这通常通过以下方式实现:
- LLM的推理能力:在调用工具前,让LLM根据对话历史和技能描述,自行推断并补全缺失的参数。例如,LLM能推断出“上周”对应一个时间范围,“重要”可能对应标签
label。 - 设置合理的默认值:如前所述,在
input_schema中设置default值。 - 设计确认环节:对于关键且无法推断的参数(比如创建MR时的
title),如果用户没提供,框架应让AI主动追问用户,而不是直接调用失败。
4.3 技能依赖与执行顺序
有些复杂任务需要按顺序调用多个技能。例如,“总结我上周的工作”可能需要:1) 从Jira获取任务,2) 从GitLab获取提交,3) 从邮箱获取相关邮件,4) 调用总结生成技能。
实现策略:
- 使用规划型Agent:采用ReAct、Plan-and-Execute等模式的Agent,让LLM自己规划步骤。
- 设计高层“编排技能”:你可以专门创建一个
skill_generate_weekly_report的技能,它的执行器内部按固定顺序调用其他几个数据获取技能,最后再调用LLM生成总结。这样对用户来说,还是一个简单的指令。
4.4 常见问题排查表
在开发和调试技能系统时,你大概率会遇到以下问题:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| AI完全不调用技能 | 1. 技能描述不清晰,LLM无法匹配。 2. LLM温度(temperature)设置过高,导致输出不稳定。 3. 技能定义未正确加载到Agent上下文中。 | 1. 优化description,使其更贴近用户自然语言。2. 将LLM的 temperature调低(如0.1),增加确定性。3. 检查框架日志,确认技能列表已成功注册。在Prompt中明确告诉LLM“你可以使用以下工具”。 |
| AI调用了错误的技能 | 技能之间的描述区分度不够。 | 重写技能描述,突出其独特性和专用场景。例如,“创建日历事件”和“创建待办事项”要明确区分。 |
| 技能调用失败,参数错误 | 1. LLM推断的参数类型或格式不对。 2. input_schema中的required字段缺失。3. 执行器输入验证失败。 | 1. 在技能描述的property中提供更详细的格式示例(如“日期格式:YYYY-MM-DD”)。2. 确保 required字段正确。3. 在执行器端添加详细的输入验证和错误日志,明确返回哪个参数有问题。 |
| 技能执行成功,但AI回复内容不佳 | 执行器返回的数据过于原始或复杂,LLM不知道如何组织语言。 | 1. 优化output_schema,只返回关键、有意义的信息。2. 可以在执行器返回数据后,设计一个固定的回复模板,让AI填充。例如:“已成功创建合并请求!MR编号为#{{mr_iid}},您可以在此处查看:{{web_url}}”。 |
| 技能执行超时或网络错误 | 第三方API响应慢或执行器本身有性能问题。 | 1. 在执行器调用外部API时设置合理的超时(如30秒)。 2. 实现重试机制(对于幂等的GET请求)。 3. 为执行器添加监控和告警。 |
4.5 安全与权限考量
当你赋予AI调用真实系统API的能力时,安全是重中之重。
- 最小权限原则:为AI Agent使用的API令牌(Token)申请最小必要权限。例如,一个只用于搜索邮件的技能,绝不应该拥有删除邮件的权限。
- 用户上下文隔离:确保技能在执行时,操作的是当前对话用户的数据,而不是其他用户的数据。这通常需要在执行器层面,将AI传入的参数与当前已认证的用户会话进行绑定。
- 输入验证与清理:执行器必须对所有输入参数进行严格的验证和清理,防止注入攻击。即使LLM是“友好”的,也要防范恶意构造的输入。
- 审计日志:记录每一次技能调用的详细信息:谁(用户)、何时、调用了什么技能、输入参数是什么、结果如何。这对于问题排查和安全审计至关重要。
回过头看,eliasjudin/oai-skills这个项目提供的远不止是一堆JSON文件。它提供了一套经过大规模实践检验的、用于构建可交互AI系统的接口设计规范和思维模式。通过深入剖析这些技能文件,我们学到了如何用结构化的方式定义AI的能力边界,如何设计人机交互的契约,以及如何将复杂的用户指令分解为可执行的原子操作。将这些模式应用到自己的项目中,能让你设计的AI助手不再是简单的聊天机器人,而是真正能融入工作流、提升效率的智能伙伴。整个过程中,最关键的转变在于思维模式:从“让AI生成一段文本”变为“让AI协调一系列工具来完成一个任务”。