news 2026/9/6 4:27:58

基于腾讯云与AI Skills的智能Agent实战:从架构到部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于腾讯云与AI Skills的智能Agent实战:从架构到部署

1. 项目概述:从零到一搭建一个真正能用的 Agent

这几年 AI 圈子的关键词从大模型本身,慢慢转移到了“怎么让模型真正干实事”上。大家手里都有 GPT-4、Claude 或者国产开源模型,但问几个问题可以,让它自动完成一整套业务流程就抓瞎了。于是 Agent 这个概念被炒得火热——但说实话,市面上的 Agent 项目鱼龙混杂,很多不过是套了个壳的聊天机器人,根本不具备“自主规划 + 调用工具 + 完成任务”的能力。

我这次做的项目,核心就是围绕腾讯云这套基础设施,把 Agent 从“能聊天”升级成“能干活”。整套方案基于一个核心思想:Agent 本身不直接写死业务逻辑,而是通过“AI Skills”这种可插拔的原子能力模块,把大模型的推理能力和外部工具、API、数据源串起来。说白了,Agent 是大脑,Skills 是手脚,大脑负责想怎么做,手脚负责真的去做。

这个项目适合谁参考?我觉得有三类人最受益:

第一类是后端开发者,想给自己的系统接入 AI 能力,但不想从头折腾模型部署和 Prompt 工程;第二类是独立开发者,想做点 AI 工具赚钱,但不想被云厂商的复杂概念绕晕;第三类是技术决策者,需要评估 Agent 落地时到底是自研框架、用现成编排工具,还是基于云平台做二次开发。

我大概用了半个月时间,从账号开通、环境配置、Skill 开发、Agent 编排,到压测和调优,把整个链路完整跑了一遍。这篇文章会把核心设计和实操细节全部摊开讲,包括我踩过的坑和几次返工的经历,希望能帮你少走弯路。

2. Agent 与 AI Skills 的前世今生:为什么“会调用工具”才是 Agent

2.1 先从概念说起:Skill、Agent、Workflow 到底是啥关系

很多新手上来就被一堆名词搞懵了。Skill 是什么?Agent 是什么?Workflow 又是什么?它们之间什么关系?

我打个比方。Agent 就像一个新入职的实习生,你说“帮我整理一份本周的销售数据周报”,他不会直接动手写 Excel,而是需要拆解任务:先查数据库、再算指标、再生成图表、最后写报告。每个环节他都不知道怎么做,但他知道要找谁帮忙。

Skill 就是那个“谁”。它是一段封装好的能力,比如“查询数据库的 Skill”、“生成图表的 Skill”、“调用钉钉接口发消息的 Skill”。每个 Skill 包含三样东西:一是给大模型看的描述说明(什么时候该用我),二是入参出参的定义,三是一段真正的执行代码。

Workflow 则是预先定义好的流水线。如果某个任务流程非常固定,比如每天凌晨跑一次数据同步,那就没必要让 Agent 每次重新规划,直接用 Workflow 把步骤串联起来,定时触发执行就行。

这三者的关系,可以理解成:Agent 负责“临场发挥”,Workflow 负责“固定套路”,Skill 是它们共同使用的基本零件。

2.2 为什么 AI Skills 是 Agent 落地的关键

我刚开始做 Agent 的时候,走过一段弯路。当时我把所有工具调用逻辑全都堆在 Prompt 里,比如在系统提示词里写“如果你需要查天气,就调用 weather_api,参数是 city,返回 JSON”。结果就是 Prompt 巨长,模型经常理解偏差,参数传错或漏传,而且每加一个工具就得改一次 Prompt,维护成本高到离谱。

后来我接触到 AI Skills 的模式,算是彻底解开了这个死结。Skill 把“功能的描述”和“功能的实现”绑在了一起,Agent 不需要靠 Prompt 里的文字描述去猜工具怎么用,而是读取 Skill 的清单文件,理解它的能力和调用方式,再动态决定是否调用。

这样做的好处非常明显:

  • 可插拔:新增能力就是增加一个 Skill 目录,不动 Agent 主逻辑。
  • 可复用:同一个 Skill 可以在不同 Agent、不同项目里反复使用。
  • 可测试:每个 Skill 是独立的,可以单独调试,排错效率高。
  • 语义清晰:大模型读的是结构化的 YAML 描述,不是一团自然语言,理解准确率显著提升。

所以我在这个项目里,把大量的精力花在了 Skill 的设计和编写上。Skill 写得好不好,直接决定了 Agent 的上限。

2.3 一个 Agent 项目的典型技术栈

这次我选用的整体技术栈是这样的:

层次选型说明
大模型底座腾讯云混元大模型 / 兼容 OpenAI 协议的模型接入通过统一网关切换,方便对比
Agent 编排框架自研轻量编排 + 任务规划模块控制 Agent 思考与行动循环
Skill 注册与发现腾讯云对象存储托管 Skill 清单支持动态加载与版本更新
工具执行层腾讯云云函数(SCF)承载 Skill 逻辑无服务器,弹性伸缩,免运维
接口网关腾讯云 API 网关统一暴露服务鉴权、限流、监控一站式搞定
配置管理环境变量 + 配置文件分离密钥与业务配置隔离

