使用 IBM Granite 和 Tavily 构建纠正性 RAG 智能体

作者

Jobit Varughese

Technical Content Writer

IBM

大型语言模型 (LLM) 功能非常强大,但其知识仅限于训练 数据集。在回答问题时,尤其是关于特定、不断发展或专有信息的问题时,LLMLLM 可能会产生幻觉或提供笼统、不相关的答案。 检索增强生成 (RAG) 通过向 LLM 提供来自外部数据源的相关检索信息来提供帮助。

然而,并非所有的 RAG 都效果相同。纠正性检索增强生成 (cRAG) 并不是简单地构建在传统 RAG 之上,而是代表了一种重大改进。它旨在通过评估检索结果的质量和相关性来变得更加稳健。如果上下文薄弱、不相关或来源不可信,cRAG 会尝试通过纠正措施来找到更好的信息,或明确拒绝回答,而不是编造响应。这种技术使得 cRAG 系统在回答政策相关问题等关键应用中更加可靠和值得信赖。

图表相关文档

在本教程中,您将学习如何利用 Watsonx 上的 IBM Granite 模型和 LangChain 构建一个稳健的纠正性 RAG 系统。类似的框架如 LlamaIndex 或 LangGraph 也可用于构建具有不同节点的复杂 RAG 流程。 微调 等技术可以进一步提高特定领域 RAG 的 LLM 性能。虽然 OpenAI 的 LLM( GPT 模型,如 ChatGPT)也是此类智能体的热门选择,不过本教程重点介绍 IBM Granite。

在这里,我们将关注一个用例:回答关于特定保单文档 (PDF) 的问题。本教程将指导您实现一个复杂的 RAG 算法,该算法能够:

  • 从您自己的 PDF 文档中检索信息 。

  • 如果内部文档不足以生成答案,智能体可以使用外部网络搜索 (Tavily) 作为后备。

  • 智能体智能地过滤掉不相关的外部结果,使答案针对私有保单。

  • 当有部分信息可用时,智能体会给出清晰、有限制的回答;当上下文缺失时,则会明确拒绝。

用例:构建可靠的保单查询智能体

本教程演示了如何构建一个保单查询智能体,该智能体旨在分析保单文档(PDF 手册)并准确回答用户查询。我们使用 IBM Granite 模型和 LangChain 来构建该智能体,并通过稳健的检索和验证步骤确保高质量、受限于信息来源的答案。

让我们了解可靠的 RAG 的关键原则如何应用于我们的用例。

关键原则应用

内部知识库 (PDF):智能体的核心知识来源是用户提供的保险单 PDF 文档。它将此文档转换为可搜索的向量存储。

外部搜索回退 (Tavily):如果内部知识库信息不足,智能体可以通过 Tavily 咨询外部网络资源。Tavily 是专为 AI 智能体和 LLM 构建的搜索引擎,通过其应用程序编程接口 (API) 为基于 RAG 的应用程序实现更快、实时的检索。

上下文评分: 基于 LLM 的检索评估器(充当评分器)将为从内部 PDF 检索到的项目相关性提供分数,同时确保只包含高质量的检索项。

查询重写: 对于网络搜索,智能体可以重新表述用户的查询,以提高找到相关外部信息的几率。

来源验证:LLM 支持的检查会评估外部网络搜索结果是否与私有保单相关,过滤掉一般信息或关于公共健康计划(如 Medi-Cal)的结果。此功能可防止产生误导性答案,并实现自我纠正,有助于知识精炼。

受限生成: 给 LLM 的最终提示严格指示其仅使用提供的上下文,提供精确答案,说明信息不可用的情况,或在有明确限制的情况下提供部分答案。此功能增强了生成响应的适应性和可靠性。

先决条件

您需要一个 IBM® Cloud 帐户 才能创建 watsonx.ai 项目。确保您有权访问 watsonx API 密钥和项目 ID。您还需要一个 Tavily AI 的 API 密钥以获得网络搜索能力。

步骤

第 1 步:设置环境

虽然您可以选择多种工具,本教程将引导您如何设置 IBM 帐户以使用 Jupyter Notebook。

  1. 使用您的 IBM Cloud 帐户登录 watsonx.ai
  2. 创建一个 watsonx.ai 项目。 您可以从项目内部获取项目 ID。点击管理选项卡。然后,从常规页面的详细信息部分复制项目 ID。您需要此 ID 来完成本教程。
  3. 创建一个 Jupyter Notebook

