
最近这个月我把自己写的多模型 AI API 聚合服务迁到了 Serverless 上说真的之前犹豫了很久总觉得无服务器弹性也就是个噱头直到某天凌晨流量突然翻了 20 倍我还在被窝里服务器却直接 CPU 跑满才知道痛。现在这套服务已经稳定跑了三个星期DeepSeek、智谱、讯飞这些大模型 API 都走同一个入口每天的调用量有三四万次我再也没有半夜爬起来扩容过。这篇文章就把我整个实战过程写出来重点讲 Serverless 架构怎么设计、函数怎么拆、动态扩缩容参数怎么配以及我在 401、context length、组织被禁用这些报错上踩过的坑。适合已经能调通大模型 API、想低成本上线的个人开发者和小团队参考。1. 为什么把 AI API 放到 Serverless 上跑1.1 你大概率也遇到过的服务器焦虑做 AI 应用很有意思但“基础设施”这件事特别磨人。你自己写了个聊天机器人接了 DeepSeek 或者智谱的 API一开始用户就自己和朋友几十个人一台 2C4G 的云服务器绰绰有余。突然哪天你的应用被哪个社区推荐了或者你做了个小工具被媒体转发用户量一下子上来服务器 CPU 冲到 100%接口大面积超时日志里全是连接超时和 502。这时候你有两个选择连夜去云控制台手动扩配然后默默祈祷别再涨了或者买一台大规格服务器平时空闲流量高峰又可能还不够。我就是被第二种折腾烦了才认真研究 Serverless。Serverless 架构对 AI API 聚合场景来说核心优势很直白没有请求的时候一个实例都不跑流量来了平台自动弹实例流量走了自动缩回去。你不需要预先买 8 核 16G 的大机器也不用担心扩容慢半拍。按调用次数和运行时长付费低谷期几乎不花钱。如果你的接口本身只是转发第三方大模型的请求不存储本地状态那 Serverless 简直天生就是为你准备的。1.2 什么样的 AI API 场景适合无服务器不是所有 AI 服务都适合扔到 Serverless。我自己的判断标准主要有几条。第一请求是短连接不是 WebSocket 长连接。你的函数对外提供 HTTP 接口内部去请求大模型 API这个模式天然匹配函数计算的“事件触发”模型。第二流量波动剧烈且不可预测。个人项目的用户行为就是晚上热闹、白天冷清周末猛、周内弱你不可能手动跟着这个节奏去调整服务器。第三服务本身无状态。用户的聊天记录、会话状态放在 Redis 或数据库里函数实例随时被杀掉也不影响那就可以放心交给 Serverless。那什么场景不适合如果你的接口需要维持一个长时间运行的进程比如你有内部的模型推理服务需要 GPU或者要维护一个持续的内存索引那 Serverless 函数短生命周期就很不合适。即便能用 GPU 函数冷启动和成本也够喝一壶的。所以先看清需求再套架构。1.3 动态扩缩容到底扩的是什么很多朋友第一次接触 Serverless 都会问动态扩缩容是不是就是“多开几个容器”方向对但理解得再细一点会更好。函数计算的资源调度单位是“实例”一个实例就是一段运行你的函数代码的沙箱环境有独立的内存和 CPU 配额。平台会根据来流量大小自动增加或者减少实例数量。所谓动态扩缩容本质就是在“实例数”和“单实例并发能力”之间做平衡。我用一个生活化的例子你把函数实例想象成奶茶店的收银员。一个人收银高峰期排了 100 个顾客那就增加收银员扩容人少了就减员缩容。但每个收银员可以同时接待多笔订单吗函数计算里是可以的通过“单实例并发度”这个参数握住。你的函数如果主要是在等大模型返回结果CPU 基本闲着那一个实例完全可以同时处理 10 个甚至 100 个请求。把单实例并发度调高平台就不需要频繁创建新实例冷启动自然少了但如果你的函数是纯计算密集型的比如自己跑一个小型 BERT 分类那单实例并发度就得调低否则内存和 CPU 会爆。明白这个关系后面配置扩缩容才有方向感。2. 整体架构设计与函数拆分2.1 文字版架构示意我这套服务的完整调用链路是这样的用户的请求先打到 API 网关网关负责鉴权、限流、跨域和参数校验。然后网关把请求转发到函数计算里的入口函数入口函数根据请求体里带的路由字段比如 provider 是 deepseek 还是 zhipu去调用对应的模型接口在拿到模型返回后统一转换成一个固定格式的 JSON 回给前端。这一步看起来简单但有几个设计决策很关键。我没有把所有模型适配逻辑写进同一个函数里硬编码而是抽象了一个 adapter 层。每个模型是一个独立的小模块暴露相同的方法chat(messages, params)。入口函数只负责选择 adapter、调用、处理异常、返回结果。这样新增一个大模型只需要加一个模块和一行映射不需要动主流程。第二个决策是我多套了一层 API 网关没有直接暴露函数计算的原生 HTTP 触发器。网关能帮我做掉一些通用的事限制陌生 IP 的呼叫频率、在函数返回超时前提前切断、缓存部分 GET 请求。虽然多跳一跳会增加几毫秒延迟但对一个 AI 接口来说完全可以接受。2.2 为什么我没有直接用一台 VPS 跑 FastAPI我考虑过用 FastAPI 部署一个常驻服务配合进程管理和 nginx 做负载均衡这也是很多人的常规操作。但对比之后Serverless 有几个点让我回不去了。首先是弹性速度在函数计算平台从 1 个实例扩容到 50 个实例通常只需要几十秒甚至几秒如果我自己搭集群需要买新机器、配置镜像、接入负载均衡早高峰早就过了。其次是成本颗粒度传统服务器是按月付固定的钱就算一天只接受 100 次请求钱也一样花Serverless 是按请求和运行时长算低谷期算下来几乎可以忽略。最后是运维成本函数计算自带监控、日志、链路追踪我不用再自己部署 Prometheus 和 Grafana。说实话数学上小规模跑一台 2C4G 的 VPS 也不贵但“运维注意力”是一种隐性成本因为它占用的全是我写业务逻辑的时间。2.3 内存、超时这些资源参数怎么定函数计算的资源参数主要有三样内存大小、执行超时时间、运行环境。以阿里云函数计算为例Python 运行时可以选择 128MB 到 3GB 的内存。我一开始图省钱选了 128MB结果处理长对话时 JSON 解析直接 OOM。后来换成 512MB稳定了。怎么评估你的依赖库比如 httpx、pydantic会占几十 MB请求和响应体如果是几千字的文本加上 Python 解释器和运行时开销128MB 确实紧张。建议至少 256MB如果还用到较多的正则或大 JSON 解析直接上 512MB。超时时间同理大模型 API 生成几千字通常要 30 秒以上我最初设置函数超时 10 秒结果频繁报 TimeoutError。最终我把函数超时设到 300 秒同时让 API 网关的超时也调整到 120 秒以上。注意不同网关的超时上限不一样有些付费版本支持到 600 秒但默认可能只有 60 秒不调整的话照样断给你看。3. 核心代码实现与部署配置3.1 一个精简但完整的入口函数我用的语言是 Python运行时选的是 Python 3.10依赖只装了 httpx。下面这个示例省去了很多细节但把关键结构拎出来了你可以直接抄import os import json import httpx API_KEYS { deepseek: os.environ.get(DEEPSEEK_API_KEY, ), zhipu: os.environ.get(ZHIPU_API_KEY, ), spark: os.environ.get(SPARK_API_KEY, ), } ENDPOINTS { deepseek: https://api.deepseek.com/chat/completions, zhipu: https://open.bigmodel.cn/api/paas/v4/chat/completions, spark: https://spark-api-open.xf-yun.com/v1/chat/completions, } async def call_model(provider: str, messages: list, params: dict): key API_KEYS.get(provider) if not key: return {error: fprovider {provider} not configured} headers { Authorization: fBearer {key}, Content-Type: application/json, } body { model: params.get(model, ), messages: messages, temperature: params.get(temperature, 0.7), max_tokens: params.get(max_tokens, 2000), } async with httpx.AsyncClient(timeout60) as client: resp await client.post( ENDPOINTS[provider], headersheaders, jsonbody, ) return resp.json() def handler(event, context): # 这里的 event 是 API 网关传入的 HTTP 对象不同平台格式有差异 if isinstance(event, str): event json.loads(event) body json.loads(event.get(body, {})) provider body.get(provider, deepseek) messages body.get(messages, []) params body.get(params, {}) # 异步调用通过一些技巧变成同步等待Demo 里简化为直接调用 import asyncio result asyncio.get_event_loop().run_until_complete( call_model(provider, messages, params) ) return { statusCode: 200, headers: {Content-Type: application/json}, body: json.dumps(result, ensure_asciiFalse), }注意几个细节。第一密钥一定不要写死在代码里用环境变量。我之前见过有人把 key 放在仓库里结果被爬虫抓走跑出一个月的账单教训很惨。第二httpx 的AsyncClient不要每个请求都新建太浪费握手资源。可以把 client 放到模块级别利用函数计算的“初始化实例”机制复用连接池。第三事件结构在每个平台不一样阿里云 FC 的 HTTP 触发器和腾讯云 SCF 的 API 网关触发器传入的 event 格式不完全一样要么自己解析要么用平台提供的 event-stub 库把事件规范成一致结构。我直接写的兼容层省掉额外依赖但代码会稍微啰嗦。3.2 动态扩缩容参数到底填多少这是标题最核心的部分。以阿里云函数计算为例在服务配置里有几个参数直接影响弹性行为单实例并发度、实例数下限、实例数上限、预留实例策略。单实例并发度默认是 1也就是一个实例同时只能处理一个请求。对 AI API 这种高 IO 场景建议调到 10 到 50。我实测下来DeepSeek 这类接口响应时间通常在 5 到 20 秒一个实例在等待网络响应的同时完全可以再接收其他请求。但如果你的函数内部有大量 CPU 计算比如自己在做向量化或图片处理那单实例并发度最好保持较低甚至 1否则内存和 CPU 会被同时跑的任务榨干。实例数下限一般是 0这样没有流量的时候平台会把实例全部回收不产生费用。实例数上限是必须设置的。我设了 50意味着最多同时有 50 个实例在处理请求再多的请求会排队或者 429。这个上限是一道保险防止你的服务被恶意刷流量也防止半夜异常流量把预算烧穿。预留实例可以按时间段设置。比如我设置“早上 8 点到晚上 12 点预留 2 个实例”这部分实例持续运行不计冷启动其余时间段不预留完全弹性。预留实例要花钱但不多我计算过它带来的收益如果平均每天有 1 万个请求且高峰期较长预留实例减少的冷启动延迟足以抵消这部分费用。3.3 部署工具与触发器配置我用的是 Serverless Devs 命令行工具配置文件s.yaml大致长这样edition: 1.0.0 services: ai-gateway: component: fc props: region: cn-hangzhou service: name: ai-gateway description: AI API gateway function: name: chat_router runtime: python3.10 handler: index.handler memorySize: 512 timeout: 300 environmentVariables: DEEPSEEK_API_KEY: ${env.DEEPSEEK_API_KEY} instanceConcurrency: 20 instanceMax: 50 triggers: - type: http name: httpTrigger qualifier: LATEST config: authType: anonymous methods: [POST]部署命令就一行s deploy -y。之后你会拿到一个 HTTP 域名把这个端点配置到你的前端或者客户端里就行。我提一句“authTypeanonymous”意味着任何知道你端点的人都能调用所以我强烈建议在 API 网关上再配一层自己的鉴权比如自定义 header 里带一个 token网关侧做校验。没有这层保护你就等着被脚本机器人刷到欠费吧。4. 压测调优与扩缩容实录4.1 压测工具和命令为了验证动态扩缩容的效果我压测了大概二十多轮。工具用的hey原因是轻量、单文件、输出了每秒请求数和延迟分位数都够直观。命令长这样hey -n 10000 -c 200 -m POST \ -H Content-Type: application/json \ -H X-Auth-Token: $GATEWAY_TOKEN \ -d {provider:deepseek,messages:[{role:user,content:你好}],params:{max_tokens:100}} \ https://ai-gateway.cn-hangzhou.fc.aliyuncs.com/chat这里-c 200表示模拟 200 个并发用户-n 10000是总共 10000 个请求。因为调用的是真实大模型 API会消耗 token所以我压测时把 max_tokens 设得很小并且用了一个轻量 prompt不然一次测试烧掉十几块钱也心疼。4.2 从压测曲线看扩缩容过程第一次压测我看到了一个特别典型的曲线前 30 秒请求成功率比较低很多请求执行时间到了 4 到 5 秒这不是模型响应慢而是冷启动。函数平台一开始只有 1 个实例突然来了 200 并发平台开始弹实例但弹出来的新实例需要拉取代码、初始化环境、导入依赖这段时间请求只能排队或者等待。30 秒到 60 秒之间实例数爬升到了满额之后成功率接近 100%平均响应时间稳定在 1.5 秒左右。这个“爬升期”就是你配置扩缩容参数时需要重点关注的。后来我把单实例并发度从默认的 1 调到 20整个曲线明显平滑了。原因是并发度 1 时每个实例只能干一个活200 并发就需要 200 个实例平台要疯狂创建实例、销毁实例调度压力极大调高并发度后几十个实例就能扛住 200 并发实例数量没那么夸张冷启动自然少了。这不是玄学核心原因在于你的函数在等待模型返回时CPU 几乎空转完全可以让位给其他请求。4.3 调优的几个关键转折点我再列一下实测过程中改过的参数和原因。第一个错误是内存设置 128MB 导致 OOM改 512MB 后不再出现内存相关报错。第二个错误是函数超时太短模型回答长时触发 TimeoutError我先是把函数 timeout 调到 300 秒同时又把 API 网关的超时策略也改成了 120 秒双管齐下才解决。第三个错误是前端拿到响应太慢我本来全部是等完整生成后一次性返回后来发现很多模型 API 支持流式输出于是我在网关层做了 SSE 透传用户看到的是打字机式输出体感延迟大幅降低。但注意流式输出会让函数实例被一个请求长期占用单实例并发度就必须调高不然一个流式请求就把实例锁住了。4.4 冷启动优化的三板斧没有完美的架构函数计算最大的敌人就是冷启动。我的优化顺序是这样的。第一把依赖体积压缩到最小。我只用 httpx、pydantic、cryptography 这几个必要包不要在函数里塞 pandas、numpy 这种“我可能以后用得上”的库。依赖越大代码包越大冷启动越慢。第二利用实例初始化的特性。函数计算支持 initializer 钩子你可以在里面建立数据库连接池、初始化 HTTP client。比如用httpx.AsyncClient创建连接池放进全局变量之后同一个实例处理请求时直接复用省去 TCPTLS 握手的时间。第三是设置预留实例。如果对首屏延迟极其敏感预留 1 到 2 个实例就能稳住基本盘。我综合了成本和体验最后设置高峰期预留 2 个实例实测 P95 延迟从 6 秒降到了 2.8 秒。5. 高频错误排查攻略5.1 401 unauthorized: incorrect api key provided 到底哪错了这个报错我见得太多了不光我自己朋友也经常发截图问我。先说结论这个错误就是服务端不认识你的 API key。但“不认识”有三种可能key 配置错了、key 本身失效、调用环境不对。我排查的第一步是先看函数计算的环境变量里有没有正确设置对应 key。经常有人把 key 写在代码里或者环境变量名称拼错了比如DEEPSEEK_API_KEY写成了DEEPSEEKKEY程序读到一个空字符串那必然是 401。第二步确认 key 在官网账户里是有效的。有些平台的 key 有过期时间或者子账户 key 被主账户撤回了也会报 401。第三步注意平台差异。有些模型平台要求在请求头加Authorization: Bearer key有些则要求自定义X-API-Key之类的字段。你在统一入口函数里写死了 Bearer 格式换了个 provider 可能就不认。5.2 api error: 400 this models maximum context length is 1048576 tokens报这个错本质上是你发送给模型的输入 token 数超过了模型允许的最大上下文。比如某个模型的上下文是 1,048,576 个 token这非常长但你的请求体如果携带了历史对话、超长文档、或者重复发了几百轮消息仍然会撞上限制。我的处理思路是三层第一层在入口函数里统计 messages 的 token 数粗略按字符数估算超过阈值就把历史 message 裁剪到最近 N 条。第二层如果用户传入的文档太长就先做文本分段分段请求或者让模型只总结每一段最后再汇总。第三层如果仍超限直接返回一个格式化的错误提示告诉用户输入太长而不是把一个原始 400 丢到前端那样用户一脸懵。代码里可以这样判断一下字数def estimate_tokens(text): # 简化估算中文按一个字符约等于 0.6 token英文约 0.25 token return len(text) * 0.55.3 api error: 400 this organization has been disabled这个报错的字面意思是“组织被禁用”实际资费场景里多半是账户欠费或者被风控。处理方法不是改代码而是登录模型平台的开发者后台查看账户状态、账单记录和维护工单。顺带说一句这类报错最容易出现在多租户场景比如你在函数里用了共享的 API key某个团队账户被禁整个入口就全挂了。我后来重构时给每个 key 加了存活状态检查被禁之后自动切换备用 key避免单点故障。如果你也做多模型聚合尽量把 provider 的可用性检测做成定时任务发现 key 失效就发告警。5.4 快速定位问题的一个土办法很多报错无法一眼定位我推荐你直接在函数里加一个 debug 开关请求头或者环境变量里设DEBUGtrue时函数把完整请求体、后端请求参数、原始返回体全部打印到日志并返回包含debug字段的 JSON。这个开关在生产环境关闭只在排查时打开。听起来笨但比看云监控的指标管用得多特别是处理不同模型返回格式差异时一次就能看出来是哪一层出了问题。6. 成本核算与省钱策略6.1 Serverless 的实际账单结构账算不对是很危险的。函数计算的费用主要四块调用次数、资源使用量GB-s、公网流量、预留实例费用。调用次数很便宜百万次几十块钱资源使用量是核心按“内存大小 × 执行秒数”累计比如 512MB 的函数执行 1 秒就是 0.5 GB-s。公网流量按 GB 计费如果你的函数要转发大模型里的上百 KB 响应这部分也要留意。预留实例费用相当于买断一部分常驻实例规格越大越贵。我一开始以为“动态扩缩容”能无限省钱结果忽略了“冷启动期间也在扣资源费”这件事。实例启动过程本身也是算进执行时间的所以频繁冷启动不仅影响延迟也影响账单。这也是为什么要把单实例并发度调高、减少实例创建次数不止是为了性能也是为了省钱。6.2 算一笔具体的账假设你每天调用 10 万次平均每次执行 1.5 秒函数内存 512MB单实例并发度 20。那么每天的资源使用量大约等于100000 次 × 1.5 秒 × 0.5 GB 75000 GB-s。一个月约 225 万 GB-s。你拿这个数乘以平台公布的单 GB-s 价格就是资源费用。不同地域、不同厂商价格差别很大我用的某个平台现在大约是每 GB-s 0.00011 元约十万分之一元那么一个月资源费约 247 元。再加上调用次数的费用和少量流量费一个月 300 元左右。对比这个量级如果是自购一台 4C8G 的 ECS差不多也是这个数但 Serverless 的好处是不管你每天是 1000 次还是 10 万次都能平滑过渡。注意这只是正常调用场景。一旦你被刷流量或者有异常循环调用费用是直线上升的。我建议在函数计算控制台设置每日预算告警超过 100 元就短信通知。别问我为什么知道。6.3 三个亲测有效的省钱技巧第一做响应缓存。对于不要求实时变化的重复问题比如固定的 FAQ、常见错误说明可以在 Redis 里缓存模型返回结果key 是“provider 消息摘要”。缓存命中不调用模型 API也不走大模型计费省下的钱立竿见影。第二请求合并。有些场景用户连续问几个小问题不必每次挨个请求模型可以在输入侧做批量处理一次对话解决多个子问题减少调用次数。第三合理设置最大实例数。上限设得过高遇到流量攻击时平台会给你弹一堆实例来“接单”那账单你根本扛不住。我设 max50 后欺负我没成功过。同时可以开启网关侧的速率限制每用户每秒钟最多多少个请求超过直接返回 429。这个大额账单的坑你们务必绕开。最后分享一个小经验我平时最常跟人强调一个字抠。这里的抠不是小气而是要对架构的每一项配置都知道为什么要这样。内存设 512MB 不是因为 128MB 不行而是我量过里面跑了哪几个依赖单实例并发度设成 20 不是跟风而是压测了 128MB 下的 CPU 耗用之后得出的结论预留实例设 2 个是因为用户访问的峰值确实出现在固定时段。数字背后都是数据不是猜的。这套 Serverless 服务上线以来我最爽的时刻不是流量暴增没挂而是看到云监控里实例数像呼吸一样自动起伏从 0 到 50 再回到 0没有任何人为干预。你要是也想搭一套这样的 AI API 入口别一上来就堆功能先把扩缩容的底子打好后面加什么模型、接什么前端都不慌。