这个组合不是我拍脑袋定的,而是基于成本、稳定性和运维负担的综合考虑。后面我会详细讲每一步为什么这么选。

3. 动手前必须想清楚的事:整体设计与架构规划

3.1 先定义清楚:我的 Agent 要解决什么问题

我在社群和评论区遇到过不少人,一上来就问“怎么做 Agent”,但问他“你想让 Agent 做什么”,他答不上来。这是做 Agent 项目最大的坑——没有明确的目标场景,技术选型无从谈起

我这边的场景很简单:做一个“智能项目助手”,它能够根据自然语言指令,自动完成以下事情:

  1. 查询腾讯云账户下各产品的账单和资源用量;
  2. 拉取对象存储里的文件并做简单分析(比如统计日志中的错误码分布);
  3. 调用企业微信 Webhook 推送汇总报告;
  4. 对以上操作生成可读的说明文档。

这个场景麻雀虽小但五脏俱全——涉及云资源查询、数据处理、外部 API 调用、内容生成,基本覆盖了 Agent 应用的核心能力模型。

3.2 架构选型对比:自研、开源框架,还是云平台

确定场景之后,我面临一个经典的选择题:Agent 架构怎么搭?

方案一:完全自研编排逻辑。从零写 ReAct 循环、上下文管理、工具调用的解析与执行。优点是可控性最强,想怎么做就怎么做;缺点是开发量大,而且很多坑别人已经踩过了,没必要重复造轮子。

方案二:基于 LangChain、Dify 等开源/半开源框架。这类框架提供了 Agent 的完整基础设施,接模型、接工具都很方便。但问题也有——抽象层次太高,出了问题很难排查,而且框架更新快,接口变动频繁,维护成本不低。

方案三:基于云平台 Serverless 能力自研轻编排。这是我在这个项目里采用的方案。核心思想是:不引入重量级框架,而是用云函数 + API 网关 + 对象存储这三大件,自己写一套极简的编排逻辑

为什么选方案三?我的考量是:

  • Agent 的核心逻辑其实不复杂,就是“观察-思考-行动-观察”的循环,用 Python 写一个几百行的循环就够了;
  • 重框架的抽象反而阻碍了我对细节的控制,尤其是我要调用大量腾讯云 OpenAPI,与其绕框架的抽象层,不如直接写 SDK 来得痛快;
  • 云函数天然支持弹性伸缩,不需要关心 Agent 跑起来之后的并发和资源问题;
  • 这套架构不存在厂商锁定,核心代码放在云函数里,哪天要迁走,改个运行环境就行。

3.3 模块划分与目录结构

整个项目的代码组织结构如下:

agent-project/ ├── agent/ │ ├── core.py # Agent 主循环:思考-行动-观察 │ ├── planner.py # 任务规划器:把复杂任务拆成子步骤 │ ├── executor.py # 动作执行器:调用 Skill 并处理结果 │ └── memory.py # 记忆模块:短期上下文 + 长期持久化 ├── skills/ │ ├── billing_query/ # 账单查询 Skill │ │ ├── SKILL.yaml # Skill 的描述文件,给大模型看 │ │ └── handler.py # Skill 的具体实现 │ ├── cos_analyze/ # 对象存储日志分析 Skill │ │ ├── SKILL.yaml │ │ └── handler.py │ └── webhook_push/ # 企业微信通知 Skill │ ├── SKILL.yaml │ └── handler.py ├── config/ │ └── settings.py # 全局配置,读取环境变量 ├── requirements.txt └── server.py # API 入口,对接 API 网关

这个结构有几个设计要点:

Skill 和 Agent 完全解耦。Agent 每次运行时扫描 skills 目录,读取所有 SKILL.yaml,把能力描述注入到系统提示词中。新增一个 Skill 只需要添加一个目录,不需要改 Agent 代码。

每个 Skill 是完整的服务单元。handler.py 导出一个统一接口run(params) -> dict,Agent 不需要关心内部实现是调 API 还是读文件。

配置集中管理。所有敏感信息走环境变量,团队协作时不会把密钥泄露到代码库。

4. 核心细节解析:AI Skills 怎么写的才算好

4.1 SKILL.yaml:写给大模型的“使用说明书”

SKILL.yaml 是整个 Skill 体系里最重要、也是大多数人写不好的文件。它是大模型判断“何时该调用这个 Skill、怎么调用”的唯一依据。写得不清楚,模型就不会用;写得太长,模型又会信息过载。

这是我的一个实际例子:

name: billing_query description: > 查询腾讯云账户的账单费用信息。当用户询问"花了多少钱"、"账单" "费用明细"、"某个产品的消费情况"时使用。可以按产品、地域、 时间范围筛选。如果不确定用户想查哪个产品的费用,主动询问。 version: 1.0.0 author: your_name parameters: type: object properties: product: type: string enum: ["cvm", "cos", "scf", "cdn", "all"] description: 要查询的云产品,默认 all 表示所有产品 default: all start_date: type: string format: date description: 开始日期,格式 YYYY-MM-DD,默认本月1号 end_date: type: string format: date description: 结束日期,格式 YYYY-MM-DD,默认今天 granularity: type: string enum: ["day", "month", "total"] description: 汇总粒度,默认 total default: total required: [] returns: type: object properties: total_cost: type: number description: 总费用,单位元 product_breakdown: type: array description: 按产品拆分的费用列表 currency: type: string description: 币种