此步骤将打开一个笔记本环境,可在其中复制本教程中的代码。或者,您可以将此笔记本下载到本地系统并将其作为资产上传到您的 watsonx.ai 项目。要查看更多 Granite 教程,请访问  IBM Granite 社区 。本教程也可在 Github 上找到。

第 2 步:设置 watsonx.ai 运行时服务和 API 密钥

  1. 创建 watsonx.ai 运行时服务实例(选择 Lite 计划,这是一个免费实例)。
  2. 生成应用程序编程接口 (API) 密钥
  3. 将 watsonx.ai 运行时服务与您在 watsonx.ai 中创建的项目关联。 

第 3 步:安装软件包

为了使用 LangChain 框架并集成 IBM WatsonxLLM,我们需要安装一些基础库。我们先来安装所需的软件包。这套工具包括用于 RAG 框架的 langchain 、用于 watsonx 集成的 langchain-ibm 、用于高效向量存储的 faiss-cpu 、用于处理 PDF 的 PyPDF2 、用于获取 嵌入 表示的sentence-transformers以及用于 Web API 调用的 requests 。这些库对于应用 机器学习 和 NLP 解决方案至关重要。

# Install Libraries
!pip install langchain langchain-ibm faiss-cpu PyPDF2 sentence-transformers requests

注意: 虽然无需 GPU 即可运行,但在纯 CPU 系统上执行速度可能较慢。此步骤将开启一个 Notebook 编程环境,您可在此直接复制并运行教程中的示例代码。本教程也可在 GitHub 上找到。

第 4 步:导入所需库

接下来,请导入所有必需的依赖模块,并安全地配置您的 watsonx 与 Tavily API 密钥以及 watsonx 项目 ID。

# Import required libraries

import os
import io
import getpass
from PyPDF2 import PdfReader
from langchain_ibm import WatsonxLLM
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document
import requests
from botocore.client import Config
import ibm_boto3
from langchain.prompts import PromptTemplate
from langchain.tools import BaseTool

# Watsonx
WML_URL = "https://us-south.ml.cloud.ibm.com"
WML_API_KEY = getpass.getpass(" Enter Watsonx API Key: ")
PROJECT_ID = input(" Enter Watsonx Project ID: ")

# Tavily
TAVILY_API_KEY = getpass.getpass(" Enter Tavily API Key: ")

print(" Credentials loaded.")

os 模块提供操作系统交互功能。

io 模块支持数据流处理操作。

getpass 模块通过安全方式采集 API 密钥等敏感信息,且不会在屏幕显示输入内容。

PyPDF2.PdfReader 组件实现 PDF 文档内容解析与提取。

langchain_ibm.WatsonxLLM 封装器支持在 LangChain 框架内便捷调用 IBM Watsonx Granite LLM。

langchain.embeddings.HuggingFaceEmbeddings 加载预训练的 HuggingFace 模型,以生成语义搜索所需的文本嵌入向量。

langchain.vectorstores.FAISS 提供高效的向量存储与相似度检索能力,用于构建可语义查询的向量索引。

langchain.text_splitter.RecursiveCharacterTextSplitter 可帮助将大段文本拆分为较小的分块,以处理无法放入内存的文档。

langchain.schema.Document 作为 LangChain 基础构件,表征附带元数据的文本单元。

requests 用于向外部 API 发起 HTTP 请求。

botocore.client.Config 是一个配置类,用于定义 AWS/IBM Cloud Object Storage 客户端的配置参数。

ibm_boto3 作为 IBM Cloud Object Storage 的 Python SDK,支持与云存储服务交互。

langchain.prompts.PromptTemplate 提供了一种为语言模型创建可重复使用的结构化提示的方法。

langchain.tools.BaseTool 作为基础工具类,支持构建可集成至 LangChain 智能体的自定义工具模块。

此步骤设置了我们处理文本、创建嵌入、将其存储在矢量数据库中并与 IBM watsonx LLM 交互所需的所有工具和模块。它建立了创建实际 RAG 系统所需的所有部件,能够获取、查询和搜索一系列数据类型。

