Skip to content

Commit f5a539e

Browse files
authored
Merge pull request #28 from oracle-devrel/update
2 parents 538d521 + b00aca4 commit f5a539e

File tree

6 files changed

+211
-130
lines changed

6 files changed

+211
-130
lines changed

agentic_rag/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The system has the following features:
1010

1111
- Intelligent query routing
1212
- PDF processing using Docling for accurate text extraction and chunking
13-
- Persistent vector storage with ChromaDB (PDF and Websites)
13+
- Persistent vector storage with ChromaDB and Oracle Database 23ai (PDF and Websites)
1414
- Smart context retrieval and response generation
1515
- FastAPI-based REST API for document upload and querying
1616
- Support for both OpenAI-based agents or local, transformer-based agents (`Mistral-7B` by default)
@@ -116,9 +116,10 @@ python main.py
116116
117117
The API will be available at `http://localhost:8000`. You can then use the API endpoints as described in the API Endpoints section below.
118118
119-
### 2. Using the Gradio Interface
119+
### 2. Using the Gradio Interface (Recommended)
120120
121121
The system provides a user-friendly web interface using Gradio, which allows you to:
122+
- Select and pull `ollama` models directly from the interface
122123
- Upload and process PDF documents
123124
- Process web content from URLs
124125
- Chat with your documents using either local or OpenAI models
@@ -357,7 +358,7 @@ The system consists of several key components:
357358
1. **PDF Processor**: we use `docling` to extract and chunk text from PDF documents
358359
2. **Web Processor**: we use `trafilatura` to extract and chunk text from websites
359360
3. **GitHub Repository Processor**: we use `gitingest` to extract and chunk text from repositories
360-
4. **Vector Store**: Manages document embeddings and similarity search using `ChromaDB`
361+
4. **Vector Store**: Manages document embeddings and similarity search using `ChromaDB` and `Oracle Database 23ai`
361362
5. **RAG Agent**: Makes intelligent decisions about query routing and response generation
362363
- OpenAI Agent: Uses `gpt-4-turbo-preview` for high-quality responses, but requires an OpenAI API key
363364
- Local Agent: Uses `Mistral-7B` as an open-source alternative