写这个 YAML 的时候,我有几个心得:

description 要写触发条件,不要只写功能。“查询账单”这种描述太泛了,模型不知道什么时候用。要像我上面那样,把用户可能问的话直接写进去:“花了多少钱”、“账单”、“费用明细”。这相当于给模型一个关键词触发地图。

parameters 要写清楚默认值和边界。模型填参数时经常漏填,所以在设计上尽量给所有参数设置默认值,让 Skill 在参数不全时也能兜底运行。required 字段尽量留空,用默认值兜底,比强制模型填参数更可靠。

枚举值降低模型出错概率。比如 product 字段,如果我写成自由字符串,模型可能会传“云服务器”“CVM”“轻量应用服务器”等各种变体。用 enum 限定之后,模型只能从给定选项里选,出错率直线下降。

这里我测试过一个很典型的案例:同一个查询任务,description 写得模糊的 Skill,模型调用成功率只有六成左右;重写描述并加上 enum 约束之后,成功率提到了 95% 以上。所以说,Skill 的描述文件不是给程序员看的,是给大模型看的,花时间打磨非常值得

4.2 handler.py:Skill 执行逻辑的几个硬性要求

Skill 的执行逻辑写在 handler.py 里,我总结了几条必须遵守的规范:

import json import logging from typing import Dict, Any logger = logging.getLogger(__name__) def run(params: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]: """ 统一入口函数,所有 Skill 必须实现。 params: 大模型根据 SKILL.yaml 填入的参数 context: 运行上下文,包含认证信息、请求ID等 """ try: # 参数解析与校验,不允许在这里抛未捕获异常 product = params.get("product", "all") start_date = params.get("start_date") end_date = params.get("end_date") granularity = params.get("granularity", "total") # 业务逻辑... result = query_billing(product, start_date, end_date, granularity) return { "success": True, "data": result, "message": f"成功查询到 {product} 的账单信息" } except Exception as e: logger.exception("billing_query skill failed") return { "success": False, "error": str(e), "message": "账单查询失败,请检查参数或稍后重试" }

第一条规范:统一入口签名。不管什么 Skill,入口函数都叫run,接收paramscontext,返回dict。这样 Agent 的调用方代码可以写得很简洁,只用一套反射逻辑就能调度所有 Skill。

第二条规范:异常绝不外抛。Skill 执行过程中,网络超时、API 返回异常、参数缺失,都是家常便饭。如果异常直接抛出去,Agent 主循环就崩了。正确做法是捕获所有异常,包装成固定格式的错误信息返回给 Agent,让模型根据错误信息决定下一步是重试还是换个方案。

第三条规范:返回结构要带“给模型看的解释”。这里我特意加了message字段,它不是给用户看的,而是给模型看的。模型决策下一轮行动时,需要知道“刚才那次调用结果意味着什么”。有个简洁的中文说明,模型的理解会准确很多。

这里还有一个细节值得注意:Skill 返回值里不要带无关紧要的调试信息。模型一次要处理的 token 是有限的,垃圾信息越多,模型越容易“分心”。我见过有人把整个 HTTP 响应头都塞进返回,结果模型把状态码当业务数据解析,闹出不少笑话。返回值只保留对下一步决策有意义的字段,其他统统过滤掉。

4.3 Skill 与 Agent 的信息交互机制

Agent 调用 Skill 的过程,我实现得比较轻量:利用 Python 的 importlib 动态扫描 skills 目录下的所有子目录,读取 SKILL.yaml,然后根据模型选择的 Skill 名称,加载对应的 handler.py。

交互链路是这样的:

  1. Agent 接收用户指令,连同所有 Skill 的 YAML 描述一起发给大模型;
  2. 大模型判断需要调用哪个 Skill,输出结构化 JSON(包含 skill 名称和参数);
  3. Agent 解析 JSON,找到对应 handler,执行run(params, context)
  4. 将返回值拼装成一条工具消息,连同对话历史再发给大模型;
  5. 大模型根据工具结果决定是继续调用其他 Skill 还是给出最终回答。

关于第 2 步,我强烈建议让模型直接输出 JSON,不要整什么 Function Calling 的特殊协议。并不是所有模型都原生支持 Function Calling,直接用文本生成 JSON 反而有最好的兼容性。我用的底层模型是混元,但即使以后换 Llama、Qwen,这套机制完全不用改。

这里踩过的一个坑是:模型生成的 JSON 偶尔不合法,比如引号没闭合、多了一个逗号。我后来加了一个“JSON 修复层”,用正则粗略提取大括号范围,再用json.loads尝试解析,失败的话就用ast.literal_eval做兼容,再不行就返回错误让模型重新生成。这个兜底逻辑把 JSON 解析失败的概率降到了 1% 以下。