第 5 步:从 IBM Cloud Object Storage 加载和处理 PDF

在此步骤中,我们将从 IBM Cloud Object Storage 加载保单 PDF。代码会读取 PDF、读取文本内容并将文本拆分为较小且可管理的分块。这些分块随后被转换为数值化嵌入向量,并存储于 FAISS 向量数据库中,为后续基于本地上下文的语义相似度检索优化查询结果。

import os, types
import pandas as pd
from botocore.client import Config
import ibm_boto3

def __iter__(self): return 0

cos_client = ibm_boto3.client(service_name='s3',
ibm_api_key_id='YOUR_IBM_API_KEY',
ibm_auth_endpoint="https://iam.cloud.ibm.com/identity/token",
config=Config(signature_version='oauth'),
endpoint_url='https://s3.direct.us-south.cloud-object-storage.appdomain.cloud')

bucket = 'YOUR_BUCKET_NAME'
object_key = 'YOUR_OBJECT_KEY'

streaming_body_2 = cos_client.get_object(Bucket=bucket, Key=object_key)['Body']
pdf_bytes = io.BytesIO(streaming_body_2.read())

reader = PdfReader(pdf_bytes)
text = ""
for page in reader.pages:
extracted = page.extract_text()
if extracted:
text += extracted

print(f" Extracted {len(text)} characters from PDF.")
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_text(text)
print(f" Split into {len(chunks)} chunks.")

embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vectorstore = FAISS.from_texts(chunks, embeddings)

print(f" Created FAISS index.")

ibm_boto3.client 支持客户端与 IBM Cloud Object Storage 交互。

Bucket 是包含 PDF 的云对象存储存储桶的名称。

object_key 是云存储桶中 PDF 的名称。

cos_client.get_object(...).read() 从云对象存储中以字节流形式获取 PDF 内容。

io.BytesIO 将 PDF 原始字节转换为 PdfReader 可解析的内存二进制流格式。

PdfReader 创建一个可以从 PDF 中解析和提取文本的对象。

page.extract_text() 提取 PDF 中单个页面的文本。

RecursiveCharacterTextSplitter 被配置为将提取的文本拆分为 500 个字符的分块,重叠 50 个字符,从而将所有内容保留在上下文中。

splitter.split_text(text) 将 PDF 文本的所有页面分割成小分块。

HuggingFaceEmbeddings 可加载预训练的句子转换器模型,将文本块编码为稠密向量表示。

FAISS.from_texts(chunks, embeddings) 构建内存式 FAISS 索引,实现基于语义相似度的文本分块检索。

本步骤完整实现了从云端 PDF 文档到 LLM 就绪文本的摄取流程,并建立实时检索所需的优化索引结构。

第 6 步:初始化 LLM 和工具

在此步骤中,您将配置驱动智能体推理的 IBM Granite LLM,并将其与 Tavily 网络搜索功能集成。通过设置 LLM 参数,确保生成事实准确、稳定性高的响应结果。

llm = WatsonxLLM(
model_id="ibm/granite-3-2b-instruct",
url=WML_URL,
apikey=WML_API_KEY,
project_id=PROJECT_ID,
params={
"max_new_tokens": 300, # ~2-3 paragraphs, good for corrective RAG
"temperature": 0.2, # low temperature = more factual, stable answers
}
)

print(" Watsonx Granite LLM ready.")
class TavilySearch(BaseTool):
name: str = "tavily_search"
description: str = "Search the web using Tavily for extra info."

def _run(self, query: str):
response = requests.post(
"https://api.tavily.com/search",
json={"api_key": TAVILY_API_KEY, "query": query}
)
response.raise_for_status()
return response.json()['results'][0]['content']


tavily_tool = TavilySearch()

WatsonxLLM 实例化 IBM watsonx 的 LLM 封装器,支持与 Granite 模型交互。

model_id="ibm/granite-3-2b-instruct" 指定采用 27 亿参数的 IBM Granite 指令微调模型,专为基于指令的生成式 AI 任务设计。

class TavilySearch(BaseTool) 定义基于 Tavily API 执行网络搜索的自定义 LangChain 工具类。

