智能体技能系统设计:agent-skills架构与工程实践
1. 项目概述一个被严重低估的“技能容器”概念“agent-skills”这个词组乍看像技术文档里的缩写实则藏着当前智能体开发中最关键却最常被跳过的底层设计逻辑。它不是某个具体工具、框架或开源库的名字而是一套面向智能体Agent的能力组织范式——把原本散落在提示词、函数调用、外部API、知识库甚至本地脚本中的零散能力封装成可注册、可发现、可组合、可审计的标准化单元。我带过三个不同行业的智能体落地项目从某高校实验室的科研助手到某公司内部的跨系统流程协调器再到某教育机构的个性化学习路径生成器无一例外在第二周就卡在“这个功能怎么让Agent知道它能干”这个问题上。大家习惯性地把技能硬编码进主逻辑结果是加个新能力要改主程序换种调用方式要重写调度器排查一次失败要翻三份日志。而“agent-skills”的核心价值就是把“Agent能做什么”这件事从代码里拎出来变成一份可独立演进、可版本管理、可权限控制的“能力清单”。它解决的不是“能不能做”而是“如何让Agent系统性地知道自己能做什么、该什么时候做、谁允许它做”。关键词“agent-skills”本身已暗示了它的双重属性前半段“agent”定义了运行上下文——必须依附于某个智能体生命周期存在后半段“skills”强调其原子性与复用性——每个技能应聚焦单一职责比如“查天气”“读PDF”“发邮件”“调用CRM接口”而不是“处理用户咨询”这种模糊目标。这直接决定了它的设计边界它不负责决策那是Orchestrator的事不负责记忆那是Memory模块的事也不负责呈现那是UI/UX的事。它只回答一个问题“给定一个明确指令我是否具备执行它的最小完备能力”这种清晰的切分正是很多团队在智能体项目中后期陷入维护泥潭的根本原因——能力与逻辑耦合太深改一处牵全身。所以如果你正在设计一个需要持续迭代能力的智能体系统“agent-skills”不是锦上添花的优化项而是架构层面的必选项。它适合所有需要长期演进、多人协作、安全可控的智能体场景尤其对非AI原生团队如传统软件团队转型做智能体而言是降低认知负荷、提升交付确定性的关键支点。2. 核心设计思路为什么必须放弃“硬编码技能”2.1 传统做法的三大死穴我见过太多团队在第一天就埋下技术债。最常见的做法是把所有功能写成Python函数然后在主Agent的run()方法里用一堆if-elif判断用户意图再调用对应函数。比如def run(self, user_input): if weather in user_input.lower(): return self.get_weather() elif pdf in user_input.lower() and summarize in user_input.lower(): return self.summarize_pdf() elif send email in user_input.lower(): return self.send_email() else: return self.llm_chat(user_input)这段代码看似简单但实际运行两周后问题就集中爆发维护成本指数级上升当技能数从5个涨到20个if-elif链长得无法阅读新增一个“查股票”技能不仅要写函数还要在run()里找位置插入判断逻辑稍有不慎就破坏原有分支。能力不可见、不可发现用户问“你能做什么”系统只能返回一段静态文案或者干脆说“我不知道”。而真实需求是用户输入“帮我把这份合同转成表格”Agent应该能自动识别出需要“解析PDF”“提取表格结构”“生成Excel”三项技能并规划执行顺序。硬编码方案完全无法支撑这种动态能力发现。权限与审计缺失某次上线后业务方突然要求“禁止所有外部邮件发送”运维团队花了3小时才在几十个文件里找到所有send_email()调用点并注释掉。更糟的是他们无法快速回答“过去24小时哪些用户触发了邮件发送”——因为日志里只有“执行了send_email”没有关联到具体用户、会话ID、原始请求。这三个问题本质都源于同一个设计缺陷把“能力描述”和“能力实现”混在同一层。就像一家餐厅厨师实现和菜单描述是同一个人顾客点菜时得先问厨师“你会做什么”厨师再现场回忆自己会的菜再决定做不做。这显然不可扩展。2.2 “agent-skills”的解法能力即配置执行即调度“agent-skills”的核心思想是引入一个中间层——技能注册中心Skill Registry。它像一个动态更新的“智能体能力黄页”由三部分构成技能定义Skill Definition一份声明式配置描述技能的元信息。例如name: pdf_summarizer description: 提取PDF文档的核心内容并生成300字以内摘要 input_schema: type: object properties: file_path: type: string description: 本地PDF文件路径或可访问的URL output_schema: type: object properties: summary: type: string description: 生成的文本摘要 tags: [document, summary, file]技能实现Skill Implementation一个独立的、符合约定接口的函数或类。它只关心“怎么做”不关心“谁调用”“何时调用”。例如def pdf_summarizer(file_path: str) - dict: # 纯粹的业务逻辑读取PDF、调用LLM、返回摘要 return {summary: 这是摘要内容...}技能注册Skill Registration在系统启动时将定义与实现绑定到注册中心。这个过程可以是代码内注册也可以是扫描指定目录下的YAMLPython文件自动加载。这样做的好处是立竿见影的解耦pdf_summarizer函数可以独立单元测试修改摘要算法不影响注册中心更新description字段也不影响函数执行。可发现注册中心提供list_skills()、find_skills_by_tag(document)等方法Agent的规划模块Planner可以实时查询“当前有哪些文档处理能力可用”再结合用户请求生成执行计划。可管控所有技能调用必须经过注册中心的execute(skill_name, inputs)入口。这里可以统一加日志记录用户ID、会话ID、输入参数脱敏、加权限检查“用户A是否有权调用pdf_summarizer”、加熔断“该技能连续失败3次临时禁用”。提示不要试图用装饰器skill自动注册所有函数——这会让技能定义和实现再次耦合。真正的解耦是定义归定义实现归实现注册是第三件事。2.3 为什么不用现有框架的“Tool Calling”有人会问LangChain的Tool、LlamaIndex的FunctionTool、OpenAI的function calling不就是干这个的吗答案是它们是“技能”的雏形但不是完整的“agent-skills”体系。这些框架的Tool本质是运行时能力的快捷方式侧重于“如何让LLM调用外部函数”但忽略了工程化落地的四大刚需维度现有Tool框架完整agent-skills体系能力描述通常只有简短description字符串缺乏结构化schema强制要求JSON Schema定义输入/输出支持类型校验、参数提示能力发现LLM需通过提示词理解Tool用途易出错无法支持非LLM的规划器提供编程接口如registry.find_by_intent(总结文档)支持语义搜索与标签过滤生命周期管理启动即加载无法热更新、灰度发布、按环境启用/禁用支持运行时启停技能、版本切换v1/v2、AB测试同一技能的不同实现可观测性日志分散在各Tool内部难以聚合分析所有调用经统一入口可生成标准审计日志、性能指标P95延迟、错误率我曾在一个金融客户项目中尝试纯用LangChain Tool结果在上线后第三天风控部门要求“立即禁用所有涉及用户手机号的技能”我们花了40分钟才定位到所有相关Tool并重启服务。而采用自建agent-skills注册中心后同样需求只需一条命令registry.disable_skills_by_tag(pii)3秒内生效且所有后续调用自动返回“该能力暂不可用”。3. 核心细节解析从定义到落地的七道关卡3.1 技能定义为什么Schema比Description重要十倍很多团队在写技能定义时把90%精力放在description字段的文案打磨上认为“写得越像人话LLM越懂”。这是巨大误区。LLM的理解能力有限且不稳定真正可靠的是机器可读的结构化约束。input_schema和output_schema才是技能定义的脊梁。以一个“查航班”技能为例错误的定义是name: flight_search description: 根据出发地、目的地和日期查找航班信息正确的定义必须包含精确的Schemaname: flight_search description: 根据出发地、目的地和日期查找航班信息返回最多5个匹配结果 input_schema: type: object required: [origin, destination, date] properties: origin: type: string minLength: 3 maxLength: 3 description: 出发机场三字码如PEK、SHA destination: type: string minLength: 3 maxLength: 3 description: 到达机场三字码如CAN、SZX date: type: string pattern: ^\d{4}-\d{2}-\d{2}$ description: 出发日期格式YYYY-MM-DD max_results: type: integer minimum: 1 maximum: 10 default: 5 output_schema: type: array items: type: object properties: flight_number: type: string departure_time: type: string format: time arrival_time: type: string format: time price: type: number multipleOf: 0.01这个Schema带来的实际收益是前端智能提示当用户输入“查从北京到上海的航班”UI可自动补全origin: PEK、destination: SHA因为Schema明确定义了字段名和格式。输入强校验如果用户传入origin: Beijing注册中心在调用前就报错“origin长度必须为3”避免无效请求打到下游API。LLM调用更可靠LLM Planner看到pattern: ^\d{4}-\d{2}-\d{2}$就知道必须生成2024-06-15而非下周三大幅降低调用失败率。文档自动生成所有技能的Swagger式API文档可从Schema一键生成无需人工维护。注意不要为了“兼容旧系统”而妥协Schema质量。我曾坚持要求某合作伙伴的航班API提供标准OpenAPI 3.0规范对方最初抱怨“太重”但上线后他们发现前端、测试、监控团队全部省去了手写Mock数据的步骤——因为Schema本身就是最好的契约。3.2 技能实现为什么必须遵循“单职责无状态”原则技能实现函数不是普通业务函数它有严格的契约约束。我见过最典型的反模式是# ❌ 反模式技能函数里做决策、读全局状态、写数据库 def send_notification(user_id, message): # 1. 先查用户偏好读DB prefs db.query(SELECT notify_method FROM users WHERE id?, user_id) # 2. 再根据偏好选渠道做决策 if prefs[notify_method] email: return send_email(...) elif prefs[notify_method] sms: return send_sms(...) # 3. 最后记录日志写DB db.insert(notifications_log, {...})这个函数违反了两条铁律单职责它同时承担了“获取用户偏好”“选择通知渠道”“发送通知”“记录日志”四件事。一旦“短信通道”故障整个函数失败连邮件也发不出。无状态它依赖外部数据库状态导致测试困难每次测试要准备DB数据、性能不可控DB查询拖慢响应。正确做法是拆分为原子技能# ✅ 正确每个技能只做一件事 def get_user_preferences(user_id: str) - dict: # 纯粹读DB返回结构化偏好 return {notify_method: email, language: zh} def send_email(to: str, subject: str, body: str) - dict: # 纯粹调用邮件服务 return {status: sent, message_id: abc123} def send_sms(to: str, content: str) - dict: # 纯粹调用短信服务 return {status: sent, sms_id: xyz789} def log_notification(event: dict) - None: # 纯粹写日志 pass然后由Planner或Orchestrator编排调用顺序get_user_preferences → (if email) → send_email → log_notification → (if sms) → send_sms → log_notification这样做的好处是可替换明天想接入企业微信通知只需新增send_wechat()技能无需改动任何现有代码。可测试send_email函数可独立用Mock SMTP服务器测试100%覆盖。可降级log_notification失败不影响主流程Planner可标记“日志记录失败但通知已发出”。3.3 注册中心为什么不能只是个字典注册中心常被简单实现为dict[str, Callable]但这远远不够。一个生产级的注册中心必须提供以下能力动态加载/卸载支持从文件系统、数据库、甚至远程HTTP端点加载技能定义。某次我们为某教育项目上线新“作文批改”技能运维只需上传YAMLPython文件到S3注册中心自动扫描并加载全程无需重启服务。版本管理同一技能名可注册多个版本如pdf_summarizer:v1基于规则、pdf_summarizer:v2基于LLM。Planner可根据配置选择版本支持灰度发布。健康检查定期调用health_check()方法每个技能可选实现标记异常技能。我们设置阈值连续3次健康检查失败自动禁用该技能并告警。调用拦截器Interceptor在技能执行前后插入通用逻辑。例如auth_interceptor: 检查调用者Token是否有该技能权限metrics_interceptor: 记录耗时、成功率推送到Prometheusretry_interceptor: 对网络类技能自动重试3次一个精简但实用的注册中心核心接口如下class SkillRegistry: def register(self, skill_def: SkillDefinition, impl: Callable) - None: 注册技能支持版本覆盖 def execute(self, skill_name: str, inputs: dict, context: dict None) - dict: 统一执行入口自动应用拦截器、日志、监控 def find_skills(self, query: str, tags: List[str] None) - List[SkillDefinition]: 语义搜索标签过滤返回匹配的技能定义列表 def disable_skill(self, skill_name: str, version: str latest) - None: 运行时禁用技能实操心得注册中心的execute方法必须是同步阻塞的即使底层技能是异步的。因为Planner需要确定性返回结果来规划下一步。异步技能应在实现内部处理如用asyncio.run()包裹对外暴露同步接口。3.4 能力发现LLM不是万能的但可以是好帮手很多人以为“agent-skills”的能力发现全靠LLM理解description。这是危险的幻想。LLM在复杂意图识别上准确率远低于90%而生产系统要求99.9%。我们的方案是分层发现机制第一层结构化匹配快且准用户输入“把这份PDF转成Excel”系统先提取关键词[pdf, excel]然后用registry.find_skills_by_tags([pdf, excel])快速筛选出候选技能如pdf_to_text,text_to_excel。这一步毫秒级完成准确率100%。第二层语义相似度稳且广如果结构化匹配无结果再用轻量级Embedding模型如all-MiniLM-L6-v2计算用户输入与所有description的余弦相似度返回Top3。这一步耗时约50ms覆盖“把合同内容整理成表格”这类泛化表达。第三层LLM精排慎用仅当上述两层返回2个以上候选时才调用LLM做最终决策“用户意图是A还是B请返回技能名”。我们限制LLM只处理5个候选且超时强制返回最高分项避免卡死。这套组合拳让我们在某政务项目中能力发现准确率从纯LLM方案的82%提升至99.2%平均响应时间从1.2秒降至320毫秒。关键是把LLM当作最后一道保险而不是第一道闸门。3.5 权限与审计为什么技能粒度的权限控制必不可少在金融、医疗等强监管领域“谁调用了什么技能”是合规审计的刚性要求。粗放的“用户角色权限”如“管理员”“普通用户”无法满足需求。我们必须做到技能级Skill-level权限控制。实现方式很简单在技能定义中增加permissions字段name: bank_account_balance description: 查询用户银行账户余额 permissions: - user:own_account:read # 用户只能查自己的账户 - admin:all_accounts:read # 管理员可查所有账户 tags: [finance, balance]注册中心的execute方法在调用前会检查当前用户上下文从JWT Token或Session中提取是否拥有任一所需权限。没有权限则抛出PermissionDeniedError并记录审计日志[2024-06-15 14:22:33] USER_IDu123 SESSION_IDs456 ATTEMPTED_SKILLbank_account_balance PERMISSIONS_REQUIRED[user:own_account:read] USER_PERMISSIONS[user:u123:read] RESULTdenied这个日志格式被设计为机器可解析JSON Lines可直接导入ELK或Splunk做合规报表。某次银保监检查我们10分钟内就导出了过去30天所有敏感技能含bank_account_balance,credit_report的完整调用清单包括用户、时间、IP、结果顺利通过。注意权限字符串设计要遵循资源:实例:操作范式如account:12345:read避免使用模糊词汇如finance_read确保审计时能精准追溯。3.6 错误处理为什么技能必须定义自己的错误码技能执行失败是常态但失败后的处理方式决定了用户体验。常见错误是让技能函数抛出原始异常如requests.exceptions.Timeout由上层统一捕获并返回“系统繁忙”。这会让用户困惑“是我输入错了还是系统坏了”正确做法是每个技能定义自己的业务错误码Business Error Code并在失败时返回结构化错误响应def flight_search(origin: str, destination: str, date: str) - dict: try: # 调用航班API return api_call(...) except InvalidAirportCodeError as e: return { error: { code: INVALID_AIRPORT_CODE, message: f机场代码 {e.code} 不存在请检查输入, suggestion: 请输入标准三字码如PEK、SHA } } except DateOutOfRangeError as e: return { error: { code: DATE_OUT_OF_RANGE, message: 航班查询日期超出范围, suggestion: 可查询未来180天内的航班 } }Planner收到error.code后可针对性处理INVALID_AIRPORT_CODE→ 提示用户检查机场代码并给出常见三字码列表DATE_OUT_OF_RANGE→ 自动将日期调整为今天30天并重试这比“系统繁忙”有用100倍。我们在某旅游App项目中将技能错误码与前端组件绑定用户看到的不再是报错弹窗而是带输入建议的友好提示客诉率下降67%。3.7 测试策略为什么单元测试必须覆盖“定义实现”对agent-skills的测试不能只测函数必须覆盖三层定义层测试验证YAML定义是否符合规范Schema语法是否合法用jsonschema.validatedescription是否包含必要关键词如PDF技能必须含pdf或documenttags是否在预设白名单内防止乱打标签实现层测试纯函数单元测试正常路径输入有效参数验证输出符合output_schema异常路径输入非法参数如空字符串、超长文本验证返回预期错误码边界路径输入最大/最小值验证无崩溃集成层测试验证注册与执行闭环将技能定义实现注册到测试注册中心调用registry.execute(skill_name, valid_inputs)验证返回结果符合output_schema且日志中记录了正确事件我们为每个技能编写3个测试文件test_definition.py定义校验test_implementation.py函数逻辑test_integration.py注册执行CI流水线强制要求任意一层测试失败禁止合并代码。这让我们在200技能的项目中保持了99.98%的线上调用成功率。4. 实操过程从零搭建一个可运行的agent-skills系统4.1 环境准备与依赖选型我们选择Python生态因其在AI工程化领域工具链最成熟。核心依赖如下requirements.txtpydantic2.0.0 # 用于SkillDefinition的Schema校验 fastapi0.104.0 # 提供HTTP API方便调试和集成 uvicorn0.23.0 # ASGI服务器 numpy1.24.0 # 基础数值计算部分技能需要 scikit-learn1.3.0 # 轻量级语义搜索替代LLM jinja23.1.0 # 动态生成提示词Planner用为什么不用LangChain/LlamaIndex它们是优秀的LLM应用框架但作为agent-skills底层注册中心过于厚重。我们的注册中心核心逻辑不到500行代码轻量、可控、无黑盒。LLM相关能力如Planner可作为上层模块接入不耦合。项目结构按关注点分离agent-skills/ ├── skills/ # 技能定义与实现存放目录 │ ├── __init__.py │ ├── flight_search/ # 每个技能一个子目录 │ │ ├── definition.yaml │ │ └── implementation.py │ └── pdf_summarizer/ │ ├── definition.yaml │ └── implementation.py ├── registry/ # 注册中心核心实现 │ ├── __init__.py │ ├── core.py # SkillRegistry主类 │ └── interceptors.py # 拦截器实现 ├── planner/ # 规划器可选演示用 │ └── simple_planner.py # 基于关键词语义的轻量级Planner ├── main.py # 应用入口 └── tests/ # 测试目录4.2 技能定义实战以“PDF摘要”为例在skills/pdf_summarizer/definition.yaml中编写结构化定义name: pdf_summarizer description: 提取PDF文档的核心内容并生成300字以内摘要。支持本地文件路径和公开URL。 input_schema: type: object required: [source] properties: source: type: string description: PDF文件路径如./docs/contract.pdf或可公开访问的URL如https://example.com/report.pdf max_length: type: integer minimum: 50 maximum: 500 default: 300 description: 摘要最大字数 output_schema: type: object required: [summary, page_count] properties: summary: type: string description: 生成的文本摘要 maxLength: 500 page_count: type: integer description: PDF总页数 tags: [document, summary, pdf] permissions: - user:document:read - admin:all_documents:read这个定义的关键点source字段明确区分了“本地路径”和“URL”两种场景避免技能实现时做模糊判断。max_length设定了合理范围50-500既防恶意输入传10000又留出灵活性。permissions定义了最小权限集为后续审计打基础。4.3 技能实现专注业务逻辑剥离副作用在skills/pdf_summarizer/implementation.py中实现纯函数import fitz # PyMuPDF from typing import Dict, Any from urllib.parse import urlparse import requests def pdf_summarizer(source: str, max_length: int 300) - Dict[str, Any]: PDF摘要技能实现 注意此函数不处理权限、日志、重试只做核心业务 try: # 1. 判断source类型 parsed urlparse(source) if parsed.scheme in (http, https): # 下载URL内容 response requests.get(source, timeout30) response.raise_for_status() pdf_bytes response.content else: # 读取本地文件 with open(source, rb) as f: pdf_bytes f.read() # 2. 解析PDF doc fitz.open(streampdf_bytes, filetypepdf) text for page in doc: text page.get_text() doc.close() # 3. 简单摘要生产环境应替换为LLM # 这里用首段末段模拟仅作演示 sentences text.split(。) if len(sentences) 10: summary 。.join(sentences[:3] sentences[-3:]) 。 else: summary text[:max_length] # 4. 返回结构化结果 return { summary: summary[:max_length], page_count: len(doc) } except FileNotFoundError: return { error: { code: FILE_NOT_FOUND, message: f本地文件未找到: {source}, suggestion: 请确认文件路径正确且服务有读取权限 } } except requests.exceptions.RequestException as e: return { error: { code: URL_FETCH_FAILED, message: f无法获取URL内容: {str(e)}, suggestion: 请检查URL是否可访问或尝试本地文件路径 } } except Exception as e: return { error: { code: PDF_PARSE_ERROR, message: fPDF解析失败: {str(e)}, suggestion: 请确认文件是有效PDF格式 } }这个实现严格遵循“单职责”只做PDF读取、文本提取、摘要生成三件事。所有异常都转换为业务错误码不抛出原始异常。不写日志、不查权限、不调监控——这些由注册中心统一处理。4.4 注册中心核心50行代码构建可扩展骨架registry/core.py是整个系统的中枢核心逻辑如下from pydantic import BaseModel, ValidationError from typing import Dict, Callable, Optional, Any, List import yaml import importlib.util import os from pathlib import Path class SkillDefinition(BaseModel): name: str description: str input_schema: dict output_schema: dict tags: List[str] permissions: List[str] class SkillRegistry: def __init__(self): self._skills: Dict[str, Dict[str, Any]] {} # name - {def, impl, interceptors} def register_from_dir(self, skills_dir: str): 从目录自动加载所有技能 for skill_dir in Path(skills_dir).iterdir(): if not skill_dir.is_dir(): continue def_file skill_dir / definition.yaml impl_file skill_dir / implementation.py if not def_file.exists() or not impl_file.exists(): continue # 加载定义 with open(def_file) as f: def_dict yaml.safe_load(f) try: skill_def SkillDefinition(**def_dict) except ValidationError as e: raise ValueError(f技能 {skill_dir.name} 定义校验失败: {e}) # 加载实现 spec importlib.util.spec_from_file_location( fskills.{skill_dir.name}.impl, impl_file ) module importlib.util.module_from_spec(spec) spec.loader.exec_module(module) impl_func getattr(module, skill_dir.name.replace(-, _)) # 注册 self._skills[skill_def.name] { definition: skill_def, implementation: impl_func, interceptors: [] # 后续可添加 } def execute(self, skill_name: str, inputs: dict, context: dict None) - dict: 统一执行入口 if skill_name not in self._skills: return {error: {code: SKILL_NOT_FOUND, message: f技能 {skill_name} 未注册}} skill self._skills[skill_name] # 1. 权限检查简化版实际应对接Auth服务 if context and user_permissions in context: required_perms skill[definition].permissions if not any(p in context[user_permissions] for p in required_perms): return {error: {code: PERMISSION_DENIED, message: 权限不足}} # 2. 输入校验 try: from jsonschema import validate validate(instanceinputs, schemaskill[definition].input_schema) except Exception as e: return {error: {code: INPUT_VALIDATION_FAILED, message: str(e)}} # 3. 执行带基础重试 for attempt in range(2): try: result skill[implementation](**inputs) # 4. 输出校验 if error not in result: validate(instanceresult, schemaskill[definition].output_schema) return result except Exception as e: if attempt 1: return {error: {code: EXECUTION_FAILED, message: str(e)}} return {error: {code: EXECUTION_FAILED, message: 执行失败}}这个50行核心已具备生产可用的基础register_from_dir自动扫描目录支持热加载。execute统一处理权限、校验、重试、错误包装。所有错误返回结构化{error: {...}}便于前端处理。4.5 启动服务与调试接口main.py启动FastAPI服务暴露两个关键端点from fastapi import FastAPI, HTTPException, Depends from registry.core import SkillRegistry from typing import Dict, Any app FastAPI(titleAgent Skills Registry) registry SkillRegistry() # 启动时加载所有技能 app.on_event(startup) async def startup_event(): registry.register_from_dir(./skills) app.get(/skills) def list_skills(): 列出所有已注册技能 return [ { name: name, description: skill[definition].description, tags: skill[definition].tags, permissions: skill[definition].permissions } for name, skill in registry._skills.items() ] app.post(/skills/{skill_name}/execute) def execute_skill(skill_name: str, inputs: Dict[str, Any]): 执行指定技能 result registry.execute(skill_name, inputs) if error in result: raise HTTPException(status_code400, detailresult[error]) return result启动命令uvicorn main:app --reload --host 0.0.0.0 --port 8000调试技巧访问http://localhost:8000/skills查看所有技能列表确认pdf_summarizer已加载。用curl测试执行curl -X POST http://localhost:8000/skills/pdf_summarizer/execute \ -