腾讯云 AI Skills 实战:构建全能 Agent 的完整指南

腾讯云 AI Skills 实战:构建全能 Agent 的完整指南 把 Agent 从“能聊天的 demo”养成一个“能自己接活、自己干活的全能选手”核心不在模型选得多大而在于你把工具、记忆、编排这些外围能力设计得多扎实。这篇文章我想把在腾讯云上用 AI Skills 做 Agent 的整套思路和踩坑记录整理出来从概念拆解到代码实现到最终上线一条线走完适合刚接触 Agent 开发、想直接落地的朋友也适合被“工具调用不稳定、Skill 老写废”坑过的人翻一翻。先说结论Agent 开发最容易被忽略的不是 Prompt 写得好不好而是“能力边界”怎么划。你会看到很多人把 Agent 做成一个到处接 API 的聊天框结果一上真实业务就崩。AI Skills 恰恰是用来解决这个问题的——它把零散的工具调用、知识检索、业务操作封装成 Agent 可以理解和执行的“技能单元”。配合腾讯云的服务器、容器、镜像服务这些基础设施整个 Agent 从开发到部署的链路可以非常顺。这篇分享不求把所有 Agent 框架都讲一遍只围绕我实际跑通的一套方案FastAPI 做服务骨架Redis 存记忆LiteLLM Proxy 做模型网关自研 Skill 注册表管工具最后用 Docker 推到腾讯云容器镜像服务再用二级域名暴露出去。每一步我都会说清楚为什么这么选以及哪些地方容易翻车。1. 先把概念捋清Agent 到底是个什么东西1.1 别再做“会聊天的机器人”了很多人一上来就把 Agent 等同于“接了大模型的聊天接口”。这其实是最大的误区。聊天机器人只有“对话”这一种交互而 Agent 的核心是“目标驱动的自主执行”。它得能理解用户想要什么结果把目标拆解成步骤调用外部工具拿到中间结果再决定下一步做什么直到任务完成或者明确告诉用户做不了。我在设计时给 Agent 定了三个基本能力规划Planner、记忆Memory、执行Execution。规划负责把任务拆成步骤记忆负责跨轮次保留上下文执行负责真正调用工具和业务接口。AI Skills 在这里扮演的角色就是把“执行”这一层做成标准化模块。如果你把这三个能力拆开看会发现很多 Agent 项目失败的原因非常一致规划太弱任务一复杂就乱记忆太浅多轮对话上下文对不上执行太脆工具一报错整个流程就中断。所以这篇文章后面所有内容我都是围绕“怎么把这三块做稳”来展开的。1.2 Skill 不是插件是 Agent 的能力封装Skill 这个词在不同框架里叫法不一样有的叫 Tool有的叫 Plugin有的叫 Action但本质都差不多它是 Agent 可以调用的一项具体能力比如“查天气”“创建工单”“执行 SQL”“搜索知识库”。为什么我强调用“Skill”而不是“Tool”因为 Tool 往往被人理解成一个单纯的函数接口而 Skill 更强调“完整的可用性”——它不只是暴露一个函数还要包含参数说明、触发条件、错误处理、返回格式甚至一小段调用指引。打个比方Tool 像一把螺丝刀你给 Agent 你就能拧螺丝Skill 像一套完整的维修流程Agent 知道什么情况下用螺丝刀、什么情况下用扳手、拧不动时怎么处理。两者都能干活但后者才是能上生产环境的东西。所以在设计 Skill 时我给自己定了几个硬性标准第一每个 Skill 必须有明确的输入输出契约第二Skill 内部必须捕获异常并把错误转成 Agent 能理解的语言第三Skill 要有幂等性执行失败重试时不能产生脏数据第四能并行的 Skill 尽量并行不能并行的一定在描述里写清楚依赖关系。1.3 harness 和 Agent 到底啥关系热搜词里很多人搜“harness 和 agent 的区别”我多说一句。Harness 是跑 Agent 的那个“外壳”它负责整个循环的控制怎么启动、每轮怎么调用模型、工具返回后怎么继续、达到什么条件停止、中途出错怎么恢复、日志怎么记录。Agent 本身是“决策大脑”它产出的是意图和动作序列但真正让这些意图跑起来的是 harness。我最早犯的错就是把 Agent 逻辑和 harness 逻辑全写在一起结果代码越写越乱想单独调试工具调用都没法下手。后来我把 harness 和 Agent 拆开harness 只管流程控制和状态管理Agent 只负责根据输入输出决策Skill 层负责真正执行。这样每一层都可以独立测试出了问题也容易定位。这个分层思路也是后续所有代码结构的基础。2. 腾讯云上搭建 Agent 基座的选型思路2.1 服务器还是容器按场景选腾讯云上跑 Agent最基础的问题是选什么载体。如果只是自己测试一台 2 核 4G 的 CVM 就够用装好 Python、Redis、Docker把服务直接跑起来。但如果想做成一个可以交付的产品我更推荐从第一天就用容器化——因为 Agent 服务的依赖特别容易膨胀模型 SDK、向量库客户端、各类业务库、配置文件手动维护环境迟早要出问题。我这次的做法是开发阶段在 CVM 上装 Docker用 docker-compose 同时拉起 Agent 服务和 Redis上线前把 Agent 服务打成镜像推到腾讯云容器镜像服务TCR再从 TCR 拉取到生产服务器运行。这样开发、测试、生产三套环境保持一致的依赖几乎不会出现“在我机器上是好的”这种问题。选 CVM 还是 TKE 容器集群取决于你的并发量。个人项目和中小业务单机 Docker 足够成本低、运维简单如果预期并发高、需要弹性伸缩再考虑 TKE 或者 Serverless 容器。我的个人建议是别一开始就上 K8sAgent 本身的调度逻辑还没跑稳先别让基础设施的复杂度拖后腿。2.2 先给 Agent 配好记忆和环境依赖Agent 的记忆模块我选 Redis原因很简单快、简单、几乎所有云服务器都能轻松部署。这里说的记忆不只是存聊天记录我分了三个层次短期记忆存当前任务的中间状态用 Redis 的 String 结构带 TTL会话记忆存多轮上下文用 List 或 Hash 存按会话 ID 隔离长期记忆存用户偏好和历史结论用 Redis 加前缀区分 key。如果你做的是知识库增强的 Agent那还要在 Redis 之外引入向量数据库比如腾讯云向量数据库或者开源的 Milvus、pgvector。这一层不是必须的取决于你的 Agent 需不需要“查资料”这个能力。至少在我这套基础版 Agent 里Redis 已经能解决 80% 的记忆需求。环境依赖上有一点特别提醒Python 的依赖尽量锁定版本尤其是 pydantic、openai、fastapi 这几个库版本一漂移很容易出现不可名状的报错。我习惯用pip freeze requirements.txt生成锁定文件Docker 构建时用这个文件安装依赖能省掉很多莫名其妙的问题。2.3 统一模型网关LiteLLM Proxy 值得加如果你只需要接一个模型服务那直接用官方 SDK 就行。但我建议你认真考虑在 Agent 和模型之间加一层 LiteLLM Proxy原因有两个第一它把不同厂商、不同模型的 API 统一成了 OpenAI 兼容格式Agent 代码里只要写一个base_url后面换模型、换供应商都不用改业务代码第二它天然支持 key 管理、限流、成本统计和日志记录生产环境排查问题非常有用。我在腾讯云服务器上就是先跑一个 LiteLLM Proxy 容器然后在 Agent 服务里把所有模型调用都指向http://localhost:4000。这样我在调试的时候可以直接用 curl 打 Proxy 看返回结果不用反复改业务代码。你如果只用腾讯云自家的大模型服务也可以直接用云上提供的兼容接口不一定非要中间层但只要你可能接多个渠道的模型这个 Proxy 就是性价比极高的投资。3. AI Skills 设计方法论让 Agent 真正“能干”3.1 Skill 的输入输出协议怎么定我见过太多 Skill 翻车根源都在输入输出协议不清晰。大模型不是人它不会“猜”你的函数要什么格式。所以你给模型看到的 Skill 描述和参数 Schema必须精确到“什么场景用、每个参数是什么意思、取值范围是什么、返回结构是什么样”。我的标准做法是用 JSON Schema 描述参数用一段自然语言描述触发条件和注意事项。比如一个“创建云服务器”的 Skill描述里会写清楚“仅当用户明确要求创建服务器时调用需要管理员权限校验”参数 Schema 里会标出region可选值、instanceType的规格列表。这样做的好处是模型在调用时有足够的上下文做判断不会把“查询服务器列表”也误触到“创建服务器”。返回格式我统一用{ status: success|error, data: {...}, message: 给用户看的说明 }。为什么不用裸数据因为 Agent 拿到返回结果后还要决定下一步动作它需要知道这次调用到底成没成功、失败原因是什么。把这些信息结构化地给到模型它才能做出正确的后续决策。3.2 一个 Skill 从定义到注册的完整步骤我习惯把 Skill 拆成三个文件定义文件描述和参数 Schema、执行文件真正的业务逻辑、注册文件把 Skill 挂到注册表里。以“获取服务器监控指标”为例定义文件长这样# skill_metrics_define.py from pydantic import BaseModel, Field class MetricsInput(BaseModel): instance_id: str Field(description云服务器实例ID形如 ins-xxxx) metric_name: str Field(description指标名称cpu_usage/mem_usage/disk_usage) period: int Field(default300, description统计周期单位秒默认300)执行文件里做真正的 API 调用然后把结果包成统一结构返回。注册文件里把input_schema、description、handler三个字段注册进一个全局的SKILL_REGISTRY字典。这样 Agent 启动时就能拿到所有可用 Skill 的清单需要生成调用计划时直接从注册表里筛。这里有个关键点不要让 Agent 直接看到你的 Python 函数代码它只需要看到“描述 参数 Schema”。原因很简单模型对纯函数代码的理解不稳定但对结构化的描述稳定得多。你写得越像一份“给外包开发看的接口文档”模型调用得越准确。3.3 Skill 编排单步工具和多步工作流的平衡Agent 的得意技是“链式调用”先查服务器列表再选目标机器再查监控指标最后生成报告。这个链条如果靠 Agent 每一步都“想”一次会很慢也容易在中途走偏。所以我在实践中做了一个折中把高频的固定流程封装成“工作流型 Skill”一次调用内部走完多步把低频的、需要灵活组合的场景留给 Agent 自己编排。举个例子。“每日巡检”这个技能我直接封装成一个 Skill内部串行执行拉实例列表 - 逐台查监控 - 汇总异常 - 生成报告。Agent 只需要调用一次“daily_inspection”剩下的逻辑全在 Skill 内部。而“用户提了一个临时需求比如‘看看这个实例昨天的流量然后对比前天的’”这种不可预测的组合就让 Agent 分别调用两个查询 Skill自己拼接结论。这样设计的好处是高频路径稳定高效低频路径灵活不僵硬。你把 90% 的确定性交给代码10% 的不确定性交给模型整体可靠性会高非常多。4. 实操把“全能 Agent”跑起来的核心闭环4.1 项目目录与服务骨架我这次项目的目录结构大致长这样agent-project/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── agent.py # Agent 核心循环 │ ├── skills/ │ │ ├── __init__.py # 自动导入并注册所有 Skill │ │ ├── registry.py # Skill 注册表 │ │ ├── metrics.py # 监控指标 Skill │ │ └── daily_check.py # 每日巡检工作流 Skill │ ├── memory/ │ │ ├── __init__.py │ │ └── redis_memory.py # Redis 记忆存储 │ └── config.py # 配置读取 ├── docker-compose.yml ├── Dockerfile └── requirements.txt入口用 FastAPI 是因为它写接口太方便了顺便还能暴露一个/health给云监控探活。Agent 核心逻辑放在agent.py里里面是一个run_agent(user_input, session_id)函数接收用户输入结合记忆和 Skill 列表调模型生成行动计划然后循环执行。服务骨架我选择了“一个进程干所有事”的写法前期业务量不大时完全够用也好调试。等并发上来了再把 Agent 服务和 Redis 拆开部署再加消息队列做异步任务。4.2 核心代码Skill 注册表和执行引擎Skill 注册表我用了一个非常简单的实现核心就是字典加注册函数# app/skills/registry.py from typing import Callable, Dict, Any SKILL_REGISTRY: Dict[str, Dict[str, Any]] {} def register_skill(name: str, description: str, input_schema: dict, handler: Callable): SKILL_REGISTRY[name] { name: name, description: description, input_schema: input_schema, handler: handler, } def get_skill_listing(): 给模型看的精简版 Skill 清单只描述不含实现 return [ { name: s[name], description: s[description], input_schema: s[input_schema], } for s in SKILL_REGISTRY.values() ] async def execute_skill(name: str, params: dict): 统一执行入口做异常兜底 skill SKILL_REGISTRY.get(name) if not skill: return {status: error, message: fSkill {name} 不存在} try: result await skill[handler](**params) return {status: success, data: result, message: ok} except Exception as e: return {status: error, message: fSkill 执行异常: {str(e)}}这里最关键的是execute_skill里的异常捕获。Agent 执行工具时最常见的崩溃就是工具内部抛了未捕获异常导致整个 Agent 循环直接终止。我在项目里踩过这个坑之后把所有 Skill 调用都收敛到这一个入口统一转成结构化错误模型就能根据错误信息自己调整参数重试或者向用户说明失败原因。Agent 核心循环我用了一个简单的max_steps限制防止模型陷入无限调用# app/agent.py async def run_agent(user_input: str, session_id: str): memory await load_memory(session_id) messages build_messages(memory, user_input, get_skill_listing()) step 0 while step MAX_STEPS: response await llm_chat(messages) if response.action finish: break if response.action call_skill: result await execute_skill(response.skill_name, response.params) messages.append(to_message(result)) step 1 await save_memory(session_id, messages) return response.final_answer你可能会问为什么不用现成的 LangChain、LangGraph我在项目初期也纠结过后来还是决定直接用轻量代码。原因一个是想完全掌控每一步的行为逻辑另一个是这类框架抽象层太多调试时一旦出问题定位成本很高。用代码把循环逻辑写明白Agent 的行为就完全可控后续扩展也方便。4.3 记忆模块用 Redis 存会话上下文记忆模块这块我最初的实现是把所有历史消息一股脑塞进上下文结果 token 消耗大、响应还慢。后来改成“滑动窗口 摘要”的策略短期保存最近 10 轮完整消息超过之后把更早的消息做一次摘要只保留摘要加最近几轮原文。Redis 里的 key 设计是agent:memory:{session_id}用 Hash 存field 分别是history、summary、metadata。每次读写都走序列化统一用 JSON 格式。TTL 我设置成 24 小时超过这个时间就自动清理避免 Redis 内存无限增长。# app/memory/redis_memory.py import json, redis redis_client redis.Redis(hostredis, port6379, db0) async def load_memory(session_id: str): data redis_client.hgetall(fagent:memory:{session_id}) return { history: json.loads(data.get(bhistory, b[])), summary: data.get(bsummary, b).decode(), } async def save_memory(session_id: str, messages: list, summary: str ): redis_client.hset( fagent:memory:{session_id}, mapping{ history: json.dumps(messages[-20:]), summary: summary, }, ) redis_client.expire(fagent:memory:{session_id}, 86400)这里要注意一个问题Redis 操作是同步阻塞的如果放在 FastAPI 的异步接口里直接调用高并发时会卡事件循环。我当时图省事直接用同步 Redis 客户端后来用httpx压测发现请求响应变慢排查半天才意识到是这个原因。现在要么用asyncio.to_thread包一层要么直接换redis.asyncio客户端。4.4 跑通一次完整的 Agent 调用把所有模块串起来后我用一个真实场景跑通了一整条链路用户说“帮我检查一下云服务器有没有异常”。Agent 先根据 Skill 清单决定调用list_instances拿到实例列表然后对每个实例调用get_metrics查 CPU 和内存发现有一台 CPU 超过 90%就调用create_alert建了一条告警最后给用户总结“ins-xxxx 这台机器 CPU 持续偏高已经帮你创建了告警规则你可以去看下监控图表。”这个过程看起来简单但真正稳定跑通依赖的是前面说的所有细节Skill 清单够清晰模型才知道第一步该查实例列表参数 Schema 够规范模型生成的参数才一次通过异常兜底够稳即使某一台实例查询失败也不会导致后续步骤全断。我把这次调用过程中的模型输出、Skill 入参、执行结果全部写到了日志里后面排查和优化都有据可查。这一步强烈建议每个做 Agent 的人都要做。没有日志的 Agent出问题就相当于大海捞针。5. 部署到腾讯云Docker 化与对外暴露5.1 容器镜像构建与推送Agent 服务打成镜像这一步核心是控制镜像体积和保证依赖一致性。基础镜像我选了python:3.11-slim比python:3.11小很多生产环境完全够用。Dockerfile 里我分了两个阶段先安装依赖再拷贝代码这样代码改了重新构建时能命中依赖层缓存构建速度快很多。FROM python:3.11-slim AS builder WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt FROM python:3.11-slim WORKDIR /app COPY --frombuilder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY . . CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000]构建完后推到腾讯云容器镜像服务的命令其实很直接docker tag myagent:latest ccr.ccs.tencentyun.com/myproject/myagent:latest docker login ccr.ccs.tencentyun.com --username你的腾讯云账号ID docker push ccr.ccs.tencentyun.com/myproject/myagent:latest这里我踩过两个坑。第一个是登录用户名不是自定义的账号名而是腾讯云的账号 ID直接用注册邮箱登录会失败得去访问管理里看你的账号 ID。第二个是第一次使用之前必须先在控制台创建命名空间和镜像仓库不然docker push会报 repository 不存在的错。5.2 域名、网关和 HTTPS 怎么配Agent 服务是一个 HTTP 接口要给前端或者外部系统调用就得有一个稳定的入口。我推荐的方式是先申请一个域名然后在腾讯云 DNSPod 控制台加一条 A 记录解析到云服务器的公网 IP。这里如果你只想用子域名比如agent.example.com直接在 DNS 解析里加一条主机记录为agent的 A 记录就行不需要再单独申请一个域名。解析生效后我用 Caddy 做反向代理配置很简单还能自动申请和续期 HTTPS 证书。对比 Nginx 要自己管理证书文件Caddy 的auto_https对个人项目友好太多了。Caddyfile 大概长这样agent.example.com { reverse_proxy localhost:8000 }有人会问能不能不用域名直接用 IP 加端口能但有两个问题一是裸 IP 没法申请可信的 HTTPS 证书数据在公网明文传输API Key 这种敏感信息绝对不能这么搞二是 IP 一旦变更所有调用方都要跟着改维护成本高。所以就算只是个人用也建议配个域名。需要注意配好域名之后腾讯云 CVM 的安全组一定要放行 80 和 443 端口否则外网访问会被拦在防火墙外面。我当时就是安全组没放行Caddy 启动了也启动成功了但外网就是访问不了排查了半天才发现是安全组的问题。5.3 上线后的观测与日志Agent 上生产之后可观测性比功能迭代更重要。我至少做了三件事一是服务日志全量输出到 stdoutDocker 会自动收集用docker logs就能查二是接了一个简单的健康检查接口/health返回服务状态和 Redis 连通性配合腾讯云的云监控定期探测三是在模型调用和 Skill 调用处打了结构化日志每条日志带session_id和step_id方便串联整个 Agent 的执行链路。你可能会觉得这些“不就是在外面包一层日志嘛”但 Agent 这种多步调用系统出错时如果看不到中间步骤你根本分不清是模型决策错、工具参数错还是下游接口错。我把一次完整调用里所有步骤的输入输出都打出来之后很多看起来玄乎的问题其实一眼就能定位到具体是哪一步挂了。6. 常见问题与排查技巧实录6.1 agent execution terminated due to error这是我在开发阶段看到最多的一条错误字面意思是“Agent 执行因错误终止”。刚遇到时一脸懵后来把所有场景复盘了一遍发现有几种高频原因第一种是 LLM 生成的动作不被 harness 识别比如 Skill 名称多了一个空格第二种是 Skill 执行过程中抛了未捕获异常直接中断了循环第三种是模型返回的 JSON 格式损坏参数解析失败。我的解决办法就是前面写的所有 Skill 调用统一走execute_skill入口异常全部捕获并结构化返回模型输出加一层 JSON 解析容错解析失败时把原始输出打到日志里再要求模型重新生成。另外给max_steps加上硬上限防止死循环消耗完 token。6.2 Redis 改密码后重启失败有朋友在腾讯云服务器上改 Redis 密码后重启一直起不来。我远程帮他排查了一遍发现是requirepass写到了不对的配置段或者密码里带了!、这类特殊字符在 systemd 传参时被转义出错。Redis 重启失败往往不是 Redis 本身的问题而是配置解析和环境变量设置的问题。我的建议是改密码不要直接改/etc/redis/redis.conf先redis-cli config set requirepass 新密码在线设置再用config rewrite把配置写回文件。这样换密码期间服务不会中断而且不容易写坏配置文件。改完一定要用redis-cli -a 新密码 ping验证一下返回 PONG 才算真的生效。6.3 Docker 推送镜像卡住或认证失败推镜像到腾讯云容器镜像服务时最常遇到两类问题一是docker login认证失败原因基本就是用户名填成了登录邮箱而不是账号 ID二是docker push时进度一直卡住不动多半是网络问题或者镜像层数太多太大可以换个时间段再试或者用docker buildx构建多平台镜像后分平台推送。还有一个我踩过的坑是镜像 tag 没有写成仓库的标准格式。腾讯云镜像仓库的地址格式是ccr.ccs.tencentyun.com/{命名空间}/{仓库名}:{tag}如果你只打了myagent:latest就 pushDocker 会尝试推到 Docker Hub然后报 denied。所以构建完一定要记得先docker tag把镜像重新标记成目标仓库的完整地址再执行 push。6.4 排查思路速查表现象可能原因排查方法模型调用超时模型网关配置故障先 curl 风格请求 LitellM Proxy确认上游模型响应正常Skill 参数报错模型生成的参数不符合 Schema查看日志里模型原始输出检查参数类型和必填项Redis 连接失败密码错误或安全组未放行端口redis-cli -a 密码 ping并在云控制台检查安全组外网无法访问服务安全组未放行 80/443在腾讯云控制台检查安全组入站规则Docker push 被拒仓库不存在或 tag 格式错误确认命名空间和仓库已创建检查镜像 tag 是否完整Agent 执行莫名其妙中断某一步工具抛了异常看日志中最后一条 Skill 调用记录定位到具体步骤这张表是我自己在排查 Agent 问题时最常用的索引。你遇到的问题可能不在里面但思路是通用的先看日志确认是模型层、工具层还是基础设施层的问题再顺着链路逐层缩小范围比瞎猜要快得多。最后再分享一个小技巧Agent 开发过程中模型输出经常会出现各种花式格式错误与其在代码里写一堆正则去解析不如让模型“自己修正”——把解析失败的原始输出返回给模型告诉它格式不正确让它重新生成一次。我实测下来这个“报错重试”的机制比任何解析容错都管用。Agent 这个方向说白了就是在确定性的代码和不确定性的模型之间找平衡你能容纳多少不确定性决定了你的 Agent 能跑多远。