腾讯云AI Skills实战:Agent与Skill架构、部署与排障指南
2026/9/7 14:25:19 网站建设 项目流程

最近有好几个朋友问我同一个问题:Agent 项目在本地能跑通,一上云就各种翻车,到底是怎么回事。这个问题我太有发言权了,因为我在腾讯云上从零搭过一个完整的多技能 Agent,中间踩过的坑两只手都数不过来。今天就把这套完整的实践过程拆开揉碎讲一遍,重点说说腾讯云 AI Skills 怎么用、Agent 和 Skill 的关系怎么理清、部署到云上要注意哪些细节。

如果你正准备在腾讯云上做 Agent 开发,或者已经搭了个框架但发现它只能“聊天不能干活”,这篇文章应该能帮你少走很多弯路。我会把架构选型、技能定义、记忆设计、镜像部署、常见排障全部串起来讲,全程不掺水,都是我实际验证过能跑通的做法。

1. 先搞清楚:Agent、Skill、Workflow 到底是什么

1.1 Agent 不是聊天机器人

很多初学者把 Agent 理解成“接了大模型 API 的聊天机器人”,这是最大的误区。Agent 的核心是自主决策加工具调用。区别在于:Chatbot 是你问一句它答一句,Agent 是你给一个目标,它自己规划步骤、调用工具、根据结果修正动作,直到完成目标。

举个例子,普通的对话机器人收到“帮我把杭州明天适合出行的时段找出来,顺便约个会议室”这种需求,大概率只能给你一段泛泛的建议。而 Agent 会先拆解任务:查天气、查会议室空闲、判断哪些时段合适、发起预约。每一步都需要调用不同的能力,这些能力就必须以技能(Skill)的形式存在。

我见过不少人把 Agent 做得特别“聪明”,prompt 写了一长串,结果一接真实业务就露馅。原因很简单:思维再强,手脚不够。一个没有技能支撑的 Agent 充其量是个“嘴强王者”,你让它查天气它只能编,你让它调接口它只能道歉。

1.2 Skill 与 Agent 的边界

一句话概括:Agent 是大脑加调度器,Skill 是手脚加工具包。Agent 负责理解、规划、拆解任务、观察结果;Skill 负责具体执行某类动作。在腾讯云 AI Skills 这套体系里,Skill 通常被定义成一个带元数据的能力单元,包含名称、用途描述、入参出参结构,Agent 通过函数调用或 HTTP 方式触发它。

边界问题如果没理清,后面写代码会非常别扭。我见过有人把所有逻辑都塞进 Agent 主进程,结果 Agent 越写越长,改一个业务细节就要动核心代码。正确姿势是:Agent 只保留决策逻辑,所有具体操作都下沉到 Skill。比如“查订单”是一个 Skill,“改订单状态”是另一个 Skill,Agent 本身不写业务代码,它负责判断“现在该用哪个 Skill”。

顺便说一句 Harness 和 Agent 的区别。Harness 是外部执行框架,负责循环控制、停止条件、工具注册、日志追踪;Agent 是里面的决策主体。很多“Agent 执行被终止”的问题,根子不在 Agent 本身,而是 Harness 的轮次上限或超时设置太激进。这块我在后面排查章节会细讲。

1.3 为什么必须把能力“技能化”

把能力封装成 Skill,而不是把它写死在 prompt 里,核心原因是三点:可复用、可测试、可观测。

可复用好理解:一个写好的“天气查询”技能,可以在旅游助手、日程管理、出行提醒多个 Agent 里共用,不用重复开发。可测试意味着每个技能能单独输入输出验证,出了问题能定位到具体技能,而不是整个对话重来。可观测就更实际了:技能调用有日志、有耗时、有成功失败统计,你才知道 Agent 到底“卡”在哪一步。

还有一个很现实的原因:LLM 上下文窗口有限。把一堆工具描述全部塞进系统提示词,不仅浪费 token,还会让模型“选择困难”。技能化之后,上层只暴露一段简洁的意图描述加参数 schema,模型在需要的时候才加载对应技能,这个思想其实和操作系统的动态加载类似。我在腾讯云 AI Skills 实践中最深的感触就是:技能定义得好不好,直接决定了 Agent 的上限。

2. 腾讯云上的 Agent 架构选型

2.1 手写还是用框架

我一开始是纯手写派,觉得框架黑盒太多,不如自己控制一切。后来发现重复劳动实在太多:会话管理、工具注册、重试机制、日志、限流,这些工程问题每个都要自己写,写完还要自己测,效率很低。后来我调整了策略:用开源框架承担 Harness 的工作,把业务逻辑全部沉淀在 Skill 层。

