Skip to content

Commit 4735c0a

Browse files
Merge pull request #1 from CodeWithSuleman/003-rag-agent-fastapi
003 rag agent fastapi
2 parents 720b0cc + 9f451fd commit 4735c0a

60 files changed

Lines changed: 3351 additions & 72 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gemini/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"github": {
44
"httpUrl": "https://api.githubcopilot.com/mcp/",
55
"headers": {
6-
"Authorization": "YOUR_GITHUB_TOKEN"
6+
"Authorization": "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"
77
}
88
}
99
}

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Python
2+
__pycache__/
3+
*.pyc
4+
*.pyo
5+
*.pyd
6+
.Python
7+
env/
8+
venv/
9+
.venv/
10+
11+
# Environment variables
12+
.env
13+
.env.*
14+
!.env.example
15+
16+
# IDEs
17+
.idea/
18+
.vscode/
19+
20+
# Other
21+
*.DS_Store
22+
*.tmp
23+
*.swp
24+
25+
.gemini/settings.json

GEMINI.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ Auto-generated from all feature plans. Last updated: 2025-12-06
77
- N/A (Content and examples primarily stored in markdown and code files; no persistent database storage for this module's direct content. Synthetic data generation will output to local files/directories as part of demos.) (004-ai-robot-brain-module)
88
- Python 3.10+ (for Whisper, LLM integration, ROS 2 actions), Markdown (for Docusaurus) + OpenAI Whisper, a suitable LLM (e.g., OpenAI API, local LLM), ROS 2 (Humble/Iron/Rolling), Isaac Sim (for humanoid simulation), Isaac ROS (for perception/navigation primitives), Docusaurus (005-capstone-autonomous-humanoid)
99
- N/A (Content and examples primarily stored in markdown and code files; no persistent database storage for this module's direct content. Whisper audio/text, LLM prompts/responses, and ROS 2 action logs will be handled ephemerally within demos.) (005-capstone-autonomous-humanoid)
10+
- Python 3.11 + `uv`, `cohere`, `qdrant-client`, `beautifulsoup4`, `requests`, `python-dotenv` (001-embedding-pipeline-setup)
11+
- Qdrant Cloud, local `.env` file for credentials. (001-embedding-pipeline-setup)
12+
- [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION] + [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION] (002-rag-retrieval-validation)
13+
- [if applicable, e.g., PostgreSQL, CoreData, files or N/A] (002-rag-retrieval-validation)
14+
- Python 3.11+ + `cohere`, `qdrant-client`, `uv` (002-rag-retrieval-validation)
15+
- Python 3.11+ + `fastapi`, `openai`, `qdrant-client`, `cohere`, `uvicorn`, `python-dotenv` (003-rag-agent-fastapi)
16+
- Qdrant Cloud (for document retrieval) (003-rag-agent-fastapi)
1017

1118
- Python 3.11 (for Gazebo/ROS 2 examples), C# (for Unity examples), Markdown (for Docusaurus) + Gazebo, Unity, ROS 2 (Humble), Docusaurus (003-digital-twin-module)
1219

@@ -26,10 +33,10 @@ cd src; pytest; ruff check .
2633
Python 3.11 (for Gazebo/ROS 2 examples), C# (for Unity examples), Markdown (for Docusaurus): Follow standard conventions
2734

2835
## Recent Changes
29-
- 005-capstone-autonomous-humanoid: Added Python 3.10+ (for Whisper, LLM integration, ROS 2 actions), Markdown (for Docusaurus) + OpenAI Whisper, a suitable LLM (e.g., OpenAI API, local LLM), ROS 2 (Humble/Iron/Rolling), Isaac Sim (for humanoid simulation), Isaac ROS (for perception/navigation primitives), Docusaurus
30-
- 004-ai-robot-brain-module: Added Python 3.10+ (for Isaac Sim, Isaac ROS, Nav2 examples, and ROS 2), Markdown (for Docusaurus) + NVIDIA Isaac Sim, NVIDIA Isaac ROS, ROS 2 (Humble/Iron/Rolling), Nav2, Docusaurus
36+
- 003-rag-agent-fastapi: Added Python 3.11+ + `fastapi`, `openai`, `qdrant-client`, `cohere`, `uvicorn`, `python-dotenv`
37+
- 003-rag-agent-fastapi: Added [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION] + [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
38+
- 002-rag-retrieval-validation: Added Python 3.11+ + `cohere`, `qdrant-client`, `uv`
3139

32-
- 003-digital-twin-module: Added Python 3.11 (for Gazebo/ROS 2 examples), C# (for Unity examples), Markdown (for Docusaurus) + Gazebo, Unity, ROS 2 (Humble), Docusaurus
3340

3441
<!-- MANUAL ADDITIONS START -->
3542
<!-- MANUAL ADDITIONS END -->

backend/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
QDRANT_API_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.Hohnd1e0bpCodE72CxrM_YcUqjpitvzMk9mi5z-J1nw"
2+
QDRANT_URL="https://02ff366a-3f5b-45be-9c8b-8b59f073c69f.europe-west3-0.gcp.cloud.qdrant.io:6333"
3+
COHERE_API_KEY="UpzOt8JFZRVEyUmwkMQ5RA9haunk5txvHsxBblPo"
4+
GEMINI_API_KEY="AIzaSyCYXZ4ngw0eUzKouiA3a00Fb491TtDkCwk"

backend/.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

backend/README.md

Whitespace-only changes.

backend/a.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import google.generativeai as genai
2+
import os
3+
from dotenv import load_dotenv
4+
load_dotenv()
5+
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
6+
for m in genai.list_models():
7+
print(m.name)

backend/database.py

Lines changed: 0 additions & 68 deletions
This file was deleted.

backend/main.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import os
2+
import uuid
3+
import logging
4+
import cohere
5+
from qdrant_client import QdrantClient
6+
from qdrant_client.http.models import Distance, VectorParams, PointStruct
7+
import requests
8+
from bs4 import BeautifulSoup
9+
from dotenv import load_dotenv
10+
from urllib.parse import urljoin, urlparse
11+
12+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13+
14+
15+
def get_all_urls(url, visited, base_domain):
16+
"""
17+
Recursively crawls a website to get all unique URLs within the same domain.
18+
"""
19+
if url in visited:
20+
return
21+
22+
if urlparse(url).netloc != base_domain:
23+
return
24+
25+
visited.add(url)
26+
try:
27+
response = requests.get(url, timeout=5)
28+
response.raise_for_status()
29+
soup = BeautifulSoup(response.content, 'html.parser')
30+
31+
for link in soup.find_all('a', href=True):
32+
absolute_link = urljoin(url, link['href']).split('#')[0]
33+
if absolute_link not in visited and urlparse(absolute_link).netloc == base_domain:
34+
get_all_urls(absolute_link, visited, base_domain)
35+
except requests.RequestException as e:
36+
logging.warning(f"Could not crawl {url}: {e}")
37+
except Exception as e:
38+
logging.error(f"An unexpected error occurred while crawling {url}: {e}")
39+
40+
41+
def extract_text_from_url(url):
42+
"""
43+
Extracts the main text content from a URL.
44+
"""
45+
try:
46+
response = requests.get(url, timeout=5)
47+
response.raise_for_status()
48+
soup = BeautifulSoup(response.content, 'html.parser')
49+
main_content = soup.find('main')
50+
if main_content:
51+
return main_content.get_text(separator=' ', strip=True)
52+
return ""
53+
except requests.RequestException as e:
54+
logging.warning(f"Could not fetch {url}: {e}")
55+
return ""
56+
except Exception as e:
57+
logging.error(f"An unexpected error occurred while extracting text from {url}: {e}")
58+
return ""
59+
60+
61+
def chunk_text(text, chunk_size=384, overlap=48):
62+
"""
63+
Splits a text into chunks of a specified size with a given overlap.
64+
"""
65+
words = text.split()
66+
if not words:
67+
return []
68+
69+
chunks = []
70+
for i in range(0, len(words), chunk_size - overlap):
71+
chunk = words[i:i + chunk_size]
72+
chunks.append(' '.join(chunk))
73+
return chunks
74+
75+
76+
def embed_chunks(chunks, co_client):
77+
"""
78+
Generates vector embeddings for a list of text chunks.
79+
"""
80+
if not chunks:
81+
return []
82+
83+
try:
84+
response = co_client.embed(texts=chunks, model="embed-english-v2.0", truncate="END")
85+
return response.embeddings
86+
except Exception as e:
87+
logging.error(f"An unexpected error occurred while embedding chunks: {e}")
88+
return []
89+
90+
91+
def save_chunks_to_qdrant(qdrant, collection_name, chunks, embeddings, chunk_to_url_map):
92+
"""
93+
Saves chunks and their embeddings to Qdrant.
94+
"""
95+
if not embeddings:
96+
return
97+
98+
points = []
99+
for i, embedding in enumerate(embeddings):
100+
points.append(PointStruct(
101+
id=str(uuid.uuid4()),
102+
vector=embedding,
103+
payload={"text": chunks[i], "source_url": chunk_to_url_map[i]}
104+
))
105+
106+
qdrant.upsert(collection_name=collection_name, wait=True, points=points)
107+
108+
109+
def main():
110+
"""
111+
Main function to run the embedding pipeline.
112+
"""
113+
load_dotenv()
114+
115+
# Cohere client
116+
cohere_api_key = os.getenv("COHERE_API_KEY")
117+
co = cohere.Client(cohere_api_key)
118+
119+
# Qdrant client
120+
qdrant_url = os.getenv("QDRANT_URL")
121+
qdrant_api_key = os.getenv("QDRANT_API_KEY")
122+
qdrant = QdrantClient(url=qdrant_url, api_key=qdrant_api_key)
123+
124+
# Collection setup
125+
collection_name = "rag_embedding"
126+
127+
# Forcefully delete and recreate with correct dimensions
128+
logging.info(f"Recreating collection '{collection_name}' with 4096 dims...")
129+
qdrant.recreate_collection(
130+
collection_name=collection_name,
131+
vectors_config=VectorParams(size=4096, distance=Distance.COSINE),
132+
)
133+
logging.info(f"Collection '{collection_name}' is now FRESH and READY.")
134+
135+
logging.info("Pipeline starting...")
136+
root_url = "https://codewithsuleman.github.io/hackathon-physical-AI-humanoid-textbook/"
137+
base_domain = urlparse(root_url).netloc
138+
visited_urls = set()
139+
logging.info(f"Crawling starting from {root_url}...")
140+
get_all_urls(root_url, visited_urls, base_domain)
141+
logging.info(f"Found {len(visited_urls)} URLs to process.")
142+
143+
all_chunks = []
144+
chunk_to_url_map = []
145+
for url in visited_urls:
146+
logging.info(f"Extracting and chunking text from {url}...")
147+
text = extract_text_from_url(url)
148+
if text:
149+
chunks = chunk_text(text)
150+
all_chunks.extend(chunks)
151+
chunk_to_url_map.extend([url] * len(chunks))
152+
153+
logging.info(f"Total chunks to process: {len(all_chunks)}")
154+
155+
batch_size = 96
156+
for i in range(0, len(all_chunks), batch_size):
157+
batch_chunks = all_chunks[i:i + batch_size]
158+
batch_urls = chunk_to_url_map[i:i + batch_size]
159+
logging.info(f"Processing batch {i // batch_size + 1}...")
160+
161+
batch_embeddings = embed_chunks(batch_chunks, co)
162+
if batch_embeddings:
163+
save_chunks_to_qdrant(qdrant, collection_name, batch_chunks, batch_embeddings, batch_urls)
164+
165+
logging.info("Pipeline finished successfully!")
166+
167+
168+
if __name__ == "__main__":
169+
main()

backend/pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[project]
2+
name = "backend"
3+
version = "0.1.0"
4+
description = "Add your description here"
5+
readme = "README.md"
6+
requires-python = ">=3.13"
7+
dependencies = [
8+
"google-generativeai>=0.8.6",
9+
]

0 commit comments

Comments
 (0)