Crea un agente RAG correttivo utilizzando IBM Granite e Tavily

Autore

Jobit Varughese

Technical Content Writer

IBM

I modelli linguistici di grandi dimensioni (LLM) sono incredibilmente potenti, ma la loro conoscenza è limitata ai set di dati di addestramento. Quando rispondono a domande, in particolare su informazioni specifiche, in evoluzione o proprietarie, gli LLM possono avere allucinazioni o fornire risposte generali e irrilevanti. La retrieval-augmented generation  (RAG) aiuta fornendo all'LLM informazioni rilevanti recuperate da fonti di dati esterne.

Tuttavia, non tutte le RAG sono uguali. La retrieval-augmented generation correttiva (cRAG) non si basa semplicemente sulla RAG più tradizionale, ma rappresenta un miglioramento significativo. È stata concepita per essere più affidabile attraverso la valutazione della qualità e della pertinenza dei risultati recuperati. Se il contesto è debole, irrilevante o proviene da una fonte inaffidabile, la cRAG tenta di trovare informazioni migliori attraverso azioni correttive o si rifiuta esplicitamente di rispondere piuttosto che inventare una risposta. Questa tecnica rende i sistemi cRAG più affidabili e sicuri per applicazioni critiche, come rispondere a domande relative alle polizze.

Diagramma documenti rilevanti

In questo tutorial, potrai imparare come creare un sistema RAG correttivo (CRag) affidabile, utilizzando i modelli IBM® Granite su Watsonx e LangChain. Framework simili come LlamaIndex o LangGraph possono essere utilizzati anche per creare flussi RAG complessi con nodi distinti. Tecniche come la messa a punto possono migliorare ulteriormente le prestazioni LLM specifiche per RAG distintive del dominio. Anche gli LLM come quelli di OpenAI (ad esempio, i modelli GPT come ChatGPT) sono scelte diffuse per questi agenti, sebbene questo tutorial si concentri su IBM Granite.

Qui ci concentreremo su un caso d'uso: rispondere a domande su uno specifico documento relativo a una polizza assicurativa (PDF). Questo tutorial ti guiderà nell'implementazione di un sofisticato algoritmo RAG che:

  • Recupera informazioni dal tuo documento PDF.

  • Se i documenti interni non sono sufficienti per generare la risposta, l'agente può utilizzare una ricerca web esterna (Tavily) come fallback.

  • L'agente filtra in modo intelligente i risultati esterni irrilevanti in modo che le risposte siano adattate alle polizze private.

  • L'agente fornirà risposte chiare e limitate con informazioni parziali se disponibili o un chiaro rifiuto laddove manchi il contesto.

Caso d'uso: creazione di un agente di query di polizze assicurative affidabile

Questo tutorial è una dimostrazione della creazione di un agente di query sulle polizze assicurative progettato per analizzare i documenti delle polizze (una brochure PDF) e rispondere in modo accurato alle domande degli utenti. Utilizziamo i modelli IBM® Granite e LangChain per creare l'agente con solide fasi di recupero e verifica che garantiscono risposte di alta qualità e vincolate all'origine.

Vediamo ora come si applicano i principi chiave di un RAG affidabile nel nostro caso d'uso.

Applicazione dei principi chiave

Knowledge base interna (PDF): la principale fonte di verità dell'agente è il PDF della polizza assicurativa fornita. Converte questo documento in un database vettoriale.

Fallback per la ricerca esterna (Tavily): se la knowledge base interna non dispone di informazioni sufficienti, l'agente può consultare fonti web esterne tramite Tavily. Tavily è un motore di ricerca creato specificamente per agenti AI e LLM che consente un recupero più rapido e in tempo reale tramite l'application programming interface (API) per le applicazioni basate su RAG.

Punteggio contestuale: il valutatore del recupero basato su LLM (che funge da selezionatore) fornirà un punteggio in base alla pertinenza degli elementi recuperati dal PDF interno, garantendo al contempo che siano inclusi solo gli elementi recuperati di alta qualità.

Riscrittura delle query: per le ricerche sul web, l'agente può riformulare la query dell'utente per aumentare le possibilità di trovare informazioni esterne pertinenti.

Verifica della fonte: un controllo basato su un LLM valuta se i risultati di ricerca web esterni sono pertinenti per una polizza assicurativa privata, filtrando le informazioni generali o i dettagli sui programmi di salute (come Medi-Cal). Questa funzione impedisce la generazione di risposte fuorvianti e consente l'autocorrezione, aiutando nell'affinamento delle conoscenze.