框架方面,LangGraph、Dify 以及社区里一些轻量 agent 框架我都试过。我的建议是:生产环境不要迷信任何框架,也不要排斥框架,而是把框架当成可更换的执行引擎。这样就算以后换框架,Skill 资产还能保留,不会被绑死。腾讯云开发者社区里关于 Agent 框架的讨论很多,观点五花八门,但大家基本都认同一点:框架解决“怎么跑”的问题,Skill 解决“能干什么”的问题,两者要解耦。

2.2 我的云端基线架构

我最终跑通的架构长这样,用纯文本画个拓扑:

用户请求 | v API 网关(二级域名 + HTTPS) | v Agent Runtime(Python 服务,跑在 CVM) |--- LiteLLM Proxy(模型网关) |--- Skill Registry(技能注册表) |--- Memory(Redis 会话记忆) | v 腾讯云容器镜像服务 TCR(镜像分发) | v 各业务 Skill(模块化部署,可独立升级)

解释一下每一层的作用。API 网关负责对外暴露统一入口,保证只有经过认证的请求能打到 Agent。Agent Runtime 是核心服务,里面运行模型调用、技能调度、记忆读写。LiteLLM Proxy 放在模型和 Agent 之间,管理多家模型的 key 和路由。技能层每个 Skill 独立部署,可以单独更新而不影响主服务。

这套架构最大的好处是职责分明,出问题能快速定位。有一次用户反馈 Agent 回答特别慢,我查了一圈,发现不是模型慢,而是某个 Skill 内部调了外部接口超时。因为链路分层清楚,日志一拉就看到了。

2.3 为什么用 LiteLLM Proxy 做模型网关

如果你只接一家模型,LiteLLM Proxy 的收益确实不大。但只要你有备用模型、要切换模型、要做 key 管理和权限隔离,它就是刚需。我用 LiteLLM Proxy 把腾讯混元、DeepSeek、OpenAI 兼容接口都统一成了同一个 OpenAI 格式底座,上层 Agent 代码不会因为换模型而改动。

这套代理还顺手解决了几个麻烦事:一是 API key 不用散落在多个服务里,统一由代理管理,安全性高很多;二是可以做限流,防止某个技能把配额打爆;三是统一日志,模型请求响应都能追踪。我的配置大致是:

model_list: - model_name: hunyuan litellm_params: model: tencent/hunyuan-lite api_key: os.environ/TENCENT_HUNYUAN_KEY - model_name: deepseek-chat litellm_params: model: deepseek/deepseek-chat api_key: os.environ/DEEPSEEK_API_KEY litellm_settings: drop_params: true max_retries: 2

这里的 model_name 是给上层 Agent 用的逻辑名,litellm_params 里写真实的模型名和密钥。切换模型时只需要改配置文件,Agent 代码完全不用动。这个“逻辑名与真实模型解耦”的思路,建议所有 Agent 项目都照做,后面会省掉无数麻烦。

3. Skills 定义与开发实战

3.1 技能描述文件的“三句话原则”

Skill 描述文件是整个 Agent 体系里最容易被低估的部分。描述写得太笼统,模型不知道该在什么时候调它;参数写得太粗糙,模型给的入参根本不合法。我摸索出一套“三句话原则”,每一个技能描述都按这个结构来写:

  • 第一句话写触发条件:什么类型的用户需求应该调用这个技能。
  • 第二句话写调用动作:这个技能能做什么,能力边界在哪。
  • 第三句话写关键限制:有没有必须的关键信息,默认值是什么。

以天气查询为例,我实际用的描述文件长这样:

name: weather_query description: | 当用户询问某个城市的天气、气温、降雨概率、空气质量、 或者出行是否适合时,使用该技能。 一次只能查询一个城市,不支持多个城市对比。 如果没有给出城市名称,默认使用定位城市。 parameters: type: object properties: city: type: string description: 城市名称,例如“杭州” date: type: string description: 日期,格式 YYYY-MM-DD,缺省为今天 required: - city

注意 description 里我特意写了“不支持多个城市对比”,这是给模型划清楚边界,避免它拿着两个城市名来调用,结果发现参数不合法还要重试。另外我还强调了“如果没有城市名默认定位”,这个细节能明显提升体验,让 Agent 少问一次废话。

3.2 参数 Schema 少而精

参数设计直接决定了工具调用的成功率,我总结的教训是:参数能少就少,每个参数都要有清楚的中文描述和必填标记,枚举值要写全。

