1. 项目概述在AWS上构建一个基于Python的LLM RAG向量数据库应用最近在跟几个做AI应用的朋友聊天大家普遍有个痛点大语言模型LLM本身知识有“保质期”让它回答最新的、或者你私有的、非公开的资料时它要么胡说八道要么直接说“我不知道”。为了解决这个问题RAG检索增强生成架构火了起来。简单说RAG就是给LLM配一个“外接大脑”——一个向量数据库里面存着你自己的知识文档。当用户提问时系统先从这个“外接大脑”里找到最相关的资料再连同问题和资料一起喂给LLM让它基于这些“证据”来生成答案这样既准确又可控。这个项目build-on-aws/llm-rag-vectordb-python就是一个非常典型的实战指南。它手把手教你如何在亚马逊云科技AWS上用Python语言从零开始搭建一套完整的RAG应用。这不仅仅是跑通一个Demo而是涉及从文档处理、向量化、存储、检索到最终集成的全链路工程化实践。无论你是想为自己的产品添加智能问答能力还是想深入理解RAG背后的工程细节这个项目都能提供一个扎实的起点。接下来我会结合自己的实操经验把这个项目拆解透并补充大量官方文档里不会写的“坑”和技巧。2. 核心架构与AWS服务选型解析2.1 为什么选择AWS作为RAG的部署平台在云上构建RAG应用选型第一件事就是云平台。AWS在这个场景下有几个天然优势。首先是生态完整性从计算、存储、数据库到AI服务AWS提供了一站式的解决方案服务之间的集成和权限管理IAM非常成熟能大大降低运维复杂度。其次是Serverless无服务器能力对于RAG这种请求量可能波动很大的应用采用Serverless服务可以做到按需使用、按量付费在项目初期或流量不确定时能有效控制成本。最后是AI服务的深度集成比如Amazon Bedrock它提供了对多个顶尖基础模型如Anthropic Claude、Meta Llama 3的统一、安全的API访问省去了自己部署和管理模型的麻烦。2.2 核心服务栈拆解与选型理由一个生产级的RAG应用通常包含以下几个核心环节对应到AWS的服务选型我的方案是这样的文档存储与原始数据处理使用Amazon S3。这是AWS的对象存储服务几乎是不二之选。它的可靠性极高成本低廉非常适合存放原始的PDF、Word、TXT等文档。我们将所有待处理的文档上传到指定的S3存储桶Bucket中。文档处理与向量化Embedding这是RAG的“预处理流水线”。这里我们需要一个计算服务来运行Python代码进行文本提取、分块Chunking和调用Embedding模型生成向量。计算服务选择对于灵活、短时间运行的预处理任务我推荐使用AWS Lambda。它是一个事件驱动的无服务器计算服务。我们可以配置一个Lambda函数当有新的文档上传到S3时它自动被触发执行预处理代码。对于处理时间可能超过15分钟Lambda单次执行上限或需要GPU加速的复杂Embedding任务则可以考虑Amazon SageMaker的Processing Job或托管端点。Embedding模型可以选择在Lambda或SageMaker中部署开源的Embedding模型如BAAI/bge-large-en-v1.5但更省事的方式是直接使用Amazon Bedrock的Titan Embeddings模型。它通过一个API调用即可完成无需管理模型基础设施并且其生成的向量与Bedrock的文本生成模型有良好的兼容性。向量存储与检索这是RAG的“外接大脑”核心。我们需要一个能高效存储和检索高维向量的数据库。首选方案Amazon Aurora PostgreSQL with pgvector。这是一个托管的关系型数据库通过pgvector扩展支持向量运算。它的优势在于第一可以和你现有的业务数据用户信息、订单记录等放在同一个数据库里简化架构第二支持完整的ACID事务和SQL查询能力强大第三对于中小规模的向量数据比如数百万条以内其性能和成本通常比专用向量数据库更有优势。这是本项目标题中vectordb部分的一个非常主流和务实的选择。备选方案专用向量数据库。如果你的数据量极大数亿以上对检索延迟要求极苛刻或者需要复杂的过滤条件可以考虑像Pinecone第三方可通过AWS Marketplace使用或Amazon OpenSearch Service内置k-NN功能这样的专用服务。但在大多数应用场景下Aurora pgvector已经足够优秀。大语言模型推理与答案生成使用Amazon Bedrock。如前所述它提供了对多个高性能LLM的托管访问。你无需关心服务器、显卡、模型版本升级只需通过API调用即可。它内置了安全性和负责任的AI功能对于企业应用很重要。应用编排与API服务最终我们需要一个服务来串联整个流程接收用户问题 - 检索向量数据库 - 调用LLM生成答案 - 返回结果。API后端可以使用AWS Lambda配合Amazon API Gateway构建一个无服务器的RESTful API。这是非常轻量和成本优化的方案。复杂工作流如果业务流程复杂涉及多个步骤和条件判断可以使用AWS Step Functions来可视化地编排整个RAG流程提高可观测性和可维护性。整个架构的数据流大致是S3文档源 - Lambda触发处理- 文本分块 - Bedrock生成向量- Aurora pgvector存储向量- API Gateway/Lambda接收查询- pgvector检索相似向量- Bedrock生成最终答案- 返回给用户。3. 实战搭建从零到一的详细步骤3.1 环境准备与基础设施部署在开始写代码之前我们需要在AWS上把“舞台”搭好。我强烈建议使用Infrastructure as Code (IaC)工具比如AWS CloudFormation或者Terraform来部署资源这能确保环境的一致性也方便重建。这里我用CloudFormation的核心概念来讲解。首先创建一个S3存储桶名字可以是your-company-rag-source-docs。记住要设置好生命周期策略比如将30天前的原始文档转移到低频访问层以节省成本。其次创建Aurora PostgreSQL数据库集群。关键步骤是启用pgvector扩展。在创建数据库参数组时需要添加一个参数shared_preload_libraries其值设置为pgvector。创建完数据库实例后连接到数据库并执行CREATE EXTENSION vector;来激活扩展。别忘了在安全组Security Group中开放5432端口给你的Lambda函数或应用服务器。然后创建用于Bedrock访问的IAM角色。Bedrock的模型访问需要显式授权。你需要在IAM控制台创建一个角色信任实体为你要使用的服务如Lambda并附加AWS托管策略AmazonBedrockFullAccess生产环境建议按需缩小权限。最后准备Lambda函数的执行角色。这个角色需要权限从上述S3桶读取对象调用Bedrock的InvokeModelAPI以及写入Aurora数据库。你需要精心配置其IAM策略。注意IAM权限是安全的核心。务必遵循最小权限原则。例如给文档处理Lambda的S3权限资源应精确到arn:aws:s3:::your-company-rag-source-docs/*动作可以是s3:GetObject和s3:ListBucket。不要直接使用*通配符。3.2 文档处理与向量化流水线实现这是RAG效果好坏的基础。糟糕的文档分块Chunking会直接导致检索质量下降。步骤一文本提取当用户上传一个PDF到S3后会触发S3事件通知调用我们的处理Lambda。Lambda函数里我们使用PyPDF2或更强大的pdfplumber库来提取文本。对于Word文档使用python-docx对于纯文本和HTML处理相对简单。这里有个坑PDF的格式千奇百怪有些是扫描件图片。对于扫描件PDF你需要先用Amazon TextractAWS的OCR服务进行文字识别这比任何开源库都强大和准确当然成本也更高。步骤二文本分块Chunking这是艺术与科学的结合。最简单的是固定大小分块比如每500个字符一块用langchain的RecursiveCharacterTextSplitter可以轻松实现。但更好的方法是按语义分块比如按段落、按标题。我常用的一个策略是“重叠分块”比如块大小500字符重叠100字符。这能防止一个完整的语义被硬生生切断在检索时上下文信息也能更好地保留。对于长文档如书籍可以先按章节分大块再在每个章节内进行重叠分块。# 示例使用LangChain的分块器 from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, length_functionlen, separators[\n\n, \n, 。, , , , , , ] ) chunks text_splitter.split_text(extracted_text)步骤三生成向量并存储对于每个文本块我们调用Bedrock的Titan Embeddings模型来生成向量。Bedrock的API返回的是一个浮点数列表即我们的向量。然后我们将(文本块, 向量, 元数据)插入到Aurora PostgreSQL中。import boto3 import json import psycopg2 from psycopg2.extras import execute_values bedrock_runtime boto3.client(bedrock-runtime, region_nameus-east-1) model_id amazon.titan-embed-text-v1 def get_embedding(text): body json.dumps({inputText: text}) response bedrock_runtime.invoke_model(bodybody, modelIdmodel_id) response_body json.loads(response.get(body).read()) return response_body[embedding] # 连接数据库 conn psycopg2.connect(hostdb_host, databasedb_name, userdb_user, passworddb_password) cur conn.cursor() # 为每个chunk生成向量并准备数据 data_to_insert [] for chunk in chunks: embedding get_embedding(chunk) # 元数据可以包含来源文件、页码、章节等信息 metadata json.dumps({source: s3_key, chunk_index: idx}) data_to_insert.append((chunk, embedding, metadata)) # 使用pgvector扩展插入数据 insert_query INSERT INTO document_chunks (text, embedding, metadata) VALUES %s # 注意embedding列的类型应为vector(维度)例如vector(1536) execute_values(cur, insert_query, data_to_insert) conn.commit()实操心得在批量处理大量文档时直接为每个小块同步调用Bedrock API可能会慢且可能遇到限流。更好的做法是使用异步调用或批量调用如果服务支持。此外务必在代码中加入重试逻辑和指数退避以处理网络波动或服务临时不可用。3.3 检索与生成核心逻辑剖析当用户提出一个问题时后端API另一个Lambda函数需要执行以下步骤问题向量化将用户的问题Query用与之前相同的Embedding模型转化为向量。向量检索在Aurora pgvector中执行相似度搜索找出与问题向量最接近的K个文本块。pgvector支持多种距离计算方式最常用的是余弦相似度操作符或L2距离-。上下文构建将检索到的Top K个文本块连同它们的一些元数据如来源组合成一个“上下文”字符串。通常会在每个块前加上“出处”之类的提示。提示词工程构建最终发送给LLM如Claude 3 Haiku的提示词Prompt。这是决定答案质量的关键。一个经典的模板是你是一个专业的助手请严格根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题请直接说“根据提供的资料我无法回答这个问题”。不要编造信息。 上下文信息 {context} 问题{question} 请根据上下文回答调用LLM生成答案将组装好的提示词发送给Bedrock的对话模型获取生成的答案。返回结果将答案、以及可选的引用来源检索到的文本块及其元数据一并返回给前端。-- 在pgvector中检索相似向量的SQL示例 SELECT id, text, metadata, 1 - (embedding %s) as cosine_similarity FROM document_chunks ORDER BY embedding %s LIMIT 5;注意事项检索数量K需要权衡。K太小可能遗漏关键信息K太大会引入噪声并增加LLM的上下文长度Token数导致成本增加和速度变慢。通常从3-5开始测试。另外可以考虑在检索时加入元数据过滤比如“只从某份手册中检索”这能极大提升精准度。4. 性能优化与成本控制实战策略4.1 向量检索性能提升技巧当你的向量表达到百万级别时简单的全表扫描排序将变得非常慢。pgvector提供了索引来加速检索。最常用的索引是IVFFlat索引。它的原理类似于K-Means聚类先将所有向量分成若干簇lists检索时先找到距离目标向量最近的几个簇然后在这几个簇里进行精确搜索。创建索引的SQL如下CREATE INDEX ON document_chunks USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);这里的lists参数是关键。一个经验法则是lists sqrt(行数)。对于100万行数据lists设为1000是合理的起点。但是必须在表有足够多的数据比如每个list至少有1000条记录后再创建索引并且索引创建后如果大量新增数据需要重建索引REINDEX以保持性能。对于极致性能要求可以探索HNSW索引它提供了更优的查询速度和召回率但创建索引的时间更长占用的空间也更大。4.2 多轮对话与历史上下文管理基础的RAG是“单轮”的。但在聊天场景中用户的问题往往有上下文关联。例如用户先问“AWS S3是什么”接着问“它怎么收费”。第二个问题中的“它”指代S3。实现多轮对话RAG有两种主流思路将历史对话也纳入检索将当前问题与之前的几轮对话QA对拼接起来作为一个新的查询语句去检索。这能帮助系统理解指代关系。让LLM自己管理上下文使用具有长上下文窗口的模型如Claude 200K将检索到的当前上下文和整个对话历史都喂给模型由模型自行理解。这种方法更简单但对模型能力要求高且Token消耗大。在Lambda无服务器架构下需要将会话状态对话历史存储到外部如Amazon DynamoDB。每次请求时先从DynamoDB读取该会话的历史记录再执行上述流程最后将本轮新的QA对写回DynamoDB。4.3 成本监控与优化措施Serverless架构按用量付费成本透明但也需要关注。Bedrock成本主要来自Embedding调用和文本生成调用。Embedding按Token数计费文本生成按输入输出Token总数计费。优化点优化提示词减少不必要的指令控制检索上下文的长度对于简单问题可以考虑使用更小、更便宜的模型如Titan Text Express。Lambda成本按请求次数和执行时间GB-秒计费。优化点确保函数代码高效如使用json.loads而非eval合理设置内存大小更大的内存可能带来更快的执行速度从而降低时间成本利用执行环境复用保持数据库连接等。Aurora成本按数据库实例规格、存储和I/O计费。优化点根据负载选择正确的实例大小为向量表设置合适的索引减少全表扫描带来的CPU消耗定期清理或归档不再需要的旧数据。监控务必启用AWS Cost Explorer和设置预算告警。在CloudWatch中为Bedrock、Lambda的调用次数和持续时间设置仪表盘以便直观了解使用模式和发现异常。5. 常见问题排查与进阶思考5.1 效果不佳问题诊断清单如果你的RAG应用回答不准确或胡编乱造可以按以下清单排查问题现象可能原因排查与解决思路答案与上下文无关幻觉1. 提示词未强制模型基于上下文。2. 检索到的上下文完全不相关。1. 强化提示词使用“严格根据上下文”、“如果不知道请说无法回答”等指令。2. 检查Embedding模型是否与文本生成模型匹配推荐使用同一家的。3. 检查检索的相似度分数如果最高分也很低如余弦相似度0.7说明检索失败需优化分块策略或Embedding模型。检索不到正确信息1. 文档分块不合理切断了语义。2. 查询问题表述与文档内容表述差异大。3. 向量索引未优化或需要重建。1. 尝试更小的块大小或重叠分块。2. 对用户查询进行“查询重写”或“查询扩展”例如用LLM将问题改写成更可能出现在文档中的形式。3. 检查索引确保数据量足够后创建并考虑重建索引。答案冗长或包含多余信息检索到的上下文块过多或包含无关信息。1. 减少检索数量K。2. 在检索后增加一个“重排序”步骤使用更精细的模型如交叉编码器对Top K结果再次排序只保留最相关的几个。3. 在提示词中要求“简洁回答”。处理长文档超时Lambda函数执行超时默认3秒最长15分钟。1. 对于超长文档拆分处理流程。用Lambda触发将文档地址放入SQS队列由EC2或Fargate任务异步处理。2. 增加Lambda超时时间和内存配置。5.2 安全性与权限管控企业级应用必须考虑安全。数据加密确保S3桶、Aurora数据库都启用了加密静态加密。Bedrock的通信默认使用HTTPS传输中加密。网络隔离将Lambda函数、Aurora数据库部署在私有子网Private Subnet中。为Lambda配置VPC通过VPC端点PrivateLink访问Bedrock和S3避免流量经过公网。权限精细化管理如前所述为每个Lambda函数创建独立的、权限最小的IAM角色。使用数据库用户和密码或IAM认证并限制其只有特定表的读写权限。输入输出审查对用户输入进行基本的清理和检查防止提示词注入攻击。对模型输出内容尤其是面向公众时可以考虑增加一层安全审查或过滤。5.3 从项目到产品可观测性与持续改进一个实验项目和一个可运营的产品之间差的是一个完整的可观测性体系。日志记录在Lambda函数中关键步骤检索开始、检索结束、调用LLM前、收到回答后使用print或logging输出结构化日志JSON格式并包含请求ID、检索到的文档ID、Token使用量等信息。CloudWatch Logs会自动收集这些日志。指标监控利用CloudWatch自定义指标记录每次问答的端到端延迟、检索到的上下文数量、LLM调用Token数、用户反馈如有。这能帮助你发现性能退化或成本异常。追踪Tracing使用AWS X-Ray来追踪一次用户请求流经API Gateway、Lambda、Bedrock、Aurora的完整路径直观定位延迟瓶颈。反馈循环设计机制收集用户反馈比如“这个回答是否有用”的按钮。将用户问题、系统检索到的上下文、生成的答案以及用户反馈一起存储下来。这些数据是优化分块策略、提示词和检索参数的黄金燃料。搭建这个RAG系统的过程就像在组装一个精密的仪器。每个环节——文档分块、向量模型、检索策略、提示词——都需要反复调试和校准。没有一劳永逸的“最佳配置”只有最适合你特定文档和业务场景的配置。我的建议是先用一个最小可行产品MVP跑通全流程然后用一批典型问题作为测试集定量地评估回答质量再针对性地迭代优化其中一个环节如此循环。在这个过程中你会对语义搜索和生成式AI有更深刻、更实战的理解。
AWS实战:基于Python与Aurora pgvector构建企业级RAG应用
1. 项目概述在AWS上构建一个基于Python的LLM RAG向量数据库应用最近在跟几个做AI应用的朋友聊天大家普遍有个痛点大语言模型LLM本身知识有“保质期”让它回答最新的、或者你私有的、非公开的资料时它要么胡说八道要么直接说“我不知道”。为了解决这个问题RAG检索增强生成架构火了起来。简单说RAG就是给LLM配一个“外接大脑”——一个向量数据库里面存着你自己的知识文档。当用户提问时系统先从这个“外接大脑”里找到最相关的资料再连同问题和资料一起喂给LLM让它基于这些“证据”来生成答案这样既准确又可控。这个项目build-on-aws/llm-rag-vectordb-python就是一个非常典型的实战指南。它手把手教你如何在亚马逊云科技AWS上用Python语言从零开始搭建一套完整的RAG应用。这不仅仅是跑通一个Demo而是涉及从文档处理、向量化、存储、检索到最终集成的全链路工程化实践。无论你是想为自己的产品添加智能问答能力还是想深入理解RAG背后的工程细节这个项目都能提供一个扎实的起点。接下来我会结合自己的实操经验把这个项目拆解透并补充大量官方文档里不会写的“坑”和技巧。2. 核心架构与AWS服务选型解析2.1 为什么选择AWS作为RAG的部署平台在云上构建RAG应用选型第一件事就是云平台。AWS在这个场景下有几个天然优势。首先是生态完整性从计算、存储、数据库到AI服务AWS提供了一站式的解决方案服务之间的集成和权限管理IAM非常成熟能大大降低运维复杂度。其次是Serverless无服务器能力对于RAG这种请求量可能波动很大的应用采用Serverless服务可以做到按需使用、按量付费在项目初期或流量不确定时能有效控制成本。最后是AI服务的深度集成比如Amazon Bedrock它提供了对多个顶尖基础模型如Anthropic Claude、Meta Llama 3的统一、安全的API访问省去了自己部署和管理模型的麻烦。2.2 核心服务栈拆解与选型理由一个生产级的RAG应用通常包含以下几个核心环节对应到AWS的服务选型我的方案是这样的文档存储与原始数据处理使用Amazon S3。这是AWS的对象存储服务几乎是不二之选。它的可靠性极高成本低廉非常适合存放原始的PDF、Word、TXT等文档。我们将所有待处理的文档上传到指定的S3存储桶Bucket中。文档处理与向量化Embedding这是RAG的“预处理流水线”。这里我们需要一个计算服务来运行Python代码进行文本提取、分块Chunking和调用Embedding模型生成向量。计算服务选择对于灵活、短时间运行的预处理任务我推荐使用AWS Lambda。它是一个事件驱动的无服务器计算服务。我们可以配置一个Lambda函数当有新的文档上传到S3时它自动被触发执行预处理代码。对于处理时间可能超过15分钟Lambda单次执行上限或需要GPU加速的复杂Embedding任务则可以考虑Amazon SageMaker的Processing Job或托管端点。Embedding模型可以选择在Lambda或SageMaker中部署开源的Embedding模型如BAAI/bge-large-en-v1.5但更省事的方式是直接使用Amazon Bedrock的Titan Embeddings模型。它通过一个API调用即可完成无需管理模型基础设施并且其生成的向量与Bedrock的文本生成模型有良好的兼容性。向量存储与检索这是RAG的“外接大脑”核心。我们需要一个能高效存储和检索高维向量的数据库。首选方案Amazon Aurora PostgreSQL with pgvector。这是一个托管的关系型数据库通过pgvector扩展支持向量运算。它的优势在于第一可以和你现有的业务数据用户信息、订单记录等放在同一个数据库里简化架构第二支持完整的ACID事务和SQL查询能力强大第三对于中小规模的向量数据比如数百万条以内其性能和成本通常比专用向量数据库更有优势。这是本项目标题中vectordb部分的一个非常主流和务实的选择。备选方案专用向量数据库。如果你的数据量极大数亿以上对检索延迟要求极苛刻或者需要复杂的过滤条件可以考虑像Pinecone第三方可通过AWS Marketplace使用或Amazon OpenSearch Service内置k-NN功能这样的专用服务。但在大多数应用场景下Aurora pgvector已经足够优秀。大语言模型推理与答案生成使用Amazon Bedrock。如前所述它提供了对多个高性能LLM的托管访问。你无需关心服务器、显卡、模型版本升级只需通过API调用即可。它内置了安全性和负责任的AI功能对于企业应用很重要。应用编排与API服务最终我们需要一个服务来串联整个流程接收用户问题 - 检索向量数据库 - 调用LLM生成答案 - 返回结果。API后端可以使用AWS Lambda配合Amazon API Gateway构建一个无服务器的RESTful API。这是非常轻量和成本优化的方案。复杂工作流如果业务流程复杂涉及多个步骤和条件判断可以使用AWS Step Functions来可视化地编排整个RAG流程提高可观测性和可维护性。整个架构的数据流大致是S3文档源 - Lambda触发处理- 文本分块 - Bedrock生成向量- Aurora pgvector存储向量- API Gateway/Lambda接收查询- pgvector检索相似向量- Bedrock生成最终答案- 返回给用户。3. 实战搭建从零到一的详细步骤3.1 环境准备与基础设施部署在开始写代码之前我们需要在AWS上把“舞台”搭好。我强烈建议使用Infrastructure as Code (IaC)工具比如AWS CloudFormation或者Terraform来部署资源这能确保环境的一致性也方便重建。这里我用CloudFormation的核心概念来讲解。首先创建一个S3存储桶名字可以是your-company-rag-source-docs。记住要设置好生命周期策略比如将30天前的原始文档转移到低频访问层以节省成本。其次创建Aurora PostgreSQL数据库集群。关键步骤是启用pgvector扩展。在创建数据库参数组时需要添加一个参数shared_preload_libraries其值设置为pgvector。创建完数据库实例后连接到数据库并执行CREATE EXTENSION vector;来激活扩展。别忘了在安全组Security Group中开放5432端口给你的Lambda函数或应用服务器。然后创建用于Bedrock访问的IAM角色。Bedrock的模型访问需要显式授权。你需要在IAM控制台创建一个角色信任实体为你要使用的服务如Lambda并附加AWS托管策略AmazonBedrockFullAccess生产环境建议按需缩小权限。最后准备Lambda函数的执行角色。这个角色需要权限从上述S3桶读取对象调用Bedrock的InvokeModelAPI以及写入Aurora数据库。你需要精心配置其IAM策略。注意IAM权限是安全的核心。务必遵循最小权限原则。例如给文档处理Lambda的S3权限资源应精确到arn:aws:s3:::your-company-rag-source-docs/*动作可以是s3:GetObject和s3:ListBucket。不要直接使用*通配符。3.2 文档处理与向量化流水线实现这是RAG效果好坏的基础。糟糕的文档分块Chunking会直接导致检索质量下降。步骤一文本提取当用户上传一个PDF到S3后会触发S3事件通知调用我们的处理Lambda。Lambda函数里我们使用PyPDF2或更强大的pdfplumber库来提取文本。对于Word文档使用python-docx对于纯文本和HTML处理相对简单。这里有个坑PDF的格式千奇百怪有些是扫描件图片。对于扫描件PDF你需要先用Amazon TextractAWS的OCR服务进行文字识别这比任何开源库都强大和准确当然成本也更高。步骤二文本分块Chunking这是艺术与科学的结合。最简单的是固定大小分块比如每500个字符一块用langchain的RecursiveCharacterTextSplitter可以轻松实现。但更好的方法是按语义分块比如按段落、按标题。我常用的一个策略是“重叠分块”比如块大小500字符重叠100字符。这能防止一个完整的语义被硬生生切断在检索时上下文信息也能更好地保留。对于长文档如书籍可以先按章节分大块再在每个章节内进行重叠分块。# 示例使用LangChain的分块器 from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, length_functionlen, separators[\n\n, \n, 。, , , , , , ] ) chunks text_splitter.split_text(extracted_text)步骤三生成向量并存储对于每个文本块我们调用Bedrock的Titan Embeddings模型来生成向量。Bedrock的API返回的是一个浮点数列表即我们的向量。然后我们将(文本块, 向量, 元数据)插入到Aurora PostgreSQL中。import boto3 import json import psycopg2 from psycopg2.extras import execute_values bedrock_runtime boto3.client(bedrock-runtime, region_nameus-east-1) model_id amazon.titan-embed-text-v1 def get_embedding(text): body json.dumps({inputText: text}) response bedrock_runtime.invoke_model(bodybody, modelIdmodel_id) response_body json.loads(response.get(body).read()) return response_body[embedding] # 连接数据库 conn psycopg2.connect(hostdb_host, databasedb_name, userdb_user, passworddb_password) cur conn.cursor() # 为每个chunk生成向量并准备数据 data_to_insert [] for chunk in chunks: embedding get_embedding(chunk) # 元数据可以包含来源文件、页码、章节等信息 metadata json.dumps({source: s3_key, chunk_index: idx}) data_to_insert.append((chunk, embedding, metadata)) # 使用pgvector扩展插入数据 insert_query INSERT INTO document_chunks (text, embedding, metadata) VALUES %s # 注意embedding列的类型应为vector(维度)例如vector(1536) execute_values(cur, insert_query, data_to_insert) conn.commit()实操心得在批量处理大量文档时直接为每个小块同步调用Bedrock API可能会慢且可能遇到限流。更好的做法是使用异步调用或批量调用如果服务支持。此外务必在代码中加入重试逻辑和指数退避以处理网络波动或服务临时不可用。3.3 检索与生成核心逻辑剖析当用户提出一个问题时后端API另一个Lambda函数需要执行以下步骤问题向量化将用户的问题Query用与之前相同的Embedding模型转化为向量。向量检索在Aurora pgvector中执行相似度搜索找出与问题向量最接近的K个文本块。pgvector支持多种距离计算方式最常用的是余弦相似度操作符或L2距离-。上下文构建将检索到的Top K个文本块连同它们的一些元数据如来源组合成一个“上下文”字符串。通常会在每个块前加上“出处”之类的提示。提示词工程构建最终发送给LLM如Claude 3 Haiku的提示词Prompt。这是决定答案质量的关键。一个经典的模板是你是一个专业的助手请严格根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题请直接说“根据提供的资料我无法回答这个问题”。不要编造信息。 上下文信息 {context} 问题{question} 请根据上下文回答调用LLM生成答案将组装好的提示词发送给Bedrock的对话模型获取生成的答案。返回结果将答案、以及可选的引用来源检索到的文本块及其元数据一并返回给前端。-- 在pgvector中检索相似向量的SQL示例 SELECT id, text, metadata, 1 - (embedding %s) as cosine_similarity FROM document_chunks ORDER BY embedding %s LIMIT 5;注意事项检索数量K需要权衡。K太小可能遗漏关键信息K太大会引入噪声并增加LLM的上下文长度Token数导致成本增加和速度变慢。通常从3-5开始测试。另外可以考虑在检索时加入元数据过滤比如“只从某份手册中检索”这能极大提升精准度。4. 性能优化与成本控制实战策略4.1 向量检索性能提升技巧当你的向量表达到百万级别时简单的全表扫描排序将变得非常慢。pgvector提供了索引来加速检索。最常用的索引是IVFFlat索引。它的原理类似于K-Means聚类先将所有向量分成若干簇lists检索时先找到距离目标向量最近的几个簇然后在这几个簇里进行精确搜索。创建索引的SQL如下CREATE INDEX ON document_chunks USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);这里的lists参数是关键。一个经验法则是lists sqrt(行数)。对于100万行数据lists设为1000是合理的起点。但是必须在表有足够多的数据比如每个list至少有1000条记录后再创建索引并且索引创建后如果大量新增数据需要重建索引REINDEX以保持性能。对于极致性能要求可以探索HNSW索引它提供了更优的查询速度和召回率但创建索引的时间更长占用的空间也更大。4.2 多轮对话与历史上下文管理基础的RAG是“单轮”的。但在聊天场景中用户的问题往往有上下文关联。例如用户先问“AWS S3是什么”接着问“它怎么收费”。第二个问题中的“它”指代S3。实现多轮对话RAG有两种主流思路将历史对话也纳入检索将当前问题与之前的几轮对话QA对拼接起来作为一个新的查询语句去检索。这能帮助系统理解指代关系。让LLM自己管理上下文使用具有长上下文窗口的模型如Claude 200K将检索到的当前上下文和整个对话历史都喂给模型由模型自行理解。这种方法更简单但对模型能力要求高且Token消耗大。在Lambda无服务器架构下需要将会话状态对话历史存储到外部如Amazon DynamoDB。每次请求时先从DynamoDB读取该会话的历史记录再执行上述流程最后将本轮新的QA对写回DynamoDB。4.3 成本监控与优化措施Serverless架构按用量付费成本透明但也需要关注。Bedrock成本主要来自Embedding调用和文本生成调用。Embedding按Token数计费文本生成按输入输出Token总数计费。优化点优化提示词减少不必要的指令控制检索上下文的长度对于简单问题可以考虑使用更小、更便宜的模型如Titan Text Express。Lambda成本按请求次数和执行时间GB-秒计费。优化点确保函数代码高效如使用json.loads而非eval合理设置内存大小更大的内存可能带来更快的执行速度从而降低时间成本利用执行环境复用保持数据库连接等。Aurora成本按数据库实例规格、存储和I/O计费。优化点根据负载选择正确的实例大小为向量表设置合适的索引减少全表扫描带来的CPU消耗定期清理或归档不再需要的旧数据。监控务必启用AWS Cost Explorer和设置预算告警。在CloudWatch中为Bedrock、Lambda的调用次数和持续时间设置仪表盘以便直观了解使用模式和发现异常。5. 常见问题排查与进阶思考5.1 效果不佳问题诊断清单如果你的RAG应用回答不准确或胡编乱造可以按以下清单排查问题现象可能原因排查与解决思路答案与上下文无关幻觉1. 提示词未强制模型基于上下文。2. 检索到的上下文完全不相关。1. 强化提示词使用“严格根据上下文”、“如果不知道请说无法回答”等指令。2. 检查Embedding模型是否与文本生成模型匹配推荐使用同一家的。3. 检查检索的相似度分数如果最高分也很低如余弦相似度0.7说明检索失败需优化分块策略或Embedding模型。检索不到正确信息1. 文档分块不合理切断了语义。2. 查询问题表述与文档内容表述差异大。3. 向量索引未优化或需要重建。1. 尝试更小的块大小或重叠分块。2. 对用户查询进行“查询重写”或“查询扩展”例如用LLM将问题改写成更可能出现在文档中的形式。3. 检查索引确保数据量足够后创建并考虑重建索引。答案冗长或包含多余信息检索到的上下文块过多或包含无关信息。1. 减少检索数量K。2. 在检索后增加一个“重排序”步骤使用更精细的模型如交叉编码器对Top K结果再次排序只保留最相关的几个。3. 在提示词中要求“简洁回答”。处理长文档超时Lambda函数执行超时默认3秒最长15分钟。1. 对于超长文档拆分处理流程。用Lambda触发将文档地址放入SQS队列由EC2或Fargate任务异步处理。2. 增加Lambda超时时间和内存配置。5.2 安全性与权限管控企业级应用必须考虑安全。数据加密确保S3桶、Aurora数据库都启用了加密静态加密。Bedrock的通信默认使用HTTPS传输中加密。网络隔离将Lambda函数、Aurora数据库部署在私有子网Private Subnet中。为Lambda配置VPC通过VPC端点PrivateLink访问Bedrock和S3避免流量经过公网。权限精细化管理如前所述为每个Lambda函数创建独立的、权限最小的IAM角色。使用数据库用户和密码或IAM认证并限制其只有特定表的读写权限。输入输出审查对用户输入进行基本的清理和检查防止提示词注入攻击。对模型输出内容尤其是面向公众时可以考虑增加一层安全审查或过滤。5.3 从项目到产品可观测性与持续改进一个实验项目和一个可运营的产品之间差的是一个完整的可观测性体系。日志记录在Lambda函数中关键步骤检索开始、检索结束、调用LLM前、收到回答后使用print或logging输出结构化日志JSON格式并包含请求ID、检索到的文档ID、Token使用量等信息。CloudWatch Logs会自动收集这些日志。指标监控利用CloudWatch自定义指标记录每次问答的端到端延迟、检索到的上下文数量、LLM调用Token数、用户反馈如有。这能帮助你发现性能退化或成本异常。追踪Tracing使用AWS X-Ray来追踪一次用户请求流经API Gateway、Lambda、Bedrock、Aurora的完整路径直观定位延迟瓶颈。反馈循环设计机制收集用户反馈比如“这个回答是否有用”的按钮。将用户问题、系统检索到的上下文、生成的答案以及用户反馈一起存储下来。这些数据是优化分块策略、提示词和检索参数的黄金燃料。搭建这个RAG系统的过程就像在组装一个精密的仪器。每个环节——文档分块、向量模型、检索策略、提示词——都需要反复调试和校准。没有一劳永逸的“最佳配置”只有最适合你特定文档和业务场景的配置。我的建议是先用一个最小可行产品MVP跑通全流程然后用一批典型问题作为测试集定量地评估回答质量再针对性地迭代优化其中一个环节如此循环。在这个过程中你会对语义搜索和生成式AI有更深刻、更实战的理解。