Generazione vincolata: il prompt finale all'LLM lo istruisce rigorosamente a utilizzare solo il contesto fornito, offrire risposte esatte, indicare quando le informazioni non sono disponibili o fornire risposte parziali con limitazioni esplicite. Questa funzione migliora l'adattabilità e l'affidabilità delle risposte generate.

Prerequisiti

Per creare un progetto watsonx.ai è necessario un account IBM Cloud . Assicurati di avere accesso sia alla tua chiave API watsonx che all'ID progetto. Avrai anche bisogno di una chiave API per Tavily AI per le funzionalità di ricerca web.

Passaggi

Passaggio 1. Configurare il tuo ambiente

Sebbene sia possibile scegliere tra diversi strumenti, questo tutorial illustra come configurare un account IBM utilizzando un Jupyter Notebook.

  1. Accedi a watsonx.ai utilizzando il tuo account IBM Cloud.
  2. Crea un progetto watsonx.ai. Puoi ottenere l'ID del progetto dall'interno del tuo progetto. Fai clic sulla scheda Gestisci. Quindi, copia l'ID del progetto dalla sezione Dettagli della pagina Generali. Per questo tutorial ti serve questo ID.
  3. Crea un Jupyter Notebook.

Questo passaggio apre un ambiente notebook in cui è possibile copiare il codice da questo tutorial. In alternativa, puoi scaricare questo notebook sul tuo sistema locale e caricarlo nel tuo progetto watsonx.ai come asset. Per visualizzare altri tutorial su Granite, visita l' IBM Granite Community. Questo tutorial è disponibile anche su Github.

Passaggio 2. Configurare un servizio watsonx.ai Runtime e una chiave API

  1. Crea un'istanza di servizio watsonx.ai Runtime (scegli il piano Lite, che è un'istanza gratuita).
  2. Genera una chiave API (application programming interface).
  3. Associa il servizio watsonx.ai Runtime al progetto che hai creato in watsonx.ai.

Passaggio 3. Installazione dei pacchetti

Per lavorare con il framework LangChain e integrare IBM WatsonXLLM, è necessario installare alcune librerie essenziali. Iniziamo installando i pacchetti richiesti. Questo set include langchain per il framework RAG, langchain-ibm per l'integrazione di watsonx, faiss-cpu per lo storage efficiente, PyPDF2 per l'elaborazione di PDF, sentence-transformer per ottenere un embeddingrichieste per chiamate API web. Queste librerie sono critiche per applicare soluzioni di machine learning e NLP.

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

Nota: non è richiesta alcuna GPU, ma l'esecuzione può essere più lenta su sistemi basati su CPU. Questo passaggio apre un ambiente notebook in cui è possibile copiare il codice da questo tutorial, a sua volta disponibile anche su GitHub.

Passaggio 4. Importa le librerie necessarie

Successivamente, importa tutti i moduli richiesti e fornisci in modo sicuro le tue chiavi API per watsonx e Tavily, insieme al tuo ID progetto watsonx.

# 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 aiuta a lavorare con il sistema operativo.

io permette di lavorare con flussi di dati.

getpass utilizza un metodo sicuro per catturare informazioni sensibili come le chiavi API e non visualizza l'input sullo schermo.

PyPDF2.PdfReader consente di estrarre il contenuto dai PDF.

langchain_ibm. WatsonxLLM ci permette di utilizzare facilmente l'LLM IBM watsonx Granite all'interno del framework LangChain.

langchain.embedding.HuggingFaceEmbeddings prende un modello HuggingFace e genera gli embedding testuali importanti per la ricerca semantica.

langchain.vectorstores.FAISS è una libreria per il storage vettoriale efficiente e la ricerca di similarità che consente di creare un indice vettoriale e interrogarlo.

langchain.text_splitter.RecursiveCharacterTextSplitter aiuta a suddividere grandi componenti di testo nei blocchi più piccoli necessari per elaborare documenti che altrimenti non entrerebbero in memoria.

langchain.schema.Document rappresenta un'unità di testo arbitraria con metadati associati che lo rendono un elemento costitutivo di langchain.

requests viene utilizzato per effettuare richieste HTTP esternamente alle API.

Botocore.client.config è una classe di configurazione utilizzata per definire le impostazioni di configurazione per un client AWS/IBM Cloud Object Storage.

ibm_boto3 è IBM Cloud Object Storage SDK per Python che aiuta a interagire con il cloud object storage.

langchain.prompts.PromptTemplate offre un modo per creare prompt strutturati e riutilizzabili per i modelli linguistici.

langchain.tools.BaseTool è la classe base da cui crei strumenti personalizzati che possono essere forniti agli agenti LangChain per l'uso.

