ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Agent技能系统设计指南:从Prompt堆砌到稳定落地的工程实践

2026/9/25 15:20:42 拓冰建站 浏览量
Agent技能系统设计指南:从Prompt堆砌到稳定落地的工程实践 做了大半年AI Agent应用我最深的感受是会聊天的模型到处都是能稳定干活的Agent万里挑一。刚开始做Agent时我跟大多数人的思路一样——把Prompt写得越来越长把工具越堆越多结果模型反而越来越飘你让它查个日志它能先给你输出两千字分析再告诉你我还没有权限执行。直到我开始认真研究agent-skills这套思路才意识到问题不在模型身上而在我们对Agent能力的组织方式。Agent真正缺的不是更聪明的脑子而是一套把知识、流程、工具调用封装成可复用单元的技能系统。这篇文章会从技能的结构设计、注册与召回机制、实战案例和我踩过的坑四个维度展开帮助正在做Agent应用或准备把LLM接入真实业务流的开发者真正理解如何让Agent拥有一技之长。1. 从堆Prompt到造技能Agent能力的第一次跃迁1.1 为什么Prompt越长Agent越废先说一个反直觉的结论一个真正好用的Agent系统提示词往往很短相反那些试图用长篇大论把业务规则全部写进Prompt的Agent在实际业务里几乎都是废柴。原因可以从注意力机制的角度来理解。LLM的注意力窗口虽然越做越大但模型对不同位置信息的敏感程度差异很大。当你把一份五千字的操作手册塞进系统提示词模型真正执行任务时往往会被中间那些看起来重要但跟当前任务无关的规则带偏。更麻烦的是长Prompt会挤占保留给工具返回结果和对话历史的空间一旦工具输出比较长上下文被截断Agent就开始丢信息、答非所问。我见过一个真实案例某个团队的Agent负责处理工单他们在系统提示词里写了网关配置、数据库连接信息、部署流程、排查规范、话术模板……结果上线后模型经常在用户只是问一句我的订单为什么没发货时在回答里附带一大段数据库配置说明。这不是模型笨而是你把所有场景的说明书一次性摆在它面前它根本没有足够的能力判断此时此刻到底该调取哪部分知识。agent-skills的出发点正是为了解决这个问题。与其把所有可能用到的知识常驻在上下文里不如把它们拆成一个一个独立封装的技能。平时技能不占上下文当Agent判断当前任务需要某项能力时再动态加载对应技能的内容。这样既保证模型不被无关信息干扰又能在关键时刻获得足够专业的临时大脑。1.2 Skills不是Tools边界到底在哪很多人第一次接触agent-skills时会把它跟函数调用Function Calling / Tools搞混我一开始也踩过这个坑。简单来说两者解决的问题不一样Tool是把一个可以被代码调用的函数暴露给模型比如get_weather(city)、send_email(to, subject, body)。它解决的是模型需要触发外部动作的问题本质上是给模型装上手和脚。Skill是把一组知识、流程、指令和参考工具的组合封装成完整操作单元通常包含结构化的说明文档、若干支持脚本或模板以及调用其他工具的清单。它解决的是模型需要在一定专业领域内完成复杂任务的问题本质上是给模型装上一套职业能力。举个更容易理解的例子。假设你想让Agent替你做服务器日常巡检。用Tool的思路你会给模型提供run_shell(command)这个工具模型自己拼命令、执行、看返回结果但整个巡检流程要看哪些指标、阈值是多少、发现问题怎么办全靠临时发挥。用Skill的思路你会创建一个服务器巡检技能里面写清楚巡检步骤、指标阈值、处置预案还附带了现成的脚本。模型一旦判断任务属于巡检就把整个技能加载进来照着技能文档一步步执行。所以两者不是替代关系而是不同层级的能力。一个完整的Agent通常需要Tool Skill两层能力Tool负责原子操作Skill负责按照专业流程把这些原子操作串起来同时补充模型在推理时需要的领域知识。1.3 agent-skills的核心理念知识跟着任务走我理解的agent-skills核心哲学一句话就能说完知识不应该常驻而应该跟着任务走。展开来就是三个动作检测、加载、执行。检测模型根据用户请求判断当前任务是否匹配某个技能的使用场景。加载命中技能后把技能对应的主文档和辅助文件注入上下文。执行模型按照技能文档的步骤结合可用工具完成任务。这套流程看似简单但它触及了Agent架构里一个根本性的效率问题。上下文窗口是有限的而Agent可能面对的业务场景是无限的。只有在必要时刻加载必要知识才是让Agent规模化处理复杂任务的正确路径。现在很多开源的agent-skills类项目本质都是围绕这三个动作做的有的偏重检测的召回质量有的偏重加载时的上下文压缩有的偏重执行的流程编排。把这个框架理解透了后面看任何实现都不会晕。2. 技能系统的骨架一个Skill到底该长什么样2.1 目录结构不止一个Markdown文件我们团队最早设计的技能极其简陋就是一个Markdown文件里面写了几行说明。后来在实测中被锤了几次才慢慢演变成下面这种标准结构skills/ system-diagnosis/ SKILL.md scripts/ disk_usage.sh mem_monitor.sh references/ threshold_guide.md troubleshooting_cases.md assets/ report_template.md一个完整技能至少包含三部分主文档SKILL.md负责告诉模型这个技能是干什么的、怎么用脚本或模板负责提供可以直接用的执行资产参考文档负责在模型需要深挖时提供额外的知识储备。为什么不能只用一个Markdown因为当技能内容多了以后一次性把所有内容塞进上下文既慢又贵。举个例子一个数据库运维技能光故障排查手册就有三千行如果每次调用都把整个手册加载进去上下文早就被撑爆了。所以我把高频使用的核心指令放在SKILL.md里低频深挖的内容放到references目录下由模型按需读取。这样就形成了两级加载先加载技能主文档再按需读取参考文档。2.2 SKILL.md的正确写法给模型一份工作手册SKILL.md是最关键的文件它直接决定模型会不会用、用得对不对。我总结了五个必须写清楚的部分技能名、触发描述、使用步骤、上下文要求、边界提醒。先给一个反面教材这是我早期写的日志分析技能的SKILL.md正文# 日志分析 分析日志文件找出异常。注意需要先登录服务器然后查看/var/log/app.log。这段描述看起来没问题但实际使用中模型的表现很不稳定。它不知道该在什么时候触发这个技能、分析时该看哪些指标、异常怎么分级、发现问题下一步做什么。后来我把这份SKILL.md重构成了下面这样--- name: log-analysis description: 分析应用日志并输出结构化异常报告。当用户提到日志报错、接口返回5xx、 服务间歇性故障、线上异常排查、崩溃分析等场景时使用。 如果用户只是询问日志文件位置无需使用本技能。 version: 2.0.0 --- # 日志分析技能 ## 适用场景 - 用户反馈线上服务异常需要从日志中定位原因 - 部署或发布后出现非预期行为需要对比发布前后的日志 - 周期性巡检中发现错误率上升需要找出异常模式 ## 使用步骤 1. 先检查日志文件路径和权限确认可读。 2. 按时间窗口提取样本优先查看最近30分钟的错误级日志。 3. 对错误信息按类型聚合统计TOP10错误类型及出现次数。 4. 对TOP3错误类型提取完整堆栈并尝试定位到具体代码模块。 5. 将结果填入报告模板输出为结构化报告。 ## 常用命令速查 - 查看最近N行日志tail -n 200 /var/log/app.log - 按关键字过滤grep -E ERROR|Exception /var/log/app.log | tail -n 50 - 按时间窗口过滤sed -n /2025-01-01T10:00:00/,/2025-01-01T10:30:00/p /var/log/app.log ## 边界与注意 - 本技能只负责日志分析不做线上变更。 - 如果日志文件过大500MB先切割或使用tail/grep抽样禁止一次性cat。 - 分析结论必须基于日志中的实际证据禁止凭空猜测。重构之后效果立竿见影。原因很简单我给了模型一个决策框架——告诉它什么时候触发、按什么顺序做、每一步做到什么程度、哪些事情不要做。模型在推理时最怕的不是知识少而是知识散。SKILL.md的作用就是把散乱的知识组织成一条清晰的操作链路让模型每一步都有依据、都有产出。2.3 参数契约没有Schema的技能就是定时炸弹第三个容易被忽略的部分是技能的参数契约。当技能需要接收用户提供的参数比如日志路径、时间范围、错误关键字时如果没有明确的参数定义模型就会自己猜猜错了就直接执行后果不堪设想。我们的做法是在SKILL.md里增加参数说明区明确每个参数的类型、是否必填、取值范围。更规范一点可以直接定义JSON Schema注册中心加载技能时做参数校验{ type: object, properties: { log_path: { type: string, description: 日志文件的绝对路径, examples: [/var/log/app.log] }, time_window: { type: string, enum: [5m, 30m, 1h, 6h, 24h], default: 30m }, error_keywords: { type: array, items: { type: string }, description: 需要重点关注的关键字列表 } }, required: [log_path] }加了参数契约之后模型在调用技能前会先想清楚参数值而不是直接把整段用户原话扔给脚本处理。这跟给函数定义类型签名是一个道理——人写代码时需要类型约束模型调度技能时同样需要。3. 手写技能注册与召回从启动到命中的完整链路3.1 技能注册表让Agent知道家里有什么技能不是挂到某个目录下就完事了Agent在启动时需要知道我有哪些技能可用。这个知道的过程叫注册。一个基础的技能注册表长这样from dataclasses import dataclass, field from pathlib import Path import yaml dataclass class Skill: name: str version: str description: str root_path: Path entry_point: str SKILL.md metadata: dict field(default_factorydict) def load_main_doc(self) - str: doc_path self.root_path / self.entry_point return doc_path.read_text(encodingutf-8) def load_file(self, relative_path: str) - str: target (self.root_path / relative_path).resolve() if not str(target).startswith(str(self.root_path.resolve())): raise PermissionError(f禁止访问技能目录之外的文件: {relative_path}) return target.read_text(encodingutf-8) class SkillRegistry: def __init__(self): self._skills: dict[str, Skill] {} self._manifest: list[dict] [] def load_from_directory(self, skills_root: str): root Path(skills_root) for skill_dir in root.iterdir(): if not skill_dir.is_dir(): continue md_path skill_dir / SKILL.md if not md_path.exists(): continue metadata self._parse_frontmatter(md_path) skill Skill( namemetadata[name], versionmetadata.get(version, 1.0.0), descriptionmetadata.get(description, ), root_pathskill_dir, metadatametadata, ) self.register(skill) def register(self, skill: Skill): self._skills[skill.name] skill self._manifest.append({ name: skill.name, description: skill.description }) def get_manifest(self) - list[dict]: 返回给LLM的精简技能清单作为召回索引 return self._manifest def get_skill(self, name: str) - Skill | None: return self._skills.get(name) def _parse_frontmatter(self, md_path: Path) - dict: raw md_path.read_text(encodingutf-8) if not raw.startswith(---): return {name: md_path.parent.name, description: } _, frontmatter, _ raw.split(---, 2) return yaml.safe_load(frontmatter)这里我特意让注册表维护一个精简版的manifest只包含技能名和描述。这个清单会以紧凑形式注入系统提示词用于让模型做召回决策。注意一个关键点注入给模型的是目录而不是全文目录体积小、信息密度高、不容易干扰主任务。3.2 描述即索引如何让模型的召回率翻倍既然清单里的description是模型召回的唯一依据那描述写得好不好就直接决定了技能命中率。我调试过好几次召回失败的问题根因几乎都出在描述上。总结下来有三个规律。第一描述必须显式写出当用户提到什么时使用把触发词列出来。模型的语义理解确实强但你指望它自己推断出用户说服务器卡了该用系统诊断技能不如直接告诉它当用户提到服务器卡顿、性能下降、资源耗尽时使用本技能。第二描述里要写什么情况不使用把容易混淆的场景排除掉。比如如果用户只是问CPU是什么不需要使用本技能。别小看这句否定句它能显著减少误触发尤其是在多个技能功能有重叠的时候。第三描述的详细程度要跟技能数量匹配。技能少的时候描述可以简短技能超过十个之后描述必须精炼到一句话内能区分彼此否则两个描述相近的技能会让模型选择困难表现就是在两个技能之间来回切换、反复加载最终输出四不像的结果。3.3 技能执行系统提示词注入与上下文管理当模型决定调用某个技能后执行框架要做两件事把技能主文档注入上下文然后把控制权交给模型。注入方式有两种一种是直接追加到系统提示词中适合技能文档较短的情况另一种是给模型提供一个类似read_skill_doc(skill_name)的工具让模型决定调用后再读取适合技能文档较长的情况。我更推荐第二种因为技能未命中时不会白白浪费上下文。这里还要注意上下文的位置管理。技能文档注入后应该放在哪里我的经验是放在系统提示词之后、对话历史之前。原因是现代Transformer对靠近输入末尾的内容注意力权重更高最新一轮用户消息应该拥有最高的注意力优先级。如果你把技能文档放在对话历史后面会盖过用户最新消息模型就沉浸在看文档里忘了用户刚才问的是什么。这种问题在加长上下文窗口的模型上尤其隐蔽因为表面上看起来上下文没满实际模型对关键信息的关注已经被稀释了。4. 实战给Agent造一个Linux系统诊断技能4.1 需求拆解与技能边界下面拿我们内部一直在用的一个技能完整演示让大家看看从零到一是怎么走的。需求很常见运维同学每天在群里被问服务器是不是挂了怎么又卡了如果有个Agent能自动上去跑一轮诊断把结论直接甩出来能省不少时间。拆解下来这个任务需要模型具备这些能力查看系统负载uptime、内存使用free、磁盘占用df、CPU占用TOP进程ps、最近日志异常dmesg tail。每个命令单独看都很简单但让模型自己临场发挥组织这一套流程结果非常不稳定——它有时跳过内存检查有时把磁盘和内存混在一起说。所以这个场景非常适合做成技能。我划定的技能边界是只做只读诊断不做任何变更操作不主动重启服务不修改配置。边界清晰才能保证技能在自动化环境下安全执行这是当时跟运维同学反复确认过的硬性要求。4.2 技能文件设计先创建目录结构skills/linux-diag/ SKILL.md scripts/ quick_diag.sh deep_diag.sh references/ metric_explanation.mdSKILL.md里给模型清晰的五步流程--- name: linux-diag description: Linux服务器只读诊断技能。当用户提到服务器卡顿、负载高、内存不足、 磁盘满、进程异常、系统变慢、需要体检服务器状态时使用。 本技能仅执行只读命令不会对系统做任何修改。 version: 1.3.0 --- # Linux 系统诊断技能 ## 使用步骤 1. 快速检查系统整体负载运行uptime记录load average。 2. 检查内存运行free -h评估可用内存和swap占用。 3. 检查磁盘运行df -h找出使用率超过80%的挂载点。 4. 检查CPU占用TOP进程运行ps -eo pid,pcpu,pmem,comm --sort-pcpu | head -20。 5. 根据数据输出诊断报告给出问题定位和初步建议。 ## 关键指标解读 - 若load average持续高于CPU核数提示系统过载 - 若内存耗尽且swap占用大提示存在内存压力 - 若磁盘使用率超85%提示需要清理或扩容。 ## 输出格式 三部分状态总览表格、异常发现列表、建议动作列表。 所有数据必须来源于命令真实输出不要编造指标。 ## 边界 - 禁止执行写操作、安装操作、重启服务。 - 禁止运行耗时超过30秒的命令。quick_diag.sh脚本示例#!/usr/bin/env bash # quick_diag.sh - 快速系统诊断脚本 # 用法: bash quick_diag.sh echo 系统负载 uptime echo echo 内存状态 free -h echo echo 磁盘使用率 df -h | grep -vE ^(tmpfs|devtmpfs|overlay) echo echo CPU占用TOP10 ps -eo pid,pcpu,pmem,comm --sort-pcpu | head -11deep_diag.sh则针对具体异常做深入检查比如磁盘inode耗尽、网络连接数异常等references/metric_explanation.md负责教模型如何解读各项指标的含义。这样分层之后主文档保持精炼深挖内容按需读取上下文压力小很多。4.3 实测效果与分析这个技能上线后我把一批服务器卡顿类的真实用户请求跑了一遍技能命中率超过九成剩下没命中的基本是用户描述太模糊。命中技能后Agent输出的诊断报告质量稳定在可以直接转发给开发的水平比我原来纯靠Prompt控制的方案强非常多。关键改进有三个。第一技能把流程固化成了操作路径模型不会漏步骤。第二输出格式有强约束报告结构统一后续接工单系统方便解析。第三命令白名单约束了模型行为不会被带偏去执行危险操作。这三点单独看都不惊艳合在一起就是生产可用和生产不可用的差距。5. 踩坑实录技能冲突、上下文污染与召回失败5.1 场景一两个技能抢活模型晕头转向有一次我同时上线了代码审查和安全审计两个技能它们的描述里都提到了检查代码中的安全问题。结果模型面对一个简单请求帮我看看这段代码时在两个技能之间反复横跳先加载代码审查技能又跑去加载安全审计技能最后输出一个四不像的结果。排查后发现这两份SKILL.md的触发描述高度重叠。解决方案是给每个技能划分清晰的触发边界代码审查聚焦逻辑正确性、性能隐患、可维护性安全审计聚焦漏洞扫描、注入风险、权限管理。同时我在各自的描述里都加了一句边界说明如果用户主要关心安全问题请使用另一个技能。边界划清之后冲突基本消失。这件事给我的教训是写技能时不能只管自己还要考虑它跟相邻技能的分界线。5.2 场景二技能内容太长把上下文窗口撑爆另一个教训来自数据报表生成技能。为了覆盖所有报表场景我往SKILL.md里塞了大量模板文档将近5000行。结果模型一加载这个技能上下文窗口就剩不下多少空间给实际数据了导致输出报表时频繁截断、丢列。后来我把SKILL.md精简到800行以内只保留场景判断、核心步骤、模板路径完整模板移到assets目录由脚本按需读取拼接不再占用模型上下文。这次改动让我深刻理解了一个原则技能文档越短越好能放在代码里做的事绝不要塞进模型上下文。上下文窗口是Agent最贵的资源任何技能都不该浪费它。5.3 场景三描述写得太抽象召回率骤降我最早写技能描述时有个习惯喜欢用高级词汇比如本技能用于对基础设施的运行状态进行全方位的态势感知与深度洞察。听起来很专业但模型根本不知道什么时候该触发。后来改成大白话当用户提到服务器卡、跑不动、负载高、内存不够用、磁盘快满了的时候使用召回率立刻上来了。原因在于用户的实际表达通常是口语化、碎片化的技能描述越贴近真实用户的语言习惯越容易被模型关联到。写技能描述时要站在用户说话的角度想而不是站在技术架构的角度想。这也是我在给团队做培训时反复强调的一个点。5.4 一个完整的排查链路召回率为什么突然掉了分享一个比较典型的排查过程。当时生产环境某个Agent的技能召回率在一次版本发布后从92%跌到了88%看起来不多但工单量大之后影响很明显。排查链路是这样的先看日志确认模型是否输出技能加载动作。结果发现部分请求根本没触发技能加载。对比模型输出和技能清单发现模型在部分回复中犹豫了很久先长篇分析最后才决定加载技能。继续查发现这次发布给系统提示词多加了一段公司背景介绍文案导致技能清单的位置被顶到了更靠前的地方而技能清单离用户消息越远模型的召回决策就越不稳定。修复方案把技能清单移到系统提示词最末尾紧接着用户消息同时删掉背景介绍里的冗余内容。这个坑本质上还是上下文位置效应在起作用。模型对越靠近末尾的内容越敏感凡是需要模型主动决策的信息都应该尽量靠近末尾。后来我把这条写进了团队规范技能清单始终放在系统提示词最后一段任何人不得在前面插入大段内容。6. 从会用到用得好质量评估与持续迭代6.1 用测试集量化技能效果技能做完不是终点关键要能量化效果。我建了一套评估框架核心是三个指标指标定义计算公式召回率需要该技能的任务中正确加载技能的比例正确加载数 / 应有技能任务数精准率加载了技能的任务中确实需要该技能的比例正确加载数 / 实际加载数完成质量加载后任务结果是否满足预期人工评分或LLM-as-Judge打分操作上我维护了一个测试集每类技能收集30到50条真实用户请求标注应该用哪个技能。每次迭代技能时统一跑一遍测试集对比指标变化。这样改描述、改步骤时心里有数不会出现这边修好了那边又坏了的回归问题。6.2 技能组合的复杂度管理当技能数量超过20个单纯让模型看目录自己选就会遇到瓶颈。这时候要做分层把技能分组比如基础设施组、业务运营组、数据分析组先让模型决定哪一组再在组内选择具体技能。这类似人类专家的知识组织方式——先分科再定位到具体专项。组内技能超过一定数量时还可以做召回改写把用户的模糊描述喂给小模型做query改写再拿改写后的query去匹配技能描述提升命中率。不过这个方案成本较高我建议先把描述优化做扎实再考虑上模型改写否则就是拿大炮打蚊子。6.3 我的最佳实践清单最后给一份可以直接照抄的清单都是我在实际项目中反复验证过的目录结构统一为skills/技能名/SKILL.md加辅助文件不要随意散放。每个新人写技能前先读三遍优秀技能的SKILL.md模仿结构再动手。技能描述写用户会怎么问不写技术上是干什么的。SKILL.md控制在800行以内超过就拆到references目录。技能边界必须写清楚不做什么拒绝动作比接受动作更重要。技能上线前至少跑20条真实测试样本记录三项指标。每次修改后重跑测试集防止指标回归。长期不用的技能要主动下架尽管它看起来没坏但会干扰模型的召回决策。做agent-skills这件事我最大的体会是Agent的能力天花板不取决于模型本身而取决于你给它组织了什么样的知识。技能系统本质上是一种知识工程只是作用对象从人变成了模型。你在给人类新人写SOP时的那些考虑——结构清晰、边界明确、有步骤有依据——放到Agent技能上完全适用。这看起来没那么炫酷但恰恰是让Agent从demo走向生产环境最扎实的一条路。