基于RAG与工具调用的AI应用“开卷考”架构:解决幻觉,提升准确性

基于RAG与工具调用的AI应用“开卷考”架构:解决幻觉,提升准确性

这次我们来看一个解决 AI 幻觉问题的思路——“开卷考”。AI 幻觉,简单说就是大模型一本正经地胡说八道,生成看似合理但实际错误或虚构的信息。这在金融、医疗、法律等对准确性要求极高的领域是致命的。与其让模型在“闭卷”状态下凭空编造,不如让它学会“开卷”,即通过检索外部知识库或调用可信数据接口来获取答案。这不仅是当前智能体(Agent)和 MCP(Model Context Protocol)等框架的核心设计理念,也是提升 AI 应用可靠性的关键路径。

本文的核心是探讨如何通过“开卷考”机制,为你的 AI 应用,无论是智能体、聊天机器人还是自动化工具,注入事实核查和知识引用的能力。我们将重点关注其实现原理、技术门槛、以及如何通过数据接口(如金融、股票接口)和 MCP 协议来构建一个可验证、可追溯的 AI 系统。无论你是想搭建一个能准确回答财经问题的智能体,还是希望你的本地模型在生成内容时能自动引用来源,这篇文章都将提供一套清晰的落地思路。

1. 核心能力速览

“开卷考”不是一个具体的软件包,而是一种架构模式和实现方案。其核心在于将大语言模型的生成能力与外部可信数据源相结合。

能力项说明
核心目标解决 AI 幻觉,提升生成内容的准确性和可信度。
实现原理检索增强生成(RAG)+工具调用(Function Calling)。模型在回答前,先检索知识库或调用 API 获取实时/准确数据,再基于这些信息生成回答。
关键技术栈大语言模型(本地/云端)、向量数据库、MCP 协议、各类数据 API(如金融、新闻、百科)。
硬件门槛灵活。纯 API 调用对本地硬件无要求;若涉及本地模型嵌入和检索,则需要 GPU/CPU 和内存支持,具体取决于模型大小。
启动与集成通常以代码库、框架插件或智能体平台(如 Dify, Coze)功能模块的形式提供,需要集成到现有应用中。
是否支持 API是。核心就是通过 API 调用来获取外部数据。
是否支持批量任务是。可以构建流水线,对批量查询进行“检索-生成”处理。
适合场景问答系统、报告生成、数据分析、智能客服、任何需要事实准确性的 AI 应用场景。

2. 适用场景与使用边界

“开卷考”机制并非万能,理解其适用边界能更好地发挥其价值。

它最适合谁?

  • 领域知识开发者:需要构建金融、法律、医疗等专业领域 AI 应用的开发者。
  • 智能体(Agent)搭建者:希望智能体能主动查询天气、股价、新闻等实时信息并据此行动。
  • 企业知识库管理者:希望将内部文档、手册作为 AI 回答的依据,避免模型胡编乱造公司政策。
  • 所有关心 AI 输出可靠性的用户:即使是普通聊天,引用来源也能大幅提升可信度。

它能解决什么问题?

  1. 事实性错误:让 AI 基于检索到的文档、数据回答问题,而非依赖内部参数化记忆。
  2. 信息过时:通过接入实时 API(如股票接口、新闻接口),获取最新信息。
  3. 领域深度不足:用专业的领域知识库(如医学文献、法律条文)增强通用模型的专业能力。
  4. 可解释性与溯源:生成的答案可以附带引用来源,方便用户核查。

它不适合什么场景?

  • 创意写作、诗歌生成:这类任务本身不需要严格的事实依据,过度约束反而会限制创造性。
  • 极度低延迟的简单对话:检索步骤会增加响应时间,对于“你好”这类问候,直接生成更高效。
  • 完全封闭、无外部数据源的环境:巧妇难为无米之炊。

合规与安全边界

  • 数据授权:确保接入的 API 或使用的知识库数据拥有合法授权,遵守相关服务条款。
  • 隐私保护:如果知识库包含用户隐私或敏感信息,需做好数据脱敏和访问控制。
  • 内容审核:即使引用了来源,模型生成的内容仍需进行合规性审核,避免产生有害信息。

3. 环境准备与前置条件

实施“开卷考”方案,你需要准备以下几个层面的环境。

1. 基础开发环境

  • 操作系统:Windows / macOS / Linux 均可,推荐 Linux 用于生产环境。
  • Python:3.8 及以上版本,这是大多数 AI 框架和库的基础。
  • 包管理工具pipconda