5. 实操过程:腾讯云环境配置与 Skill 部署

5.1 云函数(SCF)创建与配置细节

腾讯云的 Serverless 产品叫云函数 SCF,Skill 的代码我就部署在上面。创建函数的过程不细讲了,控制台操作很直观,重点说几个容易配置错的地方。

运行时选择:我用的 Python 3.9。不要用 Python 2.7,很多依赖库已经不支持了。也不建议用最新版本,有些第三方库还没跟上兼容。

内存配置:很多人图便宜选 64MB,但对于 Agent 场景来说太紧张了。Skill 执行时一般会引入 requests、tencentcloud-sdk-python 这些库,本身就要占几十 MB 内存。我建议至少选 256MB,预算充足选 512MB 更稳。

超时时间:Agent 的一个完整循环可能要经历多轮模型调用和工具调用,但注意 SCF 单次超时是给单次 Skill 执行设置的,不是给整个 Agent 会话设置的。查询类操作通常几秒内能完成,我设置 30 秒,够用且避免积压。

环境变量配置:在云函数控制台的“环境变量”里配置腾讯云 SecretId、SecretKey,还有企业微信 Webhook 地址等。千万别把这些硬编码在代码里,一方面是不安全,另一方面是后续换密钥还要重新部署代码,麻烦。

创建函数的示例命令:

# 使用 SCF CLI 部署函数 scf deploy \ --name agent-skill-billing \ --runtime Python3.9 \ --handler index.main \ --memory-size 256 \ --timeout 30 \ --env "SECRET_ID=xxx,SECRET_KEY=yyy"

5.2 API 网关:给 Agent 一个对外访问的统一入口

Agent 本身需要一个 API 入口来接收前端或客户端的请求。腾讯云 API 网关在这里的作用有两个:一是把 HTTP 请求转发给云函数,二是提供统一的鉴权和限流能力。

在 API 网关配置时,我主要做了这几件事:

路径规划:我把整个 Agent 作为单一入口,POST /agent/chat接收用户消息和会话 ID。为什么不按 Skill 拆多个路径?因为 Agent 是一场多轮对话,状态在 Server 端维护,拆太细反而增加复杂度。

鉴权方式:我开启了 API 网关的“密钥对鉴权”,在客户端调用时通过请求头传入X-API-Key。很多初学者直接开放公网访问,没有鉴权,结果被人刷接口,账单炸了。

限流配置:我设置了 QPS 限流和并发限制。单用户每秒最多 2 次请求,整体 API 每秒最多 20 次。这个配置在 Agent 场景下非常必要,因为一次用户请求背后可能要触发多次上游模型调用,如果流量不加控制,云函数费用和模型调用费用很容易失控。

5.3 域名与 HTTPS 配置要点

控制台里可以给 API 网关绑定自定义域名,需要备案,然后在域名服务商处配置 CNAME 解析到腾讯云给的默认域名。这个过程本身不复杂,但有几个细节值得注意:

HTTPS 证书:腾讯云有免费的 SSL 证书可以申请,每年续一次。配置时记得选择“强制 HTTPS”,不然 HTTP 明文请求会把用户对话内容暴露在网络链路上。

路径转发:绑定自定义域名时,可以配置路径映射。我的配置是https://api.yourdomain.com/agent/*转发到对应 API 网关服务。这里注意通配符要写对,否则请求会 404。

跨域配置:如果前端是浏览器直接调用,还需要在 API 网关配置 CORS。这个我调试的时候浪费了不少时间,浏览器的同源策略在 POST + JSON 场景下会发预检请求,处理不好就会出现“接口明明通,浏览器调不通”的怪现象。后来我在网关层加了允许的来源域名和允许的请求头,问题就解决了。

5.4 对象存储:Skill 清单与配置的热更新方案

一个容易忽略但实际很刚需的能力是:Skill 清单需要支持热更新。Agent 运行时不直接从本地目录读 SKILL.yaml,而是先从对象存储拉取一份最新的 manifest.json,再按清单加载对应的 Skill 代码。

这个设计的好处是,我更新一个 Skill 的描述或者修复一个 bug,只需要重新上传对应的压缩包到对象存储,不用去云函数控制台做代码部署。对于频繁迭代的 Agent 项目来说,这个能力非常提效。

具体实现不复杂:云函数每次启动时(或者定时 5 分钟),从对象存储拉取skills/manifest.json,解析里面的 Skill 名称、版本号和下载地址,再决定是否需要热更新。manifest 文件的格式类似:

{ "version": "2.3.0", "skills": [ { "name": "billing_query", "version": "1.2.0", "download_url": "https://xxx.cos.ap-guangzhou.myqcloud.com/skills/billing_query_1.2.0.zip", "checksum": "a1b2c3..." } ] }

为什么用对象存储而不用配置中心?因为我除了清单文件,还需要托管 Skill 的代码压缩包,对象存储天然适合这种场景,而且有 CDN 加速,拉取很快。