Questo passaggio configura tutti gli strumenti e i moduli necessari per elaborare testo, creare embedding, memorizzarli in un database vettoriale e interagire con IBM watsonx LLM. Stabilisce tutte le parti necessarie per creare un sistema RAG del mondo reale, in grado di reperire, interrogare e cercare una serie di tipi di dati.

Passaggio 5. Caricare ed elaborare un PDF da IBM Cloud Object Storage

In questa fase, caricheremo il PDF della polizza assicurativa da IBM Cloud Object Storage. Il codice legge il PDF e il contenuto del testo per poi suddividere quest'ultimo in blocchi più piccoli e gestibili. Questi blocchi vengono convertiti in embedding e memorizzati in un database vettoriale FAISS che ci prepara per la successiva ricerca di similarità semantica nel contesto locale per ottimizzare i risultati.

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 consente al client di interagire con IBM Cloud Object Storage.

Bucket è il nome del bucket di cloud object storage che contiene il PDF.

object_key è il nome del PDF nel bucket di cloud object storage.

cos_client.get_object(...).read() recupera il contenuto del file PDF nel cloud object storage come byte.

io.BytesIO converte i byte grezzi del PDF in un flusso binario in memoria in un formato che utilizzabile da PdfReader.

PdfReader crea un oggetto che può analizzare ed estrarre il testo dal PDF.

page.extract_text() estrae il testo di una singola pagina del PDF.

RecursiveCharacterTextSplitter è configurato per dividere il testo estratto in blocchi di 500 caratteri con una sovrapposizione di 50 caratteri, mantenendo quindi tutto in contesto.

splitter.split_text(text) esegue la suddivisione di tutte le pagine del testo PDF nei blocchi più piccoli.

HuggingFaceEmbeddings carica un modello di trasformatore di frasi che è stato preaddestrato per convertire i blocchi di testo in rappresentazioni vettoriali dense.

FAISS.from_texts(chunks, embeddings) costruisce un indice FAISS in memoria che consente di ricercare blocchi di testo in base alle loro similarità semantiche.

Questo passaggio gestisce l'acquisizione completa di un documento PDF dal cloud al testo pronto per l'LLM e la comoda indicizzazione per il recupero in tempo reale.

Passaggio 6. Inizializzare LLM e strumenti

In questo passaggio, configurerai l'LLM IBM Granite per guidare il ragionamento del tuo agente e integrarlo con la funzione di ricerca web di Tavily. I parametri dell'LLM sono impostati per risposte fattuali e stabili.

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 crea un'istanza del wrapper LLM per IBM watsonx, consentendo l'interazione con i modelli Granite.

model_id="ibm/granite-3-2b-instruct" è il modello IBM Granite (un modello di istruzione di parametri da 2,7 miliardi) progettato per attività di AI generativa basate su istruzioni.

class TavilySearch(BaseTool) definisce uno strumento LangChain personalizzato per l'esecuzione di ricerche web utilizzando l'API Tavily.

tavily_tool = TavilySearch() crea un'istanza eseguibile dello strumento di ricerca personalizzato Tavily.

Quando inizializziamo watsonxLLM, i valori urlapikeyproject_id delle nostre credenziali precedentemente configurate vengono passati per l'autenticazione e la connessione al servizio. I suoi parametri, come "max_new_tokens": 300, limitano la lunghezza della risposta e "temperature": 0.2 controlla la creatività dell'output, favorendo risultati più deterministici.

La definizione della classe TavilySearch include una descrizione della sua funzione. La sua logica è contenuta nel metodo def _run(self, query: str).  In questo metodo, effettuiamo una richiesta HTTP POST all'endpoint dell'API Tavily, inclusi TAVILY_API_KEY e la query di ricerca nel payload JSON. Verifichiamo, quindi, se ci sono errori HTTP con response.raise_for_status() e analizziamo la risposta JSON per accedere allo snippet di contenuto dal primo risultato di ricerca.

Questo passaggio configura il modello linguistico per la generazione di testo e include uno strumento di ricerca web esterno per aumentare la conoscenza del modello linguistico.

Passaggio 7. Definire i modelli di prompt e le funzioni di supporto

Questo passaggio definisce i vari modelli di prompt che guidano il comportamento dell'LLM nelle diverse fasi del processo RAG. Questo approccio include la richiesta di valutare la pertinenza dei blocchi di documenti interni, la riscrittura delle query degli utenti per una migliore ricerca sul Web e un nuovo prompt critico per la verifica dell'origine dei risultati della ricerca Web. Vengono inoltre definite le funzioni di supporto per l'assegnazione dei punteggi ai blocchi e il loro recupero dal database vettoriale.