2. 核心组件选择(根据方案二选一或组合)

  • 方案A:云端模型 + 向量检索(经典 RAG)
    • LLM 服务:OpenAI API、通义千问 API、DeepSeek API 等。或本地部署的模型服务(如 Ollama, vLLM)。
    • 嵌入模型:用于将文本转换为向量,例如text-embedding-ada-002,bge-large-zh。可以是云端 API 或本地模型。
    • 向量数据库:用于存储和检索向量,例如Chroma,Milvus,Qdrant,Weaviate。通常可本地部署或使用云服务。
  • 方案B:智能体框架 + 工具调用(MCP/Function Calling)
    • 智能体框架:LangChain, LlamaIndex, Dify, Coze 等。
    • 工具协议:MCP (Model Context Protocol) 服务器,或自定义 Function Calling 工具。
    • 数据接口:你需要接入的具体 API,如新浪财经股票接口、Wind 金融数据接口等。

3. 硬件资源评估

  • 纯 API 调用:只需网络和基础运行环境,无特殊硬件要求。
  • 本地嵌入模型+向量库:需要一定内存和 CPU 算力。例如,运行bge-base嵌入模型,可能需要 1-2GB 内存。
  • 本地 LLM + 全套流程:需要满足所选本地大模型的硬件要求(如 GPU 显存)。这通常不是“开卷考”的瓶颈,因为检索步骤可以独立于大模型运行。

4. 安装部署与启动方式

我们以一个典型的“本地知识库问答”场景为例,演示如何搭建一个最简单的 RAG 系统。这里使用LangChainChroma(向量数据库)和Ollama(本地运行大模型)的组合。

步骤1:安装基础库

# 创建虚拟环境(可选) python -m venv rag_env source rag_env/bin/activate # Linux/macOS # rag_env\Scripts\activate # Windows # 安装核心库 pip install langchain langchain-community chromadb pypdf sentence-transformers # 安装 Ollama 的 LangChain 集成 pip install langchain-ollama

步骤2:准备知识库文档将你的 PDF、TXT、Word 等文档放入一个目录,例如./knowledge_docs/

步骤3:编写核心应用脚本创建一个名为rag_demo.py的文件:

import os from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma from langchain_ollama import OllamaLLM from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate # 1. 加载文档 documents = [] loader = DirectoryLoader('./knowledge_docs/', glob="**/*.pdf", loader_cls=PyPDFLoader) documents.extend(loader.load()) # 可以添加其他格式的 loader,如 TextLoader # 2. 分割文本 text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) texts = text_splitter.split_documents(documents) # 3. 创建嵌入模型和向量库 # 使用本地嵌入模型,无需GPU也能运行 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5") # 持久化向量数据库到本地目录 vectorstore = Chroma.from_documents(documents=texts, embedding=embeddings, persist_directory="./chroma_db") vectorstore.persist() # 4. 连接本地大模型(确保 Ollama 服务已启动并拉取了模型,如 llama3.2) llm = OllamaLLM(model="llama3.2", base_url="http://localhost:11434") # 5. 构建检索问答链 prompt_template = """ 请根据以下上下文信息回答问题。如果上下文信息不足以回答问题,请直接说“根据提供的信息无法回答”,不要编造信息。 上下文: {context} 问题:{question} 请给出准确、基于上下文的回答: """ PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"]) qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever(search_kwargs={"k": 3}), # 检索最相关的3个片段 chain_type_kwargs={"prompt": PROMPT}, return_source_documents=True # 返回来源文档 ) # 6. 提问 query = "什么是AI幻觉?" result = qa_chain.invoke({"query": query}) print("问题:", query) print("回答:", result["result"]) print("\n--- 引用来源 ---") for i, doc in enumerate(result["source_documents"]): print(f"[{i+1}] {doc.page_content[:200]}...") # 打印片段前200字符

步骤4:启动 Ollama 服务并拉取模型

# 首先,确保安装了 Ollama (https://ollama.com/) # 拉取一个模型,例如 llama3.2 ollama pull llama3.2 # 启动 Ollama 服务(通常拉取后会自动运行)

步骤5:运行脚本

python rag_demo.py

首次运行会花费较长时间创建向量数据库。之后再次运行,可以修改代码直接加载已有的向量库,而无需重复处理文档。

5. 功能测试与效果验证

搭建好基础系统后,我们需要从多个维度验证其“开卷”能力是否有效。

5.1 基础事实问答测试