这个方案在发布新 Skill 的时候特别方便,不用碰任何线上服务,改一个 manifest.json 就能让所有 Agent 实例感知到新能力。我有一次加了新 Skill 后,十分钟内所有线上 Agent 都能用了,整个过程没有重启任何服务。

6. Agent 核心逻辑的实现:思考、行动与记忆

6.1 Agent 主循环的实现思路

接下来是重头戏:Agent 主循环。这是整个项目的灵魂,我直接给出核心代码的简化版本,然后逐段解释。

import json import time from typing import List, Dict, Any class Agent: def __init__(self, model_api, skill_registry, memory_store): self.model_api = model_api self.skill_registry = skill_registry self.memory_store = memory_store self.max_iterations = 10 def run(self, user_message: str, session_id: str) -> Dict[str, Any]: messages = self.memory_store.load(session_id) messages.append({"role": "user", "content": user_message}) system_prompt = self._build_system_prompt() for i in range(self.max_iterations): # 1. 思考:调用大模型,决定下一步动作 response = self.model_api.chat( messages=[{"role": "system", "content": system_prompt}] + messages ) action = self._parse_action(response) # 2. 如果是最终回答,循环结束 if action["type"] == "final": messages.append({"role": "assistant", "content": action["content"]}) self.memory_store.save(session_id, messages) return {"reply": action["content"], "iterations": i + 1} # 3. 行动:执行 Skill 调用 if action["type"] == "skill_call": skill_name = action["skill_name"] skill_params = action["params"] result = self.skill_registry.execute(skill_name, skill_params, {}) messages.append({ "role": "assistant", "content": f"[调用Skill {skill_name}] 参数: {json.dumps(skill_params, ensure_ascii=False)}" }) messages.append({ "role": "tool", "content": json.dumps(result, ensure_ascii=False) }) # 4. 记忆持久化(每轮都保存,防止运行中断丢失上下文) self.memory_store.save(session_id, messages) # 达到最大轮次仍未结束,强制返回错误 return {"reply": "抱歉,我没有在规定的步骤内完成这个任务,请尝试拆分成更小的请求。"} def _build_system_prompt(self) -> str: skills_desc = self.skill_registry.get_descriptions() return f"""你是一个智能助手,可以调用以下工具帮助用户完成任务: {skills_desc} 请根据用户需求,选择调用合适的工具。如果工具不满足需求,请直接作答。 你的回复必须是一个 JSON 对象,格式为: - 最终回答: {{"type": "final", "content": "你的回答"}} - 调用工具: {{"type": "skill_call", "skill_name": "工具名", "params": {{...}}}} """

这段代码虽然简洁,但包含了 Agent 机制的几个关键设计:

max_iterations 限制。这是必须的,不然 Agent 会陷入死循环。我设定的是 10 轮,覆盖绝大多数任务。如果 10 轮还没完成,说明任务本身有问题或者模型理解有偏差,直接返回提示让用户换个方式表达更合理。

每轮都做持久化。如果 Agent 跑了 5 轮后网络断了,没有持久化的话整个会话状态就丢了。我在_parse_action之后立刻保存 messages,就是为了避免中间环节崩溃导致前面的推理白费。

模型的输出必须是结构化的。整个主循环的驱动力就是把模型输出解析成action。这是一个非常关键的抽象——不管是 OpenAI 风格的消息格式还是混元的返回格式,最终统一转换成内部 action 结构,后续扩展别的模型就很方便。

6.2 任务规划:把复杂问题拆成可执行的子步骤

单轮“思考-行动”循环解决不了复杂任务,比如“帮我分析最近一周的 CDN 流量异常,并生成一份排查报告”。Agent 需要先规划,再执行,最后汇总。

我实现了一个简单的规划器,它的思路是:先让模型把这个任务拆成若干子任务,然后逐个执行。这里用到了一个很关键的技巧——不要让模型一次性规划完再执行,而是采用“边规划边调整”的策略

class Planner: def __init__(self, model_api): self.model_api = model_api def plan(self, user_message: str) -> List[Dict[str, Any]]: """把用户任务拆解为子步骤""" prompt = f"""请把以下任务拆解为最多5个可执行的子步骤。 每个子步骤应包含:步骤描述、是否需要调用工具、预计使用的工具。 任务:{user_message} 请直接输出 JSON 数组,格式如下: [{{"step": 1, "desc": "查询CDN流量数据", "skill": "cdn_query", "depends_on": []}}] """ response = self.model_api.chat([{"role": "user", "content": prompt}]) try: steps = json.loads(self._extract_json(response)) return steps except Exception: # 解析失败就退回单步执行 return [{"step": 1, "desc": user_message, "skill": None}]

这里的关键取舍是:规划器只是一个“建议器”,不是“强制执行器”。Agent 在实际执行每个子步骤时,仍然会根据当时的中间结果动态调整。比如规划时以为要查 CDN 的 Skill,执行到一半发现需要先查账号权限,Agent 会停下来先解决权限校验。

最初版本我把规划器设计成强约束的,即 Agent 必须严格按步骤执行,结果经常卡死。后来改成建议制,效果反而好很多,因为大模型的中间推理往往能发现纯静态规划看不到的问题。

