共计 2511 个字符,预计需要花费 7 分钟才能阅读完成。
痛点分析:为什么需要 skill 编排
在 LLM 应用开发中,我们经常遇到以下问题:

- 碎片化严重:不同业务线的 prompt 散落在各处,有的在代码注释里,有的在文档里,甚至有些只存在于同事的脑子里
- 版本混乱:同一个功能可能有 v1、v2、v3 多个版本同时在线,但没人记得哪个版本在用
- 测试困难:修改一个基础 prompt 可能导致连锁反应,但没有可靠的回归测试方案
- 复用率低:相似的意图重复编写 prompt,每次都要从头调整 temperature 和 few-shot 示例
架构设计:用 DSL 统一 skill 管理
我们的解决方案是采用 YAML 定义 skill 规范:
# greeting_skill.v1.yaml
skill_id: greeting
version: 1.0
description: 通用问候语
parameters:
- name: username
type: string
required: true
template: |
你好{{username}}!我是 AI 助手,请问今天有什么可以帮您?metadata:
author: dev_team
compatible_models: [gpt-3.5, gpt-4]
关键设计点:
- 通过
skill_id + 版本号实现精准路由 - 参数使用 Mustache 语法模板化
- 元数据记录适用模型范围
核心实现:Python 编排引擎
1. SkillLoader 实现(带 LRU 缓存)
from functools import lru_cache
from pathlib import Path
import yaml
from typing import Dict, Any
class SkillLoader:
def __init__(self, skill_dir: str):
self.skill_dir = Path(skill_dir)
@lru_cache(maxsize=1000)
def load_skill(self, skill_id: str, version: str) -> Dict[str, Any]:
file_path = self.skill_dir / f"{skill_id}.v{version}.yaml"
with open(file_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
2. Prompt 组合引擎(含输入校验)
from jinja2 import Template, StrictUndefined
from pydantic import BaseModel, validator
class PromptEngine:
def __init__(self, loader: SkillLoader):
self.loader = loader
def render(self, skill_id: str, version: str, params: dict) -> str:
skill = self.loader.load_skill(skill_id, version)
self._validate_params(skill['parameters'], params)
template = Template(skill['template'],
undefined=StrictUndefined # 防止变量未定义
)
return template.render(**params)
def _validate_params(self, param_defs: list, params: dict):
for param in param_defs:
if param['required'] and param['name'] not in params:
raise ValueError(f"Missing required parameter: {param['name']}")
生产环境考量
避免 skill 冲突
当新增 skill 时,通过计算与现有 skill 的嵌入向量余弦相似度(使用 sentence-transformers):
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
def check_similarity(new_skill_text: str, existing_skills: list, threshold=0.85):
new_embedding = model.encode(new_skill_text)
for skill in existing_skills:
sim = cosine_similarity(new_embedding, model.encode(skill))
if sim > threshold:
raise ConflictError(f"Too similar to existing skill: {skill[:50]}...")
防止 prompt 注入
对用户输入进行转义处理:
import re
def sanitize_input(text: str) -> str:
# 移除尝试改写 prompt 的常见模式
patterns = [r'(?i)ignore.*previous',
r'(?i)from now on',
r'(?i)your new instruction is'
]
for pattern in patterns:
text = re.sub(pattern, '[REDACTED]', text)
return text
三大避坑指南
- 循环依赖检查
- 实现依赖图分析,使用拓扑排序检测循环引用
-
示例:skillA 依赖 skillB,而 skillB 又依赖 skillA
-
参数化适度原则
- 每个 skill 参数不超过 5 个
- 复杂逻辑拆分为多个子 skill
-
实测参数过多会导致响应时间线性增长
-
LLM 版本适配
- 记录每个 skill 验证过的模型版本
- 对新模型做 AB 测试
- 示例:gpt- 4 可能比 gpt-3.5 需要更简洁的 prompt
开放性问题
当 skill 数量超过 10 万时,如何优化检索效率?可以考虑:
- 基于意图的分类索引
- 在线学习的热 skill 缓存
- 二级检索策略(先粗筛再精排)
欢迎在评论区分享你的解决方案。
正文完