tavily_tool = TavilySearch() 创建可执行的自定义 Tavily 搜索工具实例.

当我们初始化 watsonxLLM 时,之前设置凭据中的 url、 apikey和 project_id 值将被传递,用于服务认证并连接到服务。其参数如 "max_new_tokens": 300 限制响应长度,而 "temperature": 0.2 控制输出创造性,倾向于生成更具确定性的结果。

 TavilySearch 类的定义包括对其功能的描述。其逻辑包含在 def _run(self, query: str) 方法中。在此方法中,我们向 Tavily API 端点发出 HTTP POST 请求,其中包括 JSON 有效载荷中的  TAVILY_API_KEY 和搜索查询。我们然后使用 response.raise_for_status() 来验证是否存在任何 HTTP 错误并解析 JSON 响应,以获取首个搜索结果的摘要内容。

此步骤设置用于文本生成的语言模型,并通过集成外部网络搜索工具来扩展模型的知识边界。

第 7 步:定义提示模板和辅助函数

此步骤定义各种提示模板,这些模板可在 RAG 流程的不同阶段指导 LLM 的行为。这种方法包括对内部文档分块相关性评分的提示,重写用户查询以实现更好的网络搜索的提示,以及用于验证网络搜索结果来源的新提示。此外还定义了用于对分块进行评分和从向量存储中检索数据的辅助函数。

# Define Prompt Templates and Helper Functions

# Prompt for scoring the relevance of retrieved chunks
scoring_prompt_template = PromptTemplate.from_template(
"""
You are an evaluator. Score the relevance of the context chunk to the given insurance question.

Question: "{query}"

Context:
\"\"\"
{chunk}
\"\"\"

Respond only in this format:
Score: <0-5>
Reason: <one line reason>
"""
)

# Prompt for rewriting the user's query for better web search results
rewrite_prompt_template = PromptTemplate.from_template(
"""
You are a helpful assistant. Improve the following question to be clearer for an insurance information search.
Focus on making the query more specific if possible.

Original Question: "{query}"

Rewrite it to be clearer:
"""
)

# NEW: Prompt for verifying if Tavily context is from a relevant source (private policy vs. public program)
CONTEXT_SOURCE_VERIFICATION_PROMPT = PromptTemplate.from_template(
"""
You are an expert at identifying if a piece of text is from a general, public, or unrelated source
versus a specific, private, or relevant policy document.

Read the following context and determine if it appears to discuss general information,
public health programs (like Medi-Cal, Medicaid, Medicare, NHS, government-funded programs, state-funded),
or information that is clearly *not* specific to a private insurance policy like the one
the user might be asking about (assuming the user is asking about their own private policy).

If the context explicitly mentions or heavily implies public health programs, or is too general
to be useful for a specific private policy question, respond with "NO".
Otherwise (if it seems like it *could* be from a private policy context, a general insurance guide,
or does not explicitly mention public programs), respond with "YES".

Context:
\"\"\"
Response:
"""
)


# Function to score chunks using the LLM
def score_chunks(chunks, query):
scored = []
for chunk in chunks:
prompt = scoring_prompt_template.format(query=query, chunk=chunk)
response = llm(prompt).strip()

try:
# Extract score using more robust parsing
score_line = [line for line in response.splitlines() if "Score:" in line]
if score_line:
score = int(score_line[0].replace("Score:", "").strip())
else:
score = 0 # Default to 0 if score line not found
except Exception as e:
print(f" Could not parse score for chunk: {e}. Response: {response[:50]}...")
score = 0 # Default to 0 on error

scored.append((chunk, score))
return scored

# Function to retrieve documents from FAISS vector store
def retrieve_from_vectorstore(query):
# Retrieve top 8 similar documents from your PDF content
docs = vectorstore.similarity_search(query, k=8)
return [doc.page_content for doc in docs]

print(" Prompt templates and helper functions defined.")

此步骤定义各种提示模板,这些模板可在 RAG 流程的不同阶段指导 LLM 的行为。对内部文档分块相关性评分的提示,重写用户查询以实现更好的网络搜索的提示,以及用于验证网络搜索结果来源的新提示。此外还定义了用于对分块进行评分和从向量存储中检索数据的辅助函数。

PromptTemplate.from_template 作为 LangChain 工具函数,用于创建可重复使用的模板来构建提示。

