ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

腾讯云Agent养成记:从超长Prompt到Skills优先的工程化实践

腾讯云Agent养成记:从超长Prompt到Skills优先的工程化实践 在腾讯云上把一个 Agent 从“能聊两句”养成“真能干活”我前后折腾了大半年踩坑比写代码的时间都多。最开始我也迷信一条路子把需求全塞进 system prompt让模型扮演一个什么都会的全能助理。结果 Prompt 超过 2000 字之后模型开始“精神分裂”——它一会儿记得自己是数据库专家一会儿忘掉该调哪个接口甚至同一个问题换两种问法走的是完全不同的两条路。后来我下决心推倒重来换成 Skills 优先的设计Agent 只负责意图理解和调度真正干活的是一个个独立、可复用、可测试的技能模块。这篇文章就是那次重构的完整复盘也是我在腾讯云上跑 AI Skills 的最佳实践总结说白了就三件事底座怎么搭、技能怎么建模、上线之后怎么维护。1. “全能”是个陷阱先分清 Agent 和 Skill再谈养成1.1 一个超长 Prompt 带不来全能反而带来自欺欺人一开始我觉得模型越强Prompt 越长越全能。但真实项目里Prompt 膨胀带来的问题非常具体token 成本变高。每次调用都带着几千字的系统指令还没开始干活就花掉一大截上下文。模型指令遵循能力下降。指令一多模型会开始“选择性失忆”。你让它“必须调用工具”它偏要凭记忆回答因为它觉得自己的知识就够了。没法定位问题。用户说“你答错了”你根本不知道是意图识别错了、参数填错了还是工具返回错了所有逻辑都耦合在一次生成里。没法测试。Prompt 是黑盒改一句话可能引发蝴蝶效应回归测试无从谈起。Skills 的思路其实特别朴素就是把软件工程里“模块化”的思想搬进 Agent把一个大而全的指令系统拆成一组边界清晰的小技能每个技能只做一件事做扎实。后续加新能力不靠加 Prompt靠挂新 Skill。现在市面上的 Agent 框架名字很多什么 pi agent、hermes agent、autogen、langgraph改来改去外壳不同核心骨架都是一样的LLM 决策 工具执行 记忆。与其纠结用哪个框架不如先把这套骨架里的“工具执行”这一层想清楚也就是 Skill 体系怎么设计。1.2 我的理解Agent 是大脑Skill 是手想清楚 Agent 和 Skill 的边界项目才不会烂尾。我用一句话区分Agent 负责“决定做什么”Skill 负责“把事做成”。Agent 是编排层它要做的是理解用户意图、选择技能、提取参数、串联多个技能、组织最终回复。Skill 是执行层一个 Skill 就是一个可复用的工具单元比如“网页摘要”“Excel 处理”“数据库查询”“定时提醒”。Skill 不关心用户为什么需要这个结果只关心输入输出是否正确、是否超时失败。这种划分还有个隐藏好处复用。同一个“网页摘要” Skill既可以被“全能助理” Agent 调用也可以被“竞品分析” Agent 调用。Skill 之间还可以组合比如“抓网页”“转 PDF” 就能拼出一个新的能力不用写新代码。这就像你带一个项目团队Agent 是项目经理他不一定懂每一个技术细节但他知道团队里谁擅长什么Skill 是干活的人接到任务就执行做完交结果。项目经理换了一茬团队能力还在这就是 Skills 优先设计最大的价值。1.3 最小闭环设计一次完整技能调用的生命周期在动手写代码之前我先把一次技能调用拆成了七个环节后面所有开发都围绕这条链路用户输入 → 意图识别 → 技能选择 → 参数抽取 → 技能执行 → 结果校验 → 回复生成每个环节都是独立的这样就能针对性地做日志、超时、重试。比如“技能选择”错了去优化 Skill 的 description“参数抽取”错了去检查 input_schema 的定义“技能执行”超时了去优化实现和网络。如果没有这条链路一切问题都变成“模型答错了”根本无从下手。我还给每个 Skill 挂了三个统一设置超时时间、失败重试次数、异常兜底话术。这三个设置是后面排查“agent execution terminated due to error”这类问题时的救命稻草后面我会专门展开。2. 腾讯云底座搭建容器镜像、Redis 与域名入口2.1 为什么我选轻量应用服务器而不是第一步就上 K8sAgent 项目早期最忌讳过度设计。我见过不少团队上来就上 K8s结果光维护集群就把精力耗光了。我的选择是腾讯云轻量应用服务器2 核 4G系统盘 80G固定带宽。这个配置跑 litellm proxy、Redis、Agent API 服务、一个轻量的向量检索完全够用。选择轻量服务器的几个实际理由成本低按月付费早期迭代频繁随时可以销毁重建。镜像部署简单Docker 装好之后整套环境用一个 docker-compose 就能拉起来。公网 IP 固定后面做域名解析、回调、webhook 都方便。数据量上来、并发真的大了再平滑迁移到云服务器 CVM 或者容器服务 TKE底座代码不需要变。所谓“全能 Agent”前提是底座稳。这一层决定你后续迭代的幸福感。2.2 Docker 镜像推送腾讯云容器镜像服务的完整命令链本地开发完我总是把 Agent 服务打成镜像推到腾讯云容器镜像服务TCR服务器上再拉取运行。这套流程熟了之后上线就是几分钟的事。完整命令链如下在腾讯云控制台开通容器镜像服务创建一个命名空间比如 devops再创建镜像仓库比如 agent-core。在控制台生成一个访问凭证。这里特别注意凭证不是你的登录密码而是一串独立的密钥专供 docker login 使用。本机登录docker login ccr.ccs.tencentcloud.com --username 你的腾讯云账号ID --password 访问凭证给本地镜像打上完整标签docker tag agent-core:0.1.0 ccr.ccs.tencentcloud.com/devops/agent-core:0.1.0推送docker push ccr.ccs.tencentcloud.com/devops/agent-core:0.1.0登录服务器登录同一个镜像仓库拉取并运行docker pull ccr.ccs.tencentcloud.com/devops/agent-core:0.1.0 docker run -d --name agent-core -p 8000:8000 ccr.ccs.tencentcloud.com/devops/agent-core:0.1.0这里有几个坑都是我在实际推送中踩过的tag 必须带完整域名 ccr.ccs.tencentcloud.com否则 docker push 会推到 Docker Hub。私有仓库拉取时服务器上也得先 docker login。最好用--password-stdin从文件读凭证避免 shell history 里留下明文密码。镜像仓库的命名空间要提前规划好临时乱建后面很难整理。我一般按环境分devops测试、release生产。这套链路的好处是本地和服务器环境完全一致Docker 镜像一旦构建好就不会出现“在我电脑上明明能跑”这类问题。2.3 Redis 改密重启失灵的排查全过程这个坑必须单独讲因为太典型了。有段时间我在腾讯云服务器上装 Redis按网上的教程在 redis.conf 里加了 requirepass重启之后客户端一直连接失败。一开始我以为是密码写错了反复改密码、反复重启问题依旧。我停下来开始完整排查过程如下先确认 Redis 本身是否活着redis-cli -p 6379 ping。结果返回 PONG说明进程正常。查看当前生效的配置redis-cli config get requirepass。结果居然是空值。这说明我改的那个配置文件压根不是 Redis 真正加载的文件。查系统服务到底加载了哪个配置systemctl cat redis。问题找到了ExecStart 里写的是/usr/local/redis/redis.conf而我改的是/etc/redis/redis.conf。把 requirepass 写到正确的文件确认没有把配置行注释掉再次systemctl restart redis。用密码验证redis-cli -a 新密码 ping返回 PONG。如果重启后客户端还是拒绝连接还有个常被忽略的坑bind 和 protected-mode。Redis 默认只允许本机连接你如果要让 Agent 服务容器访问它需要把 bind 配成服务器的内网 IP 或 0.0.0.0并把 protected-mode 设为 no同时依赖安全组来限制来源 IP只放行确实需要访问的机器。如果你是用 Docker 跑 Redis还要注意另一件事在容器里手动改配置文件重启容器后会丢失必须通过挂载卷把配置带进去。我后来的统一做法是所有中间件一律用 docker-compose 管理配置文件挂载到宿主机目录这样改配置、重启、迁移都是可重复的。2.4 给 Agent 一个域名入口DNS 解析 Caddy 反向代理Agent 活干得再好也得有一个稳定的访问入口。我不太喜欢裸 IP 访问证书、端口、多个服务的路由都不好管。我选择用二级域名在腾讯云 DNSPod 的控制台添加一条解析记录记录类型选 A主机记录填你想要的二级域名前缀比如 agent记录值填服务器的公网 IPTTL 默认就行等解析生效。然后我用 Caddy 做反向代理它的好处是自动申请和续期 HTTPS 证书配置极简agent.example.com { reverse_proxy 127.0.0.1:8000 }把这段写进 Caddyfile重启 Caddy一个带 HTTPS 的 Agent API 入口就出来了。整个 Agent 对外只暴露 443 端口其余端口全部由防火墙收掉Redis、数据库这些中间件完全不暴露到公网。域名解析本身没什么技术含量但加上 Caddy 这层之后后面挂 Webhook、挂技能回调、按域名分路由都非常方便。3. Skill 的工程化建模从灵光一现到注册表编排3.1 一个 Skill 的身份证Manifest 该怎么写技能如果不建模就只能活在代码注释里。我要求每个 Skill 必须有一个 manifest 文件负责描述自己的身份、能力和入参。下面是我常用的结构。name: web_summarizer version: 0.1.0 description: 抓取指定 URL 的网页正文生成指定语言的中文摘要。 当用户提供链接并要求“总结、摘要、提炼要点”时使用。 如果用户没有提供链接不要使用本技能改为向用户询问链接。 input_schema: type: object properties: url: type: string description: 完整链接必须以 http:// 或 https:// 开头 max_length: type: integer description: 摘要最大字数默认 200 required: - url execute: runtime: http endpoint: http://skill-web-summarizer:9000/summarize timeout: 30这个文件看着简单但每个字段都有讲究。description 尤其关键它面向的不是人而是大模型。模型靠这段文字决定“当前这个用户请求要不要用这个技能”所以一定要写清楚什么时候用、什么时候不用、缺什么参数先提示用户。我见过太多人把 description 写成“网页摘要技能”模型自然选不准。input_schema 必须是严格的 JSON Schema这是模型抽参数的依据。required 字段列出必填参数模型如果发现用户没给就自己补上那后面执行必挂所以必要时要加一个“询问确认”的兜底技能。execute 部分我倾向于用 HTTP 接口而不是直接引用 Python 函数原因很简单技能可以独立部署、独立扩容、独立升级一个技能崩溃不会拖垮 Agent 主进程。技能之间靠接口通信职责边界天然清晰。3.2 用 litellm proxy 统一模型网关让所有 Skill 共享一套 APIAgent 项目跑起来之后你会发现模型不是只有一个简单问题用小模型省钱复杂推理要上强模型某些客户走的是专有模型。如果每个 Skill 都直连各自的模型 API配置散落、密钥混乱、切换模型要改代码维护成本极高。我的做法是引入 litellm proxy 作为统一模型网关。它对外暴露一个 OpenAI 兼容的接口内部通过配置把请求分发到不同模型厂商。用一个 config.yaml 管理model_list: - model_name: fast-chat litellm_params: model: deepseek/deepseek-chat api_key: sk-xxxx - model_name: strong-chat litellm_params: model: tencent/hunyuan-pro api_key: xxxx general_settings: master_key: sk-master database_url: postgres://...在 Agent 代码里你只需要配一个 base_url 指向 litellm proxy模型名在请求里显式指定。换模型、加模型、做模型降级全是网关层的配置变更业务代码一行不动。关于 litellm proxy 的最佳实践我总结三条密钥集中管理。所有上游模型的 api_key 只出现在服务器上的 config.yaml 和环境变量里不能进代码仓库。给不同的业务模块分不同的 key 或标记方便在网关层做配额和用量统计。配置 fallbacks 列表主模型超时或报错时自动降级到备用模型这个对稳定性提升非常明显。3.3 从 if-else 路由到模型自主决策技能少的时候很多人会直接写关键词路由URL 里面有 http 就调用网页摘要提到“数据库”就查询数据库。技能超过五个这套就崩了关键词重叠、用户表达千奇百怪、还要维护一堆优先级。我后来的做法是把所有 Skill 的 manifest 转成模型工具调用格式在每次用户请求时把工具列表发给模型让模型自己决定调哪个技能、参数怎么填。核心是把“路由逻辑”从代码里交还给模型。为了适配这个机制我会把每个 manifest 的 name、description、input_schema 映射成 tools 数组里的一个对象模型拿到这个数组后输出结构化的 tool_call 指令Agent 再根据指令去调 skill-executor。但这里有个很容易被忽略的点工具描述的质量直接决定路由准确率。我给每个技能写 description 时会刻意加入负面提示比如“如果用户没有提供链接不要使用本技能”。负面提示能显著降低误调用率。还有一个兜底策略所有技能都不匹配时不硬猜而是调用一个“澄清问题”技能让模型向用户确认意图。这个小的设计把很多隐性问题挡在了外面。3.4 Skill 的复用与演进有了技能注册表之后加一个新能力就变成三步操作实现逻辑、写 manifest、注册并测试。如果新需求和已有技能是组合关系比如“网页摘要发送邮件”我倾向先用编排层把两个已有技能串起来而不是立刻写一个新技能。这样技能数量增长会变慢但每个技能的复用率和稳定性都在提升。我还会给 manifest 加 version。注册表在服务启动时加载技能实现了新旧版本兼容就可以灰度替换。这套做法说穿了就是把代码开发里的“接口 实现 版本管理”平移到了 Agent 领域没什么玄学但极其管用。4. 记忆不是 Redis 的 TTL让 Agent 在多轮对话中真的记得住4.1 短期记忆会话窗口与 Redis 过期策略很多 Agent 产品给人“没脑子”的感觉根本原因是没有记忆设计。我最开始也把多轮历史一股脑全塞进模型上下文结果 token 消耗暴涨且对话一长模型就开始丢前面的信息。后来我按短期记忆和长期记忆分开设计。短期记忆用 Redis 存。每一轮会话都生成一个 session_id把用户输入、Agent 决策、技能调用、最终回复以 JSON 结构推到 Redis 的一个 Hash 里过期时间我一般设 2 小时。每次请求到来时取出最近 N 轮存成消息列表再叠加一个“历史摘要”。这里的关键是滑动窗口摘要超过窗口的旧消息不再逐字保留而是交给模型压缩成一两句话的摘要替代原始内容。这个机制保证上下文不会无限膨胀同时模型又能感知到更早的对话走向。Redis 的过期机制天然适合做短期记忆记得给 key 设计命名空间比如agent:{session_id}:history方便批量清理和统计。4.2 长期记忆向量化沉淀与召回短期记忆过期就没了但用户偏好、身份信息、历史结论这些不能丢。我做的长期记忆是一套“结束时的沉淀机制”在一轮对话结束时让 Agent 判断这一轮有没有值得记住的信息有就生成结构化记忆条目内容包括时间、来源技能、记忆类型偏好/事实/任务、内容本身然后转成向量写入向量检索服务。下次会话启动时先用当前请求内容做向量召回把最相关的历史记忆拼进 system prompt。这样 Agent 面对老用户时会表现得像真的“记得你”。这套设计有几个细节很值得注意不是所有信息都值得记。一定要让 Agent 先做“值得记忆”判断否则向量库里全是垃圾。记忆要带 metadata方便按用户隔离、按时间过滤。召回结果要重排序只取 topK避免历史干扰当前意图。定期清理低质量记忆。我每个月会跑一遍把“已过期的事实”比如一次性任务的记录清掉。所以记忆不是把 Redis 的 TTL 调长一点那么简单它是“筛选 - 存取 - 召回 - 更新”的闭环。4.3 一次 “agent execution terminated due to error” 的排查实录这个报错我见过太多次了很多新手看到就慌以为是 Agent 框架崩了。其实这个提示只是一个总入口真正的错误藏在日志里。我用一次真实的排查过程来说明。现象Agent 在执行“周报生成”时中断页面返回 “agent execution terminated due to error”。第一步我去看 Agent 服务的完整日志找到报错发生的位置。日志显示错误来自 skill-executor 模块是技能内部抛出的异常而不是模型调用失败。这一个信息就砍掉了一半排查方向。第二步进入 skill-executor 容器查看它近期的运行日志发现它在调用一个外部“日程数据”接口时抛了 TimeoutError。第三步我在服务器上手动 curl 那个接口发现响应正常。那问题就在容器内部目标接口的 IP 解析慢加上代码里超时时间设置只有 3 秒于是偶发超时。第四步我把所有技能的外部调用统一改成连接超时 3 秒、读取超时 10 秒并加上指数退避重试最多重试 2 次。改完之后这个报错基本没有再出现。这段排查给我的最大教训是Agent 的执行链路必须分层隔离异常。模型调用失败、技能执行失败、编排逻辑失败最好用不同的异常类否则所有错误揉在一起排查成本会直线上升。5. 从能跑到能扛测试、稳定性与成本控制5.1 自动化测试把 Agent 的决策过程变成可断言的用例Agent 是最难做测试的软件之一因为它有随机性。但并不是不能测。我把测试分成三层第一层是路由测试给定一个用户输入断言模型选中的技能是正确的。这一层直接校验模型对技能 description 的理解。第二层是参数测试给定输入和已选技能断言模型抽出的参数是否符合 JSON Schema。第三层是执行测试直接调用 skill 服务传入各种边界参数断言输出格式和错误处理是否符合预期。路由和参数测试可以 mock 掉 LLM让模型返回固定输出从而实现完全确定性的回归。我留下一批 golden tests专门在改了技能描述或者加了新技能之后跑一遍防止旧能力被改坏。执行测试则跑真实代码重点测超时、空输入、上游异常等边界场景。三层测试加起来Agent 的上线信心就是这么来的。5.2 模型降级与响应缓存省钱和求稳可以同时实现Agent 跑进生产环境钱就开始流了。我控制成本主要靠两个手段。第一个是模型降级。litellm proxy 里给每个模型配一个 fallback比如 fast-chat 主模型挂了自动切到备用模型。同时把触发条件写清楚连接超时、5xx 错误才切换不是所有异常都切。第二个是响应缓存。对完全相同的用户请求相同模型、相同参数直接把上一次的模型响应从 Redis 返回不再真正调用模型。这个精确缓存实现简单收益却不小。我实测在测试和演示阶段缓存命中率能达到三成左右。如果要想更进一步可以做向量语义缓存但工程复杂度会明显上升不建议早期就上。另外还有一个容易被忽略的成本点token 统计。litellm proxy 能把每次请求的输入输出 token 都记录到数据库我每周看一次报表哪个技能消耗大、哪个技能经常让模型重试一目了然这些都是后续优化技能实现的线索。5.3 上线后的迭代节奏最后聊聊迭代节奏。我的经验是先加 Skill再调 Prompt最后才动框架。每接到一个新需求先判断能不能用已有技能组合实现缺能力了再写新技能技能描述导致选错再去调 description。不到万不得已不去改 Agent 主架构。我每周还会做一次决策回放把这一周真实用户请求里 Agent 的选择结果拉出来标记出选错技能的样本统计错误模式。比如某几类请求频繁被路由到同一个错误技能那就说明那个技能的 description 有歧义或者 input_schema 缺了典型参数。这个回放机制比任何性能监控都更能反映 Agent 的真实健康度。“全能 Agent”不是一天养成的它是一个不断长技能、调边界、做取舍的过程。把这套迭代节奏跑顺了Agent 才会从“玩具”变成“生产力工具”。从一台腾讯云轻量服务器把整套 Agent 跑起来到现在已经稳定运行了几个月。我最大的体会是所谓“全能”靠的从来不是某一个模型的智商而是背后这套技能生态的完整度底座稳技能边界清记忆闭环测试兜底。如果你正准备在腾讯云上搭自己的 Agent别一上来就追求大而全先把一个 Skill 从设计、部署、测试、复盘完整走一遍再把这套方法复制到第二个、第三个技能上。“全能”不是终点是不断往架子上挂新工具的过程架子搭对工具只会越挂越顺手。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进