比如订单查询技能,我一开始设计了 7 个参数,包括订单号、用户 ID、下单时间范围、商品名称、订单状态、页码、页大小。结果模型经常“选择困难”,要么漏填必填项,要么把时间格式传错。后来我砍到了 3 个参数:query(支持订单号或商品名称关键词)、status(枚举:待付款、已付款、已发货、已完成)、page。模型一次就能给对。

关于布尔参数,我踩过一个坑:某技能有个 is_refund 参数,默认 false,但模型有时候传字符串 "false",有时候传布尔 false,类型不匹配导致调用报错。后来我干脆把布尔参数改成字符串枚举("yes" / "no"),彻底绕开类型问题。涉及外部接口的技能还要加一层参数预检,把模型生成的参数先校验一遍再去调真实接口,宁可多写几行代码,也别拿错误参数去污染业务数据。

3.3 工具调用链路与错误处理

一次完整的技能调用链是这样的:模型理解用户意图,返回 function call 指令;Agent Runtime 从技能注册表里找到对应 Skill,设置超时并执行;执行结果转成结构化 JSON 返回给模型,模型根据结果决定是继续下一步还是直接回答用户。

错误处理是这个链路里最容易出问题的地方,模型调用技能失败后经常“不知所措”,要么重复调用同一个技能,要么干脆认错说做不了。我的做法是给每个技能返回一个标准错误结构,包含机器可读错误码、人话描述、给模型的建议:

import time def execute_skill(skill_name: str, params: dict) -> dict: skill = skill_registry.get(skill_name) if not skill: return { "code": "SKILL_NOT_FOUND", "message": "技能不存在", "suggestion": "告诉用户该功能暂未开放", } try: result = skill.invoke(params, timeout=skill.timeout) return {"code": "OK", "result": result} except TimeoutError: return { "code": "TIMEOUT", "message": "技能调用超时", "suggestion": "请缩小查询范围后重试", } except Exception as exc: return { "code": "ERROR", "message": str(exc), "suggestion": skill.fallback_advice, }

这样模型拿到结果后能做出合理决策:超时就缩小范围重试,技能不存在就换个方式回答。另外每个技能都要单独设置超时时间,外部接口调用一般设 5 到 10 秒,内部纯计算可以 3 秒。宁可让 Agent 快失败,也别让它傻等。

4. 记忆系统:让 Agent 从“失忆”到“记牢”

4.1 三层记忆架构

很多 Agent“聊着聊着就忘了”,根因是没有记忆系统。我在腾讯云上实践出一套三层记忆架构:

  • 短期记忆:存当前会话的上下文,通常是最近 N 轮对话,放在 Redis 里,带过期时间。
  • 长期记忆:存用户的偏好、习惯、历史结论,比如“用户偏好简洁回复”“用户常用收货地址是杭州”,这部分会持久化。
  • 技能记忆:存技能调用记录,模型在多次调用同一技能时能参考上次输入,避免重复提问。

为什么技能记忆很重要?举个例子,用户让 Agent“帮我查上周的订单”,Agent 调了订单查询技能,返回结果里只有订单号和金额。用户接着说“第一个给我退款”,如果 Agent 没有技能记忆,它根本不知道“第一个”指的是哪个订单。有了调用记录,它就能把上下文串起来。

4.2 Redis 会话记忆的落地细节

短期记忆我用 Redis 实现,核心逻辑很简单:用 session_id 做 key,存一个 JSON 数组,数组里是最近 20 轮对话。每轮新增时把最旧的那条挤出去,写入后刷新过期时间,TTL 我设的是 24 小时。

import json import os import redis r = redis.Redis( host="127.0.0.1", port=6379, password=os.environ["REDIS_PASSWORD"], decode_responses=True, ) def append_message(session_id: str, role: str, content: str): key = f"session:{session_id}" history = json.loads(r.get(key) or "[]") history.append({"role": role, "content": content}) history = history[-20:] # 窗口控制 r.setex(key, 86400, json.dumps(history))

这里有两个容易忽略的点。第一,Redis 的 key 一定要带前缀(我用的 session:),避免跟其他业务 key 冲突。第二,窗口大小不是越大越好,20 轮已经能覆盖绝大多数场景,再长的历史要么用摘要压缩,要么转成长期记忆,硬塞上下文只会浪费 token 还降低响应质量。

另外提醒一下,Redis 连接参数里的密码千万别写死在代码里,用环境变量注入。我把所有密钥类配置都放在 systemd 的 EnvironmentFile 或者 docker 的环境变量里,代码仓库里只有占位符,这是 Agent 项目安全的最低要求。

4.3 长期记忆和向量检索