# 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.")

Questo passaggio definisce i vari modelli di prompt che guidano il comportamento dell'LLM nelle diverse fasi del processo RAG. Prompt per verificare la pertinenza di blocchi di documenti interni, riscrivere le query degli utenti per una migliore ricerca sul web e un prompt critico per verificare che la fonte dei risultati della ricerca sul web sia inclusa. Sono definite anche le funzioni di supporto per assegnare punteggi ai blocchi e recuperarli dal database vettoriale.

PromptTemplate.from_template è una utility di LangChain per creare un modello riutilizzabile per la costruzione di prompt.

scoring_prompt_template definisce un prompt che indica all'LLM di agire come valutatore e assegnare un punteggio di pertinenza (0—5) a un blocco di contesto specifico in base a una domanda.

rewrite_prompt_template definisce un prompt che guida l'LLM a migliorare o rendere più chiara la domanda originale dell'utente per la ricerca.

CONTEXT_SOURCE_VERIFICATION_PROMPT definisce un prompt che indica all'LLM di verificare se un blocco di testo (ad esempio, dalla ricerca web) proviene da un contesto di policy privata o da una fonte generale o pubblica.

def score_chunks(chunks, query)definisce una funzione che richiede un elenco di blocchi di testo e una query utilizza quindi l'LLM per valutare la rilevanza di ogni blocco.

def retrieve_from_vectorstore(query) definisce una funzione per recuperare i documenti più simili dal database vettoriale FAISS.

All'interno della funzione score_chunks, viene inizializzato un elenco di punteggi vuoto. Per ogni blocco, il scoring_prompt_template viene formattato con la query e il blocco specifici. Questo prompt formattato viene quindi inviato all'LLM e la risposta viene rimossa. La funzione tenta di estrarre il punteggio intero (un punteggio binario se semplificato in rilevante o non rilevante) identificando la riga "Score:" nella risposta del modello. Il blocco insieme al suo punteggio analizzato o predefinito viene quindi aggiunto all'elenco dei punteggi. Questa parte del sistema funge da valutatore o classificatore del recupero.

La funzione retrieve_from_vectorstore implementa un vectorstore.similarity_search per trovare gli otto blocchi di documento più pertinenti in base alla query e recuperare il page_content da questi oggetti LangChain Document recuperati.

Questa fase costruisce la struttura concettuale per il sistema RAG correttivo in modo che l'LLM valuti sia il contesto che come recuperare la conoscenza da fonti interne ed esterne.

Passo 8. Implementare la logica correttiva RAG

Recupero iniziale è la funzione che scansiona il database vettoriale del PDF.

Punteggio contestuale prende i blocchi PDF che sono stati recuperati per attribuire un punteggio contestuale in base alla pertinenza.

Fallback a tavily è la funzione per cui interroga Tavily (ricerca web) se non c'è abbastanza contesto pertinente dal PDF.

La verifica della fonte è un passaggio basato su LLM che verifica se i risultati di Tavily sono rilevanti per una polizza privata prima di utilizzarli. Questa funzione impedisce risposte fuorvianti dai programmi di sanità pubblica.

Riscrittura delle query e seconda ricerca tavily se non c'è ancora un buon contesto, riscrive la query e riprova la ricerca su Tavily.

Decisione finale quando c'è un contesto pertinente, viene inviata all'LLM con un prompt (rigoroso) per creare la risposta. Se non c'è un contesto rilevante dopo tutti i tentativi fattibili, invia un cortese rifiuto.

# 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.")

Il primo passaggio del parametro policy_context_keywords consente di aggiungere termini specifici della polizza (ad esempio, nome, assicuratore) per restringere le ricerche di Tavily.

MIN_CONTEXT_LENGTH definisce la lunghezza minima accettabile del contesto recuperato.

SIMILARITY_THRESHOLD definisce il punteggio minimo di rilevanza che un blocco deve avere per essere considerato "buono."

def corrective_rag(...) definisce la funzione principale che orchestra l'intero workflow RAG correttivo.

La funzione corrective_rag inizia creando retrieved_context_pieces per raccogliere il contesto pertinente. Prima recupera e valuta chunks_from_vectorstore dal database vettoriale PDF in base alla query, poi scored_chunks_vector ne analizza la rilevanza utilizzando il modello linguistico. Vengono mantenuti solo good_chunks_vector che soddisfano i  SIMILARITY_THRESHOLD.  Il current_context viene poi compilato a partire da questi pezzi.