测试目的:验证系统能否从提供的知识库中准确找到答案,而非依赖模型本身的记忆(可能错误)。

  • 输入:知识库中明确记载的问题。例如,如果你的知识库是一份产品手册,可以问“产品A的最大支持用户数是多少?”
  • 操作:运行上述脚本,传入问题。
  • 预期结果:答案应精确匹配手册中的数字,并在“引用来源”中显示包含该数字的原文片段。
  • 成功标准:答案正确,且来源可追溯。
  • 失败排查
    1. 检查文档是否被正确加载和分割(查看texts变量)。
    2. 检查向量检索是否返回了相关片段(查看result[“source_documents”])。
    3. 检查提示词(Prompt)是否明确要求模型基于上下文回答。

5.2 “闭卷”幻觉对比测试

测试目的:直观展示“开卷”与“闭卷”的差异。

  • 操作A(开卷):使用上面的 RAG 系统提问一个知识库中不存在的信息,例如“根据文档,我们公司明年计划收购哪家公司?”
  • 操作B(闭卷):直接向同一个大模型(Ollama)提问同样的问题,不提供任何上下文。
  • 预期结果
    • 开卷系统:应回答“根据提供的信息无法回答”或类似内容。
    • 纯模型:可能会编造一个看似合理的公司名称和收购细节(幻觉)。
  • 成功标准:开卷系统表现出对未知信息的克制,而纯模型产生了幻觉。

5.3 实时数据接口集成测试(MCP/工具调用示例)

测试目的:验证系统能否调用外部 API 获取实时信息。 这里以模拟一个查询天气的工具为例,展示如何通过 LangChain 的ToolAgent实现。

from langchain.agents import initialize_agent, AgentType from langchain.tools import Tool from langchain_ollama import OllamaLLM import requests # 1. 定义一个获取天气的工具函数 def get_weather(city: str) -> str: """通过模拟API获取城市天气。实际应替换为真实API调用。""" # 模拟API响应 weather_data = { "北京": "晴,15-25°C", "上海": "多云,18-28°C", "深圳": "阵雨,22-30°C" } return weather_data.get(city, f"未找到{city}的天气信息") # 2. 将函数封装成 LangChain Tool weather_tool = Tool( name="GetWeather", func=get_weather, description="根据城市名称查询当前天气。输入应为城市名,如‘北京’。" ) # 3. 初始化LLM和Agent llm = OllamaLLM(model="llama3.2", base_url="http://localhost:11434") tools = [weather_tool] agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True) # 4. 提问一个需要实时信息的问题 result = agent.run("今天深圳的天气怎么样?适合穿短袖吗?") print(result)
  • 预期结果:Agent 应识别出需要调用GetWeather工具,获取“深圳”的天气信息(阵雨,22-30°C),然后结合此信息判断是否适合穿短袖。
  • 成功标准:最终答案包含了从工具获取的真实数据,并且推理合理。

6. 接口 API 与批量任务

“开卷考”系统本身可以作为 API 服务提供,也天然支持批量处理。

6.1 构建 FastAPI 服务

将上面的 RAG 问答链封装成 Web API,方便其他系统调用。

# file: rag_api.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_ollama import OllamaLLM from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate app = FastAPI() # 初始化组件(启动时加载一次) embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5") vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings) llm = OllamaLLM(model="llama3.2") prompt_template = """...""" # 同前的提示词 PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"]) qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=vectorstore.as_retriever(), chain_type_kwargs={"prompt": PROMPT}) class QueryRequest(BaseModel): question: str top_k: int = 3 class QueryResponse(BaseModel): answer: str sources: list[str] @app.post("/query", response_model=QueryResponse) async def query_knowledge_base(req: QueryRequest): try: result = qa_chain.invoke({"query": req.question}) sources = [doc.page_content[:500] for doc in result.get("source_documents", [])] return QueryResponse(answer=result["result"], sources=sources) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)

启动服务:python rag_api.py。即可通过http://localhost:8000/query进行 POST 查询。

6.2 批量任务处理

对于需要处理大量问题的场景,可以构建批处理脚本。

# file: batch_process.py import requests import json import time api_url = "http://localhost:8000/query" questions = ["问题1", "问题2", "问题3", "..."] # 从文件读取 results = [] for q in questions: try: resp = requests.post(api_url, json={"question": q}, timeout=30) if resp.status_code == 200: results.append(resp.json()) else: results.append({"question": q, "error": resp.text}) except Exception as e: results.append({"question": q, "error": str(e)}) time.sleep(0.5) # 避免请求过快 # 保存结果 with open("batch_results.json", "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2) print(f"批量处理完成,共处理 {len(questions)} 个问题。")