scoring_prompt_template 定义提示模板,指导 LLM 扮演评估者角色,根据问题为特定上下文块分配相关性分数(0-5)。

rewrite_prompt_template 定义提示模板,引导 LLM 优化或澄清用户原始查询以提升搜索效果。

CONTEXT_SOURCE_VERIFICATION_PROMPT 定义提示模板,指示 LLM 验证文本片段是否源自私有保单上下文而非通用公开来源。

def score_chunks(chunks, query) 定义函数,用于接收文本分块列表和查询,然后使用 LLM 对每个文本分块的相关性进行评分。

def retrieve_from_vectorstore(query) 定义函数,用于从 FAISS 向量存储检索最相似的文档。

score_chunks 函数中,系统初始化空评分列表。对于每个分块, scoring_prompt_template 都会根据特定查询和分块进行格式化。然后将格式化提示发送到 LLM,并解析响应结果。该函数尝试通过识别模型响应中的“Score:”行来提取整型分数(如果简化为相关或不相关,则为二进制分数)。然后,该分块及其解析或默认分数将添加到评分列表中。这部分系统充当检索评价器或评分器。

 retrieve_from_vectorstore  函数实现了 vectorstore.similarity_search ,基于查询找到 8 个最相关的文档分块,并从这些检索到的 LangChain 文档对象中检索 page_content 。

本步骤构建了 corrective RAG 系统的概念框架,使 LLM 能够评估上下文质量,并从内部与外部知识源中检索信息。

第 8 步:实现纠正性 RAG 逻辑

初始检索 功能用于扫描 PDF 的向量存储。

上下文评分 根据相关性对已检索到的 PDF 分块进行上下文评分。

Tavily 回退机制: 当 PDF 没有足够的相关上下文,就会启动 Tavily(网络搜索)。

来源验证 是一个由 LLM 驱动的步骤,用于在使用 Tavily 结果之前检查它们是否与私有保单相关。此功能可避免公共健康计划等无关信息的干扰。

查询重写和第二次 Tavily 搜索 :如果仍无有效上下文,则重写查询并再次尝试 Tavily 搜索。

最终决策:当有任何相关上下文时,就会将其发送给 LLM,结合严格限制的提示生成答案。如果在所有可行的尝试后都没有相关的上下文,则返回礼貌的拒绝响应。

# Implement the Corrective RAG Logic

MIN_CONTEXT_LENGTH = 100 # Adjust this based on how much minimal context you expect for a partial answer
SIMILARITY_THRESHOLD = 3 # Only scores >= 3 used for vector store chunks

def corrective_rag(query: str, policy_context_keywords: list = None):
"""
Executes the Corrective RAG process to answer insurance queries.

Args:
query (str): The user's question.
policy_context_keywords (list, optional): Keywords related to the specific policy
(e.g., ["Super Star Health", "Care Health Insurance"]).
Used to make external searches more specific. Defaults to None.
Returns:
str: The final answer generated by the LLM or a predefined refusal.
"""
retrieved_context_pieces = [] # To store all relevant pieces found throughout the process

# Initial vector search & Scoring (from your PDF)
chunks_from_vectorstore = retrieve_from_vectorstore(query)
scored_chunks_vector = score_chunks(chunks_from_vectorstore, query)
good_chunks_vector = [chunk for chunk, score in scored_chunks_vector if score >= SIMILARITY_THRESHOLD]
retrieved_context_pieces.extend(good_chunks_vector)

current_context = "\n\n".join(retrieved_context_pieces)
print(f" Context length after initial vector scoring: {len(current_context)}")

# Prepare specific query for Tavily by optionally adding policy keywords
tavily_search_query = query
if policy_context_keywords:
tavily_search_query = f"{query} {' '.join(policy_context_keywords)}"

# Fallback: Tavily direct search (only if current context is too short from vector store)
if len(current_context) < MIN_CONTEXT_LENGTH:
print(f" Context too short from internal docs, trying Tavily direct with query: '{tavily_search_query}'...")
tavily_context_direct = tavily_tool._run(tavily_search_query)

