Agent Droplets 正式上线后,我把手上跑了一个月的客服Agent迁了过去。折腾一个周末,总算把一直悬着的资源调度和状态同步问题解决了。这篇文章就把这次的架构复盘、配置过程和一些踩坑记录整理出来,给准备自己部署AI Agent的朋友做参考。
先交代背景:我们的客服系统原本跑在通用云主机上,最初直接用一台8核16G的机器挂了三个Python进程,每个进程里放一个“会话状态字典”。上线第一周还好,第二周开始频繁出幺蛾子:内存涨到90%、进程重启、用户聊到一半上下文丢了。问题的根源不在代码,而在于“AI Agent”这东西和传统Web服务根本不是一个物种。传统Web是无状态、可以随便扩缩;Agent是长会话、有状态、要来回调用模型和工具,任何一步断了,整个会话都可能要重来。
所以当我看到Agent Droplets这种专门为AI Agent设计的云基础设施上线时,第一时间就去翻了它的技术文档。它把“会话”“状态”“工具”“追踪”这些Agent特有的概念做成了平台底层能力,而不像以前那样需要我们自己在应用层拼。这篇文章不是广告,是我基于实际迁移、配置、压测得出的使用总结,包括Agent Droplets的资源模型、部署配置、自动扩缩策略,还有那堆文档里不会写明白的坑。
1. 为什么通用云主机承载不了 AI Agent
1.1 从无状态请求到有状态会话的思维转变
传统后端服务的基本单位是“请求”,一次HTTP请求进来,处理完就返回,服务端可以不保存任何东西。所以负载均衡器可以把请求轮询到任意一台机器,水平扩容靠加机器就行。
AI Agent则完全不同。用户和Agent的对话是一个跨越几分钟、几小时甚至几天的会话,其中包含历史消息、中间推理结果、临时状态(比如“正在等待某个工具返回”)、持久化记忆。如果这个会话绑定在某台机器的内存里,那这台机器一旦重启或缩容,会话就没了。看起来像是“状态管理”的老问题,但Agent的复杂性在于状态不是单一字典,而是多模态的:对话历史、工具调用栈、记忆向量、任务队列位点,甚至包括外部系统里的临时数据。
1.2 Agent工作负载的典型资源画像
我给自己的Agent做过一次资源画像,发现它和Web应用完全不同。常规Web请求峰值通常能通过排队解决;Agent的“峰值”往往是一批用户同时触发复杂任务,比如每个Agent需要并行调用搜索引擎、数据库、模型接口,最后聚合。这时CPU、内存、网络、token配额四种资源同时被拉高,而且持续时间长,不是几毫秒,而是几十秒到几分钟。
具体来看有个更头疼的点:大模型推理消耗的时间通常比普通服务高几个数量级。一个Agent的处理流程可能要等待模型返回三次以上,每次2到10秒不等,期间连接一直挂着。通用云主机的默认超时配置通常会砍断长连接,导致Agent任务中断。再加上外部工具的返回时间不确定,中间状态机很容易被破坏。
1.3 会话级隔离与资源复用之间的矛盾
另一个矛盾是隔离性。传统多进程部署靠进程边界隔离,多个用户共享一台机器;但如果两个Agent之间内存泄漏或互相干扰,会把整个宿主拖垮。而Agent需要强的会话级隔离,最好每个会话运行在独立的沙箱中,有独立的依赖环境、独立的临时目录、独立的工具凭证。通用云主机提供不了这种粒度的隔离。
当然,也有人在通用云主机上做得很好,但需要自己在应用层设计会话路由、持久化、快照、分布式锁、重试队列。这些工作量大不说,还容易出隐蔽bug。所以我更倾向于一个把这些问题平台化的基础设施。
2. Agent Droplets 的核心设计与资源模型
2.1 底盘不是纯容器也不是纯虚拟机
Agent Droplets 使用了一种“轻量虚拟机+微容器”的双层调度结构。外层是轻量级虚拟化,通过硬件虚拟化做隔离,保证不同的Droplet之间不会抢占内存、不会泄露内核细节;内层再跑一个精简容器,封装Agent运行时、SDK和工具依赖。这样既获得虚拟机的强隔离,又享受容器的秒级启动。
为什么不用普通的Docker容器?因为多租户场景下容器共享宿主内核,一旦某个Agent执行危险系统调用,容器逃逸的影响范围不可控。而独立的微虚拟机架构,每个会话的内核边界独立,安全边界清晰。Agent经常要执行外部代码、调用shell工具,这种隔离非常必要。
2.2 把“会话快照”做成一等公民
传统云主机的快照是针对整台磁盘的,备份和恢复粒度太粗。Agent Droplets做了一个很有意思的设计:存储上把每个会话的完整状态抽象为一份“快照”,包括对话历史、临时变量、会话元数据、工具调用缓存,全量写入一个专用的高性能卷。默认30分钟自动做一次增量快照,也可以在关键节点手动触发。
这样Agent可以在任意时刻从一个快照恢复,而不仅仅在机器崩溃后重启。比如你的Agent要跑一个三个小时的批量分析任务,就可以定期打快照。如果中途外部API超时导致失败,直接回滚到最近一个快照继续执行,而不是从零开始。我的理解是,它把一个Agent任务变成了“可回放的事务”而不是“脆弱的临时进程”。
2.3 内置Agent运行时和SDK
Agent Droplets预装了一些主流Agent编排框架的运行时,并且针对这些框架做了内存与调度优化。更重要的是,它提供一个平台自带的Session SDK,让开发者不用关心“状态放哪”。你在代码里调session.get_state()/session.set_state(),框架会自动落到分布式状态存储,多个实例共享访问,但要留意一致性策略。
这个SDK还内置了可观测性埋点,自动把每次模型调用、工具调用、Agent决策链路上报成trace。以前我们自己搭监控链路,现在开箱就有。这一点在排查多Agent协作问题时特别有用。
2.4 资源规格与多租户配额
Agent Droplets的资源规格不完全以“vCPU/内存/磁盘”来表达,而是抽象成“会话槽位”。一个Droplet可以配置若干个并发会话槽位,每个槽位包含上下文窗口上限、工具调用并发数、内存上限、以及默认的模型token配额。比如:
| 配置项 | 示例值 | 说明 |
|---|---|---|
| session_slots | 20 | 允许同时运行的会话数 |
| context_window | 32000 tokens | 单会话上下文上限 |
| tool_call_concurrency | 5 | 单个会话内并行工具调用数 |
| memory_per_slot | 512Mi | 每个会话可使用的内存上限 |
| outbound_rate | 100 req/s | 对第三方API的默认速率 |
这样做的一个好处是计费可控。你不再为“闲置的CPU”买单,而是为“可以承载的会话负载”买单。坏处是如果你把上下文窗口调到极大,实际内存占用可能超过配额导致换页,所以下面会讲怎么配参数。
3. 从零上线一个 Agent Droplet 实例
3.1 前置准备:账号、模型密钥和工具凭证
在创建Agent Droplet之前,先把三样东西准备好:云平台账号、大模型API密钥、Agent要调用的外部服务凭证(数据库密码、HTTP API token、文件存储密钥)。Agent Droplets支持将密钥以加密环境变量或Secrets服务方式注入,不要在配置里明文写。
建议提前把所有可能用到的工具凭证集中放到平台的Secrets服务里,比如命名为:order-db-password、payment-webhook-token、embedding-key。环境变量引用形式是secret://order-db-password,在配置文件中这样引用,既安全又能把配置和密钥解耦。
3.2 使用CLI创建第一个Droplet
Agent Droplets提供了命令行工具adctl,用法和标准云CLI类似,上手不难。先登录:
adctl login --tenant demo-tenant adctl create droplet \ --name support-bot \ --region region-a \ --runtime python312 \ --agent-framework langgraph \ --session-slots 20 \ --context-window 32000 \ --snapshot-interval 30m \ --secrets order-db-password,payment-webhook-token执行完会在几秒内返回一个Droplet ID示例ad-8f3k2。第一次启动会拉取运行时镜像,所以大概需要15到30秒。如果创建完成后立即部署代码,建议先adctl wait --name support-bot --condition ready等它进入Ready状态。
3.3 用声明式配置管理Droplet
命令行的参数太多不好维护,我更推荐你把配置写成YAML,提交到代码仓库里,通过发布流程来管理。下面这份agent.yaml是我目前在用的配置,可以作为模板:
apiVersion: agent.example.io/v1 kind: Droplet metadata: name: support-bot spec: enabled: true region: region-a runtime: python312 agentFramework: langgraph resources: sessionSlots: 20 contextWindow: 32000 memoryPerSlot: 512Mi toolCallConcurrency: 5 autoScaling: enabled: true minSlots: 5 maxSlots: 40 policy: metric: activeSessions targetUtilization: 0.6 storage: snapshotInterval: 30m enableWAL: true tracing: enabled: true samplingRate: 0.1你可能注意到这里的minSlots: 5 / maxSlots: 40,意思是在并发会话低于某个阈值时,平台会回收多余槽位;高了则自动创建新的槽位。这个伸缩单位不是机器,而是会话槽位,这是和传统云主机最大的区别。
3.4 Agent代码接入SDK
配置好Droplet之后,把Agent代码上传进去。以我们的客服Agent为例,核心逻辑是接收工单、查订单库、调支付接口、生成答复。使用Agent Droplets的会话SDK之后,代码看起来和普通异步框架差别不大:
from agent_droplets import DropletSession, Message from langgraph.graph import StateGraph def query_order(state: dict) -> dict: order_id = state.get("order_id") result = db_client.query("SELECT status FROM orders WHERE id = ?", order_id) return {"db_result": result} def classify_intent(state: dict) -> dict: prompt = state["latest_message"] return {"intent": llm.classify(prompt)} def build_graph(): g = StateGraph(dict) g.add_node("classify", classify_intent) g.add_node("query", query_order) g.add_edge("classify", "query") return g.compile() def main(): app = build_graph() # 每次收到用户消息时创建一个会话 async with DropletSession(droplet_id="support-bot", user_id="u-1234") as sess: resp = await sess.run(app, input={"latest_message": "帮我查订单A1002"}) print(resp["answer"])这里需要说明,DropletSession会在底层自动完成状态保存和快照。sess.run结束后,临时状态立即持久化,下次再进入同一用户会话时,SDK会从快照恢复上下文,而不是从空状态开始。这就是为什么不需要自己在缓存里维护历史消息列表。
3.5 线上流量接入与灰度发布
创建完Droplet,还需把入口流量接进来。Agent Droplets自带了网关,支持WebSocket、SSE和HTTP。以SSE为例,只需在平台控制台绑定一个域名或路径,它会自动把长连接维持住,内部再转发给对应会话槽位。
我们上线时先切了5%的流量,观察了三个指标:会话恢复率、工具调用成功率、以及P95首响应时间。确认正常后再逐步放开到100%。灰度期间发现一个坑:默认网关的超时是60秒,而一个Agent查询复杂订单可能超过80秒。需要在网关配置中把idle_timeout调到180秒,同时给前端回调重连机制,避免用户看到“连接断开”。
4. 常见故障与排查实录
4.1 第一次调用特别慢:冷启动与预热槽位
上线之初,用户反馈每天第一个消息总要等七八秒。查trace发现,Agent的冷启动占了大头。因为初始会话槽位是空闲的,第一个请求进入时,平台要拉取镜像、启动沙箱、初始化模型SDK、加载缓存,整个过程需要好几秒。
解决办法是开启“预热槽位”。在Droplet配置里加一项prewarmedSlots: 3,让平台提前保持3个空槽位处于待命状态。代价是多占用一部分资源费用,但对于客服场景来说,首响从8秒降到1.5秒,我觉得完全值得。
4.2 高并发下大量超时:扩缩容阈值误区的坑
压测到40个并发会话时,超时率飙到20%。我们的第一反应是加资源,但看监控发现CPU和内存根本没用满。真正的问题是会话槽位不足,新会话在等待队列里排队超过了前端超时。
这里有个很关键的经验:Agent Droplets的自动扩缩指标默认是CPU或内存使用率,但对大多数Agent工作负载来说,瓶颈不是算力而是“会话槽位调度能力”。一个高I/O的Agent可能在CPU只有30%时就已经无法处理更多并行会话了,因为它在等外部API返回。
所以我把自动扩缩策略改成了基于active_sessions和waiting_sessions。具体配置:
autoScaling: policy: metric: waitingSessions target: 2 cooldown: 60s只要平均等待队列中的会话数超过2个,就增加5个槽位;如果连续15分钟等待队列为空,再回收槽位。这样超时率降到2%以内。
4.3 工具调用限流:需要内置令牌桶和重试策略
Agent经常要调第三方API,而第三方API有自己的速率限制。直接同步调用很容易触发HTTP 429。Agent Droplets SDK里提供了一个内置的调用拦截器,支持令牌桶限流和指数退避重试。配置方法很简单:
httpTool: rateLimit: perSecond: 10 burst: 20 retry: maxAttempts: 4 backoffBaseSeconds: 1 backoffMultiplier: 2我踩过的一个坑是,把全局速率和单会话速率搞混了。最初设的perSecond: 100是所有共享令牌桶的并发量,当20个会话同时调同一个外部系统时,100的速率还是被打爆。正确做法是按会话隔离令牌桶,也就是每个会话限制perSecond: 5,总并发就是5 * sessionSlots,然后和外部分系统文档对照,留出余量。
4.4 会话上下文被填满:截断与摘要策略
当会话超过contextWindow时,Agent会直接报错。早期我们简单粗暴地删掉最早的消息,结果导致Agent完全失忆,用户问“我之前不是说过吗”,它答不上来。
后来我们改成了“摘要+保留最新N轮”策略。每次快照时,如果消息超过阈值,就把早期消息丢给一个轻量模型生成摘要,替换掉原始消息,并保留最近5轮完整消息。配置如下:
contextPolicy: strategy: summarize summaryModel: light-model-x retainLatestRounds: 5 summarizeEveryMessages: 8这个策略也不是万无一失。摘要模型偶尔会丢掉关键实体,比如订单号、日期、金额。我的建议是额外维护一个“关键事实表”,在每次会话中显式写入结构化字段,比如user_name,order_id,promised_delivery_date,这些字段不允许被摘要替换。这样即便对话历史被压缩,关键业务信息仍然能查询到。
4.5 会话恢复后的幂等性:外部副作用依赖
最隐蔽的一个问题来自于“回滚快照”和“外部系统状态”的一致性。假设Agent先创建了退款单,然后崩溃,之后从快照恢复重新执行,它可能再次调用退款接口,导致重复退款。
这个问题平台本身解决不了,需要应用层设计幂等键。我的做法是,在每次调用外部“非查询类”工具时,生成一个idempotency_key(比如refund-order-A1002-会话唯一标识),存入会话状态中。SDK会把同一个会话内相同idempotency_key的请求缓存下来,恢复后直接复用上一次的结果,而不会重复执行。这种问题平时不发,一发生就是生产事故,建议所有做Agent的人提前把这个机制加进去。
4.6 观测成本控制:全量trace太贵
Agent Droplets默认会对每个模型调用和工具调用生成trace。当并发上来后,trace存储成本飙升,而且控制台检索变慢。可以把采样率调低,但要注意不能盲目调。我们保留了所有错误和慢调用(>1s),对正常调用采样率调整为5%:
tracing: enabled: true samplingRate: 0.05 forceSampleOnError: true forceSampleOnSlow: true通过这种方式,日志体量降了大约80%,但异常场景依然能查到。
5. 迁移后的一个月:真实体感与提醒
5.1 不再需要自己维护状态中心
以前我们为了保存会话状态,在业务代码里额外维护了一套缓存,还要处理缓存和数据库之间的不一致。迁移到Agent Droplets之后,这部分逻辑直接删除,代码量少了近三分之一。平台自动处理状态持久化和会话迁移,省下来的时间让我有余力去优化Agent的工具调用逻辑。
5.2 有些坑仍然属于应用层
如果你问我Agent Droplets是不是万能钥匙,我的回答是:它解决的是基础设施问题,应用层的业务逻辑仍然需要你自己想清楚。比如外部调用的幂等性、上下文摘要的准确性、Agent决策的失败回退,这些平台给不了答案。平台能给你的是一个稳定、可观测、有状态抽象的运行环境,帮你把更多精力放在Agent本身。
5.3 下一步我打算做的探索
目前我正在尝试用Agent Droplets跑一个多角色协作的小组,让每个角色跑在不同的Droplet里,通过消息总线互相对话。这种模式对会话快照和多实例同步的要求更高,但至少现在基础设施不用再折腾了。建议所有刚接触Agent基础设施的朋友,先小流量跑起来,用真实业务打磨配置,再逐步扩大规模。