RAG与大模型API实战:从零搭建本地AI搜索问答工具

发布时间:2026/10/15 4:17:18
RAG与大模型API实战:从零搭建本地AI搜索问答工具
最近在刷短视频时看到一个挺有意思的观点美国人对 AI 的情绪里带着明显的担忧甚至恐惧而国内很多用户却已经把大模型当成新型搜索引擎来用查资料、写文案、问攻略随手就是一个 AI 问答。评论区也在讨论“中美 AI 差距是不是越来越大了”。作为一个常年在业务里接大模型、调接口、做 AI 应用的开发者我觉得这类话题不能只停留在口号层面更应该从技术侧拆一拆中美 AI 产品生态到底差在哪普通开发者又能从这场“应用习惯差异”里学到什么。本文不会站队也不会输出情绪而是从技术角度分析 AI 产品落地模式的不同并带大家动手实现一个可以运行在本地、接近“AI 搜索引擎”体验的问答小工具。这个工具虽然是 Demo但它覆盖了大模型 API 接入、Prompt 设计、知识检索增强、多轮对话管理等核心知识点非常适合后端开发者入门 AI 应用开发。读完这篇文章你会理解为什么国内 AI 产品普遍长着一张“搜索框”的脸也会掌握一个可复用的“检索 生成”应用骨架还能避开很多接入大模型 API 时的常见坑。1. 现象背后的技术逻辑1.1 “把 AI 当搜索引擎用”的真实含义国内用户对 AI 的典型使用方式是打开一个对话框输入问题然后获得一段完整的答案。这种体验本质上不是“对话”而是“搜索答案”。用户并不关心模型是 ChatGPT 还是文心一言只关心答案能不能直接解决问题。这种使用习惯背后的产品逻辑是AI 充当了知识入口而不是聊天玩具。于是国内各大应用纷纷把 AI 做成了功能组件——你在办公软件里用 AI 写周报在电商平台里用 AI 总结评论在短视频应用里用 AI 回答搜索词。AI 以非常低的门槛嵌入了用户的既有行为路径。这种现象落到技术上就是典型的“RAG 大模型 API”架构用户输入问题 → 系统在知识库中检索相关片段 → 把片段拼接进 Prompt → 大模型基于片段生成答案。整条链路只暴露一个输入框给用户背后却完成了检索、匹配、生成三个环节。1.2 “怕 AI 怕得要死”的另外一面欧美市场对 AI 的讨论确实更多集中在风险侧版权诉讼、深度伪造、数据隐私、模型对齐、监管合规。ChatGPT 在欧洲上线时甚至一度面临数据保护机构的暂停要求。欧盟的《人工智能法案》也给高风险 AI 系统设置了严格的义务。这种环境下的产品形态会明显不同企业更倾向于把 AI 收敛在受控场景中比如代码助手、客服摘要、内部知识库问答而不是大规模开放给 C 端用户随意使用。AI 产品的迭代速度也会相对保守因为一旦出错法律和舆论成本都很高。所以在欧美围棋里看到很多“AI 伦理工程师”“模型安全研究员”的岗位需求这不是矫情而是合规压力驱动的真实需求。从技术栈来看欧美团队更早引入 guardrails护栏设计比如输出内容过滤、敏感词检测、模型行为约束等模块。1.3 差异的本质路线选择而非单纯“差距”与其说中美 AI 差距大不如说是两条不同的落地路线维度国内 AI 产品倾向欧美 AI 产品倾向用户入口对话式搜索、超级应用内嵌垂直工具、开发者平台、企业服务产品目标消灭信息差快速给出结论控制风险保证合规和可解释性技术重点检索增强、Prompt 工程、应用集成模型对齐、安全过滤、合规审计迭代节奏快速上线用数据验证谨慎发布用流程做防护商业模式应用内道具、会员订阅、导流API 按量计费、企业 license对于开发者来说真正值得学习的是两条路线各自的工程经验。国内路线告诉我们如何快速把大模型嵌入业务欧美路线告诉我们如何让 AI 在复杂环境下依然可控。这也是后面实战部分要设计“检索增强”和“安全过滤”两个模块的原因。2. 环境准备与版本说明在开始写代码之前先把开发环境梳理清楚。这个 Demo 会是一个 Python 项目使用 FastAPI 提供 HTTP 接口通过 OpenAI 兼容的 API 协议调用大模型接口完成问答补全。如果你使用的是国内大模型平台的 API多数厂商也会提供 OpenAI 兼容的访问地址直接替换配置即可代码逻辑不用大改。建议环境版本如下实际上可以按本地情况调整Python 3.10 pip 22.0依赖库如下openai调用大模型接口最新版本对 OpenAI 兼容协议支持更好。fastapi提供 Web 访问接口。uvicorn本地启动 FastAPI 服务。pydantic解析请求参数。安装命令pip install openai fastapi uvicorn pydantic项目结构规划如下ai-search-demo/ ├── config.py # 配置管理 ├── model_client.py # 大模型 API 统一入口 ├── retriever.py # 简单知识检索模块 ├── chatbot.py # 对话逻辑 检索增强问答 ├── app.py # FastAPI 接口层 ├── main.py # 命令行交互入口 └── docs/ ├── python_basics.txt └── llm_intro.txt本文示例的模型能力以“中等参数规模商用 API”为基准具体版本号变化较快无法保证长期有效所以不会写死某个版本。你只需要在config.py中配置正确的api_key和base_url即可。需要注意任何大模型 API 都有上下文长度限制。本文的示例会控制 Prompt 长度不让历史对话无限膨胀这也是长期运行这类应用时必须考虑的问题。3. 核心概念拆解3.1 大模型 API 是怎么工作的大模型 API 的表面形式非常简单你发送一组消息模型返回一段补全文本。关键在于消息的结构。一个典型的请求包含多个角色system设定模型的角色和回答规则。user用户的输入内容。assistant模型历史上的回答内容用于多轮对话。实际开发中新手最容易犯的错误是多轮对话不传历史消息导致模型“失忆”。正确做法是维护一个包含 system、user、assistant 消息的列表每次请求都带上最近几轮控制好 token 长度。3.2 检索增强生成RAGRAG全称 Retrieval-Augmented Generation也就是检索增强生成。传统大模型只能依靠训练时学到的知识回答但训练语料有截止时间且无法覆盖企业的私有数据。RAG 的思路是在模型回答之前先从外部知识库中检索出相关片段再把片段放入 Prompt让模型基于这些片段生成答案。这样做的好处很明显答案有依据、数据能实时更新、企业私有知识也能被模型使用。缺点是 Prompt 变长、系统多一个检索环节调试起来更复杂。在本文的 Demo 中我会用一个小型本地文档库演示检索过程。生产环境中这一步通常用向量数据库比如 Milvus、Elasticsearch、pgvector完成内部原理相似。3.3 “搜索引擎式 AI”的完整链路一个接近搜索引擎体验的 AI 应用链路如下用户输入问题。系统分析问题判断是需要实时检索、内部知识、还是直接闲聊。如果需要知识从文档库或搜索引擎 API 中获取相关片段。将检索结果结构化拼装进 Prompt。大模型生成最终答案并在答案中尽量引用来源。返回给用户同时保留对话历史。国内很多 AI 搜索产品本质上就是把这个链路做得更顺畅并且在背后接入多种数据源。开发者自己实现时可以先从一个本地文档库开始。4. 完整实战搭建一个本地 AI 搜索问答工具接下来我们进入实战部分。整个项目会拆成 5 个文件思路是“先有一个能跑通的最小系统再逐步加功能”。你可以边读边把代码复制到本地对应的文件中。4.1 编写配置文件先创建config.py把容易变动的配置集中管理。大模型的 API 地址和模型名需要根据你实际使用的平台调整不要照搬。# 文件路径ai-search-demo/config.py import os # 从环境变量读取 API Key避免写死在代码里 API_KEY os.getenv(LLM_API_KEY, your-api-key) BASE_URL os.getenv(LLM_BASE_URL, https://api.openai.com/v1) MODEL_NAME os.getenv(LLM_MODEL_NAME, gpt-3.5-turbo) # 上下文最大轮数避免历史对话无限膨胀 MAX_HISTORY_LEN 6 # 检索最多的文档片段数 TOP_K_DOCS 3这里把 API Key 放到环境变量里是工程习惯。代码仓库通常是不允许提交真实密钥的在本地实验时临时写在代码里可以但生产项目一律用环境变量或密钥管理服务。4.2 编写大模型客户端model_client.py负责封装大模型调用的细节业务层不直接接触 openai SDK。这样以后换了模型服务商只需要改一个文件。# 文件路径ai-search-demo/model_client.py from openai import OpenAI import config _client OpenAI( api_keyconfig.API_KEY, base_urlconfig.BASE_URL, ) def chat_completion(messages, temperature0.3): 调用大模型接口返回回答文本。 messages 的结构形如 [ {role: system, content: ...}, {role: user, content: ...}, {role: assistant, content: ...}, ] resp _client.chat.completions.create( modelconfig.MODEL_NAME, messagesmessages, temperaturetemperature, ) return resp.choices[0].message.contenttemperature 参数控制随机性。搜索问答类场景一般设置低一些比如 0.2 到 0.4让输出更加稳定、忠实于参考材料如果是创意写作场景可以设成 0.8 以上。4.3 编写知识检索器retriever.py负责在文档库里找和问题最相关的片段。生产环境一般用向量检索这里为了让你理解原理先实现一个最简单的关键词评分检索。# 文件路径ai-search-demo/retriever.py import os import re from collections import Counter class SimpleRetriever: def __init__(self, docs_dirdocs): self.docs [] self.load_docs(docs_dir) def load_docs(self, docs_dir): 把 docs 目录下的 txt 文件都读入内存按空行分割成片段。 for fname in os.listdir(docs_dir): if not fname.endswith(.txt): continue fpath os.path.join(docs_dir, fname) with open(fpath, r, encodingutf-8) as f: content f.read() # 按空行把长文本切片每片作为独立候选文档 blocks [b.strip() for b in re.split(r\n\s*\n, content) if b.strip()] self.docs.extend(blocks) staticmethod def score(query, doc): 计算 query 和 doc 的重合关键词得分。 query_tokens set(query.lower().split()) doc_tokens set(doc.lower().split()) overlap query_tokens doc_tokens return len(overlap) def search(self, query, top_k3): 返回和 query 最相关的 top_k 个片段。 scored [(self.score(query, doc), doc) for doc in self.docs] scored.sort(keylambda x: x[0], reverseTrue) results [doc for score, doc in scored[:top_k] if score 0] return results这个实现只是个基准。你可以发现它的问题只做简单分词不处理同义词没有语义理解。生产环境用向量检索就是为解决这些问题。不过用这个小工具跑通理解链路完全没有问题。4.4 编写对话逻辑chatbot.py是整个应用的核心。它负责组装 Prompt并决定什么时候把检索结果拼进去。这里有一个重要的设计判断并不是所有问题都需要检索。如果用户只是寒暄或明确要求总结检索反而会引入噪声。所以需要一个简单的路由逻辑。# 文件路径ai-search-demo/chatbot.py import model_client import config from retriever import SimpleRetriever retriever SimpleRetriever(docs_dirdocs) SYSTEM_PROMPT 你是知识助手。如果你获得了参考资料请优先基于资料回答并尽量复述资料中的关键信息。如果资料不足请直接说“根据现有资料无法回答”。回答要简洁不要编造事实。 def should_search(query: str) - bool: 简单判断问题以疑问词开头或包含知识类关键词时触发检索。 search_keywords [是什么, 怎么样, 如何, 为什么, 区别, 介绍, 原理] return any(k in query for k in search_keywords) def build_ranked_answer(query: str) - str: 检索资料 大模型生成回答。 docs retriever.search(query, top_kconfig.TOP_K_DOCS) if not docs: return model_client.chat_completion( [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: query}, ] ) context \n\n---\n\n.join(docs) user_content f用户提问{query}\n\n参考资料\n{context}\n\n请基于参考资料回答。 return model_client.chat_completion( [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_content}, ] ) def chat_once(history, query: str): 处理一轮对话返回新的问答内容和更新后的历史。 if should_search(query): answer build_ranked_answer(query) else: answer model_client.chat_completion( [ {role: system, content: SYSTEM_PROMPT}, *history[-config.MAX_HISTORY_LEN:], {role: user, content: query}, ] ) history.append({role: user, content: query}) history.append({role: assistant, content: answer}) # 控制历史长度避免超出上下文窗口 history history[-config.MAX_HISTORY_LEN:] return history, answer这个设计里should_search是一个很粗糙的路由逻辑。生产环境中你可以用一个更小的分类模型或者让主模型自己做判断。Demo 阶段规则也能勉强胜任。4.5 编写命令行入口main.py让你在终端直接体验对话效果# 文件路径ai-search-demo/main.py import chatbot history [] print(AI 知识问答助手已启动输入 exit 退出。) while True: query input(\n你) if query.lower() in (exit, quit): break history, answer chatbot.chat_once(history, query) print(f\nAI{answer})运行方式python main.py4.6 添加 FastAPI 接口为了让工具更像个“产品”再用 FastAPI 包一层 HTTP 接口这样前端、小程序、其他后端服务都能通过统一接口调用。# 文件路径ai-search-demo/app.py from fastapi import FastAPI from pydantic import BaseModel, Field import chatbot app FastAPI(titleAI Search Demo) class ChatRequest(BaseModel): query: str Field(..., min_length1, description用户问题) class ChatResponse(BaseModel): answer: str history: list app.post(/ask, response_modelChatResponse) def ask(req: ChatRequest): # 简化处理每次请求是独立会话不带持久化历史 history [] _, answer chatbot.chat_once(history, req.query) return ChatResponse(answeranswer, historyhistory)启动服务uvicorn app:app --reload --port 8000然后你可以直接把下面的 JSON 请求发到http://localhost:8000/askcurl -X POST http://localhost:8000/ask \ -H Content-Type: application/json \ -d {query: 什么是检索增强生成}返回格式大概如下{ answer: 检索增强生成RAG是一种让大模型在回答前先从外部知识库检索片段、并基于片段生成答案的技术方案。, history: [] }5. 常见问题与排查思路接入大模型 API 和构建检索问答系统时有几个高频问题这里整理成表格问题现象常见原因解决思路调用接口返回 401API Key 错误或未生效检查环境变量配置确认 Key 没有多余空格返回 404 或 model not found模型名称写错或当前账号无权限对照平台文档确认模型标识回答质量差答非所问Prompt 缺少约束或检索结果不相关强化 system 指令增加检索片段评分阈值回答编造事实模型没有资料支撑却强行作答在 Prompt 中强调“没有资料就拒绝回答”多轮对话后报超出上下文限制历史消息无限累积或文档太碎控制 history 长度按 token 数裁剪检索不到相关内容文档分词过于粗糙、中英文混合换向量检索或对文档做更细致切分响应速度慢单次调用链路过长、模型参数量大启用流式输出使用降级模型并行检索5.1 接口返回 401 的排查流程这个错误最常见。先确认你用的 Key 是否正确再看是不是在环境变量中引号导致空格。检查方式# 先在终端打印确认没有多余隐藏字符 python -c import os; print(repr(os.getenv(LLM_API_KEY,)))如果输出里能找到空格或换行就是配置文件的引号问题。另外有些平台的 Key 开通后需要几分钟生效遇到 401 时可以稍等片刻再试。5.2 答非所问的调试思路答非所问通常不是模型笨而是 Prompt 没有束缚住。第一个调试方向是修改 system 指令加入“你必须基于参考资料回答资料里没有要明确说明”。第二个方向是检查检索器返回的片段是否与问题相关。你可以单独把检索结果打印出来看看from retriever import SimpleRetriever r SimpleRetriever() print(r.search(什么是RAG, top_k3))如果检索结果本身不相关后面模型再聪明也没用问题在检索模块而不在生成模块。5.3 成本与超时优化真实项目中大模型 API 是按 token 计费的长时间多轮对话消耗非常快。常见优化手段对历史消息做摘要压缩而不是全部保留。检索片段做去重和截断只保留核心信息。简单问题直接走轻量模型复杂问题才走强模型。启用流式返回减少用户等待焦虑。6. 最佳实践与工程建议前面代码能跑通但离生产可用的 AI 搜索产品还有一段距离。这一节我们重点讨论工程化改造方向。6.1 用向量检索替代关键词检索简单的关键词匹配无法解决同义改写问题。用户问“大模型是什么”你可能检索不到“LLM”相关资料。生产环境建议使用向量数据库基本做法如下对文档片段做嵌入Embedding得到向量并存入向量库。用户提问时也对问题做嵌入。用余弦相似度检索 Top K 向量取对应原文片段。把片段拼入 Prompt。嵌入模型可以选择开源模型或厂商 API。这一步改造后检索质量会有质的提升。6.2 Prompt 要像代码一样被管理很多团队把 Prompt 散落在业务代码中改了一个 Prompt 还要翻代码仓库。更好的做法是把 Prompt 模板抽取到独立配置文件或 Prompt 管理平台。为不同场景建立不同 Prompt比如知识问答、闲聊、内容总结。对 Prompt 版本做记录方便 AB 测试和回滚。6.3 做好安全与内容合规这是所有 AI 产品都必须重视的一环。特别是面向 C 端用户时大模型输出的内容必须要过一道安全过滤输入侧识别恶意请求、试图让模型突破系统指令的行为。输出侧检测色情、暴力、违法信息、个人隐私泄露等风险。合规侧按照所在地区的法律法规对深度合成内容进行标识。即使是最小 Demo也应该在代码层级保留过滤入口。安全意识比安全工具更重要在生成内容已经接触用户之后再补救成本和风险都更高。6.4 记录日志为线上问题留证据AI 应用调试难度远高于普通后端应用因为同一个问题模型可能给出不同答案。必要日志至少包括用户输入原文。Prompt 最终拼装结果。检索命中的文档片段 ID。模型输出原文。温度和模型版本标识。调用耗时和 token 消耗。日志尽量结构化方便后续做数据分析和问题追踪。6.5 模型选型要分场景不要所有需求都用同一个模型。按任务难度做分级简单分类、抽取关键词可以用小模型成本低、响应快。知识问答中等模型即可配合检索增强。复杂推理、长文撰写用强模型但必须控制 Prompt 长度。模型选型是架构问题不是越贵越好。把合适的任务交给合适的模型性能和成本都能得到优化。7. 总结把 AI 当基础设施来用回到开头那个话题。国内 AI 产品更像一种效率工具贴身解决用户眼前的搜索与信息整合需求欧美 AI 产品则更强调风险治理和控制边界。两种路线各有取舍并不存在谁“全盘领先”。对开发者来说最大的收益是同时借鉴两条路线的经验用国内产品的高效落地思路快速做产品同时把欧美团队常用的安全护栏、合规审计思路内置到系统里。这样你的 AI 应用既跑得快又不会失控。本文的实战部分已经帮你搭建了一个最小可用的 AI 知识问答助手。建议你上手去换一批自己的文档比如把团队 Wiki、工作笔记放进去感受一下“检索增强 大模型”和“裸调大模型”之间的差别。你的代码可以跑通之后下一步再去研究向量检索、流式输出、安全过滤和性能优化这些都是 AI 应用开发里真实存在的高价值技能。如果这篇文章对你有帮助欢迎收藏备用也欢迎在评论区聊聊你工作中遇到的 AI 应用问题。