if tavily_context_direct:
# --- NEW STEP: Verify Tavily Context Source ---
# Ask the LLM if the Tavily result seems to be from a private policy context or a public program
verification_prompt = CONTEXT_SOURCE_VERIFICATION_PROMPT.format(context=tavily_context_direct)
is_relevant_source = llm(verification_prompt).strip().upper()

if is_relevant_source == "YES":
retrieved_context_pieces.append(tavily_context_direct)
current_context = "\n\n".join(retrieved_context_pieces) # Re-combine all good context
print(f" Context length after Tavily direct (verified and added): {len(current_context)}")
else:
print(f" Tavily direct context source rejected (e.g., public program): {tavily_context_direct[:100]}...")
# Context is NOT added, so it remains short and triggers the next fallback or final refusal

# Fallback: Rewrite query + Tavily (only if context is still too short after direct Tavily)
if len(current_context) < MIN_CONTEXT_LENGTH:
print(" Context still too short, rewriting query and trying Tavily...")
rewrite_prompt = rewrite_prompt_template.format(query=query)
improved_query = llm(rewrite_prompt).strip()

# Add policy keywords to the rewritten query too
if policy_context_keywords:
improved_query = f"{improved_query} {' '.join(policy_context_keywords)}"

print(f" Rewritten query: '{improved_query}'")
tavily_context_rewritten = tavily_tool._run(improved_query)

if tavily_context_rewritten:
# --- NEW STEP: Verify Rewritten Tavily Context Source ---
verification_prompt = CONTEXT_SOURCE_VERIFICATION_PROMPT.format(context=tavily_context_rewritten)
is_relevant_source = llm(verification_prompt).strip().upper()

if is_relevant_source == "YES":
retrieved_context_pieces.append(tavily_context_rewritten)
current_context = "\n\n".join(retrieved_context_pieces) # Re-combine all good context
print(f" Context length after rewritten Tavily (verified and added): {len(current_context)}")
else:
print(f" Tavily rewritten context source rejected (e.g., public program): {tavily_context_rewritten[:100]}...")

# --- Final Decision Point ---
# Now, `current_context` holds ALL the "good" and "verified" context we managed to gather.
# The decision to call the LLM for an answer or give a hard refusal is based on `current_context`'s length.

# Final check for absolutely no good context
# This triggers only if *no* relevant internal or external context was found or verified.
if len(current_context.strip()) == 0:
print(" No good context found after all attempts. Returning absolute fallback.")
return (
"Based on the information provided, there is no clear mention of this specific detail "
"in the policy documents available."
)

# If we have *any* context (even if short), pass it to the LLM to process
# The LLM will then decide how to phrase the answer based on its prompt instructions
# (exact, partial, or full refusal if context is irrelevant or insufficient based on its own reasoning).
final_prompt = (
f"You are a careful insurance expert.\n"
f"Use ONLY the following context to answer the user's question. If the context is too short "
f"or does not contain the answer, you must indicate that.\n"
f"Context:\n```\n{current_context}\n```\n\n" # Pass the gathered context
f"User's Question: {query}\n\n" # Pass the original query for the LLM's reference
f"NEVER add new details that are not in the context word-for-word.\n"
f"If the context clearly says the answer, give it exactly as written in the context, but in prose.\n"
f"If the context does not mention the topic at all, or the answer is not in the context, say:\n"
f"\"I'm sorry, but this information is not available in the provided policy details.\"\n"
f"If the context partially mentions the topic but does not directly answer the specific question (e.g., mentions 'dental' but not 'wisdom tooth removal'), reply like this:\n"
f"\"Based on the information provided, here’s what is known: [quote relevant details from the context related to the broad topic.] "
f"There is no clear mention of the specific detail asked about.\"\n"
f"Do NOT assume. Do NOT make up extra information.\n"
f"Do NOT generate extra questions or conversational filler.\n"
f"Final Answer:"
)

return llm(final_prompt)

print(" Corrective RAG logic implemented.")

 policy_context_keywords 参数的第一次传递允许您添加保单中的特定术语(例如保单名称、保险公司),以帮助缩小 Tavily 的搜索范围。

MIN_CONTEXT_LENGTH 定义检索上下文的最小可接受长度。

SIMILARITY_THRESHOLD 定义文本分块被视为“优质”所需的最低相关性分数。

