我最近把一台闲置的腾讯云服务器彻底折腾了一遍,从域名解析、容器镜像推送到 Litellm Proxy 部署,最后接入 OpenAI SDK 兼容层,再把 Skills 插件体系挂上去,总算跑通了一个完整可用的 AI Agent 工作台。这套东西踩坑不少,尤其是腾讯云容器镜像服务的权限配置、二级域名申请备案后的解析生效时间、以及 Redis 修改密码后重启失败的连锁反应,每一个都够写一篇单独的排错记录。今天这篇就把整个链路串起来,分享一份可以直接照抄的腾讯云 AI Skills 最佳实践。
先说清楚这套方案解决了什么问题:你不需要本地显卡,不需要买昂贵的 GPU 云主机,也不需要折腾复杂的 K8s,只要一台最基础的腾讯云轻量服务器,就能把 Agent 开发环境、模型代理层、技能插件体系全部跑起来。适合的人群很明确——正在做 AI Agent 原型验证的开发者、想把自己公司内部 API 封装成 Skills 的团队、以及想在云端搭一套统一模型网关给前端/后端同时调用的个人开发者。
1. 环境准备:腾讯云服务器选型与域名解析的隐藏坑
1.1 轻量服务器配置怎么选才不浪费钱
很多人第一步就纠结服务器配置,其实 Agent 开发场景和传统 Web 服务不一样,瓶颈通常不在 CPU 而在内存和网络带宽。我自己用的是 2C4G 的轻量应用服务器,系统盘 80G SSD,带宽 6M 峰值,日常跑 Docker 容器、Litellm Proxy、Redis、多个 Node 服务完全够用。
选型逻辑是这样的:Agent 本身是个编排层,真正吃算力的是底层模型推理,而模型推理在云端 API 完成,本机只做请求转发和逻辑编排,所以 4G 内存已经富余。但如果你的 Agent 要加载本地向量数据库做 RAG,或者要跑 embedding 模型,建议直接上 8G,因为 Chroma 或者 Qdrant 这类组件吃内存非常凶,4G 跑起来 swap 会频繁触发,响应时间直接崩。
带宽这块反而是我最后悔的。轻量服务器的峰值带宽是共享的,6M 在高峰期跑大模型流式输出时会明显感觉到瓶颈。如果你计划给团队多人共用这个 Agent 服务,建议选按流量计费的带宽模式,或者直接上独享带宽,别在带宽上省钱。
1.2 二级域名申请与解析的正确姿势
腾讯云的二级域名申请是在云解析 DNS 控制台完成的,但这里有个绝大多数新手都会踩的坑:一级域名必须完成 ICP 备案,二级域名才能正常解析访问,否则 HTTP 请求会被拦截。我自己当时图省事,拿着一个未备案的域名直接解析到服务器,结果前端页面能打开 IP 访问,域名死活不通,排查了半天才发现是备案问题。
正确的操作顺序是:
- 在腾讯云控制台先完成域名实名认证和 ICP 备案,备案审核周期一般 5-7 个工作日
- 备案通过后在云解析 DNS 控制台添加记录,主机记录填 agent,记录类型选 A,记录值填服务器公网 IP
- TTL 建议先设 600 秒,等解析稳定后再改回 60 秒
这里补充一个小技巧:备案审核期间千万不要闲着,可以先把服务器的 Docker 环境、容器镜像仓库、Redis 全部配好,因为 Docker 拉镜像和推送镜像走的是 IP 加端口,不依赖域名解析,完全不受备案影响。等域名备案下来,直接改配置文件的 base_url 即可。
1.3 Redis 修改密码后重启失败的典型排错链路
这个坑我必须单独拎出来说,因为太典型了。我最初在服务器上装 Redis 时用默认配置,后来觉得不安全,修改了 requirepass 密码,然后执行 systemctl restart redis,结果服务直接起不来。
排查链路如下:
首先看systemctl status redis,发现报错信息是Bad directive or wrong number of arguments,这句话误导性很强,我一度以为是配置文件语法写错了。后来用/usr/bin/redis-server /etc/redis/redis.conf前台启动才发现真正的报错——Redis 6.0 以上的版本,如果配置了 requirepass,必须同步配置 masterauth,否则主从同步线程会反复尝试认证失败,导致服务进入保护性退出。
解决办法其实很朴素:在 redis.conf 中同时配置这两个字段:
requirepass YourStrongPassword masterauth YourStrongPassword然后redis-cli -a YourStrongPassword ping验证。这里还有一个隐藏知识点:Redis 7.0 之后默认开启了 protected-mode,如果你用 ACL 用户体系替代 requirepass,配置语法完全不同,从 requirepass 迁移到 ACL 时不要漏掉user default on nopass ~* &* +@all这行,否则直接拒绝所有连接。
2. 容器化部署:Docker 推送腾讯云容器镜像服务的完整链路
2.1 从本地构建到镜像仓库的权限配置
把 Agent 服务容器化之后,需要推到容器镜像仓库,本地才能轻松拉取部署。腾讯云容器镜像服务 TCR 的个人版是免费的,但权限认证方式和 Docker Hub 不一样,有坑。
首先在 TCR 控制台创建命名空间和镜像仓库,命名空间是全局唯一的,比如我用的agentforge。然后登录需要生成一个临时凭证,腾讯云的镜像仓库登录凭证默认有效期是 12 小时,这点和 Docker Hub 的长期 token 完全不同,CI/CD 流水线里要特别注意定时刷新。
本地登录命令:
docker login ccr.ccs.tencentyun.com --username=1000xxxxxxx --password=xxxxx这里 username 不是你的 Docker Hub 用户名,而是腾讯云的账号 ID,可以在控制台账号信息里找到。我第一次就在这里卡了半小时,一直用自己的自定义登录名,提示认证失败。
推送镜像的标准流程:
docker tag agent-runner:latest ccr.ccs.tencentyun.com/agentforge/agent-runner:latest docker push ccr.ccs.tencentyun.com/agentforge/agent-runner:latest推完之后在服务器上拉取,建议加--digest参数固定镜像版本,避免 latest 标签被覆盖后产生非预期行为。生产环境千万别图方便用 latest,这次推个坏的上去,下次 pull 直接中招。
2.2 Docker Compose 编排腾讯云服务器上的 Agent 服务栈
涉及 Agent 的容器服务不止一个,我用 docker-compose.yml 统一管理,一份配置拉起所有服务。这里给出我的编排文件核心结构:
version: "3.8" services: redis: image: redis:7-alpine restart: always command: redis-server /usr/local/etc/redis/redis.conf volumes: - ./redis/redis.conf:/usr/local/etc/redis/redis.conf ports: - "6379:6379" litellm-proxy: image: ghcr.io/berriai/litellm:main-latest restart: always env_file: - .env ports: - "4000:4000" depends_on: - redis agent-runner: image: ccr.ccs.tencentyun.com/agentforge/agent-runner:latest restart: always environment: - LITELLM_PROXY_URL=http://litellm-proxy:4000 - REDIS_URL=redis://:YourStrongPassword@redis:6379 ports: - "3000:3000" depends_on: - litellm-proxy这个编排文件里有个非常关键的点:容器之间通信用服务名而不是 IP 地址,比如redis://litellm-proxy:4000。很多人在本地跑通后上服务器就挂了,原因就是容器内使用 localhost 指向了自身容器,而不是目标服务。Docker Compose 会自动创建内部 DNS,服务名就是容器 IP 的映射,直接用就行了。
另外 Redis 的连接串里带密码需要注意特殊字符转义。如果密码里有 @、:、/ 这类字符,必须进行 URL 编码,否则会被解析器误认为分隔符。我吃过亏,密码里放了个 @,结果连接串直接炸了,后来统一把密码改成大小写字母加数字的组合才消停。
2.3 Docker 日志与资源监控的日常体检
容器化部署起来之后,最怕的就是半夜容器挂了还不知道。我把日志采集简单接了一下,用docker logs --tail 100排查问题,同时用docker stats监控 CPU 和内存。腾讯云轻量服务器自带的基础监控只有 CPU 和带宽,不包含容器级监控,所以容器状态我来处理。
比较实用的组合是 crontab 加一段健康检查脚本,每分钟探测一次 Agent 接口的 /health 端点,连续失败三次就通过企业微信机器人发告警。这个脚本用 shell 就能写,不需要额外部署 Prometheus 全家桶,对轻量服务器非常友好。如果后续容器数量超过 5 个再考虑上完整的监控体系,前期不要过度设计。
3. 模型代理层:Litellm Proxy 部署与 OpenAI SDK 兼容层的落地
3.1 为什么需要 Litellm Proxy 作为模型统一入口
这是整套架构的枢纽。直接调用各家模型 API 的问题在于接口格式、鉴权方式、限流策略完全不同,Agent 每接一个新模型就要改一遍代码。Litellm Proxy 做的事情就是把这层差异全部屏蔽掉,对外暴露一个 OpenAI 格式的 /chat/completions 接口,内部再把请求转发给腾讯云混元、DeepSeek、通义千问等不同厂商。
选型上我对比过 One API 和 LiteLLM,最终选了 Litellm。理由是 LiteLLM 的模型兼容层更新速度极快,几乎所有新发模型当天就能支持,而且它原生支持 OpenAI SDK 的 streaming、tools、function calling 协议,对 Agent 开发特别友好。One API 的 UI 管理界面更完善,但在 Function Calling 的透传上有过兼容性问题,对需要工具调用的场景不太友好。
部署方式直接用 Docker 镜像,一条命令搞定。配置通过 config.yaml 管理,核心结构:
model_list: - model_name: hunyuan litellm_params: model: litellm_proxy/hunyuan api_key: os.environ/HUNYUAN_API_KEY api_base: https://api.hunyuan.cloud.tencent.com/v1 - model_name: deepseek litellm_params: model: litellm_proxy/deepseek-chat api_key: os.environ/DEEPSEEK_API_KEY litellm_settings: drop_params: true set_verbose: false其中model_name是你自定义的对外名称,litellm_params.model是对应厂商的真实模型名,api_base是厂商的接口地址。有个细节容易忽略:腾讯云混元的接口地址是/v1结尾,部分文档写的是/结尾,相差一个斜杠会直接导致 404。
3.2 用 OpenAI SDK 无缝切换腾讯云混元模型
Litellm Proxy 搭好之后,Agent 侧代码几乎不需要任何额外适配。只需要把 base_url 指到 Litellm 的地址,api_key 随便填一个占位符即可。我实际验证过,OpenAI 官方 Python SDK 直接改配置就能对接腾讯云混元:
from openai import OpenAI client = OpenAI( api_key="sk-litellm-local", base_url="http://your-server-ip:4000/v1" ) response = client.chat.completions.create( model="hunyuan", messages=[ {"role": "system", "content": "你是一个擅长处理数据分析的 Agent"}, {"role": "user", "content": "请分析这份销售数据的异常波动"} ], tools=[{ "type": "function", "function": { "name": "analyze_sales", "description": "分析销售数据", "parameters": {...} } }], stream=True )这段代码里的关键信息是base_url和model的对应关系。model这个参数填的并不是混元在腾讯云控制台里的模型 ID,而是你在 Litellm config.yaml 里定义的model_name,这一点非常容易混淆。我在初期的老代码里写的是hunyuan-lite这种腾讯云原始模型名,经 Litellm 转发时反而识别不了。
另外stream=True在大模型对话场景里建议默认开启,因为用户打字等不了整段生成完毕。流式模式下,Litellm 会把厂商的流式数据格式统一转换成 OpenAI 的 SSE 格式,前端的 EventSource 或者 fetch ReadableStream 可以直接消费,这个兼容层是 Litellm 做得最漂亮的地方。
3.3 模型路由、请求转发和 Token 计数的日常维护
Litellm Proxy 跑起来之后,日常维护主要是三件事:加新模型、看配额、查日志。
添加新模型只需要改 config.yaml 然后重启容器,不需要改 Agent 代码,前提是 Agent 侧已经按 OpenAI 工具调用协议写好。比如我要接入腾讯云一个新出的模型,配置里加一段:
- model_name: tencent-new-model litellm_params: model: litellm_proxy/tencent-new-model api_key: os.environ/TENCENT_API_KEY重启后立刻可以通过统一入口调用。配额查看直接用 Litellm 自带的 /spend 接口,可以按 key、按用户、按模型维度统计 token 消耗。日志方面建议开启set_verbose: true跑一两天观察,确认稳定后关掉,因为 verbose 模式下的日志量很大,轻量服务器的磁盘空间经不起几天折腾。
4. Skills 插件体系:让 Agent 真正变“全能”的关键设计
4.1 Skills 到底是什么,和普通 API 调用有什么区别
很多人以为 Skills 就是把一堆 API 封装成函数给模型调用,这个理解只对了一半。Skills 的核心价值在于它把自然语言意图到工具执行的映射过程体系化了。普通 API 调用需要你在代码里写死路由逻辑,模型只能在一组固定函数里选;而 Skills 体系则允许模型在执行过程中动态发现、加载和执行新技能,这才是 Agent 能持续进化的原因。
打个比方:普通函数调用的关系就像是拿着菜单点菜,菜单上有什么你点什么;Skills 的关系则像是给厨师一本空菜谱,他可以根据食材和口味自行研发新菜并记录进去。后者的上限高得多。
我在实际项目里维护的 Skills 目录结构如下:
/skills /data-analysis SKILL.md execute.py requirements.txt /web-search SKILL.md execute.py /internal-api SKILL.md execute.py每个技能目录里必须有一个 SKILL.md,这个文件是模型理解技能用途的入口,里面的描述质量直接决定模型能否在正确的场景下选择这个技能。写 SKILL.md 是 Agent 开发中最容易被低估的事情,很多人写完 execute.py 就不管文档了,导致模型根本不知道这个技能是干嘛用的,浪费了整套机制。
4.2 SKILL.md 的写作规范与实践经验
SKILL.md 说白了就是给大模型看的自然语言接口文档,它的质量决定技能被正确调用的概率。我推荐的结构包括以下几个方面:
首先要用一句话说明这个技能解决什么问题。其次列出该技能的典型触发场景,举出模型在用户提到哪些意图时应该考虑调用这个技能。然后是具体的工作步骤,模型的推理过程会参考这些步骤来规划执行路径。最后是必要的注意事项和边界条件,避免模型在不适用的场景下误用技能。
我在给一个内部的数据分析技能写 SKILL.md 时,初次写得太笼统,只有一句“用于分析数据”。这个粒度下模型经常在用户问天气的时候也去调用数据分析技能,调用完后生成了一个毫无意义的报告。后来我改成限定触发条件、明确输入格式、给出输出规范,调用准确率明显提升。文档里明确写了“仅当用户提供结构化数据表格或明确要求统计指标时才使用”,模型就不再乱调了。
SKILL.md 里还可以补充一些示例对话,few-shot 对模型理解技能边界很有帮助。例如:
## 触发场景示例 - 用户:分析这份 1-6 月各区域销售数据,找出下滑最明显的区域 → 调用>import subprocess import json result = subprocess.run( ["python", "execute.py"], input=json.dumps(input_data), capture_output=True, timeout=30, cwd=str(skill_dir) ) output = json.loads(result.stdout)用 subprocess 而不是直接调函数,虽然会有一点性能损耗,但带来的是稳定性和隔离性,对于一个同时跑多个技能的服务来说非常值得。另外超时时间要根据技能性质灵活调整,数据分析类技能 30 秒是底线,普通查询类技能 5 秒就足够了。
4.4 将腾讯云内部 API 封装为 Skills 的完整实例
前面讲了很多理论,这里给一个我自己实操过的完整案例:把腾讯云内部的一个短信发送服务封装成 Agent 技能。
这个服务的原始接口是 HTTP POST 请求,鉴权方式是腾讯云 API 网关的签名认证。直接让模型调用原始 HTTP 工具不是不行,但有两个问题:一是签名逻辑太复杂,模型在生成签名头时很容易出错;二是这个内部 API 涉及敏感操作,不该让模型任意拼接参数。封装成技能之后,代码内部处理签名,对外只暴露语义化参数。
execute.py 的关键结构如下:
from tencentcloud.common import credential from tencentcloud.sms.v20210111 import sms_client, models def execute(phone: str, content: str, template_id: str) -> dict: cred = credential.Credential(SECRET_ID, SECRET_KEY) client = sms_client.SmsClient(cred, "ap-guangzhou") req = models.SendSmsRequest() req.PhoneNumberSet = [phone] req.TemplateId = template_id req.TemplateParamSet = [content] req.SmsSdkAppId = SMS_APP_ID req.SignName = SMS_SIGN_NAME resp = client.SendSms(req) return {"message_id": resp.SendStatusSet[0].SerialNo}SKILL.md 则精确描述“该技能用于发送短信验证码/业务通知,用户表达『发短信』『发验证码』『通知用户』时使用,输入参数为手机号、消息内容、模板 ID”。这样封装后,Agent 就能在需要触达用户时主动调用这个技能,而且因为参数经过了强校验,不会出现格式错误导致短信发送失败。
封装时注意几个细节:配置文件不要硬编码在 execute.py 里,而是通过环境变量注入,方便在不同环境之间切换;返回结果要结构化,便于模型理解执行结果;日志要记录请求 ID、手机号、发送状态,方便排障。
4.5 Skills 生态的复用与共享:给 Agent 持续“加技能”
当你的 Agent 技能积累到一定数量后,会进入一个良性循环:每新增一个技能,Agent 的能力边界就扩大一块,能够解决的场景又多了好几类。我把已经调通的技能整理成了内部共享包,团队成员可以直接拉取,不需要重复造轮子。这里也推荐大家多关注社区里的 Skills 仓库,比如 codex skills 和 claude code skills 的官方文档里提供了很多高质量范例,学习它们的 SKILL.md 写法比自己闷头摸索效率高得多。
共享技能时建议打上版本号,用 Git 管理,Skills 目录里的每个改动都能回溯。我在复盘的时候发现,技能版本混乱导致的线上问题占比不低,Agent 调用了旧版本技能逻辑而代码仓库已经是新版本,这类问题加上版本号之后全都避免了。
5. 实测效果、性能指标与后续演进方向
整套链路跑通之后,我做了几组压测和功能验证。Agent 通过统一模型入口调用腾讯云混元,平均首字节响应时间在 800ms 左右,完整回复生成速度受限于带宽,流式输出体验基本和直连官方 API 没有差距。Litellm Proxy 单容器支撑 50 个并发请求没有出现超时,CPU 占用稳定在 40% 以下,内存占用约 1.2G。Redis 这边主要承担会话状态缓存和技能执行结果缓存,命中率大约 68%,有效降低了大模型重复调用的次数。
Skills 体系的实测效果是最明显的。加载 15 个技能的情况下,模型正确选择技能的准确率大概在 85% 上下,剩余的 15% 主要集中在技能描述之间存在语义重叠的场景。解决的办法是梳理技能边界,在 SKILL.md 里互相注明“此技能与 XX 技能的区别”,效果立竿见影。
性能指标方面有个值得留意的现象:技能数量增加后,发给模型的 tools 参数体积会快速膨胀,每个技能的平均 JSON Schema 大约 1KB,15 个技能就是 15KB 的额外上下文。这对上下文窗口较小的模型来说是一个不小的压力。我试过几种优化方案,最有效的是给高频技能建立索引,只把当前场景最可能用到的 5-6 个技能注入到 tools 参数,其余技能保持“休眠”状态,需要时再动态加载。
后续演进方向上,我在考虑引入基于 Embedding 的技能自动检索机制,将技能描述向量化后存到向量数据库,每次请求先计算用户意图与技能描述的相似度,再决定加载哪些技能。这套方案做出来之后,Agent 技能管理就真正从“手动编排”升级到“自动发现”了。另一个方向是给技能增加权限分级,敏感操作类技能需要额外授权才能执行,避免 Agent 在误解用户意图时触发高危操作。这两个方向都在验证阶段,跑通之后会再写一篇详细拆解。
如果从我实际维护这套系统的经验里提炼一句话,那就是:Agent 的外壳谁都能搭,真正决定上限的是你往里面装了多少高质量 Skills,以及你把这套技能体系管理得有多规范。腾讯云这台服务器现在每天稳定跑着我的 Agent 服务,偶尔半夜收到企业微信告警,打开看基本都是一些非致命错误,远程修一下就好,已经不怎么需要人工值守了。