AI智能体技能库设计:模块化、标准化与工程化实践
1. 项目概述一个面向AI智能体的技能库最近在折腾AI智能体Agent的开发发现一个挺有意思的现象很多开发者包括我自己在内在构建一个具备特定能力的智能体时常常会陷入“重复造轮子”的困境。比如你想让智能体学会“读取并分析PDF文件”或者“调用某个特定的API获取天气数据”这些功能模块我们通常称之为“技能”或“工具”的实现逻辑其实大同小异。每次新开一个项目都得把这些基础技能重新实现一遍不仅效率低下而且代码质量也参差不齐。这时候一个集中、规范、可复用的“技能库”就显得尤为重要了。jdrhyne/agent-skills这个项目从名字上就能看出它的定位——一个为AI智能体准备的技能集合。它不是一个完整的智能体框架而更像是一个“工具箱”或者“插件市场”。它的核心价值在于将那些在智能体开发中高频使用、相对独立的功能模块进行标准化封装和集中管理让开发者可以像搭积木一样快速为自己的智能体装配所需的能力。这个项目适合谁呢如果你正在基于LangChain、AutoGPT、CrewAI或是自定义的Agent框架进行开发并且厌倦了反复编写那些基础的工具函数如果你希望自己的智能体能力可以模块化地扩展和维护或者你只是想学习一下一个成熟的、面向生产环境的“技能”应该如何设计那么深入了解一下这个项目肯定会大有裨益。它解决的不仅仅是代码复用的问题更是推动智能体开发走向标准化、工程化的重要一步。2. 项目核心设计理念与架构拆解2.1 什么是“技能”Skill为何要抽象它在深入代码之前我们得先统一思想在这个项目的语境下“技能”到底是什么我个人的理解是技能是智能体与环境包括用户、其他系统、数据等进行交互并完成特定任务的最小能力单元。举个例子“查询数据库”是一个技能“发送邮件”是另一个技能“总结网页内容”又是一个技能。每个技能都应该具备明确的输入、输出和边界。这种抽象带来了几个巨大的好处解耦与复用智能体的核心“大脑”决策逻辑与具体的“手脚”执行能力分离。大脑只需要知道“现在需要调用查询数据库技能”并传递相应的参数而不需要关心数据库连接池怎么管理、SQL语句如何防注入。这个技能一旦写好可以被任何智能体复用。标准化接口一个设计良好的技能库会定义统一的技能接口。比如每个技能可能都需要一个run(input_parameters)方法并返回一个结构化的结果。这让技能的调用和管理变得异常简单和一致。安全与可控我们可以集中地对技能进行权限控制、输入校验、错误处理和日志记录。想象一下如果你有100个智能体都在直接裸连数据库安全审计和故障排查将是噩梦。而通过技能抽象你只需要在一个地方技能实现里加固安全措施。动态装配与热更新智能体的能力可以像手机安装APP一样动态加载。你可以根据任务场景为智能体配置不同的技能组合。甚至可以在运行时更新某个技能的版本而无需重启智能体服务。agent-skills项目正是基于这些理念构建的。它试图建立一个技能的标准范式并提供一批开箱即用、经过实践检验的高质量技能实现。2.2 技能库的典型架构模式虽然我无法看到jdrhyne/agent-skills项目内部的全部代码但根据其命名和常见的最佳实践我们可以推断其架构很可能包含以下核心层次技能接口层定义所有技能必须遵守的“契约”。通常包括技能描述技能的名称、版本、功能描述、所需参数说明等元数据。这部分信息对于智能体的“大脑”进行技能选择至关重要。执行方法核心的execute或run方法定义输入参数格式和输出结果格式。验证方法在执行前对输入参数进行校验。安全与权限声明声明该技能需要何种权限如网络访问、文件读写、敏感API调用等。基础技能实现层提供一系列通用的、底层的技能。这些是构建更复杂技能的基础砖块。例如文件操作技能读取本地文件、写入文件、列出目录。网络请求技能发送HTTP GET/POST请求处理Cookie和Session。数据转换技能JSON/XML解析、字符串模板渲染、数据格式清洗。工具调用技能封装对操作系统命令或特定命令行工具的调用。领域技能实现层在基础技能之上封装面向特定领域的、更高级的技能。这也是一个技能库价值最大的部分。例如文档处理技能基于PyPDF2或pdfplumber的PDF文本提取技能基于python-docx的Word文档处理技能。网络信息获取技能基于requests和BeautifulSoup的网页爬取与内容提取技能。第三方API集成技能调用OpenWeatherMap的天气查询技能、调用SerpAPI的搜索引擎技能、调用Twilio的短信发送技能。数据存储技能对SQLite、MySQL、PostgreSQL等数据库的增删改查技能对Redis的缓存操作技能。技能注册与管理中心一个核心的“技能仓库”负责技能的发现、加载、实例化和生命周期管理。它可能提供一个类似SkillRegistry的类允许开发者通过技能名称来查找和获取技能实例。配套工具层包括技能的单元测试、性能基准测试、文档自动生成工具、以及技能打包和发布的脚本等。这部分是项目工程化成熟度的体现。注意一个常见的误区是把“技能”做得过于庞大和复杂。技能的设计应遵循“单一职责原则”。一个技能只做好一件事。例如“获取天气”是一个技能“将天气数据生成自然语言描述”应该是另一个技能。这样的设计才能最大化复用性。3. 核心技能实现解析与设计要点3.1 一个技能的标准模板长什么样让我们来构想一个符合agent-skills项目风格的技能应该如何实现。假设我们要实现一个“获取指定城市当前天气”的技能。# 假设项目中有 base_skill.py 定义了基础接口 from abc import ABC, abstractmethod from typing import Any, Dict, Optional from pydantic import BaseModel, Field # 常用于参数验证和描述 class SkillInput(BaseModel): 技能的输入参数模型 city: str Field(..., description城市名称例如Beijing) country_code: Optional[str] Field(CN, description国家代码默认CN) class SkillOutput(BaseModel): 技能的标准化输出模型 success: bool data: Optional[Dict[str, Any]] error_message: Optional[str] class BaseSkill(ABC): 所有技能的基类 name: str base_skill description: str 基础技能无实际功能 version: str 1.0.0 required_parameters: list [] abstractmethod def execute(self, input_data: SkillInput) - SkillOutput: 执行技能的核心方法 pass def get_schema(self) - Dict: 获取技能的描述信息用于智能体理解该技能 return { name: self.name, description: self.description, parameters: self.input_model.schema() if hasattr(self, input_model) else {} }基于这个基类我们的天气技能实现如下import requests from .base_skill import BaseSkill, SkillInput, SkillOutput from pydantic import SecretStr from typing import Optional class WeatherInput(SkillInput): 天气技能的输入参数 city: str # 使用SecretStr避免在日志中泄露API密钥 api_key: SecretStr units: Optional[str] metric # 单位metric(摄氏度) 或 imperial(华氏度) class WeatherSkill(BaseSkill): 获取城市天气的技能 name get_weather description 根据城市名称获取当前的天气情况包括温度、湿度、天气状况等。 version 1.1.0 # 这里可以定义技能所需的权限或资源 required_permissions [network_access] def __init__(self, default_api_key: Optional[str] None): # 可以支持通过初始化传入默认API Key也可以在每次执行时传入 self.default_api_key default_api_key self.base_url https://api.openweathermap.org/data/2.5/weather def execute(self, input_data: WeatherInput) - SkillOutput: 执行天气查询 try: # 1. 参数准备与验证 params { q: f{input_data.city}, appid: input_data.api_key.get_secret_value() if input_data.api_key else self.default_api_key, units: input_data.units } if not params[appid]: return SkillOutput(successFalse, error_messageAPI Key未提供) # 2. 执行核心操作网络请求 response requests.get(self.base_url, paramsparams, timeout10) response.raise_for_status() # 非200状态码会抛出异常 weather_data response.json() # 3. 结果解析与标准化 standardized_data { city: weather_data.get(name), temperature: weather_data[main][temp], feels_like: weather_data[main][feels_like], humidity: weather_data[main][humidity], description: weather_data[weather][0][description], wind_speed: weather_data[wind][speed] } # 4. 返回标准化输出 return SkillOutput(successTrue, datastandardized_data) except requests.exceptions.Timeout: return SkillOutput(successFalse, error_message请求天气API超时) except requests.exceptions.RequestException as e: return SkillOutput(successFalse, error_messagef网络请求失败: {str(e)}) except KeyError as e: return SkillOutput(successFalse, error_messagef解析API响应数据失败缺少字段: {str(e)}) except Exception as e: # 兜底异常处理 return SkillOutput(successFalse, error_messagef技能执行过程中发生未知错误: {str(e)})设计要点解析输入验证使用Pydantic模型在数据进入执行逻辑前就完成类型和必填项校验。SecretStr类型能有效防止敏感信息如API Key在日志中明文泄露。错误处理技能必须健壮。我们捕获了网络超时、请求异常、数据解析异常等特定错误并提供了友好的错误信息。最后有一个兜底的Exception捕获确保技能不会因为未预料的异常而崩溃影响智能体整体运行。输出标准化无论成功失败都返回统一的SkillOutput结构。这使调用方智能体的处理逻辑变得简单一致。资源管理技能中涉及网络连接等资源要确保其被正确管理这里requests会自动处理连接关闭。可配置性通过__init__支持默认配置如API Key增加了技能的灵活性。3.2 复杂技能RAG检索增强生成技能的实现思路对于更复杂的技能如当前热门的RAG检索、增强、生成流水线也可以被封装成一个技能。这个技能内部可能协调多个子步骤或调用其他基础技能。class RAGInput(SkillInput): query: str knowledge_base_id: str # 指向特定的知识库 top_k: int 3 # 检索返回的相关片段数量 class RAGSkill(BaseSkill): name retrieval_augmented_generation description 根据用户问题从指定知识库中检索相关信息并生成增强后的回答。 version 2.0.0 required_permissions [vector_db_access, llm_api_access] def __init__(self, retriever, llm_client): # 依赖注入检索器、LLM客户端在初始化时传入 self.retriever retriever # 可能是Chroma、Pinecone等向量数据库的客户端 self.llm_client llm_client # 可能是OpenAI、Anthropic等LLM的客户端 def execute(self, input_data: RAGInput) - SkillOutput: try: # 步骤1检索 relevant_docs self.retriever.search( queryinput_data.query, top_kinput_data.top_k, filter{knowledge_base_id: input_data.knowledge_base_id} ) if not relevant_docs: return SkillOutput(successFalse, error_message未在知识库中找到相关信息。) # 步骤2构建上下文 context \n\n.join([doc.content for doc in relevant_docs]) # 步骤3调用LLM生成 prompt f基于以下上下文信息回答用户的问题。 如果上下文信息不足以回答问题请如实告知。 上下文 {context} 问题{input_data.query} 回答 llm_response self.llm_client.chat_complete(promptprompt) # 步骤4返回结果可附上引用来源 result_data { answer: llm_response.content, source_documents: [{id: doc.id, snippet: doc.content[:200]} for doc in relevant_docs] } return SkillOutput(successTrue, dataresult_data) except Exception as e: return SkillOutput(successFalse, error_messagefRAG流程执行失败: {str(e)})这个例子展示了如何将一个多步骤的复杂流程封装成一个原子技能。智能体只需要调用RAGSkill.execute()而无需关心内部复杂的检索和生成逻辑。这种封装极大地简化了智能体的认知负担。4. 技能的管理、注册与在智能体中的集成4.1 构建技能注册中心有了一个个独立的技能我们需要一个中心化的地方来管理它们。这就是SkillRegistry技能注册中心的职责。class SkillRegistry: 技能注册中心单例模式管理所有可用技能 _instance None _skills {} # 技能名称 - 技能类 的映射 def __new__(cls): if cls._instance is None: cls._instance super(SkillRegistry, cls).__new__(cls) return cls._instance classmethod def register(cls, skill_class): 注册一个技能类 if not issubclass(skill_class, BaseSkill): raise TypeError(f只能注册BaseSkill的子类当前类型{type(skill_class)}) cls._skills[skill_class.name] skill_class print(f[SkillRegistry] 技能 {skill_class.name} 已注册。) return skill_class # 支持装饰器用法 classmethod def get_skill(cls, skill_name: str, **init_kwargs): 根据技能名称获取一个技能实例 skill_class cls._skills.get(skill_name) if not skill_class: raise KeyError(f技能 {skill_name} 未在注册中心找到。) return skill_class(**init_kwargs) # 实例化技能 classmethod def list_skills(cls): 列出所有已注册的技能信息 return [{name: name, description: cls._skills[name].description} for name in cls._skills] # 使用装饰器注册技能 SkillRegistry.register class WeatherSkill(BaseSkill): # ... 之前的实现 ... pass # 或者手动注册 SkillRegistry.register(WeatherSkill)4.2 在智能体框架中集成技能智能体Agent如何发现和使用这些技能呢通常智能体会在初始化时从注册中心加载一批它被授权使用的技能并形成自己的“工具集”。class MyAgent: def __init__(self, allowed_skills: List[str]): self.skill_registry SkillRegistry() self.skills {} # 技能名称 - 技能实例 self._load_skills(allowed_skills) def _load_skills(self, skill_names): 加载允许使用的技能 for name in skill_names: try: # 假设技能初始化可能需要一些配置这里简化处理 if name get_weather: skill_instance self.skill_registry.get_skill(name, default_api_keyYOUR_DEFAULT_KEY) elif name retrieval_augmented_generation: # 复杂技能需要外部依赖在Agent初始化时注入 retriever VectorDBRetriever(endpoint...) llm OpenAIClient(api_key...) skill_instance self.skill_registry.get_skill(name, retrieverretriever, llm_clientllm) else: skill_instance self.skill_registry.get_skill(name) self.skills[name] skill_instance except Exception as e: print(f警告加载技能 {name} 失败: {e}) def get_available_tools_description(self): 生成供LLM如GPT理解的可调用工具描述列表 tools [] for name, skill in self.skills.items(): schema skill.get_schema() tools.append({ type: function, function: { name: schema[name], description: schema[description], parameters: schema[parameters] } }) return tools def execute_skill(self, skill_name: str, input_parameters: Dict) - Dict: 执行指定的技能 if skill_name not in self.skills: return {success: False, error: f技能 {skill_name} 未加载或不存在。} skill self.skills[skill_name] # 将字典参数转换为技能对应的输入模型实例 input_model skill.input_model if hasattr(skill, input_model) else SkillInput try: validated_input input_model(**input_parameters) except Exception as e: return {success: False, error: f输入参数验证失败: {e}} # 调用技能执行 result skill.execute(validated_input) return result.dict() # 将Pydantic模型转回字典这样智能体就具备了调用技能的能力。当LLM大语言模型决定要使用某个技能时它只需要输出一个结构化的调用请求例如{action: get_weather, args: {city: Shanghai}}智能体的执行引擎就会调用execute_skill方法并将结果返回给LLM进行后续处理。5. 技能开发的最佳实践与避坑指南在实际开发和贡献类似agent-skills的项目时我积累了一些经验和教训这里分享几个关键点5.1 技能设计的“三要三不要”三要要无状态Stateless技能的执行结果应只依赖于输入参数不应依赖或修改类内部的可变状态除非是缓存等明确设计。这保证了技能的幂等性和线程安全性。每次调用都像是第一次调用。要防御性编程对输入进行严格的校验和清理对第三方API调用设置超时和重试机制对可能失败的操作做好异常捕获和降级处理。一个崩溃的技能可能导致整个智能体瘫痪。要详细记录在关键步骤开始、结束、出错记录结构化的日志包括技能名称、输入参数脱敏后、执行耗时、结果状态等。这对于调试和监控至关重要。三不要不要硬编码配置API密钥、服务端点等配置信息应通过初始化参数、环境变量或配置文件传入而不是写在代码里。不要假设运行环境技能可能被部署在不同的操作系统、Python版本或网络环境下。避免使用平台特定的路径如C:\、命令或库。如果需要应在文档中明确声明依赖和前提条件。不要过度封装如果一个技能内部逻辑过于复杂超过了“单一职责”的范畴应考虑将其拆分为多个更细粒度的技能或者提供一个“组合技能”Orchestrator Skill来协调它们。保持技能的简洁和专注。5.2 性能优化与缓存策略对于一些耗时的技能如调用慢速API、处理大文件引入缓存可以极大提升智能体的响应速度。from functools import lru_cache import hashlib import json class CachedWeatherSkill(WeatherSkill): 带缓存的天气技能 lru_cache(maxsize128) def _cached_api_call(self, params_hash: str) - Dict: 实际的API调用结果被LRU缓存 # 这里模拟去掉了缓存逻辑实际应调用父类或直接请求API # 注意这个方法不应该被直接调用由execute方法统一路由 return super()._call_external_api(params_hash) # 假设父类有这个方法 def execute(self, input_data: WeatherInput) - SkillOutput: # 生成缓存键将输入参数序列化后哈希确保相同输入得到相同键 params_dict input_data.dict(exclude{api_key}) # 排除敏感信息 # 可以加入时间维度例如只缓存最近1小时的数据 # from datetime import datetime # params_dict[_cache_hour] datetime.now().hour params_json json.dumps(params_dict, sort_keysTrue) # 排序保证键顺序一致 cache_key hashlib.md5(params_json.encode()).hexdigest() try: # 尝试从缓存获取 cached_result self._cached_api_call(cache_key) if cached_result: return SkillOutput(successTrue, datacached_result) except KeyError: # 缓存未命中执行正常流程 pass # 调用父类原执行逻辑这里需要适当调整父类结构将核心API调用部分抽离 # 为简化示例我们直接调用一个假设的方法 raw_data self._fetch_weather_data(input_data) # 处理并缓存结果 standardized_data self._standardize_data(raw_data) # 更新缓存 (在真实LRU中通过调用返回结果自动缓存) # 这里需要根据实际的缓存机制调整 return SkillOutput(successTrue, datastandardized_data)缓存注意事项缓存键设计要包含所有影响结果的输入参数并排除敏感或临时数据如request_id。缓存失效对于时效性强的数据如天气、股价必须设置合理的TTL生存时间。可以在缓存键中加入时间戳如当前小时数实现自动过期。缓存粒度缓存整个技能输出结果是最简单的但有时缓存中间结果如API原始响应可能更灵活。分布式环境如果智能体部署在多实例环境中需要使用分布式缓存如Redis替代内存缓存如lru_cache。5.3 测试技能单元测试与集成测试为技能编写全面的测试是保证其可靠性的基石。测试应覆盖单元测试测试技能的纯函数逻辑如输入验证、输出格式化、错误处理分支。使用Mock对象模拟外部依赖如requests.get。集成测试在接近真实的环境下测试技能例如使用测试用的API密钥调用真实的天气服务但需注意频率限制和成本或者连接测试数据库。性能测试对于高频或耗时技能测试其响应时间和资源消耗。# 使用pytest进行单元测试示例 import pytest from unittest.mock import Mock, patch from your_skill_module import WeatherSkill, WeatherInput def test_weather_skill_missing_api_key(): 测试缺少API Key时的错误处理 skill WeatherSkill() input_data WeatherInput(cityBeijing, api_keyNone) # 假设api_key允许为None result skill.execute(input_data) assert result.success is False assert API Key in result.error_message patch(your_skill_module.requests.get) def test_weather_skill_success(mock_get): 模拟成功调用API # 1. 准备模拟响应 mock_response Mock() mock_response.status_code 200 mock_response.json.return_value { name: Beijing, main: {temp: 22.5, feels_like: 21.0, humidity: 65}, weather: [{description: clear sky}], wind: {speed: 3.1} } mock_get.return_value mock_response # 2. 执行技能 skill WeatherSkill() input_data WeatherInput(cityBeijing, api_keytest_key) result skill.execute(input_data) # 3. 验证结果 assert result.success is True assert result.data[city] Beijing assert result.data[temperature] 22.5 assert result.data[description] clear sky # 验证requests.get被以正确的参数调用了一次 mock_get.assert_called_once() call_args mock_get.call_args assert call_args[1][params][q] Beijing6. 技能库的生态建设与扩展方向一个成功的技能库项目远不止是代码的集合。围绕agent-skills这样的项目可以构建一个完整的生态。6.1 技能发现与文档自动化手动维护一个技能列表很快就会过时。理想情况下技能的元数据名称、描述、参数模式应该能够被自动发现和索引。这可以通过在技能类上使用装饰器或元类或者定期扫描代码库中的BaseSkill子类来实现。然后自动生成一个交互式的技能文档网站或API目录供开发者查询。6.2 技能商店与贡献指南鼓励社区贡献是项目壮大的关键。需要建立清晰的技能贡献指南包括代码规范要求使用类型注解、遵循PEP 8、编写完整的docstring。测试要求贡献的技能必须附带单元测试且覆盖率达标。依赖管理明确声明第三方依赖并尽量保持轻量。如果依赖很重需要考虑是否必要或提供“轻量版”技能。安全审查对涉及网络、文件、命令执行的技能进行严格的安全审查防止命令注入、路径遍历等漏洞。版本管理技能应遵循语义化版本控制当接口发生破坏性变更时需要升级主版本号。可以建立一个“技能商店”社区贡献的技能经过审核后可以被合并到主仓库或作为一个独立的插件包发布。6.3 与主流智能体框架的深度集成为了让agent-skills发挥最大价值需要为其开发与主流框架如LangChain、LlamaIndex、AutoGen的适配器。例如为LangChain提供一个SkillTool包装器from langchain.tools import BaseTool from typing import Type from pydantic import BaseModel class SkillLangChainTool(BaseTool): 将agent-skills中的技能包装成LangChain Tool skill_instance: BaseSkill # 持有技能实例 def _run(self, *args, **kwargs): # 将LangChain Tool的调用转发给技能实例 # 需要处理参数转换 input_data self.skill_instance.input_model(**kwargs) result self.skill_instance.execute(input_data) if not result.success: raise ValueError(fSkill execution failed: {result.error_message}) return str(result.data) # 或者更结构化的返回 async def _arun(self, *args, **kwargs): # 异步支持 # ... 实现异步调用 ... pass # 使用示例 weather_skill SkillRegistry.get_skill(get_weather, default_api_keyxxx) langchain_tool SkillLangChainTool(skill_instanceweather_skill) # 现在可以将 langchain_tool 加入LangChain Agent的tools列表这样的适配器能极大地降低开发者的使用门槛让他们能在自己熟悉的框架内无缝使用技能库中的强大能力。6.4 技能的组合与编排单个技能的能力是有限的真正的威力来自于技能的编排。项目可以提供高阶的“编排技能”例如顺序执行技能定义一个技能流水线前一个技能的输出作为后一个技能的输入。条件分支技能根据某个技能的执行结果决定下一步调用哪个技能。并行执行技能同时执行多个独立技能并聚合结果。循环执行技能在满足条件时重复执行某个技能。这相当于在技能之上提供了一个轻量级的工作流引擎使得构建复杂的智能体行为成为可能。7. 实战从零开始贡献一个“网页内容总结”技能最后我们以一个完整的实战案例演示如何遵循agent-skills项目的规范贡献一个新技能。假设我们要实现一个summarize_webpage技能。第一步明确技能规格功能给定一个URL抓取网页主要内容并生成简洁的文本摘要。输入URL字符串可选参数摘要长度单词数。输出摘要文本以及可能的原文标题、抓取状态。依赖requests(网络请求)beautifulsoup4(HTML解析)trafilatura或newspaper3k(正文提取)可选sumy或transformers(摘要算法)。第二步实现技能类import requests from bs4 import BeautifulSoup from typing import Optional from pydantic import HttpUrl, Field import trafilatura # 一个优秀的网页正文提取库 from .base_skill import BaseSkill, SkillInput, SkillOutput class WebpageSummaryInput(SkillInput): 网页总结技能的输入 url: HttpUrl # Pydantic会自动验证URL格式 summary_length: Optional[int] Field(200, ge50, le1000, description摘要的目标长度单词数) class SummarizeWebpageSkill(BaseSkill): name summarize_webpage description 抓取指定URL的网页内容并生成简洁的文本摘要。 version 1.0.0 required_permissions [network_access] def __init__(self, user_agent: Optional[str] None): self.session requests.Session() if user_agent: self.session.headers.update({User-Agent: user_agent}) else: self.session.headers.update({User-Agent: Mozilla/5.0 (compatible; AgentSkillsBot/1.0)}) def execute(self, input_data: WebpageSummaryInput) - SkillOutput: try: # 1. 抓取网页 resp self.session.get(str(input_data.url), timeout15) resp.raise_for_status() html_content resp.text # 2. 提取正文 (使用trafilatura它比BeautifulSoup更擅长去噪) extracted_text trafilatura.extract(html_content, include_commentsFalse, include_tablesFalse) if not extracted_text: # 回退方案使用BeautifulSoup获取所有文本 soup BeautifulSoup(html_content, html.parser) for script_or_style in soup([script, style, nav, footer, header]): script_or_style.decompose() extracted_text soup.get_text(separator , stripTrue) if not extracted_text or len(extracted_text.strip()) 50: return SkillOutput( successFalse, error_message无法从该网页提取到足够长度的正文内容。 ) # 3. 生成摘要 (这里使用简单的提取式摘要作为示例生产环境可用抽象式) summary self._generate_summary(extracted_text, input_data.summary_length) # 4. 提取标题 soup BeautifulSoup(html_content, html.parser) title soup.title.string if soup.title else 无标题 return SkillOutput( successTrue, data{ title: title, summary: summary, url: str(input_data.url), original_length: len(extracted_text.split()), summary_length: len(summary.split()) } ) except requests.exceptions.RequestException as e: return SkillOutput(successFalse, error_messagef网页抓取失败: {str(e)}) except Exception as e: return SkillOutput(successFalse, error_messagef摘要生成过程出错: {str(e)}) finally: self.session.close() def _generate_summary(self, text: str, target_length: int) - str: 简单的提取式摘要生成器实际项目应使用更先进的算法 # 这是一个非常基础的实现取前N个句子。 sentences text.replace(。, .).replace(, !).replace(, ?).split(.) sentences [s.strip() for s in sentences if s.strip()] word_count 0 selected_sentences [] for sent in sentences: words_in_sent len(sent.split()) if word_count words_in_sent target_length and selected_sentences: break selected_sentences.append(sent) word_count words_in_sent summary . .join(selected_sentences) . # 如果摘要还是太长直接截断这是最后的手段 if len(summary.split()) target_length * 1.2: words summary.split()[:target_length] summary .join(words) ... return summary第三步编写单元测试# test_summarize_webpage.py import pytest from unittest.mock import Mock, patch from your_skill_module import SummarizeWebpageSkill, WebpageSummaryInput patch(your_skill_module.trafilatura.extract) patch(your_skill_module.requests.Session.get) def test_summarize_webpage_success(mock_get, mock_extract): mock_response Mock() mock_response.status_code 200 mock_response.text htmltitleTest Page/titlebodypThis is sentence one. This is sentence two. This is sentence three./p/body/html mock_get.return_value mock_response # 模拟trafilatura提取到正文 mock_extract.return_value This is sentence one. This is sentence two. This is sentence three. skill SummarizeWebpageSkill() input_data WebpageSummaryInput(urlhttps://example.com) result skill.execute(input_data) assert result.success is True assert Test Page in result.data[title] assert sentence one in result.data[summary] mock_get.assert_called_once_with(https://example.com, timeout15)第四步更新依赖和文档在项目的requirements.txt或pyproject.toml中添加新增的依赖trafilatura,beautifulsoup4并在技能的docstring或单独的文档中详细说明技能的使用方法、参数含义和局限性。通过以上步骤你就完成了一个符合规范、功能完整、经过测试的新技能可以提交PR到agent-skills项目了。这个过程清晰地展示了如何将一个想法转化为一个可复用、可维护、可测试的智能体技能模块。