def corrective_rag(...) 定义协调整个纠正性 RAG 工作流的主函数。

 corrective_rag 函数首先创建 retrieved_context_pieces 来收集相关上下文。它首先根据查询从 PDF 向量存储中获取 chunks_from_vectorstore 并对其进行评分,然后 scored_chunks_vector 使用语言模型评估其相关性。仅保留符合 SIMILARITY_THRESHOLD 的 good_chunks_vector 。然后根据这些片段编译 current_context 。

如果current_context低于MIN_CONTEXT_LENGTH,系统将尝试网络搜索。它构建 tavily_search_query,其中可能包含 policy_context_keywords。执行直接搜索(tavily_context_direct)。至关重要的是,创建 verify_prompt 并将其发送给 LLM,用于判断网络搜索结果 ( is_relevant_source) 是否来自私有保单而非公共计划。如果答案是“是”,则添加上下文。

如果上下文仍然不足,系统将准备重写查询。它使用 rewrite_prompt 从 LLM 获取 improved_query ,然后执行第二次网络搜索(tavily_context_rewritten)。新上下文同样需经过来源验证。

最后, if len(current_context.strip()) == 0 执行最终检查。如果在所有尝试后仍未找到相关上下文,则返回预定义的拒绝消息。否则,使用所有已验证上下文创建 final_prompt ,并发送至语言模型生成最终答案。

整个 corrective_rag 函数详细实现了纠正性 RAG 的分阶段检索、评分与验证功能。它允许不断更新知识库和知识流,并能提供兼具稳健性与上下文感知能力的响应。

第 9 步:测试系统

最后,使用示例查询执行 corrective_rag 函数。关键是要提供与您的 PDF 文档密切相关的 policy_context_keywords 。这些关键词将帮助 Tavily 网络搜索更精准地匹配实际保单内容,防止通用信息或公共健康计划信息污染上下文环境。

观察打印输出的上下文长度与验证结果,了解信息流转过程。

query = "How does the policy cover for In-Patient Hospitalisation?"
result = corrective_rag(query)

print("\n FINAL ANSWER:\n")
print(result)

policy_specific_keywords = ["Super Star Health", "Care Health Insurance"] 定义了与上传保单相关的关键词列表,用于精确限定网络搜索结果范围。

query = "..." 定义用户可能提出的特定问题。

result = corrective_rag(query, policy_context_keywords=policy_specific_keywords) 调用主 corrective_rag 函数,传递用户查询及保单特定关键词以启动完整 RAG 流程。

print("\n FINAL ANSWER (...)") 在输出生成答案前显示清晰标题。

print(result) 输出 corrective_rag 系统返回的最终答案。

本步骤展示了如何通过示例查询与关键词调用完整纠正性 RAG 系统,在实际场景中演示其端到端运行机制。

关键要点

已实现的纠正性 RAG 完整协调内部 PDF 知识库与外部服务,为复杂请求检索全面信息。

它通过基于 LLM 的评分机制与关键来源验证,精准评估并过滤检索到的上下文,确保使用有效可靠的信息。

该系统通过智能重写用户查询以获取更精准、更高质量的信息,展现了优化外部搜索的能力。

通过使用受限生成技术,系统能稳定生成上下文准确的答案,如果没有足够的已知验证信息,系统会礼貌地拒绝回答。

此示例演示了如何使用 LangChain 和 watsonx 平台上的 IBM Granite LLM,在保单问题等敏感领域开发强大且值得信赖的基于 AI 的应用程序。

相关解决方案
商用 AI 智能体

构建、部署和管理强大的 AI 助手和智能体,运用生成式 AI 实现工作流和流程自动化。

    探索 watsonx Orchestrate
    IBM AI 智能体解决方案

    借助值得信赖的 AI 解决方案,您可以勾勒未来业务发展蓝图。

    深入了解 AI 智能体解决方案
    IBM Consulting AI 服务

    IBM Consulting AI 服务有助于重塑企业利用 AI 实现转型的方式。

    探索人工智能服务
    采取下一步行动

    无论您是选择定制预构建的应用程序和技能,还是使用 AI 开发平台构建和部署定制代理服务,IBM watsonx 平台都能满足您的需求。

    1. 探索 watsonx Orchestrate
    2. 深入了解 watsonx.ai