1. 项目概述:这不是又一个“AI玩具”,而是一套可落地的Agent能力构建方法论
“Agent Skills”这个词最近在技术社区和产品团队内部高频出现,但它绝不是某个新出的SDK名字,也不是某家大厂刚发布的闭源框架代号。它指的是一类围绕AI Agent实际工作能力展开的、可拆解、可训练、可验证的具体技能集合——就像人类学开车要练倒车入库、坡道起步、紧急避让一样,AI Agent要真正“干活”,也得掌握调度、工具调用、多步推理、状态记忆、错误恢复这些硬核动作。我带过好几个跨部门协作的智能体落地项目,发现90%的失败不是因为模型不够强,而是卡在“Agent不会干活”:它能流利回答问题,但一让它查数据库、改Excel、发邮件、比价下单,就卡壳、幻觉、死循环。这背后缺的不是算力,是Skills。
你可能已经试过LangChain、LlamaIndex,甚至自己搭过ReAct流程,但总感觉Agent像一个满腹经纶却手不能提篮的书生——知道该做什么,就是做不利索。这就是典型的“技能断层”:LLM有认知能力,但缺乏执行肌肉。而“Agent Skills”的核心价值,就是把抽象的“智能体行为”翻译成一组可定义、可注入、可组合、可调试的具体能力模块。比如,“自动整理会议纪要并同步到周报模板”这件事,拆开看至少涉及5个Skills:语音转写结果清洗、关键议题识别、行动项抽取、Markdown格式生成、飞书/钉钉API调用。每个Skill都可以独立开发、单独测试、按需装配。小白能上手,是因为它不强制你从零写调度器;资深工程师爱用,是因为它允许你把最复杂的业务逻辑封装进Skill里,再交给Agent当“积木”来拼。
这个标题里“小白也能轻松掌握并收藏”不是营销话术,而是设计使然:Skills本质是函数接口+轻量胶水代码+明确输入输出契约。你不需要懂Transformer结构,只要会写Python函数、能看懂JSON Schema,就能写出第一个可用的Skill。我见过某高校实验室的本科生,用3小时把实验室设备预约系统封装成一个Skill,接入后整个课题组的实验排期效率提升40%。它不依赖特定平台,不绑定某家云服务,也不需要你部署私有大模型——你可以用本地Ollama跑Qwen2,也可以调用通义千问API,只要统一遵循Skills的调用协议,Agent就能“认出”并“用好”它。接下来的内容,我会带你从零开始,亲手构建一套真实可用的Agent Skills体系,不讲虚概念,只给能立刻跑起来的代码、踩过的坑、以及为什么这么设计的底层逻辑。
2. 核心设计思路:为什么放弃“大而全”的Agent框架,选择“小而精”的Skills架构?
2.1 传统Agent框架的三大隐性成本,正在拖垮真实项目交付
过去两年我参与过7个不同行业的Agent落地尝试,从金融客服辅助到制造业设备巡检,发现一个惊人共性:项目启动时用LangChain/LlamaIndex搭建的Demo非常炫酷,但进入第二阶段——对接真实业务系统、处理异常数据、满足审计要求——80%的团队会推倒重来。根本原因在于,主流框架默认把“Agent能力”当作黑盒流程来编排,而非可管理的资产。具体表现为三个隐性成本:
第一是调试成本指数级上升。当你把“查库存→比价格→生成采购建议→邮件通知采购员”写成一条长Chain时,一旦第3步出错,你得回溯整个执行栈:是模型没理解“建议”含义?是价格API返回了空数组?还是邮件模板里漏写了供应商名称?没有清晰的Skill边界,日志里全是“LLM output: ...”,你根本不知道问题出在哪个环节。我帮某电商公司优化促销策略Agent时,光定位一个“折扣计算偏差”就花了两天——最后发现是Chain里混用了两个不同精度的价格字段,而这个细节在Chain定义里根本看不到。
第二是复用成本几乎为零。“查库存”这个动作,在A项目里是调用ERP的SOAP接口,在B项目里是连MySQL查视图,在C项目里是读取SAP RFC返回的XML。传统框架要求你为每个项目重写整个Chain,哪怕90%逻辑相同。而Skills的设计哲学是:“查库存”是一个能力,不是一段代码。你只需定义它的输入(商品ID、仓库编码)、输出(可用数量、冻结数量、单位)、约束(超时3s、重试2次),至于怎么实现,是HTTP还是JDBC,是Python还是Java,完全解耦。某制造企业把12个核心业务操作全部封装为Skills后,新产线上线时,仅替换3个Skills的实现,就完成了Agent适配,耗时不到半天。
第三是权限与审计成本被严重低估。真实企业环境里,“谁调用了什么系统、传了哪些参数、返回了什么结果”,必须可追溯、可审计。LangChain的RunnableSequence本身不记录中间态,你得自己插桩打日志,还要保证不泄露敏感字段。而Skills天然具备“能力粒度”:每个Skill可以独立配置访问令牌、设置数据脱敏规则、绑定RBAC角色。我们给某银行做的信贷初审Agent,就为“征信查询”Skill单独配置了加密传输通道和操作留痕开关,审计人员直接导出该Skill的调用流水即可,不用翻几十万行混合日志。
提示:不要被“框架很火”带偏节奏。评估一个Agent方案,先问三个问题:1)当我需要修改“发送短信”逻辑时,是否只改一个文件?2)当我需要禁用“调用财务系统”能力时,是否只需关闭一个开关?3)当我被要求提供“上周所有API调用明细”时,是否能在5分钟内导出合规报表?如果答案是否定的,那它大概率不适合生产环境。
2.2 Skills架构的四大设计原则:从“能跑”到“敢用”的关键跃迁
基于上述教训,我们提炼出Skills架构的四个不可妥协的设计原则,它们共同构成了“小白能上手、企业敢落地”的基础:
原则一:能力即接口(Capability as Interface)
每个Skill必须定义清晰的契约(Contract),包括:
input_schema:严格校验输入JSON Schema,拒绝非法字段(如传入字符串"123"而非整数123)output_schema:明确声明返回结构,Agent可据此做类型安全解析metadata:包含作者、版本、更新时间、适用场景标签(如"finance"、"low-latency")constraints:超时、重试、限流、敏感词过滤等运行时约束
这个契约不是文档,而是可执行的代码。我们用Pydantic V2实现,启动时自动校验,非法调用直接抛出SkillValidationError,而不是让错误蔓延到LLM层。某客户曾因忘记校验日期格式,导致Agent把"2024-13-01"传给财务系统,引发批量单据失败——加了Schema校验后,这类问题归零。
原则二:实现即插件(Implementation as Plugin)
Skill的逻辑实现(Implementation)与能力定义(Definition)物理分离。定义文件(inventory_skill.py)只含契约和元数据;实现文件(inventory_impl.py)含具体代码。Agent加载时,根据环境变量SKILL_IMPL=prod或SKILL_IMPL=mock动态选择实现。测试时用Mock版返回固定JSON,上线时切到真实ERP调用。这种分离让单元测试覆盖率轻松达到95%以上——你不需要启动整个Agent,只要pytest test_inventory_impl.py就能验证所有分支。
原则三:调用即事件(Invocation as Event)
每次Skill调用都生成标准化事件日志,包含:唯一trace_id、skill_name、input_hash(脱敏后)、status(success/error)、duration_ms、error_code(如TIMEOUT_001)。这些日志直送ELK,支持按skill_name聚合分析成功率、P95延迟。更关键的是,它天然支持“重放”(replay):当线上出现问题,运维可复制trace_id,用相同输入在测试环境重放整个调用链,精准复现问题。某物流公司的运单状态同步Agent,就是靠这个功能在2小时内定位到第三方API的偶发503错误。
原则四:组合即编排(Composition as Orchestration)
Skills之间不直接调用,而是通过轻量编排引擎(Orchestrator)协调。这个引擎只做三件事:1)解析LLM生成的Skill调用计划(Plan);2)按依赖关系排序执行;3)聚合结果生成下一步Prompt。它不碰业务逻辑,因此极轻量(核心代码<200行),且可被任意Agent框架集成。我们甚至把它做成独立Docker服务,供多个Agent项目共享,避免每个项目重复造轮子。
这四大原则不是理论空谈,而是用血泪换来的经验。它们共同指向一个目标:让Agent能力像乐高积木一样,可验证、可替换、可审计、可组合。接下来,我们就从零开始,亲手构建这样一套体系。
3. 核心技能模块详解:从定义、实现到调试的完整闭环
3.1 定义一个标准Skill:契约先行,拒绝模糊描述
我们以“获取用户最近3笔订单信息”为例,演示如何定义一个Production-ready的Skill。注意,这里不写任何业务代码,只聚焦契约设计——这是Skills架构的基石。
首先创建order_history_skill.py,内容如下:
from pydantic import BaseModel, Field, field_validator from typing import List, Optional, Dict, Any import re class OrderItem(BaseModel): """订单明细项""" sku_id: str = Field(..., description="商品SKU编码,格式:ABC-123") quantity: int = Field(..., ge=1, le=999, description="购买数量") unit_price: float = Field(..., gt=0.0, description="单价,单位:元") class OrderInfo(BaseModel): """订单信息""" order_id: str = Field(..., pattern=r'^ORD-\d{8}-\d{6}$', description="订单号,格式:ORD-20240501-123456") total_amount: float = Field(..., gt=0.0, description="订单总金额") status: str = Field(..., pattern=r'^(pending|shipped|delivered|cancelled)$', description="订单状态") items: List[OrderItem] = Field(..., min_items=1, max_items=50, description="订单商品列表") created_at: str = Field(..., pattern=r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$', description="创建时间,ISO8601 UTC格式") class OrderHistoryInput(BaseModel): """Skill输入契约""" user_id: str = Field(..., min_length=8, max_length=32, description="用户唯一标识,字母数字组合") limit: int = Field(3, ge=1, le=10, description="最多返回订单数,默认3") @field_validator('user_id') def validate_user_id(cls, v): if not re.match(r'^[a-zA-Z0-9]+$', v): raise ValueError('user_id must contain only letters and digits') return v class OrderHistoryOutput(BaseModel): """Skill输出契约""" orders: List[OrderInfo] = Field(..., min_items=0, max_items=10, description="订单列表") total_count: int = Field(..., ge=0, description="用户历史订单总数") # Skill元数据(非Pydantic模型,纯字典) SKILL_METADATA = { "name": "get_user_order_history", "version": "1.2.0", "description": "获取指定用户的最近N笔订单详情,含商品、金额、状态等完整信息", "author": "platform-team", "tags": ["e_commerce", "user_profile", "read_only"], "constraints": { "timeout_ms": 5000, "max_retries": 2, "rate_limit": "100/minute", "sensitive_fields": ["user_id"] # 声明敏感字段,用于日志脱敏 } }这段代码的价值远超表面:
- 输入校验即安全防线:
user_id的正则校验防止SQL注入式攻击;limit的范围限制避免恶意请求拖垮数据库。 - 输出结构即文档:
OrderInfo里的pattern强制订单号格式,下游Agent可据此做字符串匹配,无需再写正则。 - 元数据即治理依据:
tags支持按业务域筛选Skills;sensitive_fields指导日志系统自动脱敏。
注意:很多团队跳过这一步,直接写实现代码,结果后期维护成本爆炸。我见过一个项目,因未定义
total_amount的精度要求,导致前端显示“¥199.99999999999997”,客户投诉后才发现是Python浮点运算误差——如果当初在Schema里声明decimal_places=2,问题在定义阶段就被拦截。
3.2 实现Skill逻辑:Mock优先,真机验证,分层测试
定义完契约,我们创建order_history_impl.py。遵循“Mock优先”原则,先写测试友好的Mock实现:
import json from datetime import datetime, timezone from typing import List, Dict, Any from order_history_skill import OrderHistoryInput, OrderHistoryOutput, OrderInfo, OrderItem def mock_get_user_order_history(input_data: Dict[str, Any]) -> Dict[str, Any]: """Mock实现:返回固定测试数据""" # 解析输入(已由框架完成Schema校验) parsed_input = OrderHistoryInput(**input_data) # 构造模拟订单(真实项目中此处调用数据库/微服务) mock_orders = [ OrderInfo( order_id=f"ORD-{datetime.now(timezone.utc).strftime('%Y%m%d')}-000001", total_amount=299.0, status="delivered", items=[ OrderItem(sku_id="ABC-123", quantity=1, unit_price=299.0) ], created_at=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") ), OrderInfo( order_id=f"ORD-{datetime.now(timezone.utc).strftime('%Y%m%d')}-000002", total_amount=159.5, status="shipped", items=[ OrderItem(sku_id="DEF-456", quantity=2, unit_price=79.75) ], created_at=(datetime.now(timezone.utc) - timedelta(hours=2)).isoformat().replace("+00:00", "Z") ) ] # 按limit截取 orders = mock_orders[:parsed_input.limit] # 构建输出 output = OrderHistoryOutput( orders=orders, total_count=127 # 模拟用户总订单数 ) return output.model_dump()现在写真实实现real_get_user_order_history.py(简化版,仅展示关键逻辑):
import psycopg2 from psycopg2.extras import RealDictCursor from order_history_skill import OrderHistoryInput, OrderHistoryOutput, OrderInfo, OrderItem def real_get_user_order_history(input_data: Dict[str, Any]) -> Dict[str, Any]: """真实实现:查询PostgreSQL订单表""" parsed_input = OrderHistoryInput(**input_data) # 从环境变量获取DB连接(生产环境应使用Secret Manager) conn = psycopg2.connect( host="db-prod.internal", database="ecommerce", user="app_reader", password="xxx" # 实际使用密钥管理服务 ) try: with conn.cursor(cursor_factory=RealDictCursor) as cur: # 关键:使用参数化查询,杜绝SQL注入 cur.execute(""" SELECT o.order_id, o.total_amount, o.status, o.created_at, json_agg(json_build_object( 'sku_id', i.sku_id, 'quantity', i.quantity, 'unit_price', i.unit_price )) as items FROM orders o JOIN order_items i ON o.order_id = i.order_id WHERE o.user_id = %s ORDER BY o.created_at DESC LIMIT %s """, (parsed_input.user_id, parsed_input.limit)) rows = cur.fetchall() # 构建OrderInfo列表 orders = [] for row in rows: # 将items JSON字符串解析为对象列表 items_data = json.loads(row['items']) if row['items'] else [] items = [OrderItem(**item) for item in items_data] order = OrderInfo( order_id=row['order_id'], total_amount=float(row['total_amount']), status=row['status'], items=items, created_at=row['created_at'].isoformat().replace("+00:00", "Z") ) orders.append(order) # 查询总订单数(另起查询,避免COUNT(*)影响主查询性能) cur.execute("SELECT COUNT(*) FROM orders WHERE user_id = %s", (parsed_input.user_id,)) total_count = cur.fetchone()[0] output = OrderHistoryOutput(orders=orders, total_count=total_count) return output.model_dump() finally: conn.close()分层测试策略(test_order_history.py):
- Unit Test:只测Mock实现,验证输入输出契约,100%覆盖边界值(如limit=0, user_id超长)
- Integration Test:用Testcontainers启动临时PostgreSQL,测Real实现,验证SQL正确性
- E2E Test:启动完整Agent,用真实LLM生成调用指令,验证端到端流程
这种分层让Bug定位速度提升5倍。某次线上故障,我们30秒内确认是Real实现的SQL JOIN条件写错,而非Agent调度逻辑问题。
3.3 调试与可观测性:让每一次Skill调用都“看得见、管得住”
Skills架构最大的优势是调试友好。我们内置了三层可观测性:
第一层:调用前校验日志
Agent在调用Skill前,自动打印结构化日志:
[SKILL_PRE_CALL] name=get_user_order_history version=1.2.0 trace_id=abc123 input_hash=sha256:xyz [INPUT_VALIDATION] status=success fields_validated=3如果校验失败,日志明确指出:[INPUT_VALIDATION] status=failed error="user_id must contain only letters and digits"
第二层:执行中追踪日志
每个Skill实现开头插入追踪代码:
import time start_time = time.time() try: result = your_logic() duration = time.time() - start_time logger.info(f"[SKILL_EXEC] name={skill_name} status=success duration_ms={int(duration*1000)}") return result except Exception as e: duration = time.time() - start_time logger.error(f"[SKILL_EXEC] name={skill_name} status=error duration_ms={int(duration*1000)} error_type={type(e).__name__}") raise第三层:结果后置分析日志
Skill返回后,框架自动记录:
[SKILL_POST_CALL] name=get_user_order_history trace_id=abc123 output_size_bytes=1247 status=success [OUTPUT_VALIDATION] status=success fields_validated=2这些日志统一格式,可被Logstash解析,存入Elasticsearch。我们建立Kibana仪表盘,实时监控:
- 各Skill成功率(按小时/天趋势)
- P95延迟热力图(按skill_name + environment维度)
- 错误码TOP10(如
DB_TIMEOUT_001占比突增,立即触发告警)
实操心得:不要等出问题才建监控。我们在第一个Skill上线时,就配置了“成功率<99.5%”和“P95延迟>2s”双阈值告警。某次数据库慢查询,告警在故障发生后47秒就推送至值班群,比业务方反馈还快。
4. 实操全流程:从零搭建你的第一个Agent Skills系统
4.1 环境准备与依赖安装:5分钟完成最小可行环境
我们采用极简技术栈,确保小白也能10分钟跑通:
- Python 3.10+(推荐3.11,性能更好)
- Poetry(现代Python依赖管理,比pipenv更稳定)
- SQLite(零配置,适合学习)
- Ollama(本地运行开源大模型,免API Key)
步骤1:初始化项目
# 创建项目目录 mkdir my-agent-skills && cd my-agent-skills # 初始化Poetry(如未安装:pip install poetry) poetry init -n poetry add pydantic==2.7.1 fastapi uvicorn httpx python-dotenv pytest pytest-asyncio # 为本地模型添加Ollama依赖(可选,若用API则跳过) poetry add ollama步骤2:创建核心目录结构
my-agent-skills/ ├── skills/ # 所有Skill定义与实现 │ ├── __init__.py │ ├── order_history/ # 一个Skill的完整包 │ │ ├── __init__.py │ │ ├── skill.py # 定义(契约) │ │ ├── impl.py # 实现(逻辑) │ │ └── test_impl.py # 测试 ├── orchestrator/ # 编排引擎 │ ├── __init__.py │ └── engine.py ├── agent/ # Agent主程序 │ ├── __init__.py │ └── main.py ├── tests/ # 全局测试 ├── .env # 环境变量 └── README.md步骤3:配置环境变量(.env)
# 模型配置(Ollama) OLLAMA_MODEL=qwen2:1.5b OLLAMA_TIMEOUT=30 # 数据库(SQLite,开箱即用) DATABASE_URL=sqlite:///./data/app.db # Skills配置 SKILL_IMPL=mock # 切换为'real'启用真实实现步骤4:初始化数据库(init_db.py)
import sqlite3 from pathlib import Path # 创建data目录 Path("./data").mkdir(exist_ok=True) conn = sqlite3.connect("./data/app.db") cursor = conn.cursor() # 创建订单表(真实项目中此表已存在) cursor.execute(''' CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id TEXT UNIQUE NOT NULL, user_id TEXT NOT NULL, total_amount REAL NOT NULL, status TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') cursor.execute(''' CREATE TABLE IF NOT EXISTS order_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id TEXT NOT NULL, sku_id TEXT NOT NULL, quantity INTEGER NOT NULL, unit_price REAL NOT NULL, FOREIGN KEY (order_id) REFERENCES orders (order_id) ) ''') conn.commit() conn.close() print("✅ SQLite database initialized")运行:poetry run python init_db.py
至此,最小环境搭建完成。整个过程不涉及任何云服务、不需申请API Key、不依赖外部网络(Ollama模型可离线下载),真正实现“开箱即用”。
4.2 开发第一个Skill:订单历史查询(从定义到上线)
我们已在3.1和3.2节详细拆解了order_historySkill的定义与实现。现在将其整合到项目中:
步骤1:创建Skill包
mkdir -p skills/order_history touch skills/order_history/__init__.py将3.1节的skill.py内容存为skills/order_history/skill.py,将3.2节的mock_get_user_order_history函数存为skills/order_history/impl.py。
步骤2:编写Skill加载器(skills/__init__.py)
import importlib import os from typing import Dict, Any, Callable from skills.order_history.skill import SKILL_METADATA # 技能注册表 SKILLS_REGISTRY: Dict[str, Dict[str, Any]] = {} def load_skills() -> None: """动态加载所有Skill""" skill_dir = os.path.dirname(__file__) for root, dirs, files in os.walk(skill_dir): if 'skill.py' in files and '__pycache__' not in root: # 解析skill路径,如 skills/order_history/skill.py -> order_history rel_path = os.path.relpath(root, skill_dir) if rel_path == '.': continue skill_name = rel_path.replace('/', '_') # 动态导入skill模块 module_name = f'skills.{rel_path.replace("/", ".")}.skill' try: skill_module = importlib.import_module(module_name) # 注册元数据 SKILLS_REGISTRY[skill_module.SKILL_METADATA['name']] = { 'metadata': skill_module.SKILL_METADATA, 'module_path': module_name } print(f"✅ Loaded Skill: {skill_module.SKILL_METADATA['name']} v{skill_module.SKILL_METADATA['version']}") except Exception as e: print(f"❌ Failed to load Skill {rel_path}: {e}") # 首次加载 load_skills()步骤3:实现编排引擎(orchestrator/engine.py)
import json import asyncio from typing import Dict, Any, List, Optional from skills import SKILLS_REGISTRY class Orchestrator: def __init__(self): self._impl_cache = {} async def execute_skill(self, skill_name: str, input_data: Dict[str, Any]) -> Dict[str, Any]: """执行指定Skill""" if skill_name not in SKILLS_REGISTRY: raise ValueError(f"Skill '{skill_name}' not found") # 获取实现模块(根据SKILL_IMPL环境变量) impl_module_name = f'skills.{skill_name.replace("_", "_")}.impl' try: impl_module = __import__(impl_module_name, fromlist=['']) except ImportError: raise ImportError(f"Impl module {impl_module_name} not found") # 查找实现函数(约定:mock_xxx 或 real_xxx) impl_func_name = f"{'mock_' if os.getenv('SKILL_IMPL') == 'mock' else 'real_'}{skill_name}" if not hasattr(impl_module, impl_func_name): raise AttributeError(f"Function {impl_func_name} not found in {impl_module_name}") impl_func = getattr(impl_module, impl_func_name) # 执行(同步函数转异步) loop = asyncio.get_event_loop() return await loop.run_in_executor(None, impl_func, input_data) # 全局实例 orchestrator = Orchestrator()步骤4:编写Agent主程序(agent/main.py)
from fastapi import FastAPI, HTTPException, BackgroundTasks from pydantic import BaseModel from typing import Dict, Any import os from orchestrator.engine import orchestrator app = FastAPI(title="Agent Skills Demo") class SkillCallRequest(BaseModel): skill_name: str input_data: Dict[str, Any] @app.post("/call-skill") async def call_skill(request: SkillCallRequest): """暴露Skill调用接口""" try: result = await orchestrator.execute_skill(request.skill_name, request.input_data) return {"status": "success", "result": result} except Exception as e: raise HTTPException(status_code=400, detail=str(e)) # 启动时加载Skills @app.on_event("startup") async def startup_event(): print("🚀 Agent Skills System starting...") # 这里可添加更多初始化逻辑步骤5:启动服务并测试
# 启动FastAPI服务 poetry run uvicorn agent.main:app --reload --host 0.0.0.0 --port 8000 # 在另一个终端,用curl测试 curl -X POST "http://localhost:8000/call-skill" \ -H "Content-Type: application/json" \ -d '{ "skill_name": "get_user_order_history", "input_data": {"user_id": "user_12345", "limit": 2} }'你会看到类似响应:
{ "status": "success", "result": { "orders": [/* 订单数据 */], "total_count": 127 } }恭喜!你的第一个Agent Skill已成功上线。整个过程不依赖任何外部服务,所有代码均可在本地运行,且结构清晰、职责分明。
4.3 进阶实战:构建“智能会议纪要助手”Agent
现在我们将多个Skill组合,构建一个真实场景Agent:“智能会议纪要助手”。它需完成:
- 接收会议录音文本(或转写结果)
- 提取关键议题、决策项、待办事项
- 生成结构化Markdown纪要
- 同步到指定协作文档(如飞书多维表格)
Step 1:定义新Skills
extract_meeting_entities.py:输入会议文本,输出议题/决策/待办列表generate_minutes_md.py:输入结构化实体,输出Markdown字符串sync_to_feishu.py:输入Markdown,写入飞书多维表格
Step 2:编写编排逻辑(agent/meeting_agent.py)
from orchestrator.engine import orchestrator async def process_meeting_transcript(transcript: str) -> Dict[str, Any]: """会议纪要全流程处理""" # Step 1: 提取实体 entities = await orchestrator.execute_skill( "extract_meeting_entities", {"transcript": transcript} ) # Step 2: 生成Markdown markdown = await orchestrator.execute_skill( "generate_minutes_md", {"entities": entities} ) # Step 3: 同步到飞书 sync_result = await orchestrator.execute_skill( "sync_to_feishu", {"markdown_content": markdown["content"], "doc_id": "xxx"} ) return { "status": "success", "minutes_md": markdown["content"], "feishu_url": sync_result["url"] } # 在main.py中添加路由 @app.post("/process-meeting") async def process_meeting(request: MeetingRequest): result = await process_meeting_transcript(request.transcript) return resultStep 3:LLM提示词设计(关键!)
Agent不是万能的,它需要清晰的指令。我们为LLM设计结构化输出Prompt:
你是一个专业会议纪要助手。请严格按以下JSON Schema输出,不要任何额外文字: { "topics": [{"title": "string", "summary": "string"}], "decisions": [{"item": "string", "owner": "string", "deadline": "YYYY-MM-DD"}], "action_items": [{"item": "string", "owner": "string", "deadline": "YYYY-MM-DD"}] } 输入会议文本:{{transcript}}Step 4:错误处理与降级
真实场景中,sync_to_feishu可能因网络失败。我们在编排层加入降级:
try: sync_result = await orchestrator.execute_skill("sync_to_feishu", {...}) except Exception as e: # 降级:保存到本地文件,并发邮件通知管理员 local_path = await save_to_local(markdown["content"]) send_alert_email(f"Feishu sync failed, saved to {local_path}") sync_result = {"url": f"file://{local_path}"}这个“会议纪要助手”已在某科技公司落地,将平均纪要生成时间从45分钟缩短至90秒,且100%保留原始决策项。它证明了Skills架构的核心价值:复杂流程可分解、每个环节可独立优化、失败点可精准隔离。
5. 常见问题与避坑指南:来自12个真实项目的血泪总结
5.1 “Skill调用总是超时,但单独测试又很快”——网络与序列化陷阱
现象:在Agent中调用get_user_order_history,90%概率超时(5s),但直接运行test_impl.py,耗时仅120ms。
根因分析:
- 序列化开销被忽略:LLM输出的
input_data是JSON字符串,Agent框架需反序列化为Python dict,再传给Skill。当input_data含大量嵌套JSON(如长文本、Base64图片),json.loads()成为瓶颈。 - 连接池未复用:每个Skill调用新建数据库连接,而SQLite在高并发下连接建立耗时激增。
解决方案:
- 输入预处理:在Skill入口增加轻量校验,快速拒绝超大数据:
def mock_get_user_order_history(input_data: Dict[str, Any]) -> Dict[str, Any]: # 快速检查输入大小(避免反序列化后才发现) input_json = json.dumps(input_data) if len(input_json) > 1024 * 1024: # 1MB raise ValueError("Input too large") # ...后续逻辑 - 连接池化:SQLite用
aiosqlite替代psycopg2,支持异步连接池:import aiosqlite pool = await aiosqlite.create_pool("app.db", minsize=5, maxsize=20) async with pool.acquire() as conn: async with conn.execute("SELECT ...") as cursor: rows = await cursor.fetchall() - 缓存热点数据:对
user_id加LRU缓存(@lru_cache(maxsize=128)),避免重复查询。
实操心得:我在某项目中,仅加了输入大小检查,超时率从90%降至0.3%。不要迷信“框架会处理一切”,关键路径必须自己埋点监控。
5.2 “LLM生成的Skill调用参数总是格式错误”——提示词工程与Schema对齐
现象:LLM有时