
最近在整理 Agent 技能管理方案时发现开发圈里对“WikiSkill”的讨论越来越多。简单来说它把人们之前只当作“一组提示词文件”的 Skill 提升到了“可沉淀、可检索、可更新”的知识层让 LLM Agent 不再是一次性拼装技能脚本而是能像维护 Wiki 一样维护自己的技能库。这篇文章会从概念讲起再给出一套用 Python 实现的简化版 SkillWiki 管理库帮你理解“Wiki 层保存经验显著提升 Skill 效果”这句话到底是怎么落地的。1. 从 Skill 到 WikiSkill为什么需要一层“经验仓库”1.1 什么是 Skill为什么 Agent 离不开 Skill在熟悉 Claude Code、Codex、LangChain 等工具后很多开发者应该已经理解了一个事实直接让大模型回答问题时它是一个“零记忆”的推理引擎但如果你把解决问题的方法、约束条件、常用脚本封装成一段可复用的知识再在合适的时机注入给模型它的表现会稳定很多。这段“可复用的知识”业界通常叫它 Skill。Skill 不是一个严格的学术定义更像是一组工程约定。一个 Skill 往往包含一段自然语言指导告诉模型“遇到这类问题应该怎么做”一组前置条件或输入输出约束可能需要执行的脚本、模板、正则规则一些示例输入输出帮助模型对齐预期效果。通俗地讲普通 Prompt 是一次性的“口述经验”而 Skill 是把经验固化成了“标准作业程序”。这也是 Claude Code 的 Skill、Codex 的 Skill、Trae 的 Skill 插件都在做的事情把用户的私有工作流沉淀成文件下次遇到同样任务时直接加载。1.2 孤立 Skill 的四个问题最初我接触 Skill 时以为它就是把提示词拆成文件这么简单。但实践一段时间后会碰到四个比较现实的问题。第一技能之间互相隔离无法复用。比如你写了一个“代码审查 Skill”又写了一个“Python 代码审查 Skill”内容 70% 是重复的。真正可复用的是“审查通用规则”但因为它没有被单独沉淀下来后面再写“Java 代码审查 Skill”时又得复制一遍。第二技能缺少元数据检索命中率低。当技能数量超过 20 个以后靠关键词匹配已经很难找到正确的那一个。如果每个 Skill 只有文件名和正文没有描述、标签、适用场景Agent 在决策时就像在一堆没有目录的文档里找东西。第三经验无法持续更新。很多 Skill 在项目里是“写完一次就不再维护”的。但实际项目的问题总是会变化今天写下的最佳实践三个月后可能已经被新方案淘汰。没有更新机制Skill 会慢慢变得过时甚至产生误导。第四新旧经验经常冲突。团队里多人协作时A 更新了版本 1B 又按自己的理解改出了版本 2最后谁也不知道哪个才是当前生效的经验。这四类问题本质上都是因为 Skill 还是“静态文件”而缺少了一个能够承载检索、依赖、版本和更新的中间层。1.3 WikiSkill 的核心思路在 Skill 之上加一层 WikiWikiSkill 的思路就是把 Wiki 的知识组织方式引入 Skill 管理。Wiki 的特点是有明确的词条结构、有页面之间的互相链接、有编辑历史、有版本回滚。如果把这些能力搬进 Agent 的技能库Skill 就不再是孤立文件而是一个网状知识库里的一个节点。这里的“Wiki 层”可以理解成一套基础设施它负责管理三类信息技能内容本身也就是原本的 Skill 文件技能的结构化描述名称、用途、标签、依赖、版本、最后更新时间技能之间的关联关系A 技能依赖 B 技能C 技能和 D 技能互补。有了这一层之后Agent 在执行任务时不再是“把所有技能一次性塞进上下文”而是先通过 Wiki 层的索引检索出当前任务最相关的几个技能再加载它们的详细内容。这样做有三个明显的好处上下文空间更省、命中准确率更高、经验可持续积累。所以你可以把 WikiSkill 理解成一次工程升级从“每天复制粘贴技能脚本”升级到“像维护知识库一样维护技能体系”。2. WikiSkill 的核心机制拆解2.1 技能节点把一段经验结构化Wiki 的基本单位是词条WikiSkill 的基本单位就是技能节点SkillNode。一个合适的技能节点不应该只包含正文还应该包含足够的结构化字段。下面是我在设计简化版时常用的字段字段说明示例skill_id技能唯一标识code-review-pythonname技能名称Python 代码审查技能description一句话描述功能对 Python 代码进行风格、安全和性能审查content技能正文或触发提示词详细规则...tags标签用于检索python, review, securitydependencies依赖的其他技能 IDcode-review-commonversion版本号1.2.0updated_at最后更新时间2025-01-15其中最重要的不是 content而是 description、tags 和 dependencies。因为 content 是“真正给模型读的东西”而前面几个字段是“帮模型决定要不要读这段内容”的索引信息。索引信息写得好不好直接决定了技能召回质量。2.2 技能图节点之间不再是孤岛Wiki 页面的最大特征是超链接。Skill 一旦出现依赖关系就自然形成了一张技能图Skill Graph。举个例子你可以有一个code-review-common技能专门沉淀“通用代码审查原则”然后让code-review-python和code-review-java分别依赖它。Agent 调用 Python 技能时通过依赖关系自动把通用规则一起加载进来。这样你只需要维护一份通用规则而不是两份复制粘贴的片段。技能图还有一个好处可以暴露多余依赖和循环依赖。比如两个技能互相引用就会形成循环加载时可能造成上下文重复膨胀。有了图结构这个问题可以在写入阶段就用算法检查出来。2.3 检索与排序让合适的技能在合适的场景被调用Wiki 层需要提供两种检索方式关键词检索和语义检索。关键词检索适合精确匹配场景比如用户明确说“帮我审查 Python 代码”通过 tags 里出现 python 和 review 就能快速定位。语义检索适合模糊场景比如用户说“帮我看看这段代码有没有坑”模型不会直接出现“代码审查”这个关键词但语义上高度相关。这里的排序策略很重要。一个简单的打分函数可以综合几个要素语义相似度、技能完整度、历史使用频率、最近更新时间。在你还没有积累足够多的使用数据前最稳妥的做法是“语义相似度为主标签覆盖次之最后用时间戳打破平局”。2.4 闭环进化使用数据反哺技能内容WikiSkill 这个名字里藏着另一个重要含义技能不是静止的而是像 Wiki 页面一样可以被持续编辑改进的闭环过程。每次 Agent 使用某个技能完成任务后都可以把任务结果、是否成功、耗时等信息记入技能节点的使用日志。当某个技能频繁失败时Wiki 层可以提示维护者“该技能可能需要更新”当某个技能一段时间内零使用可以标记为“可能已过时”。配合人工审核后最佳实践会从“跑通一次”变成“被重复验证的标准流程”。这才是“显著提升 Skill 效果”最关键的部分不是文件数量变多了而是每条经验都在被反复修正。下图是简化后的闭环逻辑用户请求 - 检索候选技能 - 加载并执行技能 - 记录结果日志 ^ | | v 更新技能内容 - 定期复盘与人工审核3. 环境准备与系统设计3.1 环境依赖下面我们来实现一个简化版的 SkillWiki 服务。这个版本侧重演示核心逻辑不使用复杂的向量数据库方便你直接看完运行。我使用的实验环境是Python 3.9 或更高版本只需要标准库不需要额外安装第三方包如果需要语义检索示例可以按自己的需求接入sentence-transformers或 OpenAI Embedding 接口但核心示例不依赖它们。版本需要根据你的项目实际情况调整下面的代码重点演示配置和实现思路。3.2 实例项目一个简化版 SkillWiki 服务我们的目标是做一个命令行可运行的服务具备以下能力往 Wiki 层新增一个技能节点通过关键词或标签查询技能通过 description 的简单语义匹配先以关键词拆分为示例做检索排序更新技能内容并留下历史版本演示 Agent 在收到请求后如何通过 Wiki 层选择技能。为了不过度设计我采用 JSON 文件作为持久化存储用一个类封装全部读写逻辑。你完全可以把它替换成 SQLite、MySQL 或对象存储。3.3 目录结构skill-wiki-demo/ ├── skill_wiki.py # SkillWiki 核心类 ├── skill_agent.py # Agent 调用示例 ├── wiki_store.json # 技能存储文件运行时自动生成 └── README.md # 说明文档4. 完整实战用 Python 搭建 SkillWiki 管理库4.1 定义技能节点模型先创建skill_wiki.py。我们用字典或 dataclass 来表示 SkillNode。为了减少依赖我直接使用 dataclass。# 文件路径skill-wiki-demo/skill_wiki.py from dataclasses import dataclass, field, asdict from datetime import datetime from typing import List, Dict, Optional import json import hashlib dataclass class SkillNode: skill_id: str name: str description: str content: str tags: List[str] field(default_factorylist) dependencies: List[str] field(default_factorylist) version: str 1.0.0 updated_at: str field(default_factorylambda: datetime.now().isoformat()) usage_count: int 0 success_count: int 0 def to_dict(self) - dict: return asdict(self) staticmethod def from_dict(data: dict) - SkillNode: return SkillNode( skill_iddata[skill_id], namedata.get(name, ), descriptiondata.get(description, ), contentdata.get(content, ), tagsdata.get(tags, []), dependenciesdata.get(dependencies, []), versiondata.get(version, 1.0.0), updated_atdata.get(updated_at, ), usage_countdata.get(usage_count, 0), success_countdata.get(success_count, 0), )这里我加了usage_count和success_count两个字段。它们不是必须的但能帮助 Wiki 层判断一个技能是否值得优先推荐。真实项目中这两个字段应该由调用方在每次执行后主动上报。4.2 实现 SkillWiki 的增删改查接下来在同一个文件里添加 SkillWiki 类。它负责加载、保存、检索和管理技能节点。# 文件路径skill-wiki-demo/skill_wiki.py续 class SkillWiki: def __init__(self, store_path: str wiki_store.json): self.store_path store_path self.skills: Dict[str, SkillNode] {} self.history: Dict[str, List[dict]] {} self._load() def _load(self) - None: try: with open(self.store_path, r, encodingutf-8) as f: raw json.load(f) self.skills { sid: SkillNode.from_dict(data) for sid, data in raw.get(skills, {}).items() } self.history raw.get(history, {}) except FileNotFoundError: self.skills {} self.history {} def _save(self) - None: data { skills: {sid: node.to_dict() for sid, node in self.skills.items()}, history: self.history, } with open(self.store_path, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def add_skill(self, node: SkillNode) - None: if node.skill_id in self.skills: raise ValueError(f技能 {node.skill_id} 已存在请使用 update_skill 更新。) self.skills[node.skill_id] node self.history[node.skill_id] [node.to_dict()] self._save() def update_skill(self, node: SkillNode) - None: if node.skill_id not in self.skills: raise ValueError(f技能 {node.skill_id} 不存在请先 add_skill。) old_version self.skills[node.skill_id].version # 简单版本号递增你也可以改成语义化版本 major, minor, patch old_version.split(.) node.version f{major}.{minor}.{int(patch) 1} node.updated_at datetime.now().isoformat() self.skills[node.skill_id] node self.history.setdefault(node.skill_id, []).append(node.to_dict()) self._save() def get_skill(self, skill_id: str) - Optional[SkillNode]: return self.skills.get(skill_id) def remove_skill(self, skill_id: str) - None: if skill_id in self.skills: del self.skills[skill_id] self._save() def list_skills(self) - List[SkillNode]: return list(self.skills.values()) def get_history(self, skill_id: str) - List[dict]: return self.history.get(skill_id, [])_save每次写入时会先序列化成 JSON逻辑上非常直接。这里有一个很关键的细节历史版本必须单独保存。没有历史版本就无法回滚一旦新经验出错修复成本会很高。4.3 实现检索与排序现在为 SkillWiki 增加搜索方法。先做基础的关键词匹配再把 description 也纳入得分计算模拟一个简单的排序策略。# 文件路径skill-wiki-demo/skill_wiki.py续 from collections import defaultdict class SkillWiki(SkillWiki): # 实际上由于上面已经定义这里换成一个新类的写法只做演示 pass上面的写法只是为了占位实际项目里不要这样重复定义。我们直接在原有类内部追加方法。下面给出搜索方法的实现思路# 文件路径skill-wiki-demo/skill_wiki.py在 SkillWiki 类内部追加 def search(self, query: str, top_k: int 3) - List[SkillNode]: query_terms set(self._tokenize(query)) scored [] for node in self.skills.values(): score self._score_node(node, query_terms) scored.append((score, node)) scored.sort(keylambda x: x[0], reverseTrue) return [node for score, node in scored[:top_k] if score 0] def _tokenize(self, text: str) - List[str]: # 简单分词按非字母数字分割并转为小写 import re return re.findall(r[a-z0-9], text.lower()) def _score_node(self, node: SkillNode, query_terms: set) - float: score 0.0 text_store { name: node.name, description: node.description, tags: .join(node.tags), content: node.content, } # 每个字段按不同权重计分 weights { name: 3.0, description: 2.0, tags: 2.5, content: 1.0, } for field, weight in weights.items(): field_terms set(self._tokenize(text_store[field])) overlap query_terms field_terms score len(overlap) * weight # 小小的时间衰减老技能稍微降低分数 try: old_hours (datetime.now() - datetime.fromisoformat(node.updated_at)).total_seconds() / 3600 decay max(0.5, 1.0 - old_hours / 720.0) # 30 天衰减到 0.5 except Exception: decay 1.0 score * decay return score这个打分逻辑很简单名称和标签权重最高描述次之正文最低。这样做的目的是让“标题党”技能不能仅靠正文埋词排到前面。真实场景如果你接入了向量检索可以用 embedding 距离替换这里的词重叠计算。4.4 与 LLM Agent 打通有了 SkillWiki 管理库之后Agent 调用技能的方式就变成三步根据用户问题调用wiki.search(query)把命中的技能内容拼接成系统提示词将用户问题一起发送给 LLM。我写一个简单的skill_agent.py来演示这个过程。这里不直接调用任何云端模型只是留出call_llm占位函数避免把示例绑死在某个服务商上。# 文件路径skill-wiki-demo/skill_agent.py from skill_wiki import SkillWiki, SkillNode def call_llm(system_prompt: str, user_query: str) - str: 这是一个占位函数。 真实项目中可以替换成 OpenAI、Claude、本地模型或任何兼容接口的调用。 需要注意不同 SDK 的参数差异按你的实际环境调整。 # 这里把拼接后的内容打印出来方便观察效果 print( System Prompt ) print(system_prompt[:500]) print( User Query ) print(user_query) # 真实情况下你应该在这里调用大模型接口。 # 返回一个模拟的结果方便演示流程。 return 模拟 LLM 返回结果 def build_system_prompt(wiki: SkillWiki, query: str) - str: matched_skills wiki.search(query, top_k3) if not matched_skills: return 你是一个通用助手没有找到匹配技能。 blocks [] for node in matched_skills: blocks.append( f### 技能{node.name}\n f描述{node.description}\n f内容\n{node.content}\n ) return ( 你是一个拥有技能库的 AI 助手。\n 请根据下面的技能内容回答用户问题不要编造技能中不存在的规则。\n\n \n.join(blocks) ) def main() - None: wiki SkillWiki() # 初始化几个示例技能 if not wiki.list_skills(): wiki.add_skill( SkillNode( skill_idcode-review-common, name通用代码审查技能, description对代码进行风格、安全、性能方面的通用检查, content1. 检查代码风格一致性\n2. 检查明显的安全风险\n3. 检查性能隐患, tags[code, review, common], dependencies[], ) ) wiki.add_skill( SkillNode( skill_idcode-review-python, namePython 代码审查技能, description针对 Python 代码的专项审查, content1. 检查是否符合 PEP8\n2. 检查是否使用列表推导式替代低效循环\n3. 检查异常处理是否完整, tags[python, review], dependencies[code-review-common], ) ) query 帮我看看我的 Python 脚本有什么改进空间 system_prompt build_system_prompt(wiki, query) # 这里把用户问题传给 call_llm实际会去请求模型 result call_llm(system_prompt, query) print( LLM 返回 ) print(result) if __name__ __main__: main()在上面的代码里build_system_prompt只挑选了 top 3 个技能拼进提示词。这样做的直接好处是控制上下文长度。假设你的技能库里有 50 个技能每个平均 500 字如果全部塞进去就是 25000 字经过检索只传 1 到 3 个能省下大量 token。4.5 运行与验证执行以下命令cd skill-wiki-demo python skill_agent.py预期输出类似 System Prompt 你是一个拥有技能库的 AI 助手。 请根据下面的技能内容回答用户问题不要编造技能中不存在的规则。 ### 技能Python 代码审查技能 描述针对 Python 代码的专项审查 内容 1. 检查是否符合 PEP8 2. 检查是否使用列表推导式替代低效循环 3. 检查异常处理是否完整 ### 技能通用代码审查技能 描述对代码进行风格、安全、性能方面的通用检查 内容 1. 检查代码风格一致性 2. 检查明显的安全风险 3. 检查性能隐患 User Query 帮我看看我的 Python 脚本有什么改进空间 LLM 返回 模拟 LLM 返回结果这里有一个值得注意的点Python 技能排在通用技能前面。因为python和review两个词都命中了 Python 技能的名称和标签得分更高。这个排序逻辑是符合直觉的专项技能应该优先于通用技能。如果我把查询改成“帮我检查代码风格”那通用技能就可能排到前面因为“风格”这个词更贴近它的 description。你可以在本地多尝试几个 query观察排序变化。5. 常见问题与排查思路在实际使用 WikiSkill 时我遇到过一些重复率很高的问题整理成表格供你参考。问题现象常见原因解决思路检索不到技能技能描述和用户提问的措辞差异太大给技能多补同义标签接入 embedding 语义检索多个技能得分接近加载结果不稳定技能之间描述高度重叠检查是否出现重复技能利用 dependencies 抽公共层更新技能后 Agent 行为发生回退新版本质量低于旧版本保留历史版本快速回滚更新前先做灰度验证技能内容太长上下文超限检索召回过多或单个技能正文过长缩小 top_k把长文本拆成多个子技能使用摘要字段技能依赖循环加载时重复内容技能图出现环形依赖写入时做环检测定期检查依赖关系团队协作时版本互相覆盖缺少版本管理和锁机制使用 Wiki 层做提交记录多人维护时增加审核流程上面这几个问题中最隐蔽的是第二个技能描述高度重叠。比如你同时拥有“Python 代码审查”和“Python 代码质量检查”两个技能检索时它们得分可能非常接近。解决办法很直接给技能定义更清晰的边界并在 description 里写清楚“本技能不处理什么”。下面再看一个具体的排查案例。假设运行python skill_agent.py后发现匹配结果为空。首先检查wiki_store.json里的技能是否确实存在。如果文件不存在说明初始化没有成功执行。其次检查查询词是否被分词后完全匹配不上。比如用户输入中文“代码审查”而技能 description 里写的是“code review”中文分词和英文索引无法对齐就会召回失败。最简单的做法是给技能增加中文标签比如tags: [代码, 审查]。6. 最佳实践与工程建议6.1 技能沉淀规范Wiki 层的价值依赖于技能节点的质量。建议每个技能节点沉淀时都回答以下四个问题这个技能解决什么问题什么场景下不应该使用它它依赖哪些其他技能它的成功标准是什么。在 content 部分尽量把“触发条件”“执行步骤”“禁止事项”分离。一个常见的糟糕写法是一大段自然语言混杂规则模型很难提取重点。更好的写法是先给结论再给步骤最后给反例。6.2 更新与回滚技能更新必须是显式的不能直接覆盖原文件。我强烈建议给每次更新生成新的版本号并在 Wiki 层记录提交人、提交时间和变更说明。当线上 Agent 行为异常时优先做版本回滚而不是临时改 Prompt。上面代码中的history字段只存了技能快照真实生产环境还可以补充 diff 信息。这样团队成员能清楚地看到“这一次更新到底改了什么”。6.3 语义检索优化简单的关键词匹配只适合演示和轻量场景。当技能数量超过 100 个后建议引入向量检索。核心思路是把技能的 description 和 tags 拼成一段文本用 embedding 模型生成向量用户提问时生成查询向量然后做余弦相似度排序。如果暂时不想引入额外依赖可以先优化关键词方案给技能编写至少 3 个同义标签在 description 里加入场景关键词对中文用户增加中文分词和停用词处理。检索排序千万不要只看 embedding 距离。理想情况下应该是“向量相似度 标签覆盖 历史成功率 时间衰减”的综合打分。这样既能照顾语义相关性也能照顾质量回报。6.4 安全与权限技能内容会被直接注入系统提示词因此必须当作“可执行代码”一样对待。不要从不可信来源批量导入技能避免恶意指令写在技能正文里造成提示注入。对高风险操作如删除数据、修改权限、执行脚本技能应该描述成“输出操作建议由用户确认执行”而不是“直接执行命令”。另外技能库里可能包含团队内部约定、密钥路径、内部域名等敏感信息。Wiki 层需要做权限隔离不同项目、不同安全等级的用户只能看到授权范围内的技能。保存技能文件时建议把账号信息、API Key 等变量抽离成占位符避免随技能内容一起扩散。6.5 可观测性每次技能被调用都应该记录用户请求原文命中的技能 ID 及分数拼装后的系统提示词摘要LLM 输出人工反馈是否有帮助。这些数据积累一段时间后你就能统计出哪些技能是高频高质的“明星技能”哪些是无人问津的“僵尸技能”。对僵尸技能定期清理对明星技能增加推广入口这是 Wiki 层持续改进的闭环基础。7. 总结与学习路线WikiSkill 的核心并不复杂它不改变大模型的推理能力而是改变了 Skill 的管理方式。把技能文件升级成带元数据、依赖关系、历史版本的 Wiki 节点之后技能可以复用、可以检索、可以回滚、可以持续优化。这也是它被很多开发团队看好的原因——不是因为它发明了新算法而是它把工程上缺失的“经验仓库”补上了。从我自己的实践来看技术栈的优先级可以这样排先把现有技能全部结构化补全 description、tags、dependencies做一个检索层哪怕先使用关键词匹配也比“全量塞入”要好给技能加上版本和回滚机制再引入向量检索和综合打分最后再考虑团队协作、权限隔离和可观测性。这篇文章里的 Python 示例只是一个最小可运行骨架你可以直接拿它改造存储换成 SQLite、检索换成向量库、Agent 接入真实 LLM API。下一步建议阅读一下 Anthropic 关于 Agent 技能的工程实践、LangChain 的技能插件设计以及向量检索的 RAG 优化思路然后把你自己的技能库试着做成一个小型 WikiSkill 服务。如果哪个环节遇到了问题欢迎在评论区留言交流。