6.3 集成真实数据接口(示例:模拟金融数据)

以接入一个模拟的股票查询接口为例,展示如何为智能体增加“开卷”能力。

# 扩展之前的工具列表 import yfinance as yf # 示例库,需安装: pip install yfinance def get_stock_price(symbol: str) -> str: """获取股票最新价格。""" try: stock = yf.Ticker(symbol) hist = stock.history(period="1d") if hist.empty: return f"未找到股票代码 {symbol} 的数据。" latest_price = hist['Close'].iloc[-1] return f"{symbol} 的最新收盘价为 {latest_price:.2f} 美元。" except Exception as e: return f"查询股票{symbol}时出错:{e}" stock_tool = Tool( name="GetStockPrice", func=get_stock_price, description="根据股票代码(如AAPL, 0700.HK)查询最新收盘价。" ) # 将新工具加入Agent tools = [weather_tool, stock_tool] agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True) print(agent.run("苹果公司(AAPL)和腾讯(0700.HK)的股价现在是多少?哪个更高?"))

这个 Agent 会自主决定调用两次GetStockPrice工具,获取数据后进行对比分析。

7. 资源占用与性能观察

“开卷考”系统的性能开销主要来自两部分:检索生成

1. 检索阶段(向量数据库查询)

  • CPU/内存:向量相似度计算是计算密集型操作。对于千万级以下的向量,在普通 CPU 上也能在毫秒到百毫秒内完成。内存占用主要取决于加载的向量索引大小。
  • 优化建议
    • 使用HNSW等近似最近邻算法,在精度和速度间取得平衡。
    • 控制文本分块(Chunk)的大小和重叠度,过小的块会增加检索次数,过大的块会降低精度。
    • 将向量数据库部署在内存或高速 SSD 上。

2. 生成阶段(大模型推理)

  • 资源消耗:这是主要瓶颈。取决于所选大模型。
    • 本地模型:消耗 GPU 显存或 CPU 内存。7B 参数模型在 4-bit 量化下可能需要 4-6GB 显存。
    • 云端 API:无本地资源消耗,但依赖网络且产生费用。
  • 延迟:检索 + 生成的总时间。RAG 的提示词因为包含检索到的上下文,通常会比纯对话更长,因此生成时间也可能略长。
  • 观察方法
    • 本地模型:使用nvidia-smi(GPU) 或任务管理器观察显存/内存占用。
    • 延迟监控:在代码中记录每个环节(检索、生成)的耗时。

3. 整体性能调优思路

  • 缓存:对常见问题及其答案进行缓存,避免重复检索和生成。
  • 异步处理:对于批量任务,使用异步请求来提高吞吐量。
  • 分级检索:先使用简单的关键词匹配进行粗筛,再用向量检索进行精排。
  • 精简上下文:只将最相关的文本片段送入大模型,避免提示词过长。

8. 常见问题与排查方法

在构建和运行“开卷考”系统时,你可能会遇到以下典型问题。

问题现象可能原因排查方式解决方案
向量数据库检索不到相关内容1. 文档未正确加载或分割。
2. 嵌入模型不匹配或质量差。
3. 检索参数k设置过小。
1. 检查texts变量,确认文档内容已分割。
2. 尝试用不同嵌入模型。
3. 检查检索到的source_documents内容。
1. 确保文档格式被支持,调整分割参数(chunk_size,chunk_overlap)。
2. 换用更强大的嵌入模型(如bge-large)。
3. 增大k值,或使用MMR等多样性检索方法。
模型回答依然出现幻觉,无视上下文1. 提示词(Prompt)未强制要求基于上下文。
2. 上下文相关性太低,模型“看不到”答案。
3. 模型本身能力或微调问题。
1. 检查传递给模型的最终提示词,是否包含了检索到的上下文。
2. 查看模型接收到的完整输入。
1. 强化提示词,例如:“必须严格依据以下上下文...”。
2. 提升检索质量,确保返回的片段包含答案。
3. 尝试指令跟随能力更强的模型。
接入外部 API 失败或返回错误1. 网络问题。
2. API 密钥无效或配额用尽。
3. 请求参数格式错误。
4. API 服务方限制。
1. 使用curlrequests单独测试 API。
2. 查看 API 返回的错误码和消息。
1. 检查网络连接和代理设置。
2. 复核 API 密钥和请求参数。
3. 在代码中添加重试机制和错误处理。
系统响应速度慢1. 嵌入模型推理慢(本地)。
2. 向量数据库查询慢。
3. 大模型生成慢。
4. 网络延迟(云端 API)。
1. 分阶段计时,定位瓶颈。
2. 监控系统资源(CPU/GPU/内存)。
1. 使用量化后的嵌入模型。
2. 优化向量数据库索引。
3. 对大模型进行量化或使用更小模型。
4. 考虑使用 CDN 或更近的 API 端点。
智能体不调用工具1. 工具描述(description)不清晰,模型无法理解何时调用。
2. 模型(Agent)类型选择不当。
3. 提示词未激发工具使用。
1. 查看 Agent 的思考过程(verbose=True)。
2. 测试一个明确需要工具的问题。
1. 优化工具描述,明确输入输出格式和适用场景。
2. 尝试ReActOpenAI Functions类型的 Agent。
3. 在系统提示词中鼓励模型使用工具。

