1. 项目概述基于LangChain、Amazon Bedrock和OpenSearch的RAG系统最近在折腾一个企业知识库的智能问答项目核心需求是把一堆分散的PDF、Word文档、网页内容变成能“对话”的智能体。传统的基于关键词的搜索用户得自己提炼关键词结果还不一定准而直接上大语言模型LLM吧模型本身的知识是静态的回答不了你公司内部最新的产品手册或者项目文档里的内容还容易“一本正经地胡说八道”幻觉问题。这时候检索增强生成RAG就成了一个非常务实的选择。简单来说RAG就是让LLM在回答问题时先去你的专属知识库里“查资料”然后基于查到的相关资料来组织答案。这样既保证了答案的相关性和准确性又赋予了LLM实时获取外部知识的能力。我这次实践参考并深度改造了AWS官方的一个示例项目它巧妙地结合了三个核心组件LangChain作为编排框架Amazon Bedrock提供强大的基础模型Amazon OpenSearch Serverless作为向量检索数据库。这个组合拳打下来不仅性能稳定而且因为完全构建在AWS托管服务之上运维复杂度大大降低。接下来我就把这个从零搭建、踩坑、调优的全过程拆开揉碎了分享给你。2. 核心架构与组件选型解析2.1 为什么是RAG解决LLM的“知识盲区”与“幻觉”在深入技术栈之前我们必须先理解为什么RAG是当前企业级AI应用的首选架构之一。大语言模型虽然在通用知识上表现惊人但它存在两个致命短板知识滞后性模型的训练数据有截止日期无法知晓之后发生的事件或发布的文档。比如你无法直接问ChatGPT你公司上周刚更新的财务政策。领域特异性缺失模型缺乏对特定企业、垂直行业的私有知识的理解。它不懂你公司的产品代号、内部流程缩写或客户案例细节。幻觉与溯源困难模型可能生成听起来合理但完全错误的内容且你无法验证其信息源。RAG通过将“检索Retrieval”和“生成Generation”解耦优雅地解决了这些问题。其工作流可以概括为索引阶段将私有文档切块、编码为向量嵌入存入向量数据库。查询阶段当用户提问时将问题也编码为向量在向量数据库中查找语义最相似的文本块上下文。生成阶段将找到的上下文和用户问题一起拼接成提示Prompt送给LLM指令其“基于以下上下文回答问题”。这样一来LLM的答案就有了依据并且我们可以要求它引用来源实现了答案的可解释性和可控性。2.2 组件深度选型LangChain, Bedrock 与 OpenSearch Serverless这个项目的技术选型体现了现代AI应用开发的典型思路用框架管理复杂性用托管服务保证稳定与扩展。#### 2.2.1 LangChainAI应用的“粘合剂”与“调度中心”LangChain不是一个模型而是一个框架。你可以把它想象成乐高积木的底板和连接件。它提供了标准化的接口如LLMEmbeddingsVectorStore和丰富的“链Chain”与“代理Agent”模式让我们能像搭积木一样组合不同的模块。在这个项目中LangChain的核心价值在于标准化抽象无论底层用的是Bedrock的Claude还是Titan模型通过LangChain的BedrockLLM封装调用方式都是统一的。换模型可能只需要改一行配置。文档加载与处理内置了PyPDFLoaderDocxLoaderWebBaseLoader等能轻松处理多种格式的原始文档。文本分割策略提供了RecursiveCharacterTextSplitter、TokenTextSplitter等这是影响RAG效果的关键环节后面会详细讲。检索链封装提供了RetrievalQA这样的高层链把向量检索、提示组装、LLM调用整个流程打包好极大简化了开发。注意LangChain更新迭代很快API有时会有变动。建议在项目开始时锁定一个较稳定的版本例如langchain0.0.340并在虚拟环境中开发避免依赖冲突。#### 2.2.2 Amazon Bedrock企业级基础模型的“超市”Amazon Bedrock是AWS推出的全托管服务它集成了多家顶尖AI公司的基础模型FM如Anthropic的Claude、AI21 Labs的Jurassic、Cohere的Command以及亚马逊自家的Titan。你可以把它理解为一个“模型即服务MaaS”平台。选择Bedrock而非直接调用OpenAI API或部署开源模型主要基于以下几点考量安全与合规数据在AWS网络内流动无需将敏感的企业文档发送到外部API端点满足严格的数据治理要求。统一管理与成本控制所有模型的调用通过统一的AWS API和计费体系方便管理和预测成本。免运维无需关心模型的部署、扩缩容、GPU驱动等底层基础设施问题。模型多样性可以根据任务特点创意写作、逻辑推理、代码生成选择最合适的模型甚至未来可以轻松切换。在这个项目中我主要使用了Anthropic Claude Instant快速、性价比高和Claude 3 Haiku/Sonnet能力更强进行生成使用Amazon Titan Embeddings模型进行文本向量化。#### 2.2.3 Amazon OpenSearch Serverless专为向量搜索优化的“记忆库”向量数据库是RAG的“记忆”核心。我们需要一个能高效存储和检索高维向量通常是768或1024维的数据库。OpenSearch及其Serverless版本不仅支持传统的全文检索还通过k-NN插件原生支持向量相似度搜索。选择OpenSearch Serverless版本的理由真正的Serverless无需预置或管理集群容量。你只需要创建集合Collection它根据实际的数据存储量和查询流量自动伸缩按实际使用量付费。对于流量波动大的应用如内部工具可能白天查询多晚上少成本效益极高。集成与安全天然与AWS生态系统集成通过IAM角色控制访问权限VPC内网访问保障网络安全与Bedrock同处一个云环境数据传输延迟低。混合搜索能力除了纯向量搜索还支持将关键词搜索BM25分数与向量相似度分数进行混合Hybrid Search在实践中能有效提升检索质量尤其是当查询中包含具体名称、代号时。3. 实战搭建从零构建RAG流水线理论讲完我们进入实战环节。我会假设你有一个AWS账号并具备基本的Python和AWS CLI配置知识。3.1 环境准备与基础设施部署#### 3.1.1 AWS权限与Bedrock模型访问首先确保你的IAM用户或角色有足够的权限。你需要bedrock:InvokeModel(调用Bedrock模型)bedrock:ListFoundationModels(查看可用模型)aoss:*(创建和管理OpenSearch Serverless资源生产环境建议细化)s3:*(如果你的文档存放在S3需要读取权限)关键一步在AWS Bedrock控制台你需要对你计划使用的模型如Claude系列 Titan Embeddings“请求模型访问Request model access”。这是一个点击按钮的操作但必须做否则调用会失败。#### 3.1.2 创建OpenSearch Serverless向量集合我们通过AWS CLI来创建这比控制台更可重复。# 1. 创建加密策略可选但推荐 aws opensearchserverless create-security-policy \ --name vector-search-encryption-policy \ --type encryption \ --policy {Rules:[{Resource:[collection/rag-collection],ResourceType:collection}],AWSOwnedKey:true} # 2. 创建网络策略允许从你的VPC或特定IP访问 aws opensearchserverless create-security-policy \ --name vector-search-network-policy \ --type network \ --policy [{Rules:[{ResourceType:collection,Resource:[collection/rag-collection]}],AllowFromPublic:true}] # 注意生产环境应将AllowFromPublic设为false并配置具体的VPC或IP规则。 # 3. 创建数据访问策略指定哪个IAM角色可以访问集合 # 首先创建一个信任策略文件 trust-policy.json { Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { AWS: arn:aws:iam::YOUR_ACCOUNT_ID:root }, Action: sts:AssumeRole } ] } # 创建IAM角色 aws iam create-role \ --role-name os-serverless-rag-role \ --assume-role-policy-document file://trust-policy.json # 创建数据访问策略关联该角色 aws opensearchserverless create-access-policy \ --name vector-search-data-policy \ --type data \ --policy [{Rules:[{ResourceType:collection,Resource:[collection/rag-collection],Permission:[aoss:*]}],Principal:[arn:aws:iam::YOUR_ACCOUNT_ID:role/os-serverless-rag-role]}] # 4. 终于创建向量集合 aws opensearchserverless create-collection \ --name rag-collection \ --type VECTORSEARCH创建完成后记下返回的arn和dashboardUrl即OpenSearch的终端节点。集合初始化需要几分钟状态变为ACTIVE后才可使用。3.2 文档处理与向量化决定RAG效果的“基石”这是整个流程中最关键、最需要精心设计的部分。垃圾输入必然导致垃圾输出。#### 3.2.1 文档加载与文本分割的艺术LangChain提供了多种加载器。这里以PDF和网页为例from langchain.document_loaders import PyPDFLoader, WebBaseLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 加载PDF pdf_loader PyPDFLoader(path/to/your/document.pdf) pdf_docs pdf_loader.load() # 加载网页 web_loader WebBaseLoader([https://example.com/page1, https://example.com/page2]) web_docs web_loader.load() # 合并所有文档 all_docs pdf_docs web_docs接下来是文本分割。直接整篇文档塞进去效果很差因为上下文窗口有限且会引入无关噪音。分割的目标是得到语义相对完整、大小适中的“块Chunk”。text_splitter RecursiveCharacterTextSplitter( # 按字符递归分割优先尝试 \n\n, 然后 \n, 再空格最后单个字符 separators[\n\n, \n, , ], chunk_size1000, # 每个块的最大字符数 chunk_overlap200, # 块之间的重叠字符数避免语义被割裂 length_functionlen, ) split_docs text_splitter.split_documents(all_docs) print(f原始文档数{len(all_docs)} 分割后块数{len(split_docs)})实操心得chunk_size和chunk_overlap是超参数需要根据你的文档类型和模型上下文窗口调整。chunk_size一般设置在500-1500之间。技术文档可小些800叙述性文章可大些1200。Bedrock Claude的上下文窗口很大如100K但检索时我们只取top-k个块所以单个块不宜过大要保证信息密度。chunk_overlap通常设为chunk_size的10%-20%。这是防止一个完整的句子或关键概念被切到两个块边缘的“缓冲带”对保证检索上下文连贯性至关重要。测试方法随机采样一些分割后的块人工阅读检查是否保持了语义完整性。一个糟糕的分割会把一个问题描述和它的答案分到两个块里。#### 3.2.2 向量嵌入与存储我们将使用Bedrock的Titan Embeddings模型将文本块转化为向量。import boto3 from langchain.embeddings import BedrockEmbeddings from langchain.vectorstores import OpenSearchVectorSearch # 初始化Bedrock客户端和嵌入模型 bedrock_runtime boto3.client(service_namebedrock-runtime, region_nameus-east-1) embeddings BedrockEmbeddings( clientbedrock_runtime, model_idamazon.titan-embed-text-v1 # 使用Titan Embeddings模型 ) # OpenSearch Serverless 连接配置 opensearch_endpoint your-collection-endpoint.us-east-1.aoss.amazonaws.com # 替换为你的dashboardUrl index_name rag-index # 创建向量存储并批量插入文档 # 注意首次运行这会创建索引并插入所有文档耗时较长 vector_store OpenSearchVectorSearch.from_documents( documentssplit_docs, embeddingembeddings, opensearch_urlfhttps://{opensearch_endpoint}:443, http_authNone, # Serverless使用IAM认证这里留空依赖boto3的凭证 use_sslTrue, verify_certsTrue, connection_classrequests.aws4auth.AWS4Auth, # 关键使用IAM签名 index_nameindex_name, enginefaiss, # OpenSearch Serverless 推荐使用 faiss 引擎 vector_fieldembedding, text_fieldtext, )踩坑记录认证问题连接OpenSearch Serverless时最大的坑是认证。不能使用传统的用户名密码必须使用AWS Sigv4签名。OpenSearchVectorSearch的http_auth参数需要传入一个requests_aws4auth.AWS4Auth对象。确保你的环境如Lambda、EC2或本地配置了AWS CLI有正确的IAM凭证。如果遇到403错误十有八九是这里的问题。3.3 检索与生成链的组装文档入库后我们就可以组装核心的问答链了。from langchain.llms import Bedrock from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate # 1. 初始化LLM这里使用Claude Instant速度快成本低 llm Bedrock( clientbedrock_runtime, model_idanthropic.claude-instant-v1, model_kwargs{ max_tokens_to_sample: 3000, temperature: 0.1, # 低温度让答案更确定、更基于上下文 top_p: 0.9, } ) # 2. 定义提示模板 - 这是控制LLM行为的关键 prompt_template Human: 请仅根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题请直接说“根据提供的信息我无法回答这个问题”不要编造信息。 上下文 {context} 问题{question} 请用中文提供详细、准确的答案。如果答案涉及步骤或列表请清晰地列出。 Assistant: PROMPT PromptTemplate( templateprompt_template, input_variables[context, question] ) # 3. 初始化检索器 # 这里使用向量存储自带的.as_retriever()方法可以配置搜索参数 retriever vector_store.as_retriever( search_typesimilarity, # 相似度搜索 search_kwargs{k: 5} # 检索最相似的5个文本块 ) # 4. 组装检索问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # “stuff”模式将检索到的所有上下文塞进提示简单直接 retrieverretriever, chain_type_kwargs{prompt: PROMPT}, return_source_documentsTrue # 非常重要返回源文档用于溯源 ) # 5. 进行查询 query 我司最新的差旅报销政策是什么 result qa_chain({query: query}) print(答案, result[result]) print(\n--- 来源文档 ---) for i, doc in enumerate(result[source_documents]): print(f[来源{i1}] {doc.metadata.get(source, N/A)} - 片段: {doc.page_content[:200]}...)4. 高级优化与效果提升技巧基础流程跑通只是第一步。要让RAG系统真正可用、好用还需要一系列优化。4.1 检索优化从“相似”到“相关”单纯的向量相似度搜索有时会失灵比如查询“怎么申请假期”可能检索到的是“年假制度历史沿革”而不是“假期申请流程”。#### 4.1.1 混合搜索Hybrid Search结合关键词搜索BM25和向量搜索取长补短。OpenSearch支持配置混合搜索。在LangChain中可以配置retriever的search_type为similarity_score_threshold并组合其他条件或者使用更高级的SelfQueryRetriever需要元数据过滤。更直接的方式是利用OpenSearch的复合查询DSL。# 示例在创建vector_store时可以后续通过自定义查询来实施混合搜索 # 但LangChain对OpenSearch Serverless的混合搜索封装有限有时需要直接使用OpenSearch Python Client from opensearchpy import OpenSearch, RequestsHttpConnection from requests_aws4auth import AWS4Auth # 建立直接连接 auth AWS4Auth(...) # 配置你的认证 client OpenSearch( hosts[{host: opensearch_endpoint, port: 443}], http_authauth, use_sslTrue, verify_certsTrue, connection_classRequestsHttpConnection ) # 构建混合查询DSL hybrid_query { query: { hybrid: { queries: [ { match: { text: {query: query, boost: 0.3} # 关键词查询权重0.3 } }, { knn: { embedding: { vector: query_embedding, # 需要先获取查询的向量 k: 5 }, boost: 0.7 # 向量查询权重0.7 } } ] } } } response client.search(indexindex_name, bodyhybrid_query)#### 4.1.2 查询重写与扩展在将用户查询送入向量数据库前先对其进行优化。例如使用一个轻量级LLM如Claude Haiku对原始查询进行同义改写或问题分解。改写“报销咋弄” - “差旅费用报销的具体流程和所需材料是什么”分解“介绍一下项目A的架构和部署步骤” - 分解为“项目A的架构是什么”和“项目A的部署步骤有哪些”两个子查询分别检索后再综合。这能显著提升检索的召回率Recall。4.2 提示工程与上下文管理检索到的上下文如何有效地交给LLM同样影响最终答案的质量。#### 4.2.1 超越“Stuff”更智能的上下文处理模式我们之前用的chain_typestuff是最简单的把所有上下文拼在一起。如果检索到的文档总长度超过模型上下文限制就会失败。还有两种更高级的模式map_reduce先将每个检索到的文档单独生成一个答案Map再将这些答案汇总成一个最终答案Reduce。适合处理大量文档但成本高可能丢失细节。refine迭代处理文档。用第一个文档生成初始答案然后用后续文档不断去“精炼Refine”这个答案。能产生连贯、深入的答案但速度慢。对于大多数问答场景stuff模式配合合理的chunk_size和k值已经足够。如果文档块很多可以考虑使用refine。#### 4.2.2 元数据过滤在存储文档时可以附加元数据如{“source”: “员工手册.pdf”, “page”: 12, “department”: “HR”}。在检索时可以要求只检索特定来源、特定部门的文档实现更精准的答案。 在创建retriever时可以传入一个search_kwargs包含过滤器retriever vector_store.as_retriever( search_kwargs{ k: 5, filter: {term: {metadata.department: Engineering}} # 只检索工程部的文档 } )4.3 评估与迭代没有度量就没有改进如何知道你的RAG系统好不好不能只靠感觉。人工评估黄金标准构建一个测试集包含50-100个真实用户可能问的问题并准备好标准答案或期望的答案要点。定期运行测试集人工评判答案的准确性是否基于上下文、完整性是否回答了问题的所有部分和有用性。自动评估指标检索相关性计算检索到的Top-k文档中有多少是真正相关的需要人工标注。答案忠实度生成的答案有多少内容是基于检索到的上下文的而不是LLM自己编造的。可以用另一个LLM来判断。答案相关性生成的答案与问题的匹配程度。A/B测试如果你有用户流量可以部署两个不同版本例如不同的chunk_size或不同的提示模板通过用户反馈或对话完成率来评估哪个更好。5. 生产环境部署与运维考量将原型部署为7x24小时可用的服务还需要考虑更多。5.1 架构设计Serverless与微服务一个典型的生产架构可能如下前端一个Web应用如React或聊天界面。API层使用Amazon API Gateway接收用户查询。业务逻辑层使用AWS Lambda函数Python来承载我们上面编写的LangChain RAG链。Lambda无服务器自动伸缩按调用付费非常适合这种间歇性、有波动的AI工作负载。向量数据库Amazon OpenSearch Serverless如前所述。模型服务Amazon Bedrock全托管。文档更新管道当有新文档加入时可以触发一个Lambda函数或使用AWS Glue作业自动执行文档加载、分割、向量化并更新OpenSearch索引。这个管道可以由Amazon S3存放原始文档的PUT事件触发。5.2 成本监控与优化Bedrock成本主要来自Token消耗。区分输入Token和输出Token费用。Claude Instant比Claude 3便宜很多。在满足需求的前提下选用性价比高的模型。监控Bedrock的CloudWatch指标和成本分析报告。OpenSearch Serverless成本分为计算单元OCU费用和存储费用。计算单元根据搜索和索引的流量自动伸缩。优化检索逻辑避免不必要的查询设置合理的索引生命周期策略归档或删除旧数据。Lambda成本关注执行时间和内存配置。优化代码性能如缓存嵌入模型结果选择合适的内存大小CPU性能随内存增加而提升可能反而降低总成本。5.3 安全与权限最小权限原则为Lambda函数、Glue作业等分配仅需的最小IAM权限。网络隔离将Lambda、OpenSearch部署在私有子网通过VPC端点访问Bedrock避免数据在公网传输。数据加密确保OpenSearch集合启用加密默认启用AWS托管密钥。Bedrock的传输和静态数据加密由AWS负责。访问控制API Gateway可以配置API Key、Cognito用户池或JWT授权控制前端对后端API的访问。5.4 监控与告警利用Amazon CloudWatch应用指标在Lambda代码中记录自定义指标如查询延迟、检索文档数量、LLM响应时间、每次查询的Token消耗。业务指标记录每日问答次数、热门问题、无法回答“我无法回答”的比例。设置告警当错误率升高、平均延迟超过阈值或成本异常时触发告警。6. 常见问题与故障排查实录在实际搭建和运行中你几乎一定会遇到以下问题#### 6.1 连接与认证问题症状ConnectionError,403 Forbidden当连接OpenSearch Serverless时。排查检查IAM角色权限角色必须附加正确的数据访问策略。检查网络策略确保你的调用源IP或VPC在允许范围内。检查AWS凭证在Lambda中确保执行角色正确在本地确保aws configure配置正确且具有有效期内的临时令牌如果使用SSO等。检查代码中的端点Endpoint是否正确是否包含https://前缀和端口443。#### 6.2 检索效果不佳症状LLM回答“根据提供的信息我无法回答这个问题”或者答案明显与文档无关。排查检查检索结果本身打印出result[“source_documents”]人工查看检索到的文本块是否真的与问题相关。如果不相关问题在检索阶段。调整文本分割尝试减小chunk_size增加chunk_overlap。检查分割是否破坏了句子结构。调整检索数量增加search_kwargs{“k”: 5}中的k值比如到8或10提供更多上下文。检查嵌入模型尝试不同的嵌入模型如amazon.titan-embed-text-v2如果可用。确保查询时使用的嵌入模型与建索引时是同一个。尝试混合搜索如果查询中包含具体名称、代号开启关键词搜索可能会有奇效。#### 6.3 LLM答案质量差或出现幻觉症状答案模糊、答非所问或开始编造不存在的信息。排查强化提示词在提示模板中明确强调“仅根据上下文”并使用更严厉的语气如“如果上下文没有明确提及你必须回答‘我不知道’”。降低Temperature将LLM的temperature参数设为0.1或更低减少随机性。检查上下文是否充足可能检索到的文档块信息量不足。尝试优化检索见上一点或者考虑在索引阶段除了存储文本块还存储其摘要或相邻块以提供更丰富的背景。启用源溯源务必设置return_source_documentsTrue并在前端展示答案来源。这不仅能增加可信度也能帮你快速定位是哪个文档块导致了错误信息。#### 6.4 性能与延迟问题症状查询响应时间过长如10秒。排查分阶段计时在代码中记录嵌入查询、向量检索、LLM生成各阶段耗时。向量检索慢检查OpenSearch Serverless集合的OCU配置是否过小检查k值是否过大考虑对向量索引使用HNSW算法如果OpenSearch版本支持并已配置。LLM生成慢尝试换用更快的模型如Claude Instant v1减少max_tokens_to_sample限制答案长度。Lambda冷启动如果使用Lambda冷启动时加载LangChain和模型客户端可能很慢。可以考虑使用Lambda Provisioned Concurrency预置并发或使用容器镜像部署以包含依赖减少冷启动时间。也可以将嵌入模型客户端放在Lambda函数外部初始化。搭建一个高质量的RAG系统是一个持续迭代的过程没有一劳永逸的“银弹”。从基础的流水线搭建到持续的检索优化、提示工程和效果评估每一步都需要根据你的具体数据和业务需求进行精细调优。这个基于AWS全托管服务的方案为你提供了一个稳定、可扩展的起点让你能将更多精力集中在业务逻辑和效果提升上而不是基础设施的运维泥潭中。