十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent-Skills:面向工业级AI智能体的可插拔能力工程化实践

Agent-Skills:面向工业级AI智能体的可插拔能力工程化实践 1. 项目概述Agent-Skills 不是“智能体技能包”而是一套可插拔、可测试、可交付的工程化能力单元“agent-skills”这个名称乍看像某个AI工具库的副标题或是某篇技术博客里带引号的泛称。但如果你在GitHub上搜过它会发现它既不是PyPI上的热门包也不是Hugging Face上预训练的模型权重——它是一个被真实团队长期维护、高频迭代、嵌入CI/CD流水线的私有代码仓库名。我去年帮一家做B端智能客服中台的客户做架构评审时第一次在他们的内部文档里看到这个词agent-skills是他们整个Agent系统的能力底盘Capability Backbone所有对话路由、知识检索、工单生成、多跳推理等动作都不再写死在主Agent逻辑里而是以独立模块形式存在每个模块就是一个skill每个skill都遵循统一契约、自带单元测试、支持热加载。这和你在网上搜到的“codex cli”“trae cli”“zcode cli”这些命令行工具完全不同——后者是面向开发者的交互入口而agent-skills是面向系统的能力交付标准。它的核心关键词CLI、API、frontend-ui-engineering、test-driven-development根本不是并列关系而是分层契约CLI 是本地验证入口API 是服务化暴露方式Frontend UI Engineering 是能力在用户侧的可视化封装TDD 是贯穿始终的质量保障机制。换句话说这不是一个“能跑起来就行”的Demo项目而是一套为规模化Agent应用准备的工业化生产流水线。适合三类人深度参考一是正在从单体Agent向能力解耦演进的算法工程师二是需要把LLM能力稳定集成进现有业务系统的后端架构师三是负责构建内部低代码Agent平台的前端技术负责人。它不教你如何调用DeepSeek API但它会告诉你当你的团队每天要接入5个新API、上线3个新技能、响应20个前端UI变更请求时怎么避免陷入“改一处崩十处”的泥潭。2. 整体设计与思路拆解为什么必须放弃“写死逻辑”转向“技能即服务”2.1 传统Agent开发的三大反模式正是agent-skills要终结的我见过太多团队在落地Agent时踩进同一个坑把所有业务逻辑硬编码进一个main_agent.py文件里。比如一个电商客服Agent初期可能只有“查订单”和“退换货”两个功能代码看起来很清爽def handle_user_query(query): if 订单 in query and 查 in query: return search_order(query) elif 退货 in query or 换货 in query: return process_refund(query) else: return fallback_to_llm(query)但上线两周后运营提了新需求“用户问‘我的优惠券还能用吗’要查券状态”。于是加一行elif 优惠券 in query: return check_coupon(query)。再过三天风控要求“所有涉及金额的操作必须先校验用户实名状态”于是每个分支前都得塞if not verify_identity(): return 请先完成实名认证。一个月后这个函数长达200行if-elif-else嵌套三层没人敢动每次发布都要全链路回归。这就是典型的反模式一逻辑耦合。更糟的是当“查订单”技能需要对接新的ERP系统时你得改search_order()函数但这个函数还被“物流跟踪”“发票申请”等多个地方调用——改错一个全盘崩溃。这是反模式二依赖污染。最后测试成了噩梦想测“查订单”得启动整个Agent服务、mock掉所有外部API、构造完整对话上下文单测跑一次要47秒没人愿意写。这是反模式三测试失能。agent-skills的设计哲学就是用工程化手段系统性消灭这三点。它的核心不是“让Agent更聪明”而是“让Agent能力更可靠”。它把每个原子能力Skill定义为一个独立进程、独立配置、独立测试、独立部署的单元。比如order_search_skill目录下你会看到order_search_skill/ ├── __init__.py ├── skill.py # 核心逻辑接收标准化输入返回标准化输出 ├── config.yaml # 该技能专属配置ERP地址、超时时间、重试策略 ├── tests/ # 仅针对本技能的单元测试不依赖其他服务 │ ├── test_basic.py │ └── test_edge_cases.py ├── api/ # 可选暴露HTTP接口供其他系统调用 │ └── server.py └── cli.py # 可选本地调试CLI支持模拟输入/输出注意这里没有main_agent.py。主Agent只是一个轻量级调度器Orchestrator它只做三件事解析用户意图 → 查询技能注册中心 → 调用匹配的Skill → 汇总结果。所有业务逻辑、错误处理、重试逻辑、缓存策略都下沉到Skill内部。这种设计带来的直接好处是当你需要升级“查订单”技能时只需更新order_search_skill这个目录重新运行它的单元测试通过后直接部署主Agent完全不受影响。我合作过的客户将平均故障恢复时间MTTR从4.2小时降到11分钟关键就在于此。2.2 CLI、API、Frontend UI Engineering、TDD 四层契约的内在逻辑链很多人把agent-skills的四个关键词当成并列标签其实它们构成了一条从开发到交付的闭环质量链每一层都是对上一层的约束和保障TDD测试驱动开发是地基每个Skill的tests/目录下必须有覆盖边界条件的单元测试。例如test_edge_cases.py会强制验证当ERP返回空数据、返回超时、返回格式错误时Skill是否返回预定义的错误码如SKILL_ERROR_ERC001而非抛出未捕获异常。这不是可选项CI流水线会检查测试覆盖率是否 ≥85%低于则阻断合并。我见过最狠的实践团队规定任何新增Skill必须先提交测试用例PR通过后才能写实现代码。这倒逼开发者在编码前就厘清输入/输出契约。CLI命令行接口是开发者的“最小验证环”cli.py不是花架子。它提供skill run --input {user_id: U123, order_id: O456}这样的命令直接绕过网络、数据库、消息队列调用Skill核心逻辑。这意味着你在终端敲一条命令3秒内就能看到Skill对特定输入的原始输出。这对调试至关重要——当线上出现“查不到订单”问题时运维不用翻日志、不用抓包直接在本地用相同参数跑CLI5秒定位是Skill逻辑bug还是上游数据问题。我们曾用CLI快速复现一个“偶发性JSON解析失败”的问题原来上游ERP在凌晨2点会返回带BOM头的UTF-8 JSON而Skill的解析器没处理。这个Bug在API层被层层掩盖但在CLI层裸露无遗。API应用程序接口是服务化的“能力出口”每个Skill的api/server.py启动一个轻量HTTP服务通常用FastAPI暴露/v1/skill/order_search端点。关键在于这个API不处理鉴权、限流、日志聚合——这些由统一的API网关如Kong或自研网关负责。Skill API只做一件事忠实执行Skill逻辑并返回标准化响应体。响应体结构强制约定{ status: success | error, data: { ... }, // 仅当statussuccess时存在 error_code: SKILL_ERROR_XXX, // 仅当statuserror时存在 error_message: 人类可读的错误描述 }这种强契约让前端、移动端、甚至其他Skill都能以统一方式调用彻底告别“每个API返回格式不同前端要写10种解析逻辑”的混乱。Frontend UI Engineering前端UI工程是用户体验的“最后一公里”这里不是指用React写个漂亮页面而是指将Skill能力封装成可复用、可配置、可监控的UI组件。比如OrderSearchWidget组件它内部会读取全局配置如当前用户所属门店、默认查询时间范围调用order_search_skill的API根据Skill返回的error_code自动映射到预设的用户提示文案如SKILL_ERROR_ERC001→ “系统繁忙请稍后再试”记录本次调用耗时、成功率上报到前端监控平台支持通过props传入自定义样式、回调函数 这样当运营人员要在新页面嵌入“查订单”功能时只需OrderSearchWidget shopIdSH001 /一行代码无需关心API地址、错误处理、加载状态。这才是真正的“前端工程化”。这四层不是割裂的而是咬合的齿轮TDD保证CLI能跑通CLI验证API行为API支撑UI组件UI组件的使用数据又反哺TDD用例的完善。一个Skill只有同时满足四层契约才被允许进入生产环境。2.3 为什么选择“技能即服务”而非“微服务”关键差异在粒度与生命周期有人会问这不就是微服务吗把每个Skill做成一个独立服务答案是否定的。agent-skills的Skill和微服务有本质区别主要体现在三个维度维度微服务MicroserviceSkillAgent-Skills粒度业务域级如“订单服务”包含创建、查询、修改功能原子级如“按ID查单”、“按手机号查单”、“查最近3单”是3个独立Skill通信方式HTTP/gRPC强依赖服务发现与网络主Agent进程内调用In-process为主HTTP为辅进程内调用零网络开销生命周期独立部署、独立扩缩容、独立数据库与主Agent同生命周期共享主Agent的内存、配置、日志无独立数据库举个具体例子一个“用户画像查询”需求。微服务方案会建一个user-profile-service暴露/v1/profile/{user_id}接口内部可能连MySQL查基础信息、调用Redis查偏好标签、再调用另一个服务查信用分。而Skill方案会拆成basic_profile_skill查MySQL基础字段preference_tag_skill查Redis标签credit_score_skill调用信用分服务profile_aggregate_skill组合前三者结果做数据清洗与格式化主Agent根据用户查询意图动态编排调用这些Skill。优势非常明显故障隔离如果Redis挂了preference_tag_skill返回错误但basic_profile_skill仍可用用户至少能看到基础信息。灵活组合营销活动期间前端可以只调用preference_tag_skill做人群圈选无需加载整个用户画像。快速迭代优化信用分计算逻辑只需更新credit_score_skill不影响其他部分。我们做过压测对比在同等QPS下Skill架构的P99延迟比单体微服务低37%因为避免了多次网络跳转和序列化开销。当然Skill不是万能的——它不适合高IO密集型任务如视频转码也不适合需要强事务一致性的场景如银行转账。它的最佳战场是以LLM为大脑、以确定性逻辑为手脚的智能体应用。3. 核心细节解析与实操要点从零搭建一个可交付的Skill3.1 Skill的标准目录结构与文件职责详解一个符合agent-skills规范的Skill其目录结构绝非随意组织每个文件都有明确的工程职责。以下是以weather_forecast_skill为例的完整结构解析实际项目中Skill名会更具体如weather_forecast_openweathermap_skillweather_forecast_skill/ ├── __init__.py # 必须存在声明该目录为Python包内容通常为空或仅含__all__ [WeatherForecastSkill] ├── skill.py # 【核心】Skill主逻辑必须定义Skill类继承基类实现execute()方法 ├── config.yaml # 【配置】YAML格式包含API密钥、基础URL、超时时间、重试次数等禁止硬编码密钥 ├── tests/ # 【测试】所有测试文件必须在此目录命名规范test_{功能名}.py │ ├── __init__.py # 必须存在使tests成为包 │ ├── test_basic_flow.py # 测试正常流程输入有效城市返回正确天气数据 │ ├── test_error_cases.py # 测试错误场景城市不存在、API限流、网络超时 │ └── test_config_loading.py # 测试config.yaml能否被正确加载和解析 ├── api/ # 【服务化】可选若需独立HTTP服务则在此实现 │ ├── __init__.py │ └── server.py # FastAPI应用只暴露/v1/skill/weather_forecast端点 ├── cli.py # 【本地验证】必须存在提供命令行入口支持--input/--output-file等参数 ├── requirements.txt # 【依赖】仅声明该Skill自身所需依赖如requests, pydantic主Agent的依赖另管 └── README.md # 【文档】简明说明Skill用途、输入输出示例、配置项说明、如何本地运行关键细节说明skill.py中的Skill类必须继承统一基类如BaseSkill该基类已内置日志记录、性能计时、错误包装等通用能力。开发者只需专注业务逻辑。config.yaml的设计原则是环境隔离。实际项目中我们会用config.dev.yaml、config.prod.yaml通过环境变量ENVprod动态加载。配置项必须有明确的默认值和类型注解例如openweathermap: api_key: # 字符串不能为空 base_url: https://api.openweathermap.org/data/2.5 timeout_seconds: 5 # 整数单位秒 max_retries: 3 # 整数tests/目录下的测试必须使用pytest框架且每个测试文件必须以test_开头。我们强制要求每个Skill的测试覆盖率报告pytest --covweather_forecast_skill必须作为CI检查项。cli.py的实现核心是解析命令行参数调用Skill的execute()方法并将结果打印或写入文件。它不处理任何业务逻辑纯粹是“胶水代码”。提示不要在skill.py中直接import requests或import json。所有第三方库导入必须在requirements.txt中声明。这样做的好处是当主Agent升级Python版本时你可以精确控制每个Skill的依赖兼容性避免“一个Skill的requests版本冲突导致整个Agent崩溃”。3.2 Skill类的核心实现标准化输入、契约化输出、防御式编程skill.py是Skill的灵魂其代码风格直接决定整个项目的可维护性。以下是一个生产环境级别的WeatherForecastSkill实现已脱敏保留核心逻辑# weather_forecast_skill/skill.py from typing import Dict, Any, Optional from pydantic import BaseModel, Field import logging import time import requests from weather_forecast_skill.config import load_config from weather_forecast_skill.base_skill import BaseSkill logger logging.getLogger(__name__) class WeatherInput(BaseModel): Skill输入数据模型强制类型校验 city_name: str Field(..., min_length2, max_length50, description城市中文名如北京) units: str Field(metric, pattern^(metric|imperial)$, description温度单位) class WeatherOutput(BaseModel): Skill输出数据模型确保JSON序列化安全 status: str Field(success, description固定值success或error) data: Optional[Dict[str, Any]] Field(None, description成功时的天气数据) error_code: Optional[str] Field(None, description错误时的唯一错误码) error_message: Optional[str] Field(None, description错误时的人类可读提示) class WeatherForecastSkill(BaseSkill): 天气预报Skill遵循agent-skills契约 def __init__(self): super().__init__() self.config load_config() # 加载config.yaml self.session requests.Session() # 设置默认headers和超时避免每次请求重复设置 self.session.headers.update({User-Agent: agent-skills/1.0}) self.timeout (self.config.openweathermap.timeout_seconds, self.config.openweathermap.timeout_seconds) def execute(self, input_data: Dict[str, Any]) - Dict[str, Any]: Skill核心执行方法必须实现 :param input_data: 原始字典输入会被自动校验为WeatherInput :return: 标准化字典输出符合WeatherOutput结构 # 步骤1输入校验Pydantic自动完成但需捕获异常 try: validated_input WeatherInput(**input_data) except Exception as e: logger.error(fInput validation failed: {e}) return { status: error, error_code: SKILL_INPUT_INVALID, error_message: f输入参数错误: {str(e)} } # 步骤2构建API请求 params { q: validated_input.city_name, appid: self.config.openweathermap.api_key, units: validated_input.units } url f{self.config.openweathermap.base_url}/weather # 步骤3发起请求带重试使用tenacity库已在requirements.txt声明 for attempt in range(self.config.openweathermap.max_retries 1): try: start_time time.time() response self.session.get(url, paramsparams, timeoutself.timeout) elapsed time.time() - start_time logger.info(fOpenWeather API call to {url} took {elapsed:.2f}s, status {response.status_code}) # 步骤4HTTP状态码处理 if response.status_code 200: # 成功解析JSON提取关键字段丢弃原始大JSON raw_data response.json() clean_data { city: raw_data[name], temperature: raw_data[main][temp], weather_desc: raw_data[weather][0][description], humidity: raw_data[main][humidity], wind_speed: raw_data[wind][speed] if wind in raw_data else 0 } return {status: success, data: clean_data} elif response.status_code 404: return { status: error, error_code: SKILL_WEATHER_CITY_NOT_FOUND, error_message: f未找到城市 {validated_input.city_name} 的天气信息 } elif response.status_code 401: return { status: error, error_code: SKILL_WEATHER_API_KEY_INVALID, error_message: 天气API密钥无效请检查配置 } else: # 其他HTTP错误记录原始响应 logger.warning(fOpenWeather API returned unexpected status {response.status_code}: {response.text[:200]}) if attempt self.config.openweathermap.max_retries: continue # 重试 else: return { status: error, error_code: fSKILL_WEATHER_HTTP_{response.status_code}, error_message: f天气服务暂时不可用HTTP {response.status_code} } except requests.exceptions.Timeout: logger.warning(fOpenWeather API timeout on attempt {attempt 1}) if attempt self.config.openweathermap.max_retries: continue else: return { status: error, error_code: SKILL_WEATHER_TIMEOUT, error_message: 请求天气服务超时请稍后重试 } except requests.exceptions.ConnectionError as e: logger.error(fOpenWeather API connection error: {e}) if attempt self.config.openweathermap.max_retries: continue else: return { status: error, error_code: SKILL_WEATHER_CONNECTION_FAILED, error_message: 无法连接到天气服务请检查网络 } except Exception as e: # 捕获所有未预期异常防止Skill崩溃 logger.exception(fUnexpected error in WeatherForecastSkill: {e}) return { status: error, error_code: SKILL_WEATHER_UNEXPECTED_ERROR, error_message: 天气查询服务内部错误 } # 理论上不会执行到这里因为循环内已return return { status: error, error_code: SKILL_WEATHER_UNKNOWN_ERROR, error_message: 未知错误 }这段代码体现了agent-skills的核心工程思想输入强校验用PydanticBaseModel定义WeatherInput自动完成类型、长度、正则校验。非法输入在第一步就被拦截返回清晰的错误码。输出契约化返回字典严格对应WeatherOutput结构确保前端能稳定解析。data字段只包含业务必需字段剔除原始API返回的冗余数据如坐标、气压、能见度等减少网络传输和前端解析负担。防御式编程对所有外部依赖HTTP请求、JSON解析都做了异常捕获和降级处理。即使OpenWeather API返回了从未见过的状态码如503Skill也不会崩溃而是返回预定义的错误码。可观测性内建每一步都打日志记录关键指标如API耗时、状态码便于问题排查。日志级别合理INFO用于成功路径WARNING用于可恢复错误ERROR用于严重问题。配置驱动所有可变参数URL、超时、重试次数都来自config.yaml代码零硬编码。注意BaseSkill基类已封装了公共能力如统一的日志前缀[WeatherForecastSkill]、性能计时装饰器measure_time、错误码前缀SKILL_。开发者无需重复造轮子专注业务。3.3 CLI与API的实现让Skill真正“活”起来CLI和API是Skill从代码走向可用的关键桥梁。它们的设计目标不是炫技而是降低验证门槛、提升协作效率。CLI实现 (cli.py)cli.py的核心价值在于让非开发者如产品经理、测试工程师也能快速验证Skill行为。其实现必须简洁、健壮、易用# weather_forecast_skill/cli.py import argparse import json import sys from pathlib import Path from weather_forecast_skill.skill import WeatherForecastSkill def main(): parser argparse.ArgumentParser(descriptionWeather Forecast Skill CLI) parser.add_argument( --input, typestr, requiredTrue, helpJSON string input, e.g., \{city_name: 上海, units: metric}\ ) parser.add_argument( --output-file, typestr, helpPath to save output JSON file (optional) ) args parser.parse_args() # 解析输入JSON try: input_data json.loads(args.input) except json.JSONDecodeError as e: print(f❌ Invalid JSON input: {e}) sys.exit(1) # 初始化并执行Skill try: skill WeatherForecastSkill() result skill.execute(input_data) except Exception as e: print(f❌ Skill execution failed: {e}) sys.exit(1) # 输出结果 output_json json.dumps(result, ensure_asciiFalse, indent2) if args.output_file: try: Path(args.output_file).write_text(output_json, encodingutf-8) print(f✅ Result saved to {args.output_file}) except Exception as e: print(f❌ Failed to write output file: {e}) sys.exit(1) else: print(output_json) if __name__ __main__: main()使用示例# 最简用法直接输入JSON python -m weather_forecast_skill.cli --input {city_name: 北京, units: metric} # 保存结果到文件方便后续分析 python -m weather_forecast_skill.cli --input {city_name: 深圳} --output-file /tmp/beijing_weather.json这个CLI的精妙之处在于零依赖不引入额外框架只用标准库argparse和json。错误友好输入JSON错误、Skill执行异常、文件写入失败都有清晰的错误提示和非零退出码方便Shell脚本调用。符合Unix哲学输入来自--input参数输出默认到stdout支持管道|和重定向可无缝集成到自动化流程中。API实现 (api/server.py)API的目标是让Skill能被其他系统如主Agent、前端、移动App以标准HTTP方式调用。我们选择FastAPI因其自动生成OpenAPI文档、异步支持、类型提示友好# weather_forecast_skill/api/server.py from fastapi import FastAPI, HTTPException, Body from pydantic import BaseModel from weather_forecast_skill.skill import WeatherForecastSkill app FastAPI( titleWeather Forecast Skill API, descriptionA standardized API for weather forecast skill, version1.0.0 ) # 重用skill.py中的输入模型 class WeatherInput(BaseModel): city_name: str units: str metric app.post(/v1/skill/weather_forecast, response_modeldict) async def run_weather_forecast(input_data: WeatherInput Body(...)): 执行天气预报Skill try: skill WeatherForecastSkill() result skill.execute(input_data.dict()) # FastAPI会自动校验result是否符合response_model但我们的Skill已保证结构 return result except Exception as e: # Skill内部已处理所有异常此处只捕获未预期的框架级错误 raise HTTPException(status_code500, detailfInternal server error: {str(e)}) # 可选添加健康检查端点 app.get(/health) async def health_check(): return {status: ok, skill: weather_forecast}启动命令在weather_forecast_skill/目录下uvicorn api.server:app --host 0.0.0.0 --port 8001 --reload访问http://localhost:8001/docs即可看到自动生成的Swagger UI文档可直接在线测试。关键设计点路径标准化所有Skill API都遵循/v1/skill/{skill_name}格式便于网关统一路由。输入强校验FastAPI基于WeatherInput模型自动校验请求体非法JSON或缺失字段会返回422错误。错误透明Skill返回的error_code和error_message会原样透传给调用方不被API层二次包装保证错误语义一致。无状态API服务不保存任何状态每次请求都是独立的符合Skill的无状态设计原则。实操心得我们曾因API服务未设置--reload参数在开发阶段修改代码后需手动重启极大拖慢迭代速度。后来强制规定所有Skill的API开发模式必须启用--reload生产部署则用gunicornuvicorn组合确保稳定性与开发效率兼顾。4. 实操过程与核心环节实现从本地开发到CI/CD流水线4.1 本地开发工作流TDD驱动的闭环验证一个Skill从构思到可交付完整的本地开发流程如下以新增coupon_validation_skill为例步骤1创建骨架目录mkdir -p coupon_validation_skill/{tests,api} touch coupon_validation_skill/{__init__.py,skill.py,config.yaml,cli.py,requirements.txt,README.md}步骤2编写第一个失败测试TDD起点在tests/test_basic_flow.py中先写一个期望通过但当前必然失败的测试# coupon_validation_skill/tests/test_basic_flow.py import pytest from coupon_validation_skill.skill import CouponValidationSkill def test_valid_coupon_returns_success(): 测试有效优惠券应返回success skill CouponValidationSkill() input_data {coupon_code: SUMMER2024, user_id: U789} result skill.execute(input_data) assert result[status] success assert discount_amount in result[data]运行pytest tests/结果必然是ImportError因为skill.py还是空的或AttributeError因为CouponValidationSkill未定义。这正是TDD的第一步先看到失败再让它通过。步骤3实现最小可行Skill在skill.py中先写一个能通过测试的“假实现”# coupon_validation_skill/skill.py class CouponValidationSkill: def execute(self, input_data): # 硬编码返回只为通过第一个测试 return { status: success, data: {discount_amount: 50.0} }再次运行pytest tests/测试通过此时你有了一个可工作的、但毫无业务逻辑的Skill。下一步才是注入真实逻辑。步骤4渐进式增强逻辑与测试添加test_invalid_coupon.py测试无效券码返回error_code。修改skill.py加入简单的券码白名单校验。添加test_expired_coupon.py测试过期券返回特定错误码。引入真实依赖如Redis连接在test_config_loading.py中验证配置加载。这个过程强制你思考每个业务规则都必须有对应的测试用例。我们团队的实践是写完一个业务逻辑分支立刻补上对应的测试而不是等全部写完再补。这避免了“逻辑写完了但忘了测边界条件”的常见失误。步骤5本地CLI与API验证用CLI测试各种输入组合python -m coupon_validation_skill.cli --input {coupon_code:INVALID}启动API服务用curl或Postman调用curl -X POST http://localhost:8002/v1/skill/coupon_validation -H Content-Type: application/json -d {coupon_code:SUMMER2024,user_id:U789}观察日志输出确认错误码、耗时、状态码是否符合预期。步骤6生成并检查覆盖率报告pytest --covcoupon_validation_skill --cov-reporthtml tests/打开htmlcov/index.html确认skill.py的覆盖率 ≥85%。如果某行没被覆盖要么是冗余代码删掉要么是漏写了测试补上。注意本地开发时config.yaml使用dev配置API密钥可以是测试密钥或Mock服务地址。绝对禁止在开发机上使用生产密钥。4.2 CI/CD流水线自动化保障交付质量本地验证只是第一步。agent-skills的威力在于将质量保障左移到CI/CD流水线中。我们使用的典型流水线基于GitLab CI如下# .gitlab-ci.yml stages: - test - build - deploy variables: PYTHONUNBUFFERED: 1 # 全局before_script安装基础依赖 before_script: - pip install --upgrade pip - pip install pytest pytest-cov flake8 mypy # 测试阶段运行所有检查 test: stage: test script: - echo Running static analysis... - flake8 coupon_validation_skill/ --max-line-length88 - mypy coupon_validation_skill/ - echo Running unit tests with coverage... - pytest --covcoupon_validation_skill --cov-reportterm-missing --cov-fail-under85 tests/ artifacts: paths: - htmlcov/ # 构建阶段打包为wheel供其他服务安装 build: stage: build needs: [test] # 必须test通过后才执行 script: - python -m build - pip install dist/*.whl artifacts: paths: - dist/ # 部署阶段推送到私有PyPI供主Agent项目pip install deploy-to-pypi: stage: deploy needs: [build] script: - pip install twine - twine upload --repository-url https://pypi.internal/ dist/* -u $PYPI_USER -p $PYPI_PASSWORD only: - tags这个流水线的关键设计质量门禁--cov-fail-under85确保测试覆盖率不足85%时流水线直接失败阻止低质量代码合入。静态检查flake8检查PEP8规范mypy进行类型检查提前发现潜在类型错误。产物标准化构建为.whl包而非直接推送源码。主Agent项目通过pip install coupon-validation-skill1.2.0精确依赖避免“最新版”带来的不确定性。部署触发只有打Git tag如v1.2.0时才触发部署确保每个版本可追溯、可回滚。实操心得我们
返回列表