Skip to content

Commit 8b01b67

Browse files
log_analysis_multi_agent_rag (#273)
* log_analysis using self corrective RAG
1 parent 39959d2 commit 8b01b67

File tree

11 files changed

+382
-0
lines changed

11 files changed

+382
-0
lines changed
136 KB
Loading
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Multi Agent Self Corrective RAG
2+
3+
# Overview
4+
5+
The Self-Corrective Multi-Agent RAG system uses a graph-based workflow to process queries, retrieve relevant documents, grade document relevance, generate responses, and self-correct through query transformation. Initially, it retrieves relevant documents using a hybrid retrieval approach that combines BM25 and FAISS, ensuring efficient document search. The retrieved documents are then graded for relevance using NVIDIA AI endpoints for embeddings and reranking. Based on the most relevant documents, the system generates a response or, if necessary, transforms the query to refine the search
6+
7+
We are calling this tool as BAT.AI (Bug Automation Tool)
8+
# Target Audience
9+
Devlopers : This tool is designed for developers who need to quickly analyze log files and gain actionable insights using large language model (LLM). The system automatically refines prompts to ensure optimal results, offering developers an intuitive way to interact with log data and streamline their debugging process.
10+
11+
# Components
12+
- bat_ai.py: Defines the main workflow graph using LangGraph.
13+
- graphnodes.py: Contains the node implementations for the workflow graph.
14+
- multiagent.py: Implements the HybridRetriever class for document retrieval.
15+
- graphedges.py: Contains the implementation of the edges for decision making
16+
- binaryscroes.py: Contains the formatted output information
17+
- utils.py : It helps to implement the queries, retrieve relevant documents, grade their relevance, and generate responses using a multi-agent RAG system.
18+
- example.py: The script that analyzes a specified log file for errors based on a user-provided question, leveraging the workflow module to process and generate relevant insights.
19+
20+
![SW Architecture](<BAT.AI SW Architecture Diagram.drawio.png>)
21+
22+
# Key Features
23+
Hybrid document retrieval (BM25 + FAISS)
24+
Document relevance grading
25+
Self-corrective query transformation
26+
NVIDIA AI-powered embeddings and reranking
27+
28+
# Setup
29+
Install the required dependencies.
30+
Set up the NVIDIA API key in your environment.
31+
Prepare your document corpus and update the file path in the code.
32+
33+
# Code
34+
`python main.py path/to/your/logfile.txt --question "What are the critical errors in the log file?"`
35+
36+
# Software Components
37+
NVIDIA NIM Microservices
38+
- NIM of meta/llama-3.1-70b-instruct
39+
- Retriever Models
40+
- NIM of nvidia/llama-3_2-nv-embedqa-1b-v2
41+
- NIM of nvidia/llama-3_2-nv-rerankqa-1b-v2
42+
43+
44+
# Workflow
45+
46+
1. Retrieve Relevant Documents:
47+
The system searches for and retrieves log entries or documents that are most relevant to the user's query.
48+
2. Grade Document Relevance:
49+
The retrieved documents are evaluated and ranked based on their relevance to the input query.
50+
3. Generate a Response or Transform the Query:
51+
The system generates a response based on the most relevant documents or modifies the query to refine the search for better results.
52+
4. Evaluate the Generation and Decide to Output or Continue the Process:
53+
The quality of the generated response is assessed; if it meets the required standards, it’s outputted. If not, the query is further refined and the process repeats.
54+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
from langgraph.graph import END, StateGraph, START
3+
from graphnodes import Nodes
4+
from graphedges import Edge
5+
from typing_extensions import TypedDict
6+
from typing import List
7+
class GraphState(TypedDict):
8+
"""
9+
A current Graph State
10+
Attributes:
11+
path: log file path
12+
question: The current question being processed.
13+
This can be a user-inputted query.
14+
generation: The current LLM (Large Language Model) generation.
15+
This is used to keep track of the different stages of the language model's output.
16+
documents: A list of relevant documents that have been retrieved.
17+
These documents are used as input for the LLM to generate a response
18+
question: str
19+
sub_questions: List[str]
20+
generation: str
21+
documents: List[str]
22+
"""
23+
path : str
24+
question: str
25+
generation: str
26+
documents: List[str]
27+
28+
29+
bat_ai = StateGraph(GraphState)
30+
31+
# Define the nodes
32+
bat_ai.add_node("retrieve", Nodes.retrieve)
33+
bat_ai.add_node("rerank", Nodes.rerank)
34+
bat_ai.add_node("grade_documents", Nodes.grade_documents)
35+
bat_ai.add_node("generate", Nodes.generate)
36+
bat_ai.add_node("transform_query", Nodes.transform_query)
37+
38+
# Build graph
39+
bat_ai.add_edge(START, "retrieve")
40+
bat_ai.add_edge("retrieve", "rerank")
41+
bat_ai.add_edge("rerank", "grade_documents")
42+
bat_ai.add_conditional_edges(
43+
"grade_documents",
44+
Edge.decide_to_generate,
45+
{
46+
"transform_query": "transform_query",
47+
"generate": "generate",
48+
},
49+
)
50+
bat_ai.add_edge("transform_query", "retrieve")
51+
bat_ai.add_conditional_edges(
52+
"generate",
53+
Edge.grade_generation_vs_documents_and_question,
54+
{
55+
"not supported": "generate",
56+
"useful": END,
57+
"not useful": "transform_query",
58+
},
59+
)
60+
61+
app = bat_ai.compile()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from langchain_core.pydantic_v1 import BaseModel,Field
2+
# Data models
3+
class GradeDocuments(BaseModel):
4+
"""Binary score for relevance check on retrieved documents."""
5+
binary_score: str = Field(description="Documents are relevant to the question, 'yes' or 'no'")
6+
7+
class GradeHallucinations(BaseModel):
8+
"""Binary score for hallucination present in generation answer."""
9+
binary_score: str = Field(description="Answer is grounded in the facts, 'yes' or 'no'")
10+
11+
class GradeAnswer(BaseModel):
12+
"""Binary score to assess answer addresses question."""
13+
binary_score: str = Field(description="Answer addresses the question, 'yes' or 'no'")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import bat_ai
2+
import argparse
3+
4+
def process_input(question,file):
5+
inputs = {"question": question,"path" : file}
6+
for output in bat_ai.app.stream(inputs):
7+
for key, value in output.items():
8+
print(f"{key}:")
9+
10+
generation = value["generation"]
11+
text_without_newlines = generation.replace('\n', '')
12+
print(f"Output: {text_without_newlines}")
13+
return text_without_newlines
14+
15+
16+
if __name__ == "__main__":
17+
parser = argparse.ArgumentParser(description="Analyze log file for errors")
18+
parser.add_argument("log_path", help="Path to the log file")
19+
parser.add_argument("--question", default="Analyze the log file and find the failure messages from the same", help="Question to ask about the log file")
20+
args = parser.parse_args()
21+
resposne = process_input(args.question,args.log_path)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from utils import automation
2+
class Edge:
3+
def decide_to_generate(state):
4+
"""
5+
Determines whether to generate an answer, or re-generate a question.
6+
7+
Returns:
8+
str: Binary decision for next node to call
9+
"""
10+
11+
print("ASSESS GRADED DOCUMENTS")
12+
state["question"]
13+
filtered_documents = state["documents"]
14+
15+
if not filtered_documents:
16+
print(
17+
"DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY"
18+
)
19+
return "transform_query"
20+
print("---DECISION: GENERATE---")
21+
return "generate"
22+
23+
def grade_generation_vs_documents_and_question(state):
24+
"""
25+
Determines whether the generation is grounded in the document and answers question.
26+
27+
Returns:
28+
str: Decision for next node to call
29+
"""
30+
31+
question = state["question"]
32+
documents = state["documents"]
33+
generation = state["generation"]
34+
35+
print("GRADE GENERATED vs QUESTION")
36+
score = automation.answer_grader.invoke({"question": question, "generation": generation})
37+
grade = score.binary_score
38+
if grade == "yes":
39+
print("DECISION: GENERATION ADDRESSES QUESTION")
40+
return "useful"
41+
print("DECISION: GENERATION DOES NOT ADDRESS QUESTION")
42+
return "not useful"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from langchain_nvidia_ai_endpoints import NVIDIARerank
2+
api_key = "<add your api key>"
3+
from multiagent import HybridRetriever
4+
import io
5+
from contextlib import redirect_stdout, redirect_stderr
6+
from utils import automation
7+
8+
9+
class Nodes:
10+
@staticmethod
11+
def retrieve(state):
12+
print("---RETRIEVE---")
13+
question = state["question"]
14+
path = state["path"]
15+
hybrid_retriever_instance = HybridRetriever(path, api_key)
16+
hybrid_retriever = hybrid_retriever_instance.get_retriever()
17+
with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()):
18+
documents = hybrid_retriever.get_relevant_documents(question)
19+
20+
return {"documents": documents, "question": question}
21+
22+
@staticmethod
23+
def rerank(state):
24+
print("NVIDIA--RERANKER")
25+
question = state["question"]
26+
documents = state["documents"]
27+
reranker = NVIDIARerank(model="nvidia/llama-3.2-nv-rerankqa-1b-v2", api_key=api_key)
28+
documents = reranker.compress_documents(query=question, documents=documents)
29+
return {"documents": documents, "question": question}
30+
31+
@staticmethod
32+
def generate(state):
33+
print("GENERATE USING LLM")
34+
question = state["question"]
35+
documents = state["documents"]
36+
37+
generation = automation.rag_chain.invoke({"context": documents, "question": question})
38+
return {"documents": documents, "question": question, "generation": generation}
39+
40+
@staticmethod
41+
def grade_documents(state):
42+
print("CHECKING DOCUMENT RELEVANCE TO QUESTION")
43+
question = state["question"]
44+
ret_documents = state["documents"]
45+
46+
filtered_docs = []
47+
for doc in ret_documents:
48+
score = automation.retrieval_grader.invoke(
49+
{"question": question, "document": doc.page_content}
50+
)
51+
grade = score.binary_score
52+
if grade == "yes":
53+
print("---GRADE: DOCUMENT RELEVANT---")
54+
filtered_docs.append(doc)
55+
else:
56+
print("---GRADE: DOCUMENT NOT RELEVANT---")
57+
return {"documents": filtered_docs, "question": question}
58+
59+
@staticmethod
60+
def transform_query(state):
61+
62+
print("REWRITE PROMPT")
63+
question = state["question"]
64+
documents = state["documents"]
65+
66+
better_question = automation.question_rewriter.invoke({"question": question})
67+
print(f"actual query : {question} \n Transformed query:{better_question}")
68+
return {"documents": documents, "question": better_question}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
3+
from langchain.text_splitter import RecursiveCharacterTextSplitter
4+
from langchain_community.document_loaders import TextLoader
5+
from langchain.retrievers import EnsembleRetriever
6+
from langchain_community.retrievers import BM25Retriever
7+
from langchain_community.vectorstores.faiss import FAISS
8+
import argparse
9+
class HybridRetriever:
10+
def __init__(self, file_path, api_key):
11+
self.file_path = file_path
12+
os.environ["NVIDIA_API_KEY"] = api_key
13+
self.embeddings = self.initialize_nvidia_components()
14+
self.doc_splits = self.load_and_split_documents()
15+
self.bm25_retriever, self.faiss_retriever = self.create_retrievers()
16+
self.hybrid_retriever = self.create_hybrid_retriever()
17+
18+
def initialize_nvidia_components(self):
19+
embeddings =NVIDIAEmbeddings(model="nvidia/llama-3.2-nv-embedqa-1b-v2", truncate="NONE")
20+
return embeddings
21+
22+
def load_and_split_documents(self):
23+
loader = TextLoader(self.file_path)
24+
docs = loader.load()
25+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=5000, chunk_overlap=600)
26+
doc_splits = text_splitter.split_documents(docs)
27+
return doc_splits
28+
29+
def create_retrievers(self):
30+
bm25_retriever = BM25Retriever.from_documents(self.doc_splits)
31+
faiss_vectorstore = FAISS.from_documents(self.doc_splits, self.embeddings)
32+
faiss_retriever = faiss_vectorstore.as_retriever(search_type="similarity_score_threshold", search_kwargs={'score_threshold': 0.8})
33+
return bm25_retriever, faiss_retriever
34+
35+
def create_hybrid_retriever(self):
36+
hybrid_retriever = EnsembleRetriever(retrievers=[self.bm25_retriever, self.faiss_retriever], weights=[0.7, 0.3])
37+
return hybrid_retriever
38+
39+
def get_retriever(self):
40+
return self.hybrid_retriever
41+
42+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"qa_system_prompt": "Act as an experienced QA automation engineer with expertise in analyzing logs and extract details from the same. Your job is to analyze the provided log file and answer user questions to help them file an actionable bug. Answer solely based on the following context:\n<Documents>\n{context}",
3+
"qa_user_prompt": "{question}",
4+
"re_write_system": "You are an expert in prompt engineering for GenAI RAG application. Your job is to write effective prompt to help retrier in fetching accruate documents. You a question re-writer that converts an input question to a better version that is optimized for vectorstore retrieval.",
5+
"re_write_human": "\n\nHere is the initial prompt: \n\n {question} \n Formulate an improved prompt by keeping the original intent to make sure accurate results get generated.",
6+
"grade_system": "You are a grader assessing relevance of a retrieved document to a user question. It does not need to be a stringent test. The goal is to filter out erroneous retrievals. If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.",
7+
"grade_human": "Retrieved document: \n\n {document} \n\n User question: {question}",
8+
"hallucination_system": "You are a grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts. Give a binary score 'yes' or 'no'. 'Yes' means that the answer is grounded in / supported by the set of facts.",
9+
"hallucination_human": "Set of facts: \n\n {documents} \n\n LLM generation: {generation}",
10+
"answer_system": "You are a grader assessing whether an answer addresses / resolves a question. Give a binary score 'yes' or 'no'. 'Yes' means that the answer resolves the question.",
11+
"answer_human": "User question: \n\n {question} \n\n LLM generation: {generation}"
12+
}
3.49 KB
Binary file not shown.

0 commit comments

Comments
 (0)