agentic_rag/articles/kubernetes_rag.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ Then, we can start setting up the solution in our cluster by following these ste
146146
kubectl apply -n agentic-rag -f local-deployment/service.yaml
147147
```
148148
149+
If for some reason, after applying these, there's a `NoSchedule` policy being triggered, you can untaint the nodes and try again:
150+
151+
```bash
152+
kubectl taint nodes -l node.kubernetes.io/instance-type=VM.GPU.A10.1 nvidia.com/gpu:NoSchedule-
153+
# make sure to select your own instance shape if you're using a different type than A10 GPU.
154+
```
155+
149156
5. Monitor the Deployment
150157
151158
With the following commands, we can check the status of our pod:

agentic_rag/gradio_app.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ def chat(message: str, history: List[List[str]], agent_type: str, use_cot: bool,
9191
# Skip analysis for General Knowledge or when using standard chat interface (not CoT)
9292
skip_analysis = collection == "General Knowledge" or not use_cot
9393

94+
# Map collection names to actual collection names in vector store
95+
collection_mapping = {
96+
"PDF Collection": "pdf_documents",
97+
"Repository Collection": "repository_documents",
98+
"Web Knowledge Base": "web_documents",
99+
"General Knowledge": "general_knowledge"
100+
}
101+
102+
# Get the actual collection name
103+
actual_collection = collection_mapping.get(collection, "pdf_documents")
104+
94105
# Parse agent type to determine model and quantization
95106
quantization = None
96107
model_name = None
@@ -354,26 +365,35 @@ def create_interface():
354365
repo_button = gr.Button("Process Repository")
355366
repo_output = gr.Textbox(label="Repository Processing Output")
356367

368+
# Define collection choices once to ensure consistency
369+
collection_choices = [
370+
"PDF Collection",
371+
"Repository Collection",
372+
"Web Knowledge Base",
373+
"General Knowledge"
374+
]
375+
357376
with gr.Tab("Standard Chat Interface"):
358377
with gr.Row():
359378
with gr.Column(scale=1):
360-
# Create model choices with quantization options
361379
standard_agent_dropdown = gr.Dropdown(
362380
choices=model_choices,
363381
value=model_choices[0] if model_choices else None,
364382
label="Select Agent"
365383
)
366384
with gr.Column(scale=1):
367385
standard_collection_dropdown = gr.Dropdown(
368-
choices=["PDF Collection", "Repository Collection", "General Knowledge"],
369-
value="PDF Collection",
370-
label="Knowledge Collection"
386+
choices=collection_choices,
387+
value=collection_choices[0],
388+
label="Select Knowledge Base",
389+
info="Choose which knowledge base to use for answering questions"
371390
)
372391
gr.Markdown("""
373392
> **Collection Selection**:
374393
> - This interface ALWAYS uses the selected collection without performing query analysis.
375394
> - "PDF Collection": Will ALWAYS search the PDF documents regardless of query type.
376395
> - "Repository Collection": Will ALWAYS search the repository code regardless of query type.
396+
> - "Web Knowledge Base": Will ALWAYS search web content regardless of query type.
377397
> - "General Knowledge": Will ALWAYS use the model's built-in knowledge without searching collections.
378398
""")
379399
standard_chatbot = gr.Chatbot(height=400)
@@ -385,23 +405,24 @@ def create_interface():
385405
with gr.Tab("Chain of Thought Chat Interface"):
386406
with gr.Row():
387407
with gr.Column(scale=1):
388-
# Create model choices with quantization options
389408
cot_agent_dropdown = gr.Dropdown(
390409
choices=model_choices,
391410
value=model_choices[0] if model_choices else None,
392411
label="Select Agent"
393412
)
394413
with gr.Column(scale=1):
395414
cot_collection_dropdown = gr.Dropdown(
396-
choices=["PDF Collection", "Repository Collection", "General Knowledge"],
397-
value="PDF Collection",
398-
label="Knowledge Collection"
415+
choices=collection_choices,
416+
value=collection_choices[0],
417+
label="Select Knowledge Base",
418+
info="Choose which knowledge base to use for answering questions"
399419
)
400420
gr.Markdown("""
401421
> **Collection Selection**:
402422
> - When a specific collection is selected, the system will ALWAYS use that collection without analysis:
403423
> - "PDF Collection": Will ALWAYS search the PDF documents.
404424
> - "Repository Collection": Will ALWAYS search the repository code.
425+
> - "Web Knowledge Base": Will ALWAYS search web content.
405426
> - "General Knowledge": Will ALWAYS use the model's built-in knowledge.
406427
> - This interface shows step-by-step reasoning and may perform query analysis when needed.
407428
""")
@@ -485,6 +506,7 @@ def create_interface():
485506
- Select which knowledge collection to query:
486507
- **PDF Collection**: Always searches PDF documents
487508
- **Repository Collection**: Always searches code repositories
509+
- **Web Knowledge Base**: Always searches web content
488510
- **General Knowledge**: Uses the model's built-in knowledge without searching collections
489511
490512
3. **Chain of Thought Chat Interface**:

agentic_rag/img/architecture.png

-2.94 KB
Loading

agentic_rag/local_rag_agent.py

Lines changed: 94 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -245,126 +245,132 @@ def process_query(self, query: str) -> Dict[str, Any]:
245245
else:
246246
return self._generate_general_response(query)
247247
else:
248-
# For PDF or Repository collections, use context-based processing
248+
# For PDF, Repository, or Web collections, use context-based processing
249249
if self.use_cot:
250250
return self._process_query_with_cot(query)
251251
else:
252252
return self._process_query_standard(query)
253253

254254
def _process_query_with_cot(self, query: str) -> Dict[str, Any]:
255-
"""Process query using Chain of Thought reasoning with multiple agents"""
256-
logger.info("Processing query with Chain of Thought reasoning")
257-
258-
# Get initial context based on selected collection
259-
initial_context = []
260-
if self.collection == "PDF Collection":
261-
logger.info(f"Retrieving context from PDF Collection for query: '{query}'")
262-
pdf_context = self.vector_store.query_pdf_collection(query)
263-
initial_context.extend(pdf_context)
264-
logger.info(f"Retrieved {len(pdf_context)} chunks from PDF Collection")
265-
# Don't log individual sources to keep console clean
266-
elif self.collection == "Repository Collection":
267-
logger.info(f"Retrieving context from Repository Collection for query: '{query}'")
268-
repo_context = self.vector_store.query_repo_collection(query)
269-
initial_context.extend(repo_context)
270-
logger.info(f"Retrieved {len(repo_context)} chunks from Repository Collection")
271-
# Don't log individual sources to keep console clean
272-
# For General Knowledge, no context is needed
273-
else:
274-
logger.info("Using General Knowledge collection, no context retrieval needed")
275-
255+
"""Process query using Chain of Thought reasoning"""
276256
try:
277-
# Step 1: Planning
278-
logger.info("Step 1: Planning")
279-
if not self.agents or "planner" not in self.agents:
280-
logger.warning("No planner agent available, using direct response")
281-
return self._generate_general_response(query)
257+
# Get context based on collection type
258+
if self.collection == "PDF Collection":
259+
context = self.vector_store.query_pdf_collection(query)
260+
elif self.collection == "Repository Collection":
261+
context = self.vector_store.query_repo_collection(query)
262+
elif self.collection == "Web Knowledge Base":
263+
context = self.vector_store.query_web_collection(query)
264+
else:
265+
context = []
266+
267+
# Log number of chunks retrieved
268+
logger.info(f"Retrieved {len(context)} chunks from {self.collection}")
282269

283-
plan = self.agents["planner"].plan(query, initial_context)
284-
logger.info(f"Generated plan:\n{plan}")
270+
# Create agents if not already created
271+
if not self.agents:
272+
self.agents = create_agents(self.llm, self.vector_store)
285273

286-
# Step 2: Research each step (if researcher is available)
287-
logger.info("Step 2: Research")
274+
# Get planning step
275+
try:
276+
planning_result = self.agents["planner"].plan(query, context)
277+
logger.info("Planning step completed")
278+
except Exception as e:
279+
logger.error(f"Error in planning step: {str(e)}")
280+
logger.info("Falling back to general response")
281+
return self._generate_general_response(query)
282+
283+
# Get research step
288284
research_results = []
289-
if self.agents.get("researcher") is not None and initial_context:
290-
for step in plan.split("\n"):
285+
if self.agents.get("researcher") is not None and context:
286+
for step in planning_result.split("\n"):
291287
if not step.strip():
292288
continue
293-
step_research = self.agents["researcher"].research(query, step)
294-
research_results.append({"step": step, "findings": step_research})
295-
# Don't log source indices to keep console clean
296-
logger.info(f"Research for step: {step}")
289+
try:
290+
step_research = self.agents["researcher"].research(query, step)
291+
# Extract findings from research result
292+
findings = step_research.get("findings", []) if isinstance(step_research, dict) else []
293+
research_results.append({"step": step, "findings": findings})
294+
295+
# Log which sources were used for this step
296+
try:
297+
source_indices = [context.index(finding) + 1 for finding in findings if finding in context]
298+
logger.info(f"Research for step: {step}\nUsing sources: {source_indices}")
299+
except ValueError as ve:
300+
logger.warning(f"Could not find some findings in initial context: {str(ve)}")
301+
except Exception as e:
302+
logger.error(f"Error during research for step '{step}': {str(e)}")
303+
research_results.append({"step": step, "findings": []})
297304
else:
298305
# If no researcher or no context, use the steps directly
299-
research_results = [{"step": step, "findings": []} for step in plan.split("\n") if step.strip()]
306+
research_results = [{"step": step, "findings": []} for step in planning_result.split("\n") if step.strip()]
300307
logger.info("No research performed (no researcher agent or no context available)")
301308

302-
# Step 3: Reasoning about each step
303-
logger.info("Step 3: Reasoning")
309+
# Get reasoning step
310+
reasoning_steps = []
304311
if not self.agents.get("reasoner"):
305312
logger.warning("No reasoner agent available, using direct response")
306313
return self._generate_general_response(query)
307314

308-
reasoning_steps = []
309315
for result in research_results:
310-
step_reasoning = self.agents["reasoner"].reason(
311-
query,
312-
result["step"],
313-
result["findings"] if result["findings"] else [{"content": "Using general knowledge", "metadata": {"source": "General Knowledge"}}]
314-
)
315-
reasoning_steps.append(step_reasoning)
316-
# Log just the step, not the full reasoning
317-
logger.info(f"Reasoning for step: {result['step']}")
316+
try:
317+
step_reasoning = self.agents["reasoner"].reason(
318+
query,
319+
result["step"],
320+
result["findings"] if result["findings"] else [{"content": "Using general knowledge", "metadata": {"source": "General Knowledge"}}]
321+
)
322+
reasoning_steps.append(step_reasoning)
323+
logger.info(f"Reasoning for step: {result['step']}\n{step_reasoning}")
324+
except Exception as e:
325+
logger.error(f"Error in reasoning for step '{result['step']}': {str(e)}")
326+
reasoning_steps.append(f"Error in reasoning for this step: {str(e)}")
318327

319-
# Step 4: Synthesize final answer
320-
logger.info("Step 4: Synthesis")
328+
# Get synthesis step
321329
if not self.agents.get("synthesizer"):
322330
logger.warning("No synthesizer agent available, using direct response")
323331
return self._generate_general_response(query)
324332

325-
final_answer = self.agents["synthesizer"].synthesize(query, reasoning_steps)
326-
logger.info("Final answer synthesized successfully")
333+
try:
334+
synthesis_result = self.agents["synthesizer"].synthesize(query, reasoning_steps)
335+
logger.info("Synthesis step completed")
336+
except Exception as e:
337+
logger.error(f"Error in synthesis step: {str(e)}")
338+
logger.info("Falling back to general response")
339+
return self._generate_general_response(query)
327340

328341
return {
329-
"answer": final_answer,
330-
"context": initial_context,
331-
"reasoning_steps": reasoning_steps
342+
"answer": synthesis_result["answer"],
343+
"reasoning_steps": reasoning_steps,
344+
"context": context
332345
}
346+
333347
except Exception as e:
334348
logger.error(f"Error in CoT processing: {str(e)}")
335-
logger.info("Falling back to general response")
336-
return self._generate_general_response(query)
349+
raise
337350

338351
def _process_query_standard(self, query: str) -> Dict[str, Any]:
339-
"""Process query using standard approach without Chain of Thought"""
340-
# Initialize context variables
341-
pdf_context = []
342-
repo_context = []
343-
344-
# Get context based on selected collection
345-
if self.collection == "PDF Collection":
346-
logger.info(f"Retrieving context from PDF Collection for query: '{query}'")
347-
pdf_context = self.vector_store.query_pdf_collection(query)
348-
logger.info(f"Retrieved {len(pdf_context)} chunks from PDF Collection")
349-
# Don't log individual sources to keep console clean
350-
elif self.collection == "Repository Collection":
351-
logger.info(f"Retrieving context from Repository Collection for query: '{query}'")
352-
repo_context = self.vector_store.query_repo_collection(query)
353-
logger.info(f"Retrieved {len(repo_context)} chunks from Repository Collection")
354-
# Don't log individual sources to keep console clean
355-
356-
# Combine all context
357-
all_context = pdf_context + repo_context
358-
359-
# Generate response using context if available, otherwise use general knowledge
360-
if all_context:
361-
logger.info(f"Generating response using {len(all_context)} context chunks")
362-
response = self._generate_response(query, all_context)
363-
else:
364-
logger.info("No context found, using general knowledge")
365-
response = self._generate_general_response(query)
366-
367-
return response
352+
"""Process query using standard RAG approach"""
353+
try:
354+
# Get context based on collection type
355+
if self.collection == "PDF Collection":
356+
context = self.vector_store.query_pdf_collection(query)
357+
elif self.collection == "Repository Collection":
358+
context = self.vector_store.query_repo_collection(query)
359+
elif self.collection == "Web Knowledge Base":
360+
context = self.vector_store.query_web_collection(query)
361+
else:
362+
context = []
363+
364+
# Log number of chunks retrieved
365+
logger.info(f"Retrieved {len(context)} chunks from {self.collection}")
366+
367+
# Generate response using context
368+
response = self._generate_response(query, context)
369+
return response
370+
371+
except Exception as e:
372+
logger.error(f"Error in standard processing: {str(e)}")
373+
raise
368374

369375
def _generate_text(self, prompt: str, max_length: int = 512) -> str:
370376
"""Generate text using the local model"""
@@ -456,7 +462,7 @@ def main():
456462
parser.add_argument("--model", default="mistralai/Mistral-7B-Instruct-v0.2", help="Model to use")
457463
parser.add_argument("--quiet", action="store_true", help="Disable verbose logging")
458464
parser.add_argument("--use-cot", action="store_true", help="Enable Chain of Thought reasoning")
459-
parser.add_argument("--collection", choices=["PDF Collection", "Repository Collection", "General Knowledge"],
465+
parser.add_argument("--collection", choices=["PDF Collection", "Repository Collection", "General Knowledge", "Web Knowledge Base"],
460466
help="Specify which collection to query")
461467
parser.add_argument("--skip-analysis", action="store_true", help="Skip query analysis step")
462468
parser.add_argument("--verbose", action="store_true", help="Show full content of sources")

0 commit comments

Comments
 (0)