FROST-SOP 的 Skill 系统:如何把 AI 能力变成可编排的乐高积木

FROST-SOP 的 Skill 系统:如何把 AI 能力变成可编排的"乐高积木"

作者 :神通说

日期 :2026-07-07

主题 :SOP工程 | 周二轮换

项目 :FROST-SOP(工程平台)

仓库gitee.com/liao_liang_...


一个尴尬的现实

大多数 AI Agent 框架都有同一个问题:能力是写死的

你想让 Agent 发邮件?写一段代码。想让 Agent 查数据库?再写一段代码。想让 Agent 先查数据库再发邮件?把两段代码拼起来。

这种方式有三个致命缺陷:

  1. 不可复用:同样的能力在不同 SOP 里要重复实现
  2. 不可测试:能力嵌入在流程里,无法单独验证
  3. 不可编排:想用不同方式组合能力?只能重写

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

这个注册表有三个重要功能:

  1. 注册与发现:所有 Skill 启动时注册,运行时按需获取
  2. 能力搜索:LLM Agent 可以用自然语言描述需求,系统自动匹配最合适的 Skill
  3. 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()

注意这个实现中的几个工程细节:

  1. 安全约束 :只允许 SELECT,且设置了 PRAGMA query_only 双重保险
  2. 结果标准化 :无论查询什么表,返回格式都是 {"columns": [...], "rows": [...], "row_count": N}
  3. 异常处理:所有异常都被捕获并转化为标准错误格式,不会导致整个 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 工程化的三个核心问题:

  1. 能力碎片化 → 标准化 Skill 接口,每个能力都是独立积木
  2. 流程不可复用 → 声明式 SOP 定义,能力组合零代码
  3. 执行不可追溯 → 完整审计日志,每步可查可回溯

这不是什么革命性的创新------这是对工程常识的回归。软件工程的模块化和可测试性原则,在 AI Agent 领域同样适用。


🔗 项目链接

FROST 是思想源头,FROST-SOP 是思想开花结果。


如果这篇文章对你有帮助,欢迎给项目点个 Star ⭐,这是对开源最大的支持!