9. 最佳实践与使用建议

要让“开卷考”系统稳定、可靠地运行,遵循以下实践至关重要。

1. 数据源质量优先

  • 准确性:确保知识库文档和接入的 API 数据源本身是准确、权威的。垃圾进,垃圾出。
  • 时效性:建立数据更新机制。过时的知识库同样会导致“幻觉”(输出过时信息)。
  • 结构化:尽量使用结构化或半结构化数据(如 Markdown、JSON),便于解析和检索。

2. 提示词工程是关键

  • 明确指令:在提示词中清晰、强硬地要求模型“基于给定上下文回答”。
  • 提供格式示例:对于需要特定格式(如列表、表格)的回答,在上下文中提供例子。
  • 设置拒绝回答的边界:明确告知模型,当上下文不足时,应回答“不知道”。

3. 实施多层验证

  • 答案一致性检查:对于关键问题,可以用不同检索参数或模型多次生成答案,进行交叉验证。
  • 来源可信度评估:如果可能,对检索到的文档来源进行可信度打分,优先使用高可信度来源。
  • 人工审核流水线:在正式发布前,对系统输出进行抽样人工审核。

4. 工程化与监控

  • 日志记录:详细记录每次请求的查询、检索到的文档、生成的答案、耗时和模型用量。
  • 性能监控:监控 API 响应时间、错误率、Token 消耗等指标。
  • 版本控制:对知识库、模型版本、提示词模板进行版本管理,便于回滚和对比实验。

5. 合规与伦理

  • 数据版权:仅使用你有权使用的数据和文档。
  • 用户告知:当 AI 的回答基于特定数据源时,应向用户明确说明。
  • 避免滥用:防止系统被用于生成虚假信息或进行欺诈。设置内容安全过滤器。

10. 总结与下一步

“解决 AI 幻觉,开卷考是当前最务实、最有效的工程化方案。” 它不追求创造一个全知全能的模型,而是通过架构设计,让模型学会“查阅资料”和“使用工具”。本文从概念到实践,演示了如何通过 RAG 和智能体工具调用来构建这样一个系统。

最值得尝试的第一步:选择一个你熟悉的领域(比如你的个人笔记、产品文档),用 LangChain + Chroma + 本地模型(如 Ollama)搭建一个最小可用的知识库问答系统。亲自体验从“幻觉频出”到“有据可查”的转变。

最容易踩的坑

  1. 提示词太弱:模型会忽略上下文。务必强化指令。
  2. 检索质量差:分块不合理或嵌入模型不合适,导致找不到答案。多调试检索部分。
  3. 工具描述模糊:智能体无法理解何时该调用工具。把工具描述写得像给新手看的说明书。

后续扩展方向

  • 多模态“开卷”:不仅处理文本,还能检索图片、表格中的信息来回答问题。
  • 复杂推理链:让模型进行多步检索和推理,解决更复杂的问题。
  • 自我修正:让模型对初步答案进行事实核查,调用搜索工具验证自己的回答。
  • 与 MCP 生态集成:探索将你的数据源或工具封装成标准的 MCP 服务器,使其能够被 Claude Desktop、Cursor 等更多智能体平台直接调用。

将 AI 从“天才的臆想者”转变为“严谨的研究助理”,开卷考是必经之路。这套方法论和工具链已经相当成熟,投入实践的门槛并不高,但其对应用可靠性的提升是立竿见影的。建议收藏本文,在构建下一个需要准确性的 AI 功能时,随时回来参考。