前阵子帮一个团队搭建基于腾讯云的智能体项目,折腾到凌晨才发现,问题根本不在模型选择,而在 Agent 本身:它什么都会一点,却什么都做不精。你让它做代码评审,它只能泛泛说“建议加强异常处理”;你让它查一段变更的危害模式,它甚至不知道从哪个配置文件入手。后来我们换了一种思路,把“领域知识 + 作业规范 + 可执行动作”打包成一个个 AI Skills,挂载到 Agent 上,整个系统的可用性一下就上来了。这篇文章就是我在这套实践中沉淀下来的完整方法论,主题围绕 Agent、腾讯云和 AI Skills 的落地配合,适合正在做智能体应用、或者想把自己手头重复性专业工作“教”给 Agent 的开发者。
1. Agent 失灵时,缺的不是模型而是“专业动作包”
1.1 “什么都知道”和“能把事做对”是两回事
先讲一个反复出现的现象。很多人刚开始做 Agent,以为塞一个大模型进去,再给它一堆工具列表,它就能变成全能助手。实际上模型只是推理引擎,它最擅长的是“对已知知识进行组合”,但到了具体业务现场,专家靠的不是通用推理,而是那些被验证过无数次的动作套路:先看什么、按什么顺序查、哪些指标必须满足、出现哪种信号要立刻停止。这些套路无法只靠提示词一两句话表达清楚,更不可能靠模型临时“想出来”。
我经常把这个区别类比成新人入职。新人聪明、学得快,但如果公司没给岗位说明书、操作手册和检查清单,他只能凭常识做事,结果就是看起来在努力,产出却没法用。AI Skills 就是给 Agent 发的“岗位说明书 + 操作手册 + 检查清单”,让它不只拥有通用智能,还拥有某个专业方向的可执行能力。
1.2 Skill、Agent、Workflow 的分工边界
项目里经常有人把 Agent、Skill、Workflow 混着用,导致架构混乱。用我的话给它们划一条清晰边界:
- Agent:决策主体,负责理解目标、规划步骤、调用资源,是“大脑和中枢”。
- Skill(技能包):一组可被 Agent 按需加载的专业能力,是“专业知识胶囊”,不强调固定流程,更强调“遇到这类任务时你会怎么做”。
- Workflow(工作流):已经被固化成明确步骤的流程,状态流转清晰,一般不需要模型参与判断,是“流水线”。
Skill 和 Workflow 的核心区别在于:Workflow 是“按图索骥”,Skill 是“授人以渔”。Agent 加载了一个 Skill 之后,仍然需要结合实际任务现场做判断,只是它的判断边界被 Skill 限定到了正确轨道上。而腾讯云在整条链路中的角色,则是给 Skill 提供远程执行环境、文件读取能力、持久化上下文和稳定可回滚的发布通道。
1.3 为什么选择腾讯云承载这套动作包
有人问:Skill 为什么不能全部放在本地,或者简单塞进模型上下文里?能,但你会发现两类问题。
第一类是内容越来越大。一份真正可用的技能包包含说明文档、参考模板、检查脚本、样例输入输出,动不动几百甚至上千行。如果每次对话都把它完整塞进上下文,推理成本会飞涨,模型注意力也会被噪声干扰。正确做法是让技能包“就近存放、按需加载”。
第二类是 Agent 需要动手做事。比如调用业务系统、扫描代码目录、读取文件、写审计记录。这些动作必须有一个运行环境。腾讯云在这类场景里的优势是基建完整:对象存储存储技能包与历史记录,云函数托管无状态执行逻辑,API 网关提供统一 HTTPS 入口,再加上版本管理和多种鉴权能力,能让我们把专业技能从“文字描述”升级为“可调用服务”。
2. 从零搭一个能落地的技能包:目录结构决定 Agent 的执行精度
2.1 技能包的标准文件树与命名规范
任何 Skill 在上云之前,先得保证它在本机是一个结构清晰的目录。我团队目前沿用的推荐结构是:
review-assistant/ ├── SKILL.md ├── scripts/ │ ├── scan_patterns.py │ └── collect_metadata.py ├── assets/ │ ├── review_templates/ │ │ └── concise_zh.md │ └── samples/ │ ├── input_1.py │ └── output_1.md ├── references/ │ ├── dangerous_patterns.md │ └── coding_standards.md └── version.txt几个关键目录的职责要分清楚:
SKILL.md:技能包入口,也是 Agent 判断“要不要加载这个技能”的第一依据。scripts/:可执行脚本,负责做 Agent 不擅长的事,比如遍历文件、算统计量、提取结构化信息。assets/:模板和样例,给 Agent 提供“照着写出什么样子”的参照物。references/:参考资料,只有当 Agent 进入这个技能的执行上下文才需要读取。
命名上我建议遵循领域-动作的格式,比如review-assistant、weekly-report-builder。这样做的好处是当 Agent 装了十几个技能包时,技能索引依然清晰,触发命中不会混乱。
2.2 用 SKILL.md 说清“什么时候用、怎么用、不能用”
SKILL.md 的编写质量,几乎决定了技能包的上限。一份负责的 SKILL.md 至少应该包含元信息区与正文区,下面是一个简化范例:
--- name: review-assistant description: 当用户要求做代码评审、检查代码变更质量、交付前审查时使用。适用于 Python/TypeScript 项目。 version: 1.4.0 author: your-team ---description字段是最容易被低估的。它不是给你自己看的,而是给 Agent 做技能路由用的。写得越精确,Agent 才越知道什么场景该调用,什么场景不该调用。我见过很多技能没被触发,原因不是功能不好,而是描述里写的是“这是一个代码评审技能”,而不是“当用户要求审查一段变更时,使用这个技能”。
正文区我会固定写出这些内容:
- 技能目标与适用场景;
- 完整操作步骤列表;
- 每一步的输入来源;
- 明确的停止条件或禁用边界;
- 至少一个输入输出示例的索引。
最关键的技巧是:一定要写清“不能做什么”。Agent 是推理模型,给它一条边界模糊的技能,它很容易在不该用的地方生搬硬套。比如代码评审技能里明确写一句“本技能不适用于生成新代码,只负责审查已有代码”,能省下大量后期纠偏成本。
2.3 可执行脚本是技能包的“手”
Skill 如果只有文字描述,本质上还是一堆提示词,Agent 仍然会空想。为了让 Agent 真正“动手”,我习惯把机械性工作抽成 Python 脚本。以代码评审场景为例,最容易自动化的是危险模式扫描,比如搜出代码里的TODO、FIXME、调试打印、硬编码密钥、可疑的空异常捕获。把这些模式写进脚本,由脚本返回结构化结果,Agent 再有针对性地做深度审查,效率和准确性都能提升。
2.4 参考资材与样例输出:让 Agent 照着样子做
大模型在开放式输出时质量不稳定,但在“给定参照物”的时候通常表现得不错。所以在技能包里放足够的质检模板与样例输出是性价比很高的一件事。
我们在assets/samples/下放一对“输入变更文件”和“期望审查结果”。这样子 Agent 执行前会先读样例,模仿里面的语气、结构和判断粒度,而不是每次生成一套风格完全不同的审查报告。另一个容易被忽略的点是:样例应当同时包含“好样例”和“坏样例”。给模型看一个优秀输出它会模仿,再给一个踩坑错误输出它能避开同类问题。
3. 发布到腾讯云的三种姿势:静态包、云函数工具与混合接线
3.1 哪些技能适合放对象存储,哪些必须上云函数
技能包发布到腾讯云以后,才真正具备“团队共享、随时调用”的价值。根据技能类型不同,我们一般有三种承载方式:
| 承载方式 | 适合场景 | 优点 | 注意点 |
|---|---|---|---|
| 对象存储(COS)静态托管 | 纯知识型、模板型的轻量技能 | 成本低、读取快、更新简单 | 没有计算能力,Agent 只能“读”不能“跑” |
| 云函数 + API 网关 | 需要执行脚本、访问外部数据的技能 | 无服务器、弹性扩缩、便于暴露稳定接口 | 需要关注超时与鉴权配置 |
| 对象存储 + 云函数混合 | 既有大段知识库又有工具动作的重技能 | 知识放存储,动作走函数,分层清晰 | 需要维护双方同步关系 |
第一个实践项目中,很多技能只要能做成“静态包”就不急着上云函数,因为静态包更新成本低,拖个文件到存储桶里就发布完了。只有技能里明显包含“不可由大模型凭空完成的动作”时,才值得引入云函数。
3.2 云函数包装“变更文件与危险模式扫描”
下面分享一个我已经在用的云函数雏形,作用是给代码评审技能提供一个“远程执行动作”:按仓库名扫描指定目录,返回危险模式命中行。我选择直接使用腾讯云函数 SCF 来承载它。
# index.py import json import os from pathlib import Path BASE_DIR = Path(os.environ.get("REPO_BASE_DIR", "/data/repos")) DANGEROUS_PATTERNS = [ (r"TODO|FIXME|XXX", "known_todo"), (r"print\(|console\.log", "debug_print"), (r"password\s*=\s*['\"][^'\"]+['\"]", "hardcoded_password"), (r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "hardcoded_api_key"), (r"except\s*:\s*(#.*)?$", "bare_except"), ] SKIP_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build"} SKIP_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".lock", ".pdf"} def scan_file(path): hits = [] try: lines = path.read_text(encoding="utf-8", errors="ignore").splitlines() except Exception: return hits for lineno, line in enumerate(lines, start=1): for pattern, category in DANGEROUS_PATTERNS: import re if re.search(pattern, line): hits.append({ "file": str(path.relative_to(BASE_DIR)), "line": lineno, "content": line.strip()[:120], "category": category, }) break return hits def scan_repo(repo_name, rel_path=""): root = BASE_DIR / repo_name if not root.exists(): return {"ok": False, "error": f"repo {repo_name} not found"} target = root / rel_path if rel_path else root if not target.exists(): return {"ok": False, "error": f"path {rel_path} not found"} hits = [] if target.is_file(): hits.extend(scan_file(target)) else: for path in target.rglob("*"): if not path.is_file(): continue if any(part in SKIP_DIRS for part in path.relative_to(target).parts): continue if path.suffix in SKIP_EXTS: continue hits.extend(scan_file(path)) return {"ok": True, "count": len(hits), "hits": hits[:200]} def main_handler(event, context): try: body = json.loads(event.get("body") or "{}") repo_name = body.get("repo_name", "") rel_path = body.get("rel_path", "") except json.JSONDecodeError: return {"statusCode": 400, "body": json.dumps({"ok": False, "error": "invalid json"}, ensure_ascii=False)} if not repo_name or "/" in repo_name or ".." in repo_name: return {"statusCode": 400, "body": json.dumps({"ok": False, "error": "invalid repo_name"}, ensure_ascii=False)} result = scan_repo(repo_name, rel_path) return { "statusCode": 200, "headers": {"Content-Type": "application/json"}, "body": json.dumps(result, ensure_ascii=False), }这里说三个设计细节:
- 仓库名做了严格校验,拒绝路径穿越。云函数会被 API 网关暴露到公网,入参不可信是第一原则。
- 扫描结果的
count字段很重要。Agent 拿到它之后,能快速判断这次变更的整体风险密度,而不用逐条通读 200 条命中记录。 - 返回内容里中文与特殊字符使用
ensure_ascii=False输出,避免 Agent 读到一堆\uXXXX转义,影响可读性。
3.3 API 网关配置要点:只暴露该暴露的“那扇门”
云函数开发完,还需要通过 API 网关把它变成可访问的 HTTPS 接口。配置的时候,我踩过几个值得你避开的坑:
- 函数超时时间不要用默认 3 秒,扫描代码目录很容易超时。我通常会调大到 30 秒,超时时间本身也要写进技能包的说明里,不然 Agent 遇到超时不知道是网络问题还是代码问题。
- 接口路径不要裸放。先配置鉴权,可以用腾讯云 API 网关的密钥签名,也可以附加一层自定义请求头校验。技能包与云函数之间约定一个很隐蔽的请求头,成本最低,但能挡住大量扫描流量。
- 很多新人会用云服务器开放端口来跑这种服务,然后为了出网把安全组改成 0.0.0.0/0 全放通。这是很危险的。云函数这类无服务器方案天然回避了“开放所有端口”的问题,因为公网入口只有 API 网关这一扇门,安全边界清晰得多。如果技能确实需要运行在其他端口,也只应在安全组中放通特定来源 IP。
API 网关默认会生成一个绑定 HTTPS 的调用域名,形如:
https://<service-id>-<random>.ap-shanghai.apigateway.myqcloud.com/review/scan我们团队自己用的时候,还额外申请了二级域名并绑定过来,因为默认域名太长,Agent 在记录和引用时容易截断或抄错。绑定自定义域名时记得同步上传 HTTPS 证书,证书过期往往是最隐蔽的故障源。
3.4 本地调试与云端联调的配合
Skill 上云以后,我会同时维护两种调用方式:本地直接用 Python 函数模拟,云端则通过 curl 验证真实链路。一个常用的 curl 调试命令如下:
curl -sS https://<your-api-domain>/review/scan \ -X POST \ -H "Content-Type: application/json" \ -H "X-Skill-Token: your-token" \ -d '{"repo_name": "demo-service"}'调试时建议先拿真实样例测一遍,确认返回结构和 SKILL.md 里描述的一致。多一个字段、少一个字段都会影响 Agent 后续判断。返回的 JSON 结构一旦确定下来,尽量不要随意变更,如果非变不可,技能包版本号要同步升级。
4. 让 Agent 用对技能包:接入层的几种走法与触发条件调优
4.1 姿势一:轻技能直接注入上下文
接入方式的选择逻辑很简单:技能包里到底有没有不可替代的“执行动作”?
如果技能只是知识型,比如“按这个模板写周报”“按这套标准做产品文案检查”,那不需要额外发 HTTP 请求。这类技能可以直接把SKILL.md中的核心内容注入系统提示词,或者由 Agent 框架在检测到任务类型时动态加载对应技能的 Markdown 内容。把技能包放在对象存储里,就是为了让这份加载动作可以远程完成,Agent 不用把几百个技能全部塞进上下文,只需要在需要时拉取一份。
这种姿势的优点是快,缺点是上下文占用高。所以技能本身的 Markdown 要克制,只保留步骤、边界、样例引用,不要把所有参考资料都堆进去。
4.2 姿势二:把技能包装成可调用工具
像前面那个云函数,必须走“工具调用”路线。常见实现方式是把函数封装成 Function Calling 格式,或通过 MCP 协议暴露给 Agent 框架。接入之后,Agent 的行为模式变成了:先判断用户意图属于代码评审场景,然后调用scan_repo工具获取危险模式,再基于命中结果结合参考资料生成最终审查结论。
这个过程中有一个关键习惯:工具返回的数据,要能在二次调用中被原样传递。比如扫描结果里包含命中的文件与行号,Agent 如果觉得需要更深分析,可以把这个路径作为参数传给下一个工具。所以每个工具函数的入参、出参设计,都要考虑多个 Agent 回合的连续性,而不是只服务单次调用。
4.3 描述越精确,技能才越容易被“看见”
要让 Agent 准确调用某个技能,参数往往不是模型问题,而是技能描述问题。我总结了一个触发条件自查表:
| 自查项 | 错误写法 | 正确写法 |
|---|---|---|
| 触发场景 | 代码评审技能 | 当用户希望审查代码变更、检查提交质量、或发现安全隐患时使用 |
| 数据要求 | 获取仓库扫描结果 | 当需要扫描代码中危险模式时调用 scan_repo 工具 |
| 禁用条件 | 不适用于生成代码 | 如果用户仅要求生成新代码,不要使用本技能 |
| 输出格式 | 输出报告 | 输出必须包含摘要、风险列表、按严重程度排序的建议 |
这个自查表帮助我们在项目里解决了很多“Agent 就是不调用技能”的困惑。模型不傻,只是它不知道自己手里这把刀应该何时出鞘。
4.4 错误返回也要“面向 Agent 设计”
工具调用一定会出错。最常见的错误包括:文件不存在、仓库名称拼写错误、云函数超时、网络抖动。
这里的经验是:云函数返回错误时,不要只丢一个 HTTP 500。Agent 接到的信息越结构化,它恢复的能力越强。我在所有自定义函数里统一使用如下错误结构:
{ "ok": false, "error_code": "repo_not_found", "message": "仓库 demo-service 不存在,请检查 repo_name", "suggestion": "从以下仓库中选择:svc-payment, svc-order" }加了suggestion字段以后,Agent 往往能自己修正参数并重新发起调用,不再动不动就向用户摊手说“系统出错了”。这种“让 Agent 能自愈”的设计,复杂度不高,但对整体体验提升非常明显。
5. 从小技能到“全能”Agent:记忆、状态治理与失控兜底
5.1 让技能包拥有记忆:历史结果沉淀进存储
一个技能如果每次调用都是无状态的,做一次需求分析、输出一份结论,记录没保存,那它就很难和用户形成持续合作关系。真正“全能”的 Agent 需要拥有对这个技能执行历史的记忆。
我们把每次技能调用产生的结构化摘要写进对象存储,文件格式统一为 JSONL,每条记录包含时间戳、入参摘要、调用结果、输出评估。下次 Agent 执行同类任务时,可以先花一次工具调用读取最近的历史记录,参考上一次的输出口径,再开始新的工作。这比把所有历史都塞进对话上下文要便宜得多,也让技能具备了一种轻量级长期记忆。
这份历史数据还能用来做技能质量回溯。某天用户投诉输出质量下降,可以直接查对应日期的 JSONL 文件,看是哪一次提示词调整或参考模板更新导致的行为漂移,改回去就好。
5.2 让 Agent 的出错率可控:结构化输出与重试策略
大模型输出天然有不确定性,尤其是让 Agent 直接生成可解析表单、代码或长报告时,偶尔会出现结构断裂。为了控制出错率,技能包里我会强制要求“先输出摘要表,再输出明细”。摘要表通常是一个包含固定字段的 JSON 对象,就算后面正文内容再乱,程序层也能把它抓出来渲染。
更关键的是在 Agent 侧做自动重试。比如前面云函数偶发超时,我们会配置“最多重试三次,每次等待递增”,同时在技能说明里提示 Agent:如果第一次调用失败,等待数秒后重试再发起。这种做法和人工排查问题的思路一模一样,重要的是别让 Agent 遇到失败就地弃权。
5.3 Agent 使用技能时的安全边界
技能包把模型、代码、云资源连接起来后,攻击面也随之扩大。必须建立几条安全铁律:
- 密钥不进技能包。云函数需要的 API 密钥、数据库账密一律从环境变量或凭据管理系统读取,SKILL.md、引用资料、样例代码中禁止出现明文凭据,仓库里也要做自动扫描。
- 入参必须校验。云函数无论是否部署在公网,都默认当作公网接口来设计。路径穿越、命令注入、超大请求体这几种风险都要提前防。
- 危险动作要人工确认。删除、批量发送、扣费、变更线上配置这些动作,不应该由 Agent 自行完成。项目里我建议通过 webhook 把待确认内容推到个人或团队的 IM 工具,等人工点击放行后再继续执行。这一步听着麻烦,但它能让技能放开手脚去干活的同时,始终保留一条可控的底线。
5.4 版本管理:让每次技能升级都可回滚
腾讯云函数的版本与别名机制可以很好地解决发布问题。我在团队中遵循以下流程:
# 技能代码与 SKILL.md 一并打 tag git tag review-assistant-v1.4.0 # 构建并发布到云函数作为新版本 scf deploy --target-version 1.4.0 # 将线上别名指向稳定版本 # 先用测试别名指向新版本,验证通过后再切换线上别名这样做的意义在于:一项技能从“更新代码”到“影响用户”之间,隔了一道可逆的闸门。一旦新版本的输出风格、工具参数、提示词微调出了问题,我们可以让线上别名秒级回滚到旧版本,而不用重新改代码。
6. 我踩过的坑和现在仍在用的团队约定
6.1 四个印象最深的坑
第一个坑:把所有技能内容塞进 System Prompt。早期为了让 Agent “记住”技能,我把一份几千字的 SKILL.md 直接粘贴进系统提示词。结果模型上下文被大量规范占据,简单任务也开始啰嗦,调用响应变慢。后来改成按需加载后,效果立竿见影。
第二个坑:描述里只写“何时用”,不写“何时不用”。某个发版分析技能因为没有写禁用条件,Agent 在一个明显不相关的话题里硬套分析框架,生成了很长的噪声内容。写技能文档时,边界条件要和操作步骤一样重要。
第三个坑:不发样例就能 AI 自己“意会”。模型对很多任务的理解和你不一样。你以为输出格式很自然,它却真的能写跑偏。只要在技能包 assets 里放一对输入输出样例,立刻能看到效果改善。
第四个坑:云函数默认配置直接上线。我第一次部署扫描函数时没调超时,代码目录稍微大一点就返回超时,Agent 反复报错。后来才意识到,云函数超时、并发、内存这些参数,必须依据技能的实际负载来做配置,并在测试阶段把边界场景连同异常一起验证。
6.2 沿用下来的团队约定
现在团队每次新增或者修改技能,都会强制走一套固定动作:先写或更新 SKILL.md,补上精确的触发与禁用条件;准备至少一组端到端测试输入和预期输出;在测试环境用 Agent 实际跑通三次,记录成功率和失败原因;发布打 tag;生产环境通过版本别名灰度放量。
技能并不是越多越好。一个 Agent 装了几百个技能,真正的效果往往不如只装十个“用得深、边界清、质量高”的核心技能。我始终建议团队把精力聚焦在最痛的那几个场景上,让每个技能都经得起真实业务的反复锤炼。AI Skills 方法论本身并不复杂,复杂的是一遍遍打磨技能边界、样例、错误处理和安全机制的过程。把这些基本功做扎实,“全能 Agent”就不再只是一句口号。