FROST-SOP 的 Skill 系统:如何把 AI 能力变成可编排的"乐高积木"
作者 :神通说
日期 :2026-07-07
主题 :SOP工程 | 周二轮换
项目 :FROST-SOP(工程平台)
一个尴尬的现实
大多数 AI Agent 框架都有同一个问题:能力是写死的。
你想让 Agent 发邮件?写一段代码。想让 Agent 查数据库?再写一段代码。想让 Agent 先查数据库再发邮件?把两段代码拼起来。
这种方式有三个致命缺陷:
- 不可复用:同样的能力在不同 SOP 里要重复实现
- 不可测试:能力嵌入在流程里,无法单独验证
- 不可编排:想用不同方式组合能力?只能重写
FROST-SOP 的 Skill 系统,就是为了解决这个问题。
Skill 是什么?
在 FROST-SOP 的设计中,Skill(技能)是最小的可复用能力单元。
一个 Skill 做且只做一件事:
- 发送邮件
- 查询数据库
- 调用外部 API
- 生成报告
- 解析文件
就像乐高积木------每块积木形状固定、接口标准,但你可以用它们搭建任何东西。
python
from abc import ABC, abstractmethod
from typing import Any, Dict
class BaseSkill(ABC):
"""所有 Skill 的抽象基类"""
@property
@abstractmethod
def name(self) -> str:
"""技能唯一标识"""
pass
@property
@abstractmethod
def description(self) -> str:
"""技能描述,用于 LLM 选择"""
pass
@property
def parameters(self) -> Dict[str, Any]:
"""输入参数 Schema(JSON Schema 格式)"""
return {}
@abstractmethod
async def execute(self, **kwargs) -> Dict[str, Any]:
"""执行技能,返回结果"""
pass
async def validate(self, **kwargs) -> bool:
"""验证输入参数"""
# 基于 parameters schema 自动验证
return True
这个设计看起来简单,但它暗含了几个关键决策:
决策一:异步执行
所有 Skill 的 execute 方法都是 async 的。因为真实的 Agent 场景里,90% 的能力涉及 I/O(网络请求、数据库查询、文件读写),同步执行会严重拖慢整个系统。
决策二:Schema 驱动的参数验证
每个 Skill 声明自己的 parameters,系统在执行前自动验证。这意味着 SOP 编排器不需要知道每个 Skill 的具体参数------它只需要看 Schema 就知道能不能传对。
决策三:标准化返回
所有 Skill 返回统一的 Dict[str, Any] 格式。SOP 编排器可以用统一的方式处理结果,不需要针对每个 Skill 写特殊的解析逻辑。
Skill 注册表:能力的"电话簿"
有了 Skill 的定义,下一个问题是:系统怎么知道有哪些 Skill 可用?
FROST-SOP 使用一个 SkillRegistry(技能注册表) 来解决这个问题:
python
class SkillRegistry:
"""技能注册表 - 管理所有可用的 Skill"""
def __init__(self):
self._skills: Dict[str, BaseSkill] = {}
def register(self, skill: BaseSkill) -> None:
"""注册一个 Skill"""
if skill.name in self._skills:
raise ValueError(f"Skill '{skill.name}' 已注册")
self._skills[skill.name] = skill
def get(self, name: str) -> BaseSkill:
"""按名称获取 Skill"""
if name not in self._skills:
raise KeyError(f"Skill '{name}' 未找到")
return self._skills[name]
def list_available(self) -> list:
"""列出所有可用技能"""
return [
{
"name": skill.name,
"description": skill.description,
"parameters": skill.parameters
}
for skill in self._skills.values()
]
def find_by_capability(self, query: str) -> list:
"""按能力描述搜索 Skill(供 LLM 使用)"""
results = []
for skill in self._skills.values():
if query.lower() in skill.description.lower():
results.append(skill)
return results
这个注册表有三个重要功能:
- 注册与发现:所有 Skill 启动时注册,运行时按需获取
- 能力搜索:LLM Agent 可以用自然语言描述需求,系统自动匹配最合适的 Skill
- Schema 暴露:SOP 编排器可以直接读取每个 Skill 的参数 Schema,实现自动化的参数映射
实战:一个完整的 Skill 实现
让我们看一个真实的 Skill 实现------数据库查询技能:
python
import sqlite3
from typing import Any, Dict, List
class DatabaseQuerySkill(BaseSkill):
"""执行只读 SQL 查询"""
@property
def name(self) -> str:
return "database_query"
@property
def description(self) -> str:
return "在指定数据库上执行只读 SQL 查询,返回结果集"
@property
def parameters(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "要执行的 SQL 查询语句"
},
"database": {
"type": "string",
"description": "数据库名称",
"default": "main"
},
"limit": {
"type": "integer",
"description": "最大返回行数",
"default": 100
}
},
"required": ["sql"]
}
async def execute(self, sql: str, database: str = "main",
limit: int = 100) -> Dict[str, Any]:
# 安全检查:只允许 SELECT
if not sql.strip().upper().startswith("SELECT"):
return {
"success": False,
"error": "安全限制:只允许 SELECT 查询"
}
try:
conn = sqlite3.connect(f"data/{database}.db")
conn.execute(f"PRAGMA query_only = ON") # 强制只读
cursor = conn.execute(sql)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchmany(limit)
return {
"success": True,
"columns": columns,
"rows": [dict(zip(columns, row)) for row in rows],
"row_count": len(rows)
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
finally:
conn.close()
注意这个实现中的几个工程细节:
- 安全约束 :只允许 SELECT,且设置了
PRAGMA query_only双重保险 - 结果标准化 :无论查询什么表,返回格式都是
{"columns": [...], "rows": [...], "row_count": N} - 异常处理:所有异常都被捕获并转化为标准错误格式,不会导致整个 SOP 崩溃
SOP 编排器:让 Skill 协同工作
单个 Skill 只是一个积木,真正的力量来自 SOP 编排器------它定义了 Skill 之间的执行顺序和数据流转。
python
class SOPEngine:
"""SOP 执行引擎"""
def __init__(self, registry: SkillRegistry):
self.registry = registry
self.execution_log: List[Dict] = []
async def execute_sop(self, sop_definition: Dict,
context: Dict = None) -> Dict:
"""执行一个 SOP"""
context = context or {}
results = {}
for step in sop_definition["steps"]:
skill_name = step["skill"]
skill_params = step["parameters"]
# 参数解析:支持引用前序步骤的输出
resolved_params = self._resolve_params(
skill_params, results, context
)
# 获取并执行 Skill
skill = self.registry.get(skill_name)
result = await skill.execute(**resolved_params)
# 记录执行日志
self.execution_log.append({
"step": step["name"],
"skill": skill_name,
"params": resolved_params,
"result": result,
"timestamp": datetime.now().isoformat()
})
# 保存结果供后续步骤引用
results[step["name"]] = result
# 检查是否需要中断
if not result.get("success", True) and step.get("halt_on_error", True):
break
return {
"success": all(r.get("success", True) for r in results.values()),
"steps": results,
"log": self.execution_log
}
def _resolve_params(self, params: Dict,
results: Dict, context: Dict) -> Dict:
"""解析参数中的引用表达式"""
resolved = {}
for key, value in params.items():
if isinstance(value, str) and value.startswith("${"):
# 引用表达式:${step_name.output_key}
ref = value[2:-1]
step_name, output_key = ref.split(".")
if step_name in results:
resolved[key] = results[step_name].get(output_key)
elif step_name == "context":
resolved[key] = context.get(output_key)
else:
resolved[key] = value
return resolved
这个编排器的核心能力是 参数引用------一个步骤的输出可以作为下一个步骤的输入:
yaml
# SOP 定义示例:每日数据报告
name: "daily_report"
steps:
- name: "query_metrics"
skill: "database_query"
parameters:
sql: "SELECT * FROM daily_metrics WHERE date = '2026-07-07'"
- name: "format_report"
skill: "report_generator"
parameters:
title: "每日运营数据报告"
data: "${query_metrics.rows}" # 引用上一步的查询结果
template: "daily_summary"
- name: "send_email"
skill: "email_sender"
parameters:
to: "${context.recipient}" # 引用上下文变量
subject: "每日报告 - 2026-07-07"
body: "${format_report.html}" # 引用报告生成结果
${query_metrics.rows} 这样的引用语法,让 SOP 定义变得声明式------你只需要描述"数据从哪来、到哪去",不需要写任何胶水代码。
为什么这个设计很重要?
1. 能力可测试
每个 Skill 都可以单独测试,不需要启动整个系统:
python
async def test_database_query_skill():
skill = DatabaseQuerySkill()
result = await skill.execute(
sql="SELECT name FROM users LIMIT 5"
)
assert result["success"] is True
assert result["row_count"] <= 5
2. SOP 可审计
每一步的输入、输出、时间戳都被记录在 execution_log 中。出了问题,可以精确定位到哪个步骤、什么参数导致的。
3. 能力可组合
新的业务需求不需要写新代码------只需要用现有的 Skill 组合成新的 SOP:
yaml
# 新需求:用户流失预警
name: "churn_alert"
steps:
- name: "check_inactive"
skill: "database_query"
parameters:
sql: "SELECT * FROM users WHERE last_login < date('now', '-30 days')"
- name: "analyze_pattern"
skill: "llm_analysis"
parameters:
data: "${check_inactive.rows}"
prompt: "分析这些用户的流失模式..."
- name: "notify_team"
skill: "email_sender"
parameters:
to: "${context.alert_email}"
subject: "用户流失预警"
body: "${analyze_pattern.summary}"
三个已有的 Skill,一个新的 SOP,一个全新的业务能力。这就是"乐高积木"式的工程化。
与主流框架的对比
| 能力 | FROST-SOP Skill | LangChain Tool | CrewAI Tool |
|---|---|---|---|
| 异步执行 | ✅ 原生 async | ⚠️ 部分支持 | ❌ 同步 |
| Schema 验证 | ✅ JSON Schema | ⚠️ 手动 | ❌ 无 |
| SOP 编排 | ✅ YAML 声明式 | ❌ 代码硬编码 | ⚠️ 有限 |
| 执行审计 | ✅ 完整日志 | ❌ 无 | ❌ 无 |
| 参数引用 | ✅ ${step.output} |
❌ 手动传递 | ⚠️ 有限 |
FROST-SOP 的设计哲学不是"让 AI 更聪明",而是让 AI 的能力更可治理------可复用、可测试、可编排、可审计。
总结
FROST-SOP 的 Skill 系统解决了 AI Agent 工程化的三个核心问题:
- 能力碎片化 → 标准化 Skill 接口,每个能力都是独立积木
- 流程不可复用 → 声明式 SOP 定义,能力组合零代码
- 执行不可追溯 → 完整审计日志,每步可查可回溯
这不是什么革命性的创新------这是对工程常识的回归。软件工程的模块化和可测试性原则,在 AI Agent 领域同样适用。
🔗 项目链接
- FROST(教学框架):gitee.com/liao_liang_...
- FROST-SOP(工程平台):gitee.com/liao_liang_...
FROST 是思想源头,FROST-SOP 是思想开花结果。
如果这篇文章对你有帮助,欢迎给项目点个 Star ⭐,这是对开源最大的支持!