长期记忆不能简单用 JSON 存,因为搜索效率太低。我的方案是:把用户偏好和技能调用摘要文本分块,调用 embedding 接口做向量化,存到向量数据库里。Agent 每次对话开始时,先检索和当前会话语义最相关的 topK 条记忆,作为背景信息注入 prompt。

这个方案的落地细节是,写入长期记忆的时机很关键。我是在技能调用完成、且对话产生明确结论后触发写入,比如用户说“以后都先查天气再推荐穿搭”,这种明显的偏好指令必须抓住。写入文本不要简单记“用户想要查天气”,而是带上下文:“用户希望出行推荐前先查询天气,若气温低于 15 度应提示带外套”。这种带业务语义的记忆,召回后对模型才有真正的参考价值。

长期记忆这块初期不建议上太重的基础设施,一个轻量向量库完全够用。等记忆量超过几十万条再考虑迁到腾讯云向量数据库或更专业的方案。

5. 部署上线:Docker、镜像仓库、二级域名与 HTTPS

5.1 构建镜像并推送腾讯云容器镜像服务

我在云上部署的教训是:远程服务器上手工装环境是初级玩法,一次两次能忍,要频繁更新就完全不可控。后来全部改成容器化,构建镜像推送到腾讯云容器镜像服务(TCR),CVM 上只负责拉取和运行。

Dockerfile 我写得比较克制,基础镜像直接用 python:3.11-slim,依赖用腾讯云 pip 源加速,避免超时:

FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple COPY . . EXPOSE 8000 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

镜像构建好后,推送到 TCR 的流程是:先登录仓库,再打标签,最后推送。腾讯云 TCR 有公网和内网两个入口,CVM 和仓库在同一个地域时一定要用内网地址,速度快很多:

docker login ccr.ccs.tencent.com -u your_username --password-stdin docker tag agent-runtime:v1 ccr.ccs.tencent.com/your_namespace/agent-runtime:v1 docker push ccr.ccs.tencent.com/your_namespace/agent-runtime:v1

标签命名我建议加一层业务含义,比如 agent-runtime:v1.2.0,别用 latest 当唯一标签。latest 会造成“明明更新了镜像,容器里还是老代码”的诡异问题。推送完成后,CVM 上直接用 docker pull 拉取,跑起来就完事。

5.2 二级域名申请与 HTTPS 配置

“腾讯云怎么申请二级域名”这个问题被问得特别多。其实二级域名的本质是在已备案的主域名下添加一条 DNS 解析记录。比如主域名是 example.com,你在 DNSPod 控制台添加一条 A 记录,主机记录填 dev,记录值填 CVM 的公网 IP,这样 dev.example.com 就是你的二级域名了。

有了二级域名之后还要配 HTTPS,因为很多回调场景(比如企业微信、飞书、第三方 webhook)都强制要求 HTTPS,否则直接拒绝调用。我是用 Nginx 做反向代理,把 443 端口的 HTTPS 请求转发到本机 8000 的 Agent 服务上:

