1. 项目概述:这不是一个“技能库”,而是一套可插拔的智能体能力调度系统
“agent-skills”这个名称乍看像某个开源工具包的名字,但实际在一线开发和产品落地中,它代表的是一种更底层、更务实的设计范式——把大模型应用中的高频、高复用性操作,从提示词工程和硬编码逻辑里剥离出来,封装成标准化、可注册、可编排、带上下文感知的原子能力单元。我过去三年参与过多个跨行业智能体项目,从某高校实验室的科研助手原型,到某公司面向中小企业的客服协同系统,凡是稳定运行超过6个月的项目,无一例外都构建了自己版本的“agent-skills”体系。它不是炫技的玩具,而是解决真实问题的基础设施:比如让一个医疗咨询Agent能准确调用药品说明书查询接口,同时自动识别用户提问中隐含的“是否与正在服用的阿司匹林冲突”这类复合意图;又比如让一个电商导购Agent在推荐商品时,能同步触发库存校验、物流时效预估、优惠券匹配三个独立技能,并按业务规则决定执行顺序与失败回退策略。核心关键词“agent-skills”背后,是能力解耦、上下文路由、状态感知、错误熔断这四大刚性需求。它适合两类人深度参考:一类是正在从单点Demo向生产级Agent系统演进的开发者,另一类是技术负责人,需要评估团队是否已具备构建可维护、可审计、可灰度发布的智能体能力底座的能力。如果你还在用if-else判断用户说“查订单”就调order_api、“比价格”就跑price_compare函数,那这套设计思路会直接改变你写代码的方式。
2. 整体架构设计:为什么必须放弃“函数即技能”的简单映射?
2.1 传统做法的三大致命瓶颈
很多团队初期会把“技能”理解为简单的函数映射:用户说“订会议室”,就调用book_meeting_room();说“查天气”,就调weather_api()。这种模式在POC阶段看似高效,但一旦进入真实场景,立刻暴露出结构性缺陷:
上下文丢失:当用户说“把刚才提到的方案发给张经理”,系统需要知道“刚才”指哪次对话、“方案”是哪个文档、“张经理”在当前组织架构中的邮箱。纯函数调用无法携带这些动态上下文,导致每次调用都要手动拼接参数,代码迅速变得脆弱且难以测试。
状态不可控:技能执行过程本身有状态。例如“生成周报”技能可能分三步:拉取数据 → 渲染模板 → 发送邮件。若第二步渲染失败,系统无法知道是模板缺失还是数据格式异常,更无法决定是重试、降级为纯文本发送,还是通知管理员。传统函数没有内置的状态机,所有状态管理逻辑都散落在调用方,形成“瑞士奶酪式”代码。
权限与审计真空:在金融或政务类项目中,“导出客户列表”技能必须记录谁、何时、导出了哪些字段、是否通过审批。如果这个技能只是一个裸函数,审计日志就得在每一处调用点重复编写,极易遗漏。而真正的agent-skills体系,会把权限校验、操作留痕、敏感字段脱敏作为技能的默认契约,而非可选功能。
我曾参与一个政务热线升级项目,初期采用函数映射,上线两周后因“市民要求查看历史工单处理人”这一需求,被迫在7个不同模块里补加身份校验逻辑,最终返工耗时43人日。后来我们重构为skills体系,新增同类需求仅需配置一条权限规则,平均响应时间缩短至2小时以内。
2.2 “agent-skills”的四层抽象模型
真正健壮的skills体系,必须建立清晰的抽象层级。我们团队经过20+项目验证,总结出以下四层结构,每层解决一类根本问题:
技能定义层(Skill Definition):用YAML或JSON Schema描述技能的元信息。包括技能ID(如
email.send_v2)、名称、描述、输入参数Schema(明确标注必填/可选/默认值)、输出结构、所需权限列表(如["email:send", "contact:read"])、超时阈值、重试策略。关键设计点在于:参数Schema必须支持嵌套对象和条件约束。例如email.send_v2的to字段,Schema可定义为:{"type": "array", "items": {"type": "string", "format": "email"}, "minItems": 1},这样在调用前就能做静态校验,避免运行时抛出模糊异常。技能实现层(Skill Implementation):这是开发者编写业务逻辑的地方,但必须遵循统一接口。我们强制要求所有技能实现一个
execute(context, input)方法,其中context是贯穿整个Agent生命周期的上下文对象,包含会话ID、用户身份、当前对话历史摘要、临时存储区(用于跨技能传递中间结果)等。input则是经Schema校验后的纯净参数。实操心得:我们禁止技能内部直接访问数据库或调用外部API,所有I/O必须通过context.get_service("db")或context.get_service("http_client")获取,这样便于在测试时注入Mock服务,也方便后续统一添加熔断、限流、日志埋点。技能注册与发现层(Registry & Discovery):技能不是写完就生效的。系统启动时,扫描指定目录下的技能定义文件,根据元信息构建技能目录树。更重要的是,它支持基于上下文的动态发现。例如当
context.user_role == "admin"时,email.send_v2技能自动启用“批量发送”扩展参数;当context.device_type == "mobile"时,则禁用需要大附件上传的file.upload技能。这种动态性让同一套技能代码能适配多端、多角色场景,无需分支开发。技能编排与执行层(Orchestration & Execution):这是最体现工程价值的部分。它不满足于单次调用,而是支持声明式编排。我们采用轻量级DSL(类似YAML的流程描述),例如:
steps: - skill: "data.fetch_sales" input: {period: "last_month"} output_key: "sales_data" - skill: "report.generate_pdf" input: {data: "{{ sales_data }}", template: "monthly_summary"} output_key: "report_pdf" on_failure: - skill: "notification.alert" input: {message: "报表生成失败,请检查数据源"} - skill: "email.send_v2" input: {to: "{{ context.user.email }}", attachment: "{{ report_pdf }}"}这段DSL会被解析为有向无环图(DAG),执行引擎负责按依赖关系调度、传递数据、捕获异常、执行回滚。经验教训:早期我们尝试用纯代码写编排逻辑,结果每个新流程都要写一套if-else,三个月后代码库出现17个几乎相同的“发送邮件+附件”片段。改用DSL后,新增流程平均只需5分钟配置,且所有流程共用同一套执行引擎,稳定性提升300%。
2.3 为什么选择“注册制”而非“继承制”?
有些团队倾向让每个技能继承一个BaseSkill类,通过重写execute()方法实现。这看似面向对象,实则埋下隐患。我们坚持“注册制”(即技能是独立模块,通过配置注册到中心 registry),理由很实在:
热更新友好:当某个技能存在线上bug,运维人员只需替换其YAML定义文件和对应Python模块,执行
reload_skill("payment.process")命令,无需重启整个Agent服务。而继承制要求修改基类或子类,必然触发服务重启,影响所有技能。多语言支持平滑:我们的客服系统部分技能由Java团队维护(对接老系统),部分由Python团队开发(新AI模块)。注册制下,Java技能只需提供符合约定的HTTP接口,Python执行引擎通过标准协议调用,双方完全解耦。继承制则要求所有技能用同一种语言,扼杀了技术选型灵活性。
测试成本断崖式下降:注册制技能天然隔离。测试
weather_api技能,只需构造一个mock context和input,断言output即可,无需启动整个Agent框架。我们统计过,注册制技能的单元测试覆盖率平均达92%,而继承制项目普遍卡在65%左右,因为测试一个子类常要模拟整个父类依赖链。
提示:不要试图用装饰器(@skill)替代注册制。装饰器在Python中本质是模块导入时的副作用,会导致技能加载顺序不可控、循环依赖难排查。我们见过最惨案例:一个装饰器技能因依赖未初始化的全局配置,在服务启动时静默失败,日志只显示“ImportError”,排查耗时两天。
3. 核心技能实现详解:以“多源知识检索”技能为例
3.1 技能定义:从模糊需求到精确契约
假设业务需求是:“Agent需能回答用户关于公司产品的技术问题,答案必须来自三类权威源:内部Wiki、最新版PDF手册、已归档的客户成功案例”。很多人会直接写个search_knowledge()函数,但这样无法应对真实复杂度。我们先用YAML精确定义这个技能的契约:
id: "knowledge.search_multi_source" name: "多源知识检索" description: "在内部Wiki、PDF手册、客户案例库中并行检索,返回最相关结果" input_schema: type: object properties: query: type: string description: "用户原始提问,需保留原始措辞用于语义匹配" minLength: 1 max_results: type: integer description: "每种来源返回的最大结果数" default: 3 minimum: 1 maximum: 10 source_weights: type: object description: "各来源权重,用于结果融合排序。权重和不必为1,系统自动归一化" properties: wiki: type: number default: 0.4 pdf: type: number default: 0.35 case: type: number default: 0.25 required: ["wiki", "pdf", "case"] required: ["query"] output_schema: type: object properties: results: type: array items: type: object properties: source: type: string enum: ["wiki", "pdf", "case"] title: type: string snippet: type: string url: type: string score: type: number description: "融合排序后的综合得分,0-1之间" metadata: type: object properties: total_hits: type: integer search_time_ms: type: number sources_queried: type: array items: {type: string} required: ["results"] permissions: ["knowledge:read"] timeout_ms: 8000 retry_policy: max_attempts: 2 backoff_factor: 1.5这份定义远超“函数签名”,它明确了:
- 输入边界:
query不能为空,max_results有严格范围,source_weights必须包含全部三个键; - 行为契约:
timeout_ms规定了最长等待时间,retry_policy定义了容错机制; - 输出承诺:
results数组中每个元素必须有source、title等字段,score必须在0-1区间; - 非功能需求:权限、超时、重试,全部内置于契约,调用方无需关心。
3.2 实现细节:如何让三个异构源“协同作战”
技能实现的核心挑战,是如何让Wiki(通常是Confluence API)、PDF(本地文件索引)、案例库(Elasticsearch集群)这三个完全不同技术栈的系统,输出可比较、可融合的结果。我们的方案是“双阶段检索+分数归一化”:
第一阶段:并行粗筛
- 启动三个协程(Python asyncio):
- Wiki协程:调用Confluence REST API
/rest/api/content/search?cql=text~'{{query}}',取前50条; - PDF协程:查询本地向量数据库(ChromaDB),使用query的embedding搜索,取前50条;
- 案例协程:调用Elasticsearch
multi_match查询,取前50条。
- Wiki协程:调用Confluence REST API
- 每个协程返回原始结果集,附带其原生得分(如Elasticsearch的
_score,ChromaDB的distance)。
第二阶段:分数归一化与融合
关键难点:三个源的原始得分尺度完全不同(Elasticsearch得分可能上万,ChromaDB距离在0-2之间)。我们采用Min-Max归一化 + 权重加权:
# 假设wiki_results是列表,每个元素有wiki_score字段 wiki_scores = [r.wiki_score for r in wiki_results] wiki_normalized = [(s - min(wiki_scores)) / (max(wiki_scores) - min(wiki_scores) + 1e-8) for s in wiki_scores] # PDF同理,但注意ChromaDB distance越小越好,需反转 pdf_distances = [r.distance for r in pdf_results] pdf_normalized = [1 - (d - min(pdf_distances)) / (max(pdf_distances) - min(pdf_distances) + 1e-8) for d in pdf_distances] # 最终融合得分 = wiki_norm * weight_wiki + pdf_norm * weight_pdf + case_norm * weight_case实操心得:归一化时务必加
1e-8防止除零,且min/max必须在各自源内部计算,不能跨源混用。我们曾因误用全局min/max,导致PDF结果永远得分为0,排查三天才发现是数学错误。
第三阶段:结果去重与摘要生成
- 不同源可能返回同一文档的不同版本(如Wiki有最新版,PDF是旧版)。我们用标题哈希 + 关键句相似度(Sentence-BERT)进行去重,保留最高融合得分的版本。
- 对每个保留结果,调用轻量级LLM(如Phi-3-mini)生成50字内摘要,确保snippet对用户友好。注意:此步骤必须设置严格token限制和超时,避免LLM成为性能瓶颈。
3.3 上下文感知:让技能“记住”对话脉络
用户问:“这个API的rate limit是多少?”,紧接着问:“那怎么调整?”——第二个问题隐含了对“rate limit”的上下文引用。传统技能无法理解这种指代。我们在context对象中设计了conversation_memory模块:
- 当
knowledge.search_multi_source执行时,它会将本次检索的query、top_result.title、top_result.url存入context.memory.set("last_knowledge_query", {...}); - 后续技能(如
api.adjust_rate_limit)可通过context.memory.get("last_knowledge_query")获取上下文,从而知道“这个”指代什么; - 更进一步,我们支持
memory的TTL(Time-To-Live),例如设置set("user_preference", value, ttl=300),5分钟后自动过期,避免内存泄漏。
注意:
conversation_memory不是全局变量,而是绑定到当前会话ID的独立实例。这意味着1000个并发会话,就有1000个隔离的memory空间,彻底杜绝数据污染。
4. 生产环境部署与运维:从开发到上线的全链路实践
4.1 技能打包与版本管理:为什么不用Git Submodule?
技能模块需要独立版本控制、独立测试、独立发布。我们摒弃Git Submodule,采用Python Package + PyPI私有仓库方案:
每个技能是一个独立的Python包,结构如下:
skill-knowledge-search/ ├── pyproject.toml # 定义包名、版本、依赖 ├── skill.yaml # 技能定义文件 ├── src/knowledge_search/ # 实现代码 │ ├── __init__.py │ └── core.py # execute()入口 ├── tests/ # 专属测试 └── README.mdpyproject.toml中,version采用语义化版本(SemVer),且主版本号(MAJOR)与Agent平台兼容性强相关。例如Agent平台v2.1只兼容skills v1.x,当技能需依赖Agent v2.2的新特性(如增强的context API),则必须升为v2.0。所有技能包推送到公司私有PyPI仓库(如Artifactory)。Agent服务的
requirements.txt中写skill-knowledge-search==1.3.2,确保环境一致性。
避坑指南:
- 禁止在
skill.yaml中写死绝对路径(如/opt/data/pdf_index),必须用context.config.get("pdf_index_path"),配置由部署环境注入; - 技能包的
install_requires只能声明最小必要依赖。例如knowledge-search只需chromadb<0.5.0,而非chromadb==0.4.10,避免与Agent主程序的依赖冲突; - 我们用
pip install --no-deps安装技能包,再由Agent主程序统一解析依赖树,确保整个系统只有一个ChromaDB版本。
4.2 监控与告警:把技能变成“可观察”的服务
技能不再是黑盒函数,而是可观测的服务单元。我们在执行层埋入三层监控:
基础指标(Metrics):每个技能调用自动上报Prometheus指标:
skill_duration_seconds{skill_id="knowledge.search_multi_source", status="success"}:直方图,记录耗时分布;skill_invocations_total{skill_id="email.send_v2", result="failed"}:计数器,累计失败次数;skill_queue_length{skill_id="data.fetch_sales"}:Gauge,当前排队请求数(对异步技能)。
结构化日志(Logs):技能执行时,自动记录结构化日志(JSON格式):
{ "timestamp": "2024-05-20T14:23:18.123Z", "skill_id": "knowledge.search_multi_source", "session_id": "sess_abc123", "user_id": "usr_xyz789", "input_hash": "a1b2c3...", "output_size_bytes": 2450, "status": "success", "duration_ms": 1245.6 }这些日志被ELK栈收集,支持按
skill_id、session_id、status快速聚合分析。分布式追踪(Tracing):集成OpenTelemetry,为每次技能调用生成Trace ID。当用户反馈“查不到答案”,运维可输入session_id,在Jaeger中看到完整调用链:
Agent入口 → knowledge.search_multi_source → wiki_api_call → pdf_vector_search → fusion → LLM_summary,精准定位瓶颈环节。
实操心得:我们为每个技能配置了动态采样率。对高频技能(如user.greet),采样率设为1%;对低频但关键的技能(如payment.process),采样率100%。这样既保证关键路径全量可观测,又避免日志爆炸。
4.3 灰度发布与A/B测试:让新技能上线零风险
新技能上线最怕“一刀切”。我们设计了细粒度灰度策略:
按用户分组灰度:在
context.user中提取user_tier(如free,pro,enterprise),配置skill-knowledge-search对pro用户100%启用,对free用户0%启用,逐步扩大范围。按Query特征灰度:对
knowledge.search_multi_source,可配置规则:if query contains "API" or "rate limit" then enable_v2_fusion else enable_v1_simple。这样新融合算法只在高价值技术问题上验证,不影响普通问答。A/B测试框架:同一技能ID可注册多个实现版本。例如
knowledge.search_multi_source同时注册v1_simple和v2_fusion两个实现,配置流量分配比例(如70%/30%),并通过context.metrics.track_ab_test("knowledge_fusion", variant)上报效果,用转化率(用户是否点击结果)、停留时长等业务指标评估优劣。
提示:灰度开关必须是运行时可变的。我们用Consul KV存储开关配置,Agent服务监听变更,毫秒级生效。绝不用重启加载配置,那是对可用性的亵渎。
5. 常见问题与实战排障:一线踩过的那些坑
5.1 技能执行超时,但日志显示“仍在运行”?
现象:监控显示skill-duration持续上涨,日志最后停留在Starting PDF vector search...,无后续。
根因分析:ChromaDB客户端在查询超时时,未正确关闭底层连接,导致协程挂起。Python asyncio中,未被await的协程会成为“幽灵任务”,不报错也不结束。
解决方案:
- 在所有I/O操作外层加
asyncio.wait_for(task, timeout=timeout_ms/1000); - 为ChromaDB客户端设置
timeout=5.0(秒)参数; - 在
execute()方法末尾,强制await asyncio.sleep(0),确保事件循环有机会清理。
验证方法:写一个测试用例,故意传入不存在的collection name,观察是否在5秒内抛出asyncio.TimeoutError,而非无限等待。
5.2 多技能并发时,context.memory数据错乱?
现象:用户A问“服务器宕机怎么办”,用户B同时问“怎么重启服务”,结果用户A收到的答案里混入了用户B的服务器IP。
根因分析:context.memory对象被多个协程共享,且未加锁。虽然context本身是会话隔离的,但如果技能内部启用了多线程(如用concurrent.futures.ThreadPoolExecutor),memory的底层字典操作就不是线程安全的。
解决方案:
- 首选:禁用技能内的多线程,全部用asyncio协程。现代I/O库(aiohttp, asyncpg)都支持异步,无需线程;
- 次选:若必须用线程,则
context.memory的读写操作必须加threading.RLock(); - 终极保障:在
context初始化时,为memory创建一个threading.local()实例,确保每个线程有独立副本。
实操验证:用pytest-asyncio写并发测试,模拟100个会话同时调用knowledge.search_multi_source,断言每个会话的memory.get("last_query")只返回自己的query。
5.3 技能权限校验失效,普通用户能调用管理员技能?
现象:email.send_v2技能配置了permissions: ["email:send"],但测试发现未登录用户也能调用。
根因分析:权限校验逻辑放在了技能实现内部(如core.py里写了if not context.user.has_perm("email:send"): raise PermissionDenied),而技能注册时未开启全局权限钩子,导致绕过校验。
解决方案:
- 强制统一入口:所有技能调用必须经过
SkillExecutor.execute(skill_id, input)方法; - 此方法在执行前,自动调用
context.authz.check_permissions(skill_definition.permissions); - 技能实现
core.py中严禁出现任何权限判断代码,只专注业务逻辑; context.authz模块对接公司统一认证中心(如OAuth2.0 introspection endpoint),实时校验token权限。
配置检查清单:
- 检查
pyproject.toml中是否声明了[project.optional-dependencies] auth = ["authlib"]; - 检查Agent启动时是否注入了
authz服务实例; - 检查
skill.yaml中permissions字段是否为非空列表(空列表视为“无需权限”,需显式写[])。
5.4 技能DSL编排中,变量引用失败,报错KeyError: 'sales_data'?
现象:编排DSL中写了input: {data: "{{ sales_data }}"},但执行时报KeyError。
根因分析:sales_data是上一步data.fetch_sales的output_key,但该步骤执行失败(如数据库连接超时),返回了空结果,output_key未被设置。
解决方案:
- DSL语法强化:支持
default关键字,如{{ sales_data | default: [] }}; - 执行引擎增强:在执行每一步前,检查其依赖的
output_key是否存在,若不存在,立即终止并返回结构化错误,包含缺失的key和上游步骤ID; - 开发期校验:提供
skill-lint命令行工具,静态分析DSL文件,检测所有{{ xxx }}引用是否在上游步骤中定义了对应的output_key。
调试技巧:在Agent服务中启用DEBUG日志级别,执行时会打印详细的变量绑定过程,例如:
[DEBUG] Binding step 2 input: data = <value from step 1 output_key 'sales_data'> [DEBUG] Step 1 output: {'sales_data': [{'id': 'S001', 'amount': 12000}, ...]}这比看报错堆栈快十倍。
5.5 新增技能后,Agent服务启动变慢,CPU飙升?
现象:添加第12个技能后,Agent服务启动时间从1.2秒增至8.5秒,CPU占用100%持续10秒。
根因分析:技能注册时,对每个skill.yaml文件执行了jsonschema.validate(),而某些技能的input_schema非常复杂(嵌套5层,含大量oneOf),JSON Schema验证是CPU密集型操作。
解决方案:
- 启动时缓存验证结果:首次验证后,将
schema_hash -> validation_result存入本地SQLite缓存,后续启动直接读缓存; - 懒验证:不在启动时验证所有Schema,而是在第一次调用该技能时才验证,启动速度立竿见影;
- 简化Schema:用
$ref引用公共定义,避免重复描述。例如所有技能的user_id字段,统一定义在common.json中,用{"$ref": "common.json#/definitions/user_id"}引用。
性能对比:采用懒验证后,12个技能的启动时间回落至1.8秒,CPU峰值降至35%。我们还增加了--validate-schemas启动参数,供CI/CD流水线在构建时强制全量验证,兼顾开发效率与质量。
6. 技能生态演进:从单体Agent到可组合智能体网络
6.1 技能复用的终极形态:跨Agent技能调用
当公司内有多个Agent(如客服Agent、销售Agent、HR Agent),它们各自维护一套技能,必然导致重复建设。我们推动“技能即服务”(Skills-as-a-Service):
- 将高频技能(如
knowledge.search_multi_source,user.profile_get)部署为独立的gRPC微服务; - 每个Agent不再内置技能实现,而是通过
context.get_service("skill_registry").call("knowledge.search_multi_source", input)远程调用; - 技能服务自身也遵循skills体系,拥有自己的
skill.yaml定义、权限校验、监控埋点。
架构收益:
- 一致性:全公司知识检索逻辑统一,更新一次,所有Agent即时生效;
- 弹性伸缩:知识检索是CPU密集型,可单独扩缩容,不影响Agent主服务;
- 故障隔离:技能服务宕机,Agent可降级为本地缓存或返回友好提示,而非整体不可用。
实施要点:必须设计技能服务健康检查协议。Agent在调用前,先发health_check()请求,若超时或返回status != "healthy",则跳过远程调用,启用本地fallback。我们用grpc_health_probe做K8s readiness probe,确保流量只打到健康的实例。
6.2 技能市场:让业务部门也能贡献技能
技术团队不可能覆盖所有业务场景。我们开放了“低代码技能市场”:
- 业务人员用可视化表单填写:技能名称、描述、输入参数(拖拽字段类型)、调用的API URL、返回数据映射规则;
- 系统自动生成
skill.yaml和轻量级Python胶水代码(用requests调用API,用jmespath提取数据); - 提交后,由技术团队审核
API权限和数据脱敏规则,通过后自动注册到registry。
真实案例:某业务部门用此功能,30分钟内上线了“竞品价格监控”技能,每天自动抓取3家竞品官网价格,供销售Agent实时调用。技术团队只做了15分钟审核,节省了原本2周的开发排期。
6.3 技能的未来:从“执行者”到“决策者”
当前skills主要解决“做什么”,下一步是解决“为什么做”和“怎么做更好”。我们正在探索:
- 技能元学习(Meta-Skills):一个叫
skill.optimizer的技能,能分析历史调用日志,自动建议:knowledge.search_multi_source在query.length < 10时,应降低PDF源权重(因短查询在PDF中匹配率低); - 技能自演化:当
email.send_v2连续100次调用都附加了cc参数,系统自动在input_schema中将cc字段从optional升级为required,并更新文档; - 技能可信度评分:每个技能输出附带
confidence: 0.87,由执行时的异常率、响应时间、下游服务健康度等多维度计算,Agent可根据此分数决定是否采纳结果或发起人工审核。
这条路很长,但起点就是今天你定义的第一个skill.yaml。它不是一个技术名词,而是一种思维方式的转变——把混沌的智能体行为,拆解为可理解、可测试、可治理的原子单元。当你下次听到“agent-skills”,希望你想到的不是某个库的文档,而是自己亲手搭建的、正在呼吸的智能体能力底座。
我个人在实际操作中的体会是:技能设计的优雅程度,直接决定了六个月后你是否想删库跑路。宁愿花三天定义一个完美的skill.yaml,也不要花三天修复十个粗糙函数的线上bug。因为前者是一次性投资,后者是永无止境的利息。