6.3 短期记忆与长期记忆:别让 Agent 忘了自己说过的话

Agent 的记忆机制直接决定了对话体验。我这里做了两层:

短期记忆 = 对话上下文,保存在内存或 Redis。每一轮的 messages 都会完整保存,直到会话结束或多轮对话超过模型上下文窗口。这块用 Redis 实现很方便,天然支持过期时间设置,比如 30 分钟无活跃就清理。

长期记忆 = 用户偏好和关键事实,存数据库。比如用户之前提到“我常用的实例在广州地域”,这个信息值得长期记住。我在 Agent 每次回答结束后,会额外让模型判断“当前对话中是否有值得长期记忆的事实”,有则提取出来写入数据库。

长期记忆的粒度要控制好,不能什么都记。我只提取三类信息:用户的固定偏好、项目中需要反复使用的上下文、业务流程中约定的规则。事实证明,克制的记忆比贪婪的记忆更有效,因为记忆太多会干扰模型对当前任务的注意力。

6.4 并发与配额控制:别让 Agent 把你账户刷爆

Serverless 架构的最大优势是弹性,但弹性也有副作用——如果 Agent 逻辑有 bug,或者用户恶意刷接口,云函数会被瞬间拉起成百上千个实例,账单直接起飞。

我做了三层防护:

第一层:API 网关限流。前文提到过,单用户 QPS 限制,超了直接返回 429。这是最外层的防线。

第二层:云函数并发配额。在 SCF 控制台可以设置函数级别的并发上限。我设置的是 50,也就是说整个函数同时最多 50 个实例在跑,超过的请求排队或丢弃。

第三层:Agent 内部预算控制。我给每个会话设置了一个“步数预算”和“费用预算”。每调用一次模型或执行一个 Skill,都会记账。超过预算就直接终止会话,让用户重新发起请求。

这里有一个很多人忽略的细节:模型调用的费用往往比云函数计算费用高一个数量级。云函数跑一天可能就几块钱,但模型 API 在 Agent 循环里被频繁调用,一天几十上百块很正常。所以控制 Agent 的迭代次数,本质上是控制模型调用次数,这是成本控制的核心杠杆。

7. 问题排查实录:那些让我头秃的典型 Bug

7.1 模型老是不按 JSON 格式输出怎么办

这是 Agent 开发中最常见的头疼问题。明明系统提示词里说了必须输出 JSON,模型偏要自作主张输出一段自然语言。

我的排查过程分三步:

第一步,检查提示词。把 JSON 格式要求放在提示词的最末尾,离模型的输出位置更近,模型遵循的概率会显著提高。

第二步,增加“小样本示例”。在系统提示词里给一个完整的输入输出示例,模型模仿能力很强,效果比单纯说“请输出 JSON”好得多。

第三步,实现兜底解析。前两步还失败的时候,就用我前面提到的“JSON 修复层”。用正则提取最外层的花括号,然后用容错解析器解读。如果还是不行,就告诉模型“你上一轮输出格式不对,请重新输出 JSON”,通常第二次就能成功。

我做了个统计,加了小样本示例后,格式错误率从 12% 降到了 3% 左右。所以遇到格式问题,优先优化提示词,而不是堆代码。

7.2 Skill 调用时参数总是传错

有时候模型明明调对了 Skill,但参数传得离谱,比如把“2024-01-01”传给了 limit 字段(原本应该是整数)。这种问题定位后发现,根源基本都在 SKILL.yaml 的参数描述上。

解决方式是提供更明确的类型和格式约束。YAML 里的typeformatenum字段要写清楚。我自己测试下来,最有效的是在描述里加一个示例值:

start_date: type: string format: date description: 开始日期,格式 YYYY-MM-DD,例如 2024-01-01

加上“例如 xxx”这五个字,模型传参准确率提升非常明显。

7.3 云函数冷启动导致首请求超时

Serverless 的冷启动是个绕不开的话题。Agent 场景下,每次用户发消息,云函数可能刚被拉起,初始化依赖库就要花 2-3 秒,再加上模型调用的时间,用户感知明显卡顿。

我做了两个优化:

一是预留并发实例。在 SCF 控制台可以设置“预置并发”,让 2-3 个实例常驻。这样请求进来直接复用已初始化的实例,首请求延迟从 3 秒降到 200 毫秒左右。

二是把重依赖放到函数初始化阶段。在main.py顶层导入所有 SDK 并做初始化,不要在每次 Skill 调用时重新 import。Python 的 import 机制虽然会缓存,但处理大型 SDK 时开销依然不小。

7.4 排错技巧:日志与追踪体系怎么搭

Agent 排错比普通后端难,因为链路长:用户输入 → 模型推理 → 工具执行 → 再次推理 → 最终输出。任何一环出错都可能导致整体行为异常。

我的做法是:每个会话生成一个 trace_id,从入口开始贯穿所有环节。API 网关接收请求时生成 trace_id,传入云函数,云函数里每次模型调用和 Skill 执行都打印带 trace_id 的结构化日志。

日志格式我用的是 JSON Lines:

