ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

腾讯云上AI Skills实践:从Function Calling到Agent稳定部署

腾讯云上AI Skills实践:从Function Calling到Agent稳定部署 从去年开始我陆陆续续做了几个 Agent 方向的小项目从最开始用大模型 API 硬怼 Function Calling到后面自己封装执行器再到最近在腾讯云上把整套流程沉淀成 AI Skills踩了不少坑也总结出一套相对稳定的实践路径。这篇东西不聊玄乎的 Agent 理论就围绕如何在腾讯云上把 AI Skills 真正跑起来、跑得稳这件事把从设计到部署再到排障的完整过程掰开揉碎讲一遍。如果你正准备从能跑通的 demo迈向能上线的 Agent这篇应该能帮你省下几周的试错时间。1. 内容整体设计与思路拆解1.1 为什么 Agent 项目最容易卡在能力复用上先说一个很普遍的现象很多人第一次做 Agent都是拿着一两个工具函数去接大模型比如查天气、算汇率跑通了就觉得很兴奋。但一旦业务复杂度上来要接十几个甚至几十个工具问题就暴露了——大模型经常选错工具、参数传不对、工具返回结果一长就把上下文撑爆。最后整个 Agent 看起来能用实际上换个场景就崩。我自己的体会是Agent 项目的瓶颈从来都不是能不能调用工具而是能力怎么组织、怎么描述、怎么被模型正确选择。这就是 AI Skills 存在的意义。所谓 AI Skills不是简单地把函数改个名字挂上去而是把某类任务的处理能力封装成一个自包含的单元。它包含模型选择、提示词模板、输入参数定义、执行逻辑、输出标准化这几个要素。说得接地气一点Skills 就像是给 Agent 配备的专业技能包Agent 负责判断什么时候该用哪个包Skill 负责把这件事真正干完。在我这个项目里我把 Skills 设计成了三层结构基础 Skills单点能力比如调用外部 API、读写数据库、执行脚本。组合 Skills把多个基础 Skills 编排成一条工作流比如拉取数据 - 清洗 - 生成报告。决策 Skills负责让 Agent 在某些分支场景下做判断选择走哪条后续路径。这个分层设计的核心思路是让大模型只需要在相对小范围的工具集中做选择而不是面对一个几十项的扁平列表。这样既降低了模型误选概率也让每个 Skill 内部的逻辑可以独立迭代、独立测试不至于改一个函数就影响整个 Agent。1.2 方案选型为什么我没有一头扎进现成框架现在市面上的 Agent 框架很多从 LangChain 到各种国产框架上手都很快。但项目做深之后我反而觉得框架帮我们解决的是通用流程问题而 Agent 真正难的是业务化适配。框架越重定制起来越别扭。我的选择是底层用 Python FastAPI 自建一个轻量执行服务Agent 的大脑直接走大模型 API 的 Function Calling 能力Skills 以 JSON 配置 Python 实现文件的方式组织在一起。理由有三个第一透明可控。每个 Skill 的调用日志、参数记录、执行耗时都可以清清楚楚看到出了问题能直接定位到具体环节而不是在黑盒框架里猜。第二部署轻量。整个 Agent 服务就是一个 FastAPI 进程打包成 Docker 镜像后扔到腾讯云轻量服务器上就能跑不依赖重型中间件。第三Skill 可以热插拔。新加一个 Skill 只需要写一个实现文件加一段配置不需要改动 Agent 主流程。这个对于后续持续加能力非常重要。当然不使用重型框架不等于从零造轮子。Function Calling 本身的解析、流式输出这些底层能力我直接依赖大模型 API 的官方 SDK省去了大量重复工作。整体架构一句话总结就是轻核心重 Skill让扩展成本降到最低。1.3 项目落地后的能力边界与适用场景这个项目做下来我把它定位成中轻度任务自动化的 Agent 底座适合的场景包括企业内部的数据查询与报表生成比如让 Agent 根据自然语言查询数据库并输出 Excel。运维侧告警的初步分析与处理建议用 Skills 封装日志检索、指标查询接口。内容生产类的半自动流水线比如让 Agent 完成素材抓取、大纲生成、初稿撰写。不适合的场景也很明确需要强多轮记忆的复杂对话、涉及敏感操作如直接改生产库的任务这类还是得有人在环里盯着。所以在设计 Skills 时我会刻意把高风险操作隔离成独立 Skill并在 Agent 层设置二次确认机制。宁可流程繁琐一点也不能让模型在无人监管的情况下直接执行破坏性动作。2. 核心细节解析与实操要点2.1 Skill 的标准结构一份配置管住一切我在项目中把每个 Skill 定义为一个目录里面包含两个文件skill.json和impl.py。这个结构的灵感来自工程上配置与实现分离的原则好处是让非开发人员也能通过改 JSON 来调整 Skill 行为而不需要动代码。skill.json的核心字段我整理成了下面这个模板{ name: database_query, description: 根据用户自然语言描述生成并执行安全的数据库查询语句返回结构化查询结果。仅支持 SELECT 查询禁止执行 INSERT/UPDATE/DELETE 操作。, parameters: { type: object, properties: { query: { type: string, description: 用户希望查询的信息描述比如“最近7天订单量按天统计” }, limit: { type: integer, description: 返回结果的最大行数默认50, default: 50 } }, required: [query] }, execution: { timeout_seconds: 30, max_retries: 2, allowed_models: [gpt-4o, deepseek-chat] } }这里最需要注意的是description字段。很多人写 Skill 时容易忽略这个字段的重要性随手写一句查询数据库就完事了。但实际经验告诉我description就是给大模型看的产品说明书写得好不好直接决定模型能不能在正确的时机选中这个 Skill。我的经验是 description 必须包含三要素这个 Skill 能做什么、适合在什么场景下触发、有什么限制条件。比如上面这个例子我刻意加上了仅支持 SELECT 查询禁止执行 INSERT/UPDATE/DELETE这个限制目的就是让模型在遇到写操作时不要选这个 Skill而是走另一个需要人工确认的 Skill。2.2 Function Calling 场景下的参数设计技巧在 Function Calling 的机制下模型是根据参数定义的描述来生成调用参数的。所以参数的description写得越具体模型传参就越准确。这里分享几个实操细节第一参数能少则少。每个多余参数都是模型出错的潜在风险点。一个 Skill 的参数最好控制在 3 个以内能用默认值解决的绝不让模型自己生成。第二用枚举值约束自由输入。比如需要指定时间范围时我会定义成time_range: { type: string, enum: [today, yesterday, 7d, 30d], description: 查询的时间范围可选值固定为 today/yesterday/7d/30d }这样模型就不会自由发挥出最近一个礼拜这种让下游解析头疼的表述。第三布尔值参数慎用。大模型对布尔值的理解经常有偏差有时候会传字符串 true有时候会传 1。我建议把布尔值改为字符串枚举比如status: {enum: [enabled, disabled]}解析时更稳。第四当参数描述有歧义时在description里给出一个示例。比如时间格式为 YYYY-MM-DD例如 2025-03-15这种示例能显著提升模型传参的准确性。2.3 Skill 执行逻辑与上下文裁剪策略Skill 的实现文件impl.py我统一暴露一个入口函数execute(params: dict) - dict返回值必须是可 JSON 序列化的。这里有个硬性要求返回的文本不能太长。大模型的上下文窗口是有限的一个 Skill 返回上万字的 JSON不仅浪费 token还会干扰模型对后续对话的理解。我的处理策略是三层裁剪在 Skill 内部做聚合数据库查询结果先按维度聚合只返回统计结果而不是原始明细。在返回前做长度截断超过 2000 字符的内容自动截取关键信息并加提示结果已截断需要完整数据请增加 limit 参数。在主流程做缓存去重同一会话内短时间内重复调用同一 Skill 且参数相同直接命中缓存不重复执行。这三层下来实测平均每次工具调用返回给模型的内容从之前的 6000 多字符降到了 1500 字符左右效果非常显著模型在长对话中的迷失感明显减少。2.4 模型选择与 Skill 的绑定关系不是所有 Skill 都适合用同一个模型来处理。我在设计中允许每个 Skill 指定allowed_models字段比如复杂分析类 Skill 用更强的模型简单查询类 Skill 用性价比更高的模型。这个绑定关系的实现是在 Agent 核心层做一个路由逻辑模型在调用 Skill 之前Agent 会检查当前会话使用的模型是否在 Skill 的 allowed_models 列表中如果不在自动切换或降级处理。不过这里要提醒一句模型切换会丢失部分上下文连贯性所以我的实践是——默认对话用轻量模型只有复杂推理步骤才临时切换到强模型切换前把关键上下文压缩成一个摘要再传过去。3. 实操过程与核心环节实现3.1 腾讯云环境准备服务器、域名与安全组先把云上环境搭起来。我用的是腾讯云轻量应用服务器2核4G 的配置足够跑这个 Agent 服务。系统选 Ubuntu 22.04Docker 和 Docker Compose 都提前装好。如果是打算对外提供服务建议提前准备一个域名并完成 ICP 备案。腾讯云控制台里可以直接申请免费 SSL 证书配合 Nginx 来做 HTTPS 反向代理。域名这块我走的是腾讯云域名注册 DNSPod 解析整个流程在控制台就可以完成不需要额外工具。安全组配置是很多人容易忽略的坑。常规做法是只放行 80、443 和 SSH 端口就够了但不少人在调试阶段图省事直接把所有端口都放开了。这个做法是真的危险我见过好几台服务器因为安全组配置过于宽松被扫描入侵的案例。我的建议是Agent 服务本身监听内网端口Docker 容器端口不要直接映射到公网通过 Nginx 反向代理统一暴露 443。3.2 Agent 服务搭建从代码到 Docker 镜像Agent 核心代码结构我按模块拆分主要包含这几个部分agent-core/ ├── main.py # FastAPI 入口负责 HTTP 接口与生命周期管理 ├── core/ │ ├── router.py # 意图识别与 Skill 选择调度 │ ├── context.py # 会话上下文管理 │ └── executor.py # Skill 执行器负责超时控制、重试与异常捕获 ├── skills/ │ ├── database_query/ │ │ ├── skill.json │ │ └── impl.py │ └── http_request/ │ ├── skill.json │ └── impl.py └── requirements.txtFastAPI 入口的核心逻辑其实不复杂就是把接收到的用户消息拼上系统提示词调用大模型 API 的 Function Calling 接口如果模型返回了 function_call就解析出 skill 名称和参数执行对应 Skill再把结果回传给模型继续生成。这里有个细节值得展开Function Calling 的循环调用次数必须有限制。如果没有限制模型在某些极端情况下会陷入调用 Skill - 得到结果 - 再调用的死循环浪费大量 token 和时间。我的做法是设置最大轮次为 5 轮超过后强制让模型基于已有信息给出最终回答。Dockerfile 我写得很精简用的是python:3.11-slim作为基础镜像安装依赖后直接拷贝代码最终镜像体积控制在 300MB 左右在腾讯云容器镜像服务上构建和拉取都很快。FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple/ COPY . . EXPOSE 8080 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8080, --workers, 2]用腾讯云镜像源加速 pip 安装这是在国内服务器上部署时很实用的小技巧能省下不少时间。3.3 部署脚本编排Compose 一把梭服务涉及的不只是 Agent 本身旁边还挂了 Redis 做会话缓存和 Skill 结果缓存。用 Docker Compose 编排两个容器管理起来非常方便。version: 3.8 services: redis: image: redis:7-alpine restart: always volumes: - redis-data:/data command: redis-server --requirepass ${REDIS_PASSWORD} networks: - agent-net agent: build: . restart: always environment: - REDIS_URLredis://:${REDIS_PASSWORD}redis:6379/0 depends_on: - redis networks: - agent-net ports: - 127.0.0.1:8080:8080 volumes: redis-data: networks: agent-net:这里把 Agent 服务的端口映射到了127.0.0.1:8080意思是只有本机可以访问外部请求必须经过 Nginx 转发。这个小细节非常关键——如果直接映射成0.0.0.0:8080等于把没有鉴权的服务裸奔在公网上任何人都能调用。Redis 的密码我是通过.env文件注入的没有硬编码在 Compose 配置里。之前吃过这个亏有一次把配置推到公开仓库Redis 密码直接暴露了还好只是测试环境否则后果不堪设想。现在我的原则是所有敏感信息一律走环境变量代码库里只放.env.example模板。3.4 内网穿透与公网访问Nginx 反向代理Nginx 配置也是踩过坑之后才总结出来的。起初我以为反代就简单转个端口后来发现 WebSocket 支持和请求体大小都要单独配置尤其是 Agent 服务可能接收比较大的输入参数时Nginx 默认的 1MB 请求体限制会直接导致 413 错误。我的 Nginx 配置核心片段server { listen 443 ssl; server_name agent.example.com; ssl_certificate /etc/nginx/ssl/agent.example.com.pem; ssl_certificate_key /etc/nginx/ssl/agent.example.com.key; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; # 支持 WebSocket 长连接 } }加上proxy_set_header Upgrade和Connection upgrade是为了支持后续可能用到的流式输出和 WebSocket 交互。虽然现在的接口还是普通 HTTP 请求但这一点预留能省掉将来返工的时间。3.5 调用链路的完整走通验证部署完成之后我习惯用 curl 做一轮完整的链路验证。比如测试查询数据库最近7天订单量这个场景请求会经历以下路径用户请求到达 Nginx转发到 FastAPIAgent 将用户消息发给大模型带上 database_query 这个 Skill 的定义模型返回 function_call参数为{query: 最近7天订单量按天统计, limit: 7}Agent 执行 Skill拼接 SQL 并查询数据库查询结果经裁剪后回传给模型模型基于结果生成最终自然语言回答返回给用户用 curl 模拟一次请求curl -X POST https://agent.example.com/v1/chat \ -H Content-Type: application/json \ -H Authorization: Bearer ${API_KEY} \ -d { message: 帮我统计一下最近7天的订单量按天看一下趋势, session_id: test-001 }返回结果正常的话响应里应该包含模型的最终回答和完整的调用日志。我建议在开发阶段把每个环节的耗时也打出来这样一眼就能看出瓶颈在哪里。从我的实测数据来看一次典型调用的耗时分布是模型意图识别 1.2 秒左右Skill 执行 0.3 秒最终生成回答 2.5 秒左右。如果 Skill 执行耗时明显偏高那就要去查 Skill 内部逻辑了。4. 常见问题与排查技巧实录4.1 Agent execution terminated due to error 通用解法这个错误我在开发过程中遇到了不下十次。它不是某个具体代码报错而是 Agent 在执行过程中某个环节出了问题整个任务被终止。可能的原因非常多我整理了一个排查顺序看日志中最近一次大模型 API 调用是否返回了异常状态码比如 401、429、500。看 Function Calling 解析环节是否报错比如模型返回了不存在的 Skill 名称或参数格式非法。看 Skill 执行器是否捕获到了异常比如超时、外部 API 不可达。看回传模型的上下文是否超出了 token 限制。我的习惯是在 Agent 核心层加一个全局异常捕获把错误信息统一格式化为 Error: {skill_name} failed with {error_message}然后把这个错误信息回传给模型让模型基于错误信息重新规划或直接告诉用户失败原因。实测下来这种方法比直接抛异常要优雅得多用户至少能得到一个有意义的提示而不是看到一个干巴巴的报错。4.2 模型反复调用同一个 Skill 的循环问题前面提到我设置了最大轮次限制但还有一种情况是模型调用 Skill 成功后得到的结果没有正确传回模型导致模型以为还缺信息继续调用同一个 Skill。这个问题通常出在 Function Calling 结果的组装格式上。以 OpenAI 兼容接口为例模型返回 function_call 之后正确的做法是把模型的原始 response message 和 tool 的返回值都放进消息列表再调用一次接口。如果漏掉了原始 response message模型就丢失了之前的调用意图很容易重复调用。我在 executor 里处理这一段时加了详细的日志打印把每次发给模型的 messages 数量、内容摘要都记录下来。调这个 bug 的时候就是靠日志才发现原来每次调用时 messages 里面少了 model 的原始回复。4.3 上下文越来越长导致响应变慢Agent 对话轮次多了之后消息列表会越来越长最终导致两个问题响应变慢、费用变高。我用的方案是滑动窗口 摘要压缩。具体做法是保留最近 20 条消息作为完整上下文20 条之前的消息用一次独立调用压缩成一段摘要再把摘要作为系统提示词的一部分传给模型。这样既保留了对话的连贯性又把上下文长度控制在稳定范围内。这里有个经验值摘要压缩的调用建议用便宜快速的模型因为摘要质量只要大概准确就行不需要特别强的推理能力。把昂贵模型的使用集中在真正需要推理的核心环节上整个项目的运行成本能降下来不少。4.4 Redis 密码修改后重启失败的坑这个坑在热词里有人提到过我也踩过一模一样的。场景是Redis 容器跑得好好的我改了配置里的 requirepassdocker compose restart 之后发现容器一直起不来日志里报Ready to accept connections之后就循环退出。排查下来发现原因很有趣Redis 在启动时发现/data目录下已经有旧的持久化文件但该文件是使用旧密码写入的主人的密码校验逻辑和新配置冲突了。解决方案也不复杂把旧的 dump.rdb 文件备份后删除让 Redis 重新初始化。这个问题的教训是修改 Redis 配置之后不要只 restart要先看日志确认是否和其他配置项冲突。另外容器化部署下 Redis 的持久化文件不要盲目挂载到宿主机的固定路径否则迁移环境时会遇到各种奇奇怪怪的问题。4.5 问题排查速查表我把日常运维中最常见的问题整理成了一张速查表方便快速定位现象可能原因排查方向模型报工具不存在Skill 名称拼写错误或未注册检查 skills 目录配置是否加载成功参数解析失败模型传参类型与定义不符看日志中完整参数 JSON再优化参数 description 或枚举约束Skill 执行超时外部 API 慢或网络不通在 Skill 内部加超时与重试超过阈值快速失败并返回明确错误响应内容为空模型生成了空的 content检查是否为流式中断对空输出做兜底提示503 错误频发Agent 容器无可用 worker检查 uvicorn workers 数量与 CPU 内核是否匹配400 bad request请求体超过 Nginx 限制调大 client_max_body_size这张表是我在项目上线后持续补充整理的现在已经成为团队排障的第一参考。5. 进阶实践与优化方向5.1 多 Skill 编排把单点能力串成工作流当 Skills 数量积累到一定程度你就会发现让模型自由选择 Skill的模式在复杂任务上不够用了。比如帮我拉取昨天的销售数据生成一份日报并发送到群里这个任务涉及三个 Skill数据查询、文案生成、消息推送。靠模型一步步调用不是不行但经常会在中间环节出错。我的做法是引入一个编排层用一份流程配置定义 Skill 的执行顺序、输入输出依赖和分支条件模型只需要理解这份流程配置然后按步骤执行。这样复杂任务被拆解成固定流水线每个环节都有明确输入输出出错概率大幅下降。这个方案和用 Agent 框架内置的工作流有本质区别框架的工作流是代码硬编码的而我的方案是配置化的改流程不用改代码业务人员也能维护。5.2 Skill 结果缓存与复用哪些 Skill 的执行结果值得缓存我的判断标准是输入参数相同、输出内容稳定、且结果在短期内有效。典型的例子是获取本周汇率、查询服务器当前负载这类数据变更不频繁的查询。我在 Agent 核心层做了一个简单的缓存装饰器通过参数哈希生成缓存 keyRedis 里设置过期时间。实测下来在报表生成类场景中缓存命中率能达到 30% 左右这 30% 的请求直接跳过了模型调用和 Skill 执行响应时间从 4 秒降到了 100 毫秒以内效果可以说是立竿见影。5.3 基于反馈的 Skill 自我改进机制最后聊一个我自己正在探索的方向给 Skill 加效果反馈回路。具体做法是每次 Agent 调用某个 Skill 后让用户给结果打分有用/没用如果连续多次没用系统会自动把该 Skill 标记为待优化并在后续对话中降低模型选择它的优先级。这种机制的灵感来自推荐系统的反馈闭环。虽然目前实现还比较初级但方向是对的——AI Skills 不是一次性写死的东西而是应该在真实使用中不断被评估、被迭代的活体。一个 Skill 用的人多了、验证多了才会越来越贴合业务需求。我在这轮迭代中最深的感受是做一个能用的 Agent 不难难的是让它在真实场景里稳定可靠地工作。AI Skills 解决的本质问题是给 Agent 的能力建立一套工程化的管理规范。它让每个能力都可测试、可观测、可迭代而不再是一个个散落在代码里的孤立函数。这也是我把标题叫做养成记的原因——Agent 不是一次开发完就结束的东西它需要像养孩子一样在真实环境里不断观察、调整、喂新能力才能慢慢长成你想要的样子。
返回列表