server { listen 443 ssl; server_name dev.example.com; ssl_certificate /etc/nginx/ssl/example.pem; ssl_certificate_key /etc/nginx/ssl/example.key; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

证书我用的是腾讯云提供的免费 SSL 证书,申请下来后配置到 Nginx 就行。有个细节提醒:Agent 服务本身不需要绑定公网端口,监听 127.0.0.1 就够了,对外只暴露 Nginx 的 443 端口,这样安全性和可维护性都好很多。

5.3 进程守护与更新

Agent 服务不能裸跑,我用的是 systemd,这样能满足开机自启和崩溃自动重启两个需求。实际配置片段:

[Unit] Description=Agent Runtime After=network.target [Service] User=ubuntu WorkingDirectory=/opt/agent ExecStart=/opt/agent/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 Restart=always RestartSec=3 EnvironmentFile=/etc/agent.env [Install] WantedBy=multi-user.target

EnvironmentFile 这个字段是关键,所有敏感配置,包括 Redis 密码、模型 API key,都在这个文件里,代码仓库里不出现任何明文密钥。更新 Agent 镜像时,我的流程是:本地构建新镜像,push 到 TCR,然后 ssh 到 CVM 上 docker compose pull 再 up,整个过程不用登录服务器装任何依赖。这也是容器化的最大红利:发布流程从“登录服务器怀疑人生”变成了“三条命令搞定”。

6. 常见问题与排查技巧实录

6.1 Redis 改密码后重启失败

这个坑太经典了,必须单列一节。在腾讯云服务器上装 Redis,修改 requirepass 之后重启一直失败,多半是以下原因之一。第一个是 systemd 的 ExecStart 里没加载配置文件,服务还在用默认配置启动,Redis 根本读不到你改的密码。第二个是 redis.conf 文件权限不对,导致 Redis 进程没有权限读取,启动直接报错。

排查顺序我总结成一张表:

症状可能原因解决方式
systemd 启动失败ExecStart 未指定配置文件更新 unit 文件,显式带配置路径
前台启动正常,systemd 失败unit 文件环境变量缺失检查 User、EnvironmentFile 配置
Redis 起来了但客户端 AUTH 失败依赖方还在用旧密码同步修改所有连接 Redis 的服务配置
外网连不上 Redisprotected-mode 和 bind 限制确认是否需要公网访问,默认建议仅内网
Redis 启动日志有权限报错配置文件权限过严调整 redis.conf 属主和读权限

先跑 redis-server /path/to/redis.conf 前台启动,确认配置本身没问题,再排查 systemd,顺序一定不要反。改完密码还有个连带问题:所有依赖 Redis 的服务,比如 Agent 的记忆模块、LiteLLM Proxy 的缓存,都要同步改配置。我之前遇到过“Redis 进程明明是活的,但 Agent 的存储模块起不来”的诡异问题,最后发现就是密码没同步。

6.2 agent execution terminated due to error

这个报错我最早遇到时毫无头绪,后来把执行链路一层层拆开才发现是技能调用超时。但不是只有超时会触发这个提示,还有几个高频原因我也遇到:Harness 的最大轮次设置过小,模型还没完成任务就被强制终止;某个技能抛了未捕获异常,导致整个执行循环崩掉;模型生成了不存在的技能名称,调用时找不到对应函数。

解决办法分三步。第一步,给每个技能单独设置超时,不要让一个慢技能拖垮整个 Agent。第二步,Harness 的最大轮次从默认值往上调,我的项目里设成 30 轮,足够大多数任务使用。第三步,给技能调用加一层“预检”:在调真实业务接口前先校验参数合法性,发现参数明显不对就及时返回错误,别把脏数据带进真实系统。

这里也顺带提一下 Agent 安全:技能权限一定要最小化,生产库的写操作、删操作别随随便便暴露给 Agent。我在技能注册表里给每个技能挂了所需的权限标签,Agent 本身没有全局权限,只有通过技能才能触碰数据,这样即使模型被诱导乱来,影响范围也有限。

6.3 模型调用超时与限流

本地调 DeepSeek 或腾讯混元一般很快,但云上服务在高峰期经常遇到 30 秒超时,尤其是 Agent 内部要连续调用多次模型时,用户侧体感会被放大好几倍。核心解法是:对外 API 改成异步任务加状态轮询,不要让用户一直等同步响应;模型调用侧做重试和熔断,单个模型连续失败就切到备用模型。

限流这块我踩过一个坑:某天 Agent 突然大量调用某个模型,把配额打爆了,所有请求都被限流。后来我在 LiteLLM Proxy 层给每个逻辑模型加了速率限制,再配合缓存机制,重复的查询直接走缓存,不重复消耗模型调用。Agent 项目上线以后一定要盯着一类指标:技能成功率、平均耗时、模型调用成本,这三项能反映系统八成以上的健康度。

6.4 Agent 相关概念易混点速查

我把这段时间被问到最多的几个概念整理成一张速查表,建议收藏备用:

概念定位说明
Agent决策主体负责理解需求、拆解任务、调用技能、复盘结果
Skill能力单元封装具体业务动作,可复用、可测试、可独立部署
Harness执行框架提供循环、停止条件、工具注册等工程能力
Workflow流程编排固定任务流程,适合步骤明确的场景
Framework开发框架已经实现的 Harness 加部分脚手架,可以换成别的

这份表特别适合面试和团队沟通,概念一致了,讨论方案能省一半时间。很多人纠结 Agent 和 Workflow 到底选哪个,我的经验是:任务路径变化多、需要模型自主决策的用 Agent;任务每一步都固定、不需要发挥的用 Workflow,别为了“智能”而上 Agent,反而把稳定的业务搞得不稳定。

最后分享一点我个人的体会。把 Agent 做成“全能”,关键不在模型选得有多强,而在技能资产积累得够不够多、够不够扎实。我在腾讯云上把这套体系跑通之后,新增一个业务能力只需要写一个 Skill 定义文件,再配上对应实现,Agent 主流程完全不用动。这种“能力可插拔”的架构方式,才是 AI Skills 实践带来的最大红利。如果你正在搭自己的 Agent,建议先从一两个高频技能入手,跑通整个闭环再逐步扩展,步子太大容易摔,技能多了管理成本也会上来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询