{"trace_id": "abc123", "event": "model_request", "input_tokens": 1200, "output_tokens": 300, "latency_ms": 1500} {"trace_id": "abc123", "event": "skill_call", "skill_name": "billing_query", "params": {"product": "all"}, "latency_ms": 800} {"trace_id": "abc123", "event": "skill_result", "success": true, "data_summary": "total_cost=12.34"}

排查问题时,直接按 trace_id 过滤日志,就能还原整个 Agent 的思考与行动过程。说实话,这个日志体系是我认为整个项目里性价比最高的投入——没有它,面对一个表现异常的 Agent,真的是盲人摸象。

7.5 常见问题速查表

现象可能原因解决方案
模型不调用 Skill 直接回答SKILL.yaml 描述不清晰,模型不知道何时该用优化 description,增加触发词和示例
Skill 执行成功但 Agent 回复“无法获取信息”返回值格式不符合模型预期或字段名模糊在 message 字段中补充自然语言说明
对话超过几轮后响应变慢历史消息太长,超出模型上下文限制实现摘要压缩,把早期消息浓缩成要点
多用户并发时互相串号Session 管理不当,上下文被覆盖使用 Redis,key 按 session_id 隔离
查询类 Skill 偶尔报权限错误云函数角色权限配置不全在 SCF 角色中授予对应的云产品只读权限
模型把日期格式传成时间戳参数描述不够明确YAML 中增加 format 和示例
API 网关返回 504云函数执行超时,模型调用或 Skill 执行过长检查超时设置,规划 Agent 轮次上限
新部署的 Skill 不生效对象存储 manifest 缓存未刷新更新 manifest.json 版本号并配置 CDN 刷新

8. 性能调优与成本控制:Agent 项目能不能省钱跑

8.1 模型调用的降本妙招

Agent 项目最大成本在模型 API 调用,一个完整任务可能要调 3-8 次模型。降本的核心思路是减少不必要的模型调用

第一个技巧:普通问题走小模型,复杂问题走大模型。我在 Agent 主循环前加了一个“意图识别”步骤,用一个便宜的小模型(比如混元 Turbo)判断问题的复杂度。简单问题直接让小模型回答,只有需要多轮推理或工具调用时才切换到强模型。测试下来,约 40% 的问题可以在小模型层结束,整体模型成本下降约 30%。

第二个技巧:缓存重复的自然语言处理结果。用户经常会问类似的问题,比如“昨天账单多少”和“查询昨天的账单费用”。我对用户输入做了归一化处理(去除停用词、统一时间表达),然后用语义向量提取特征,在 Redis 里查询是否命中缓存。命中就直接返回上次结果,不再触发模型调用。但要注意,带时效性的数据查询不适合缓存,需要配合时效判断。

第三个技巧:减少历史消息的冗余。把早期轮次的长文本消息做摘要,只保留提炼后的要点发给模型。比如用户上传了一份很长的日志文件,Agent 在第一轮读取后做了分析,后续轮次就没有必要把完整日志继续放在上下文里,把它压缩成“日志包含 XX 条错误,主要分布……”即可。

这些优化手段叠在一起,模型 token 消耗能减少一半左右。别小看这个比例,Agent 跑到生产环境,一个月省下来的就是真金白银。

8.2 云函数成本控制

云函数的计费跟“调用次数 × 配置内存 × 运行时长”相关,Agent 场景的特点是调用频繁但单次运行时间短。成本控制的核心是:降低不必要的实例启动,减少运行时长

我做了三个优化:

一是把 Skill 执行和模型调用并行化。Agent 如果需要同时查询账单和资源用量,两个 Skill 之间没有依赖关系,用 asyncio 并发执行,总耗时从两个 Skill 的串行时间变成最长的那个。时间短了,费用自然就下来了。

二是合理设置内存。内存大小直接影响计费单价,但也不是越小越好。内存太小会导致容器 OOM,函数不停重启,费用反而更高。我实测过:256MB 对查询类 Skill 足够,512MB 对处理大数据量的分析类 Skill 更稳。按不同类型 Skill 分配不同内存的函数,比一刀切用 512MB 更省钱。

三是打开函数的“请求多实例复用”。云函数在同一实例上处理完一个请求后,会保留一段时间等待下一个请求。复用已初始化的实例,能省掉重复的冷启动开销。

8.3 监控与告警配置

Agent 类应用和普通后端应用的监控侧重点不同。除了基础的 CPU、内存、错误率,还要重点盯这几个指标:

  • 单次会话平均迭代轮数:如果超过 8 轮,说明 Agent 在绕圈子,大概率有逻辑问题。
  • Skill 调用成功率:低于 90% 就说明 Skill 的描述或实现有问题,需要优化。
  • 平均响应延迟:Agent 首字返回时间一般要控制在 3 秒内,超过就需要检查是哪一环节拖慢了。
  • 模型 API 费用日趋势:这个必须每天看,一旦发现异常飙升,立刻检查是否有死循环或恶意刷接口。

腾讯云本身就提供监控告警能力,在 SCF 控制台可以直接配置基于日志关键词的告警。比如设置“skill_call failed”这个关键词出现 5 次就触发告警,发短信和邮件通知。