Se il current_context è inferiore a MIN_CONTEXT_LENGTH, il sistema tenta una ricerca sul web. Costruisce tavily_search_query, incorporando potenzialmente policy_context_keywords. Viene eseguita una ricerca diretta(tavily_context_direct). Fondamentalmente, viene creato un verification_prompt e inviato all'LLM per determinare se il risultato della ricerca web (is_relevant_source) proviene da una polizza privata anziché da un programma pubblico. In caso affermativo, viene aggiunto il contesto.

Se il contesto rimane insufficiente, il sistema si prepara a riscrivere la query. Utilizza rewrite_prompt per ottenere una improved_query dall'LLM, quindi esegue una seconda ricerca sul web (tavily_context_rewritten). Anche questo nuovo contesto viene sottoposto alla stessa verifica della fonte.

Infine, if len(current_context.strip()) == 0 è un ultimo controllo. Se dopo tutti i tentativi non viene trovato alcun contesto rilevante, viene restituito un messaggio di rifiuto predefinito. Altrimenti, viene creato un final_prompt con tutto il contesto verificato e inviato al modello linguistico per generare la risposta finale.

L'intera funzione corrective_rag gestisce in dettaglio le funzioni graduali di recupero, punteggio e verifica del RAG correttivo. Consente l'aggiornamento costante della knowledge base e del flusso di conoscenze e offre il beneficio di risposte affidabili e consapevoli del contesto.

Passaggio 9. Testare il sistema

Infine, esegui la funzione corrective_rag con una query di esempio. È fondamentale fornire policy_context_keywords specifiche per il tuo documento PDF. Queste parole chiave aiuteranno la ricerca web di Tavily a essere più pertinente alla tua politica effettiva, evitando che informazioni generiche o relative a programmi di salute pubblica contaminino il contesto.

Osserva le istruzioni di stampa per il contesto, la lunghezza e i risultati della verifica per comprendere il flusso di informazioni.

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"] definisce una lista di parole chiave pertinenti alla polizza assicurativa caricata, contribuendo a restringere il campo di ricerca sul web.

query = "... " definisce la domanda particolare che un utente potrebbe porre.

result = corrective_rag(query, policy_context_keywords=policy_specific_keywords) chiama la funzione principale corrective_rag e passa la query dell'utente e le parole chiave specifiche della politica per iniziare l'intero processo RAG.

print("\n FINAL ANSWER (...)") visualizza un'intestazione chiara prima di stampare la risposta generata.

print(result) restituisce la risposta finale restituita dal sistema corrective_rag.

Questo passaggio mostra come richiamare il sistema RAG correttivo completo con una query di esempio e delle parole chiave, dimostrandone la funzionalità end-to-end in uno scenario reale.

Risultati principali

Il RAG correttivo implementato ha coordinato completamente una knowledge base PDF interna con un servizio esterno (Tavily) per recuperare informazioni complete per richieste complesse.

Ha valutato accuratamente e filtrato il contesto recuperato utilizzando una valutazione basata su LLM e una verifica critica delle fonti per garantire che vengano utilizzate informazioni valide e affidabili.

Il sistema ha dimostrato la capacità di migliorare la ricerca esterna riscrivendo in modo intelligente le query degli utenti per richiedere informazioni più mirate e di qualità superiore.

Utilizzando la generazione vincolata, veniva comunemente generata una risposta affidabile e contestualmente accurata e il sistema si rifiutava cortesemente di rispondere se non c'erano abbastanza informazioni di verifica note.

Questo esempio ha dimostrato come gli LLM LangChain e IBM Granite su watsonx possono essere utilizzati per sviluppare applicazioni potenti e basate su AI affidabile in domini sensibili, come ad esempio porre domande sulle polizze assicurative.

Soluzioni correlate
Agenti AI per il Business

Crea, implementa e gestisci assistenti e agenti AI potenti che automatizzano workflow e processi con l'AI generativa.

    Scopri watsonx Orchestrate
    Soluzioni per agenti AI IBM

    Costruisci il futuro della tua azienda con soluzioni AI di cui puoi fidarti.

    Esplora le soluzioni basate su agenti AI
    Servizi AI di IBM Consulting

    I servizi di AI di IBM Consulting aiutano a reinventare il modo in cui le aziende lavorano con l'AI per la trasformazione.

    Esplora i servizi di intelligenza artificiale
    Prossimi passi

    Sia che tu scelga di personalizzare app e competenze precostituite o di creare e implementare servizi di agenti personalizzati utilizzando uno studio di AI, la piattaforma IBM watsonx è la soluzione che fa per te.

    Scopri watsonx Orchestrate Esplora watsonx.ai