将 Agentic RAG 应用迁移至 AWS Serverless 的五个关键架构决策与踩坑指南
- 作者

- 姓名
- Nino
- 职业
- Senior Tech Editor
将复杂的 Agentic RAG(检索增强生成代理)应用从单台虚拟机迁移到完全无服务器(Serverless)云架构,在理论设计上看似简单:将常驻服务替换为云厂商的托管服务、绑定事件触发器,即可将闲置基础设施成本降低至接近零。然而,在真实的云原生工程实践中,往往需要根据云厂商的具体限制调整既有方案。
在为 Agentic RAG 应用设计云原生后端时,除了像 n1n.ai 这样能大幅简化多模型接入的高效 LLM API 聚合平台外,开发者还需要解决底层云资源、计算节点与数据持久层之间的协同问题。
本文将深入探讨将一个运行在单台 EC2 实例(t4g.small,部署了 PostgreSQL、Streamlit、本地 PyTorch 向量模型及 Grafana)上的 Agentic RAG 应用,迁移至由 AWS Lambda、Amazon Bedrock、CloudFront、S3 Vectors 及 DynamoDB 构成的 Serverless 架构时所面临的五个关键重构决策。
架构演进对比:迁移前后
| 架构维度 | 迁移前(单机 EC2 t4g.small) | 迁移后(Serverless 目标架构) |
|---|---|---|
| 计算与托管 | 单台 VM 运行 Streamlit + Python 后端应用 | CloudFront + S3 静态前端;AWS Lambda 处理 API 请求 |
| 数据库与检索 | PostgreSQL(关系型)+ minsearch(TF-IDF) | /tmp 目录下的 SQLite 文件 + Amazon DynamoDB |
| 模型推理引擎 | 本地 CPU 运行 PyTorch 模型(闲置成本 ~$15/月) | Amazon Bedrock 托管 API(Claude 3.5 / Nova / Titan) |
| 向量检索 | 内存索引 | Amazon S3 Vectors + SQLite FTS5 |
| 数据同步管道 | 本地同步运行脚本 | AWS Step Functions + Bedrock Batch 批处理作业 |
决策一:用 S3 挂载 SQLite 与 DynamoDB 替代 Aurora Serverless v2
遇到的坑
原计划采用 Amazon Aurora Serverless v2 替代传统 PostgreSQL,并通过 HTTPS 协议的 RDS Data API 供 AWS Lambda 调用,从而避免将 Lambda 放入 VPC 所带来的高额 NAT Gateway 费用。
然而,Aurora Serverless v2 设有 0.5 ACU(Aurora 容量单位)的最低运行下限,即使没有任何流量,每月固定支出也高达约 43 美元,是原单机 EC2 成本的三倍。虽然可以配置 0 ACU 自动暂停,但其触发暂停的最小间隔为 300秒,且冷启动恢复需要 15秒至 30秒。对于低频访问的 Demo 系统,首次请求面临 30秒延迟会严重破坏用户体验。
解决方案:只读 SQLite 数据库构件
分析业务数据发现,系统数据库仅包含 278个项目记录及数千条库元数据,总体大小约 88 KB。这些数据仅在数据摄入管道运行时重新生成,在前端查询阶段完全只读。
因此,架构进行了如下调整:
- 离线 AWS Step Functions 数据管道在构建阶段将数据编译为包含 FTS5 全文检索索引 的
projects.sqlite文件。 - 将该 SQLite 文件上传至 Amazon S3。
- AWS Lambda 在冷启动时将
projects.sqlite下载至本地/tmp临时目录。 - 动态写操作(对话历史、用户反馈、Token 消费账本)分离至 Amazon DynamoDB。
整体数据库月度支出从约 43 美元骤降至不到 1 美元。
+-------------------+ 1. 构建数据库 +-------------------+
| Step Functions | --------------------> | S3 存储桶 |
| 数据摄入管道 | | (projects.sqlite) |
+-------------------+ +-------------------+
|
| 2. 冷启动下载
v
+-------------------+ 3. 本地文件查询 +-------------------+
| AWS Lambda API | --------------------> | Lambda /tmp 目录 |
| (只读查询路径) | | (SQLite + FTS5) |
+-------------------+ +-------------------+
意外收获:检索质量提升
PostgreSQL 的全文检索函数 ts_rank_cd 仅基于词频和位置打分,缺乏全局逆文档频率(IDF)加权。而 SQLite 内置的 bm25() 函数能够对稀有词赋予更高的权重。迁移到 SQLite FTS5 后,关键词检索的匹配相关度甚至优于原 PostgreSQL 方案。
SQL 兼容性改写示例
针对 SQLite 不支持 PostgreSQL 独有 DISTINCT ON 语法的问题,使用了 GROUP BY 与 MAX() 聚合函数进行等价替换:
-- 原 PostgreSQL 查询语句(使用 DISTINCT ON)
SELECT DISTINCT ON (author) repo, author, github_url, score, author_total_score
FROM projects
WHERE author_total_score IS NOT NULL
ORDER BY author, author_total_score DESC;
-- 改写后的 SQLite 等价语句(使用 GROUP BY 与 MAX 聚合)
SELECT repo, author, github_url, score, MAX(author_total_score) AS author_total_score
FROM projects
WHERE author_total_score IS NOT NULL AND author IS NOT NULL
GROUP BY author
ORDER BY author_total_score DESC
LIMIT ?;
决策二:放弃 Lambda 响应流式传输,改用 JSON 缓冲输出
矛盾点
AWS Lambda 支持响应流式传输(Response Streaming),允许将大模型的生成 Token 实时推送至前端。然而,AWS Lambda 的托管流式响应目前仅原生支持 Node.js 运行时。若要在 Python 环境下实现,必须构建自定义运行时 Layer 或引入 AWS Lambda Web Adapter。
架构权衡分析
在 Agentic RAG 系统中,后端 Agent 在生成最终回答之前,需要执行多达 6 轮的工具调用(Tool Calls)、查询改写(Query Rewrite)以及重排序(Rerank)。
[用户输入] -> [查询重写] -> [向量检索] -> [重排序] -> [Agent工具调用循环] -> [最终答案生成]
|<------------------------------ 非流式耗时阶段 ------------------------------>| |<- 流式输出区间 ->|
流式传输仅能优化最后一步文本生成的感知延迟,用户依然需要等待前期所有 Agent 链条执行完毕。
安全与计费陷阱
在 Serverless 环境下启用流式响应存在潜在的计费漏洞:如果客户端在流式传输过程中中断连接(例如关闭浏览器标签页),底层的 Lambda 函数与 Bedrock 模型的计算过程并不会立即终止,仍会继续扣减 Token 预算。对于公网开放的项目,这可能引发恶意刷量风险。
结论:采用标准的 JSON 缓冲响应形式,不仅简化了 Python 运行时的维护成本,还确保了在异常中断场景下的计费可控性。
决策三:绕过 CloudFront OAC 限制,设计源站证明请求头
遇到的问题
在标准架构中,CloudFront 与 Lambda Function URL 之间通常采用 Origin Access Control (OAC) 结合 AuthType: AWS_IAM 进行鉴权。
然而,CloudFront OAC 在处理 HTTP POST 请求时不会计算请求体 Payload 的哈希值,导致带有请求体的 POST 请求发送至 Lambda Function URL 时会触发 InvalidSignatureException 签名错误。由于 /api/ask 和 /api/feedback 均依赖 POST 提交数据,OAC 无法直接在此类接口上生效。
若引入 Amazon API Gateway,虽然能解决鉴权问题,但 API Gateway 存在 29秒硬性超时限制,极易被多轮 Agent 循环触发。
解决方案:自定义源站验证头(Proof-of-Origin)
最终方案将 Lambda Function URL 配置为 AuthType: NONE,并利用 CloudFront 在转发请求时附加自定义 HTTP 请求头进行防盗刷校验:
[前端客户端]
|
v HTTP POST
[Amazon CloudFront]
|
| 附加请求头: X-Origin-Secret: <Token>
v
[AWS Lambda Function URL (AuthType: NONE)]
|
| 使用 secrets.compare_digest() 校验
v
[执行 Agent RAG 业务逻辑]
Python 校验代码实现
为防止针对请求头的安全时序攻击,需要使用常数时间比较算法:
import os
import secrets
import json
ORIGIN_SECRET = os.environ.get("ORIGIN_SECRET_VAL")
def lambda_handler(event, context):
headers = event.get("headers