9. 从开发到上线:一个完整项目的落地复盘

9.1 开发时间线参考

阶段耗时核心内容
需求确认与场景定义1 天确定 Agent 要解决的业务问题,定义验收标准
环境搭建与账号开通0.5 天开通云函数、API 网关、对象存储,配置密钥
Skill 开发(3 个)3 天编写 SKILL.yaml 和 handler.py,本地调试通过
Agent 主循环开发2 天实现思考-行动-观察循环,接入模型 API
集成联调2 天API 网关、云函数、对象存储全链路打通
性能调优与安全加固1 天并发控制、限流配置、日志体系搭建
测试与验收1 天覆盖正常流程、异常流程、并发场景

总计约 10 个工作日。说实话,比我想象中要快,关键是没有被重框架束缚,很多逻辑直接用云函数实现,省去了搭建和维护基础设施的时间。

9.2 几个“早知道就好了”的经验

第一,Skill 数量不是越多越好。我开始做了 8 个 Skill,后来发现很多功能的概念边界重叠,模型经常选错。砍到 3 个核心 Skill 之后,准确率反而上去了。Skill 的边界要清晰,每个 Skill 负责一个领域,交集越小,模型越容易分辨。

第二,Prompt 工程依然是核心竞争力。就算有再好的 Agent 框架和 Skill 体系,最终驱动这一切的还是模型对自然语言的理解。我在 Agent 主循环的提示词上迭代了很多版本,每次都记录下模型的表现变化,最后沉淀出一套稳定的模板,这个投入比写业务代码还要值得。

第三,提前规划好测试集。上线之前,我整理了一份约 50 条常见用户问题,覆盖各种边界情况,比如“帮我查一下所有实例的费用”(需要遍历产品枚举)、“昨天和今天的差别是什么”(需要连续两次调用后比较)。每轮迭代都用这份测试集做回归,避免改了一个功能又弄坏了另一个功能。

第四,安全的坑趁早填。我早期开放了 API 网关但没做鉴权,被一个扫描器发现后疯狂刷接口,一晚上产生了几百元费用。教训很深刻。如果你也准备上线 Agent 服务,先把鉴权和限流做好再谈功能,这不是可选优化,而是必选项。

9.3 后续可以扩展的方向

这个项目做完了,但 Agent 和 AI Skills 的想象空间远不止于此。我自己已经在规划几个扩展方向:

一是把 Skill 生态开放出来。目前的技能都是我自己写的,下一步可以做成一个类似插件市场的机制,让其他人贡献技能,Agent 根据任务动态搜索并拉取合适的 Skill。

二是加入多模态能力。比如支持用户上传图片,Agent 调用图像分析的 Skill 理解图片内容,再结合业务数据做综合判断。

三是接入更多数据源。目前主要是腾讯云自身的资源和账单数据,后续可以接入 MySQL、对象存储、企业内部的 API,让 Agent 成为真正意义上的“业务大脑”。

四是做多 Agent 协作。单个 Agent 负责一个领域,多个 Agent 之间通过消息队列互相协作,比如“数据分析 Agent”把结果交给“报告生成 Agent”,再由“推送 Agent”发给相关人员。

这些方向在不断试错中会逐步成型,但核心的架构理念——Agent 负责认知决策、Skill 负责具体执行、云平台负责弹性底座——已经验证是稳定可靠的。

最后说点真实的感受。做这个项目之前,我也被各种 Agent 概念绕得云里雾里,什么 Autonomous Agent、Multi-Agent、ReAct、Plan-and-Execute,名词一大堆。真正自己动手实现一遍,从零到一写完一个能跑通全流程的 Agent 之后,才发现这些概念背后的本质其实非常朴素:把大模型的推理能力和外部世界的执行能力连接起来,让 AI 不仅能想,还能做

腾讯云这套基础设施扮演的,就是一个稳定、弹性的执行底座。云函数提供计算能力,API 网关提供接入能力,对象存储提供动态更新能力,而我自己要做的,就是把这个底座和大模型之间那层胶水写好。这个胶水层,说难也难,说简单也简单——难的是一开始没想清楚架构,简单的是想清楚之后,整个实现过程非常顺畅。

希望这篇文章能帮你少走一些弯路。如果你也在折腾 Agent 和 AI Skills,欢迎在评论区交流你的踩坑经历,或者分享你总结出来的最佳实践。毕竟这个领域变化太快,每个人踩过的坑,对后来者都是宝贵的经验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 4:23:14

主从复制与Redis集群深度对比:从数据分片到水平扩展的架构演进

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 4:23:09

推荐系统GPU优化:变长序列处理的三条路线与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 4:20:12

2026年iOS开发平台选型指南:原生、跨平台与低代码如何选?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 4:19:08

混合大模型架构实战:多Agent协同、模型路由与高可用体系搭建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 4:18:06

LDR6500 IO通知切换主从模式:Type-C视频扩展实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 4:17:41

物联网环境监测系统设计:WIFI+RGB+超声波+人体感应集成方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华