Arif commited on
Commit
704b133
·
1 Parent(s): 92ab414

Demo data added for query test

Browse files
app/core/llm.py CHANGED
@@ -1,22 +1,15 @@
1
- from langchain_ollama import ChatOllama
2
- try:
3
- from langchain.prompts import PromptTemplate
4
- except ImportError:
5
- from langchain_core.prompts import PromptTemplate
6
-
7
- from langchain_core.output_parsers import StrOutputParser
8
 
9
  class OllamaLLM:
10
  def __init__(self, base_url: str, model: str):
11
- self.llm = ChatOllama(
12
- base_url=base_url,
13
- model=model,
14
- temperature=0.2, # Lower for more factual responses
15
- )
16
 
17
- # RAG-specific prompt template
18
- self.prompt_template = PromptTemplate(
19
- template="""You are a helpful AI assistant. Use the following context to answer the question accurately and concisely.
20
 
21
  Context:
22
  {context}
@@ -29,30 +22,31 @@ Instructions:
29
  - Keep your answer clear and concise (max 3-5 sentences)
30
  - Cite specific parts of the context when relevant
31
 
32
- Answer:""",
33
- input_variables=["context", "question"]
34
- )
35
 
36
- self.chain = self.prompt_template | self.llm | StrOutputParser()
37
-
38
- def generate(self, question: str, context: str) -> str:
39
- """Generate answer using RAG context"""
40
- return self.chain.invoke({
41
- "question": question,
42
- "context": context
43
- })
44
-
45
- async def generate_stream(self, question: str, context: str):
46
- """Stream LLM responses"""
47
- async for chunk in self.chain.astream({
48
- "question": question,
49
- "context": context
50
- }):
51
- yield chunk
52
-
53
- from langchain.memory import ConversationBufferMemory
54
-
55
- class ConversationalRAG:
56
- def __init__(self, rag_chain):
57
- self.rag_chain = rag_chain
58
- self.memory = ConversationBufferMemory()
 
 
 
 
1
+ import requests
2
+ import json
3
+ from typing import Dict
 
 
 
 
4
 
5
  class OllamaLLM:
6
  def __init__(self, base_url: str, model: str):
7
+ self.base_url = base_url.rstrip('/')
8
+ self.model = model
 
 
 
9
 
10
+ def generate(self, question: str, context: str) -> str:
11
+ """Generate answer using RAG context"""
12
+ prompt = f"""You are a helpful AI assistant. Use the following context to answer the question accurately and concisely.
13
 
14
  Context:
15
  {context}
 
22
  - Keep your answer clear and concise (max 3-5 sentences)
23
  - Cite specific parts of the context when relevant
24
 
25
+ Answer:"""
 
 
26
 
27
+ try:
28
+ response = requests.post(
29
+ f"{self.base_url}/api/generate",
30
+ json={
31
+ "model": self.model,
32
+ "prompt": prompt,
33
+ "stream": False,
34
+ "options": {
35
+ "temperature": 0.2,
36
+ "top_p": 0.9,
37
+ "top_k": 40
38
+ }
39
+ },
40
+ timeout=60
41
+ )
42
+
43
+ if response.status_code == 200:
44
+ result = response.json()
45
+ return result.get("response", "Error generating response")
46
+ else:
47
+ return f"Error: Ollama returned status {response.status_code}"
48
+
49
+ except requests.exceptions.RequestException as e:
50
+ return f"Error connecting to Ollama: {str(e)}"
51
+ except Exception as e:
52
+ return f"Unexpected error: {str(e)}"
app/core/vector_store.py CHANGED
@@ -12,17 +12,20 @@ class VectorStore:
12
 
13
  def _ensure_collection(self):
14
  """Create collection if it doesn't exist"""
15
- collections = self.client.get_collections().collections
16
- collection_names = [col.name for col in collections]
17
-
18
- if self.collection_name not in collection_names:
19
- self.client.create_collection(
20
- collection_name=self.collection_name,
21
- vectors_config=VectorParams(
22
- size=self.vector_size,
23
- distance=Distance.COSINE
 
 
24
  )
25
- )
 
26
 
27
  def add_documents(self, texts: List[str], embeddings: List[List[float]],
28
  metadata: List[Dict] = None):
@@ -50,10 +53,14 @@ class VectorStore:
50
  def search(self, query_embedding: List[float], limit: int = 5,
51
  score_threshold: float = 0.7):
52
  """Search for similar documents"""
53
- results = self.client.search(
54
- collection_name=self.collection_name,
55
- query_vector=query_embedding,
56
- limit=limit,
57
- score_threshold=score_threshold
58
- )
59
- return results
 
 
 
 
 
12
 
13
  def _ensure_collection(self):
14
  """Create collection if it doesn't exist"""
15
+ try:
16
+ collections = self.client.get_collections().collections
17
+ collection_names = [col.name for col in collections]
18
+
19
+ if self.collection_name not in collection_names:
20
+ self.client.create_collection(
21
+ collection_name=self.collection_name,
22
+ vectors_config=VectorParams(
23
+ size=self.vector_size,
24
+ distance=Distance.COSINE
25
+ )
26
  )
27
+ except Exception as e:
28
+ print(f"Warning: Could not connect to Qdrant: {e}")
29
 
30
  def add_documents(self, texts: List[str], embeddings: List[List[float]],
31
  metadata: List[Dict] = None):
 
53
  def search(self, query_embedding: List[float], limit: int = 5,
54
  score_threshold: float = 0.7):
55
  """Search for similar documents"""
56
+ try:
57
+ results = self.client.search(
58
+ collection_name=self.collection_name,
59
+ query_vector=query_embedding,
60
+ limit=limit,
61
+ score_threshold=score_threshold
62
+ )
63
+ return results
64
+ except Exception as e:
65
+ print(f"Search error: {e}")
66
+ return []
app/models/schemas.py CHANGED
@@ -1,10 +1,6 @@
1
  from pydantic import BaseModel
2
  from typing import List, Optional
3
 
4
- class DocumentUpload(BaseModel):
5
- filename: str
6
- content: str
7
-
8
  class QueryRequest(BaseModel):
9
  question: str
10
  top_k: Optional[int] = 5
 
1
  from pydantic import BaseModel
2
  from typing import List, Optional
3
 
 
 
 
 
4
  class QueryRequest(BaseModel):
5
  question: str
6
  top_k: Optional[int] = 5
app/services/document_processor.py CHANGED
@@ -4,7 +4,6 @@ import pypdf
4
  from docx import Document
5
  from langchain_text_splitters import RecursiveCharacterTextSplitter
6
 
7
-
8
  class DocumentProcessor:
9
  def __init__(self, chunk_size: int = 500, chunk_overlap: int = 50):
10
  self.text_splitter = RecursiveCharacterTextSplitter(
 
4
  from docx import Document
5
  from langchain_text_splitters import RecursiveCharacterTextSplitter
6
 
 
7
  class DocumentProcessor:
8
  def __init__(self, chunk_size: int = 500, chunk_overlap: int = 50):
9
  self.text_splitter = RecursiveCharacterTextSplitter(
data/documents/ai_basics.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Artificial Intelligence is transforming the world. Machine learning enables computers to learn from data. Deep learning uses neural networks with multiple layers. Natural language processing helps computers understand human language. Computer vision allows machines to interpret visual information.
data/documents/ml_concepts.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Machine Learning Fundamentals
2
+
3
+ Supervised Learning: Training models with labeled data. Examples include classification and regression tasks.
4
+
5
+ Unsupervised Learning: Finding patterns in unlabeled data. Clustering and dimensionality reduction are common techniques.
6
+
7
+ Reinforcement Learning: Learning through trial and error with rewards and penalties. Used in robotics and game playing.
8
+
9
+ Feature Engineering: The process of selecting and transforming variables to improve model performance.
10
+
11
+ Model Evaluation: Using metrics like accuracy, precision, recall, and F1-score to assess model quality.
data/documents/rag_explanation.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Retrieval-Augmented Generation (RAG) System
2
+
3
+ RAG combines retrieval and generation to create more accurate AI responses.
4
+
5
+ The process works in three steps:
6
+ 1. Document Ingestion: Documents are split into chunks and converted to vector embeddings
7
+ 2. Retrieval: When a query comes in, relevant chunks are found using similarity search
8
+ 3. Generation: The LLM uses retrieved context to generate accurate, grounded answers
9
+
10
+ Benefits of RAG:
11
+ - Reduces hallucinations by grounding responses in actual documents
12
+ - Enables knowledge updates without retraining models
13
+ - Provides source citations for transparency
14
+ - Works with private, domain-specific data
15
+
16
+ RAG is ideal for enterprise knowledge bases, customer support, and research applications.
main.py CHANGED
@@ -1,6 +1,144 @@
1
- def main():
2
- print("Hello from generative-ai-portfolio-project!")
 
 
 
 
 
 
 
 
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  if __name__ == "__main__":
6
- main()
 
 
1
+ from fastapi import FastAPI, UploadFile, File, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from app.config import get_settings
4
+ from app.core.embeddings import EmbeddingGenerator
5
+ from app.core.vector_store import VectorStore
6
+ from app.core.llm import OllamaLLM
7
+ from app.services.document_processor import DocumentProcessor
8
+ from app.services.rag_chain import RAGChain
9
+ from app.models.schemas import QueryRequest, QueryResponse, IngestResponse
10
+ import tempfile
11
+ import os
12
 
13
+ # Initialize FastAPI app
14
+ app = FastAPI(
15
+ title="RAG Portfolio Project",
16
+ description="Production-grade Retrieval-Augmented Generation system",
17
+ version="1.0.0"
18
+ )
19
+
20
+ # Add CORS middleware
21
+ app.add_middleware(
22
+ CORSMiddleware,
23
+ allow_origins=["*"],
24
+ allow_credentials=True,
25
+ allow_methods=["*"],
26
+ allow_headers=["*"],
27
+ )
28
+
29
+ # Initialize components
30
+ settings = get_settings()
31
+
32
+ try:
33
+ embedding_generator = EmbeddingGenerator(settings.embedding_model)
34
+ vector_store = VectorStore(
35
+ host=settings.qdrant_host,
36
+ port=settings.qdrant_port,
37
+ collection_name=settings.qdrant_collection_name,
38
+ vector_size=embedding_generator.dimension
39
+ )
40
+ llm = OllamaLLM(settings.ollama_base_url, settings.ollama_model)
41
+ document_processor = DocumentProcessor()
42
+ rag_chain = RAGChain(embedding_generator, vector_store, llm)
43
+
44
+ print("✅ All components initialized successfully!")
45
+ except Exception as e:
46
+ print(f"❌ Error initializing components: {e}")
47
+ # Create dummy components for now
48
+ embedding_generator = None
49
+ vector_store = None
50
+ llm = None
51
+ document_processor = None
52
+ rag_chain = None
53
+
54
+ @app.get("/")
55
+ async def root():
56
+ return {
57
+ "message": "RAG Portfolio Project API",
58
+ "status": "running",
59
+ "docs": "/docs"
60
+ }
61
+
62
+ @app.get("/health")
63
+ async def health_check():
64
+ # Check if services are running
65
+ ollama_status = True
66
+ qdrant_status = True
67
+
68
+ try:
69
+ if llm:
70
+ # Test Ollama connection
71
+ test_response = llm.generate("test", "test context")
72
+ ollama_status = "Error" not in test_response
73
+ except:
74
+ ollama_status = False
75
+
76
+ try:
77
+ if vector_store:
78
+ # Test Qdrant connection
79
+ vector_store.client.get_collections()
80
+ except:
81
+ qdrant_status = False
82
+
83
+ return {
84
+ "status": "healthy" if (ollama_status and qdrant_status) else "degraded",
85
+ "ollama_connected": ollama_status,
86
+ "qdrant_connected": qdrant_status
87
+ }
88
+
89
+ @app.post("/ingest/file", response_model=IngestResponse)
90
+ async def ingest_file(file: UploadFile = File(...)):
91
+ """Upload and ingest a document into the RAG system"""
92
+ if not rag_chain:
93
+ raise HTTPException(status_code=503, detail="RAG system not initialized")
94
+
95
+ try:
96
+ # Save uploaded file temporarily
97
+ with tempfile.NamedTemporaryFile(delete=False, suffix=file.filename) as tmp:
98
+ content = await file.read()
99
+ tmp.write(content)
100
+ tmp_path = tmp.name
101
+
102
+ # Process document
103
+ chunks = document_processor.process_document(tmp_path)
104
+
105
+ # Ingest into RAG system
106
+ result = rag_chain.ingest_documents(chunks)
107
+
108
+ # Clean up
109
+ os.unlink(tmp_path)
110
+
111
+ return IngestResponse(**result, message=f"Successfully ingested {file.filename}")
112
+
113
+ except Exception as e:
114
+ raise HTTPException(status_code=500, detail=str(e))
115
+
116
+ @app.post("/query", response_model=QueryResponse)
117
+ async def query(request: QueryRequest):
118
+ """Query the RAG system"""
119
+ if not rag_chain:
120
+ raise HTTPException(status_code=503, detail="RAG system not initialized")
121
+
122
+ try:
123
+ result = rag_chain.query(request.question, request.top_k)
124
+ return QueryResponse(**result)
125
+
126
+ except Exception as e:
127
+ raise HTTPException(status_code=500, detail=str(e))
128
+
129
+ @app.delete("/reset")
130
+ async def reset_collection():
131
+ """Reset the vector collection (delete all documents)"""
132
+ if not vector_store:
133
+ raise HTTPException(status_code=503, detail="Vector store not initialized")
134
+
135
+ try:
136
+ vector_store.client.delete_collection(settings.qdrant_collection_name)
137
+ vector_store._ensure_collection()
138
+ return {"status": "success", "message": "Collection reset successfully"}
139
+ except Exception as e:
140
+ raise HTTPException(status_code=500, detail=str(e))
141
 
142
  if __name__ == "__main__":
143
+ import uvicorn
144
+ uvicorn.run(app, host=settings.app_host, port=settings.app_port)
pyproject.toml CHANGED
@@ -16,6 +16,7 @@ dependencies = [
16
  "qdrant-client>=1.15.1",
17
  "ragas>=0.3.7",
18
  "rank-bm25>=0.2.2",
 
19
  "sentence-transformers>=5.1.2",
20
  "unstructured>=0.18.15",
21
  "uvicorn>=0.38.0",
 
16
  "qdrant-client>=1.15.1",
17
  "ragas>=0.3.7",
18
  "rank-bm25>=0.2.2",
19
+ "requests>=2.32.5",
20
  "sentence-transformers>=5.1.2",
21
  "unstructured>=0.18.15",
22
  "uvicorn>=0.38.0",
scripts/demo_queries.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ from typing import Dict
4
+
5
+ BASE_URL = "http://localhost:8000"
6
+
7
+ def query_rag(question: str, top_k: int = 5) -> Dict:
8
+ """Query the RAG system"""
9
+ response = requests.post(
10
+ f"{BASE_URL}/query",
11
+ json={"question": question, "top_k": top_k}
12
+ )
13
+ return response.json()
14
+
15
+ def print_result(question: str, result: Dict):
16
+ """Pretty print query results"""
17
+ print("\n" + "="*80)
18
+ print(f"❓ QUESTION: {question}")
19
+ print("="*80)
20
+ print(f"\n💡 ANSWER:\n{result['answer']}\n")
21
+ print(f"📚 SOURCES ({result['context_used']} chunks used):")
22
+ for idx, source in enumerate(result['sources'], 1):
23
+ print(f" {idx}. {source['source']} (chunk {source['chunk_index']}, score: {source['score']:.3f})")
24
+ print("="*80)
25
+
26
+ def main():
27
+ """Run demo queries"""
28
+ demo_queries = [
29
+ "What is deep learning and how does it work?",
30
+ "Explain the RAG process in simple terms",
31
+ "What are the benefits of using RAG systems?",
32
+ "What is supervised learning?",
33
+ "How does reinforcement learning work?",
34
+ ]
35
+
36
+ print("\n🚀 RAG SYSTEM DEMO")
37
+ print("="*80)
38
+
39
+ # Check health first
40
+ health = requests.get(f"{BASE_URL}/health").json()
41
+ print(f"\n✅ System Status: {health['status']}")
42
+ print(f" Ollama: {'✓' if health['ollama_connected'] else '✗'}")
43
+ print(f" Qdrant: {'✓' if health['qdrant_connected'] else '✗'}")
44
+
45
+ # Run queries
46
+ for question in demo_queries:
47
+ try:
48
+ result = query_rag(question)
49
+ print_result(question, result)
50
+ except Exception as e:
51
+ print(f"\n❌ Error: {e}")
52
+
53
+ print("\n✨ Demo complete!")
54
+
55
+ if __name__ == "__main__":
56
+ main()
uv.lock CHANGED
@@ -159,6 +159,15 @@ wheels = [
159
  { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
160
  ]
161
 
 
 
 
 
 
 
 
 
 
162
  [[package]]
163
  name = "annotated-types"
164
  version = "0.7.0"
@@ -548,7 +557,7 @@ wheels = [
548
 
549
  [[package]]
550
  name = "datasets"
551
- version = "4.2.0"
552
  source = { registry = "https://pypi.org/simple" }
553
  dependencies = [
554
  { name = "dill" },
@@ -567,9 +576,9 @@ dependencies = [
567
  { name = "tqdm" },
568
  { name = "xxhash" },
569
  ]
570
- sdist = { url = "https://files.pythonhosted.org/packages/70/48/0186fbc4b86a4f9ecaf04eb01e877e78b53bfa0b03be9c84b2298431ba33/datasets-4.2.0.tar.gz", hash = "sha256:8333a7db9f3bb8044c1b819a35d4e3e2809596c837793b0921382efffdc36e78", size = 582256, upload-time = "2025-10-09T16:10:15.534Z" }
571
  wheels = [
572
- { url = "https://files.pythonhosted.org/packages/91/9e/0bbbd09b116fd8ee2d3617e28e6598551d2f0f24d3a2ce99cc87ec85aeb0/datasets-4.2.0-py3-none-any.whl", hash = "sha256:fdc43aaf4a73b31f64f80f72f195ab413a1141ed15555d675b2fd17926f8b026", size = 506316, upload-time = "2025-10-09T16:10:13.375Z" },
573
  ]
574
 
575
  [[package]]
@@ -631,16 +640,17 @@ wheels = [
631
 
632
  [[package]]
633
  name = "fastapi"
634
- version = "0.119.1"
635
  source = { registry = "https://pypi.org/simple" }
636
  dependencies = [
 
637
  { name = "pydantic" },
638
  { name = "starlette" },
639
  { name = "typing-extensions" },
640
  ]
641
- sdist = { url = "https://files.pythonhosted.org/packages/a6/f4/152127681182e6413e7a89684c434e19e7414ed7ac0c632999c3c6980640/fastapi-0.119.1.tar.gz", hash = "sha256:a5e3426edce3fe221af4e1992c6d79011b247e3b03cc57999d697fe76cbf8ae0", size = 338616, upload-time = "2025-10-20T11:30:27.734Z" }
642
  wheels = [
643
- { url = "https://files.pythonhosted.org/packages/b1/26/e6d959b4ac959fdb3e9c4154656fc160794db6af8e64673d52759456bf07/fastapi-0.119.1-py3-none-any.whl", hash = "sha256:0b8c2a2cce853216e150e9bd4faaed88227f8eb37de21cb200771f491586a27f", size = 108123, upload-time = "2025-10-20T11:30:26.185Z" },
644
  ]
645
 
646
  [[package]]
@@ -812,6 +822,7 @@ dependencies = [
812
  { name = "qdrant-client" },
813
  { name = "ragas" },
814
  { name = "rank-bm25" },
 
815
  { name = "sentence-transformers" },
816
  { name = "unstructured" },
817
  { name = "uvicorn" },
@@ -837,6 +848,7 @@ requires-dist = [
837
  { name = "qdrant-client", specifier = ">=1.15.1" },
838
  { name = "ragas", specifier = ">=0.3.7" },
839
  { name = "rank-bm25", specifier = ">=0.2.2" },
 
840
  { name = "sentence-transformers", specifier = ">=5.1.2" },
841
  { name = "unstructured", specifier = ">=0.18.15" },
842
  { name = "uvicorn", specifier = ">=0.38.0" },
@@ -1450,7 +1462,7 @@ wheels = [
1450
 
1451
  [[package]]
1452
  name = "langsmith"
1453
- version = "0.4.37"
1454
  source = { registry = "https://pypi.org/simple" }
1455
  dependencies = [
1456
  { name = "httpx" },
@@ -1461,9 +1473,9 @@ dependencies = [
1461
  { name = "requests-toolbelt" },
1462
  { name = "zstandard" },
1463
  ]
1464
- sdist = { url = "https://files.pythonhosted.org/packages/09/51/58d561dd40ec564509724f0a6a7148aa8090143208ef5d06b73b7fc90d31/langsmith-0.4.37.tar.gz", hash = "sha256:d9a0eb6dd93f89843ac982c9f92be93cf2bcabbe19957f362c547766c7366c71", size = 959089, upload-time = "2025-10-15T22:33:59.465Z" }
1465
  wheels = [
1466
- { url = "https://files.pythonhosted.org/packages/14/e8/edff4de49cf364eb9ee88d13da0a555844df32438413bf53d90d507b97cd/langsmith-0.4.37-py3-none-any.whl", hash = "sha256:e34a94ce7277646299e4703a0f6e2d2c43647a28e8b800bb7ef82fd87a0ec766", size = 396111, upload-time = "2025-10-15T22:33:57.392Z" },
1467
  ]
1468
 
1469
  [[package]]
@@ -3469,28 +3481,28 @@ wheels = [
3469
 
3470
  [[package]]
3471
  name = "ruff"
3472
- version = "0.14.1"
3473
- source = { registry = "https://pypi.org/simple" }
3474
- sdist = { url = "https://files.pythonhosted.org/packages/9e/58/6ca66896635352812de66f71cdf9ff86b3a4f79071ca5730088c0cd0fc8d/ruff-0.14.1.tar.gz", hash = "sha256:1dd86253060c4772867c61791588627320abcb6ed1577a90ef432ee319729b69", size = 5513429, upload-time = "2025-10-16T18:05:41.766Z" }
3475
- wheels = [
3476
- { url = "https://files.pythonhosted.org/packages/8d/39/9cc5ab181478d7a18adc1c1e051a84ee02bec94eb9bdfd35643d7c74ca31/ruff-0.14.1-py3-none-linux_armv6l.whl", hash = "sha256:083bfc1f30f4a391ae09c6f4f99d83074416b471775b59288956f5bc18e82f8b", size = 12445415, upload-time = "2025-10-16T18:04:48.227Z" },
3477
- { url = "https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224", size = 12784267, upload-time = "2025-10-16T18:04:52.515Z" },
3478
- { url = "https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5", size = 11781872, upload-time = "2025-10-16T18:04:55.396Z" },
3479
- { url = "https://files.pythonhosted.org/packages/1e/5a/e890f7338ff537dba4589a5e02c51baa63020acfb7c8cbbaea4831562c96/ruff-0.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed04f0e04f7a4587244e5c9d7df50e6b5bf2705d75059f409a6421c593a35896", size = 12226558, upload-time = "2025-10-16T18:04:58.166Z" },
3480
- { url = "https://files.pythonhosted.org/packages/a6/7a/8ab5c3377f5bf31e167b73651841217542bcc7aa1c19e83030835cc25204/ruff-0.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9e6cf6cd4acae0febbce29497accd3632fe2025c0c583c8b87e8dbdeae5f61", size = 12187898, upload-time = "2025-10-16T18:05:01.455Z" },
3481
- { url = "https://files.pythonhosted.org/packages/48/8d/ba7c33aa55406955fc124e62c8259791c3d42e3075a71710fdff9375134f/ruff-0.14.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fa2458527794ecdfbe45f654e42c61f2503a230545a91af839653a0a93dbc6", size = 12939168, upload-time = "2025-10-16T18:05:04.397Z" },
3482
- { url = "https://files.pythonhosted.org/packages/b4/c2/70783f612b50f66d083380e68cbd1696739d88e9b4f6164230375532c637/ruff-0.14.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:39f1c392244e338b21d42ab29b8a6392a722c5090032eb49bb4d6defcdb34345", size = 14386942, upload-time = "2025-10-16T18:05:07.102Z" },
3483
- { url = "https://files.pythonhosted.org/packages/48/44/cd7abb9c776b66d332119d67f96acf15830d120f5b884598a36d9d3f4d83/ruff-0.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7382fa12a26cce1f95070ce450946bec357727aaa428983036362579eadcc5cf", size = 13990622, upload-time = "2025-10-16T18:05:09.882Z" },
3484
- { url = "https://files.pythonhosted.org/packages/eb/56/4259b696db12ac152fe472764b4f78bbdd9b477afd9bc3a6d53c01300b37/ruff-0.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0bf2be3ae8521e1093a487c4aa3b455882f139787770698530d28ed3fbb37c", size = 13431143, upload-time = "2025-10-16T18:05:13.46Z" },
3485
- { url = "https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151", size = 13132844, upload-time = "2025-10-16T18:05:16.1Z" },
3486
- { url = "https://files.pythonhosted.org/packages/65/6e/d31ce218acc11a8d91ef208e002a31acf315061a85132f94f3df7a252b18/ruff-0.14.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:747d583400f6125ec11a4c14d1c8474bf75d8b419ad22a111a537ec1a952d192", size = 13401241, upload-time = "2025-10-16T18:05:19.395Z" },
3487
- { url = "https://files.pythonhosted.org/packages/9f/b5/dbc4221bf0b03774b3b2f0d47f39e848d30664157c15b965a14d890637d2/ruff-0.14.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5a6e74c0efd78515a1d13acbfe6c90f0f5bd822aa56b4a6d43a9ffb2ae6e56cd", size = 12132476, upload-time = "2025-10-16T18:05:22.163Z" },
3488
- { url = "https://files.pythonhosted.org/packages/98/4b/ac99194e790ccd092d6a8b5f341f34b6e597d698e3077c032c502d75ea84/ruff-0.14.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0ea6a864d2fb41a4b6d5b456ed164302a0d96f4daac630aeba829abfb059d020", size = 12139749, upload-time = "2025-10-16T18:05:25.162Z" },
3489
- { url = "https://files.pythonhosted.org/packages/47/26/7df917462c3bb5004e6fdfcc505a49e90bcd8a34c54a051953118c00b53a/ruff-0.14.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0826b8764f94229604fa255918d1cc45e583e38c21c203248b0bfc9a0e930be5", size = 12544758, upload-time = "2025-10-16T18:05:28.018Z" },
3490
- { url = "https://files.pythonhosted.org/packages/64/d0/81e7f0648e9764ad9b51dd4be5e5dac3fcfff9602428ccbae288a39c2c22/ruff-0.14.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cbc52160465913a1a3f424c81c62ac8096b6a491468e7d872cb9444a860bc33d", size = 13221811, upload-time = "2025-10-16T18:05:30.707Z" },
3491
- { url = "https://files.pythonhosted.org/packages/c3/07/3c45562c67933cc35f6d5df4ca77dabbcd88fddaca0d6b8371693d29fd56/ruff-0.14.1-py3-none-win32.whl", hash = "sha256:e037ea374aaaff4103240ae79168c0945ae3d5ae8db190603de3b4012bd1def6", size = 12319467, upload-time = "2025-10-16T18:05:33.261Z" },
3492
- { url = "https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl", hash = "sha256:59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1", size = 13401123, upload-time = "2025-10-16T18:05:35.984Z" },
3493
- { url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z" },
3494
  ]
3495
 
3496
  [[package]]
 
159
  { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
160
  ]
161
 
162
+ [[package]]
163
+ name = "annotated-doc"
164
+ version = "0.0.2"
165
+ source = { registry = "https://pypi.org/simple" }
166
+ sdist = { url = "https://files.pythonhosted.org/packages/c4/92/2974dba489541ed4af531d00a4df075bc3a455557d3b54fd6932c51c95cc/annotated_doc-0.0.2.tar.gz", hash = "sha256:f25664061aee278227abfaec5aeb398298be579b934758c16205d48e896e149c", size = 4452, upload-time = "2025-10-22T18:38:52.597Z" }
167
+ wheels = [
168
+ { url = "https://files.pythonhosted.org/packages/bd/ee/cc5109cdd46a6ccd3d923db3c5425383abe51b5c033647aad1b5e2452e82/annotated_doc-0.0.2-py3-none-any.whl", hash = "sha256:2188cb99e353fcb5c20f23b8bc6f5fa7c924b213fac733d4b44883f9edffa090", size = 4056, upload-time = "2025-10-22T18:38:51.24Z" },
169
+ ]
170
+
171
  [[package]]
172
  name = "annotated-types"
173
  version = "0.7.0"
 
557
 
558
  [[package]]
559
  name = "datasets"
560
+ version = "4.3.0"
561
  source = { registry = "https://pypi.org/simple" }
562
  dependencies = [
563
  { name = "dill" },
 
576
  { name = "tqdm" },
577
  { name = "xxhash" },
578
  ]
579
+ sdist = { url = "https://files.pythonhosted.org/packages/2a/47/325206ac160f7699ed9f1798afa8f8f8d5189b03bf3815654859ac1d5cba/datasets-4.3.0.tar.gz", hash = "sha256:bc9118ed9afd92346c5be7ed3aaa00177eb907c25467f9d072a0d22777efbd2b", size = 582801, upload-time = "2025-10-23T16:31:51.547Z" }
580
  wheels = [
581
+ { url = "https://files.pythonhosted.org/packages/ca/51/409a8184ed35453d9cbb3d6b20d524b1115c2c2d117b85d5e9b06cd70b45/datasets-4.3.0-py3-none-any.whl", hash = "sha256:0ea157e72138b3ca6c7d2415f19a164ecf7d4c4fa72da2a570da286882e96903", size = 506846, upload-time = "2025-10-23T16:31:49.965Z" },
582
  ]
583
 
584
  [[package]]
 
640
 
641
  [[package]]
642
  name = "fastapi"
643
+ version = "0.120.0"
644
  source = { registry = "https://pypi.org/simple" }
645
  dependencies = [
646
+ { name = "annotated-doc" },
647
  { name = "pydantic" },
648
  { name = "starlette" },
649
  { name = "typing-extensions" },
650
  ]
651
+ sdist = { url = "https://files.pythonhosted.org/packages/f7/0e/7f29e8f7219e4526747db182e1afb5a4b6abc3201768fb38d81fa2536241/fastapi-0.120.0.tar.gz", hash = "sha256:6ce2c1cfb7000ac14ffd8ddb2bc12e62d023a36c20ec3710d09d8e36fab177a0", size = 337603, upload-time = "2025-10-23T20:56:34.743Z" }
652
  wheels = [
653
+ { url = "https://files.pythonhosted.org/packages/1d/60/7a639ceaba54aec4e1d5676498c568abc654b95762d456095b6cb529b1ca/fastapi-0.120.0-py3-none-any.whl", hash = "sha256:84009182e530c47648da2f07eb380b44b69889a4acfd9e9035ee4605c5cfc469", size = 108243, upload-time = "2025-10-23T20:56:33.281Z" },
654
  ]
655
 
656
  [[package]]
 
822
  { name = "qdrant-client" },
823
  { name = "ragas" },
824
  { name = "rank-bm25" },
825
+ { name = "requests" },
826
  { name = "sentence-transformers" },
827
  { name = "unstructured" },
828
  { name = "uvicorn" },
 
848
  { name = "qdrant-client", specifier = ">=1.15.1" },
849
  { name = "ragas", specifier = ">=0.3.7" },
850
  { name = "rank-bm25", specifier = ">=0.2.2" },
851
+ { name = "requests", specifier = ">=2.32.5" },
852
  { name = "sentence-transformers", specifier = ">=5.1.2" },
853
  { name = "unstructured", specifier = ">=0.18.15" },
854
  { name = "uvicorn", specifier = ">=0.38.0" },
 
1462
 
1463
  [[package]]
1464
  name = "langsmith"
1465
+ version = "0.4.38"
1466
  source = { registry = "https://pypi.org/simple" }
1467
  dependencies = [
1468
  { name = "httpx" },
 
1473
  { name = "requests-toolbelt" },
1474
  { name = "zstandard" },
1475
  ]
1476
+ sdist = { url = "https://files.pythonhosted.org/packages/37/21/f1ba48412c64bf3bb8feb532fc9d247b396935b5d8242332d44a4195ec2d/langsmith-0.4.38.tar.gz", hash = "sha256:3aa57f9c16a5880256cd1eab0452533c1fb5ee14ec5250e23ed919cc2b07f6d3", size = 942789, upload-time = "2025-10-23T22:28:20.458Z" }
1477
  wheels = [
1478
+ { url = "https://files.pythonhosted.org/packages/b4/2b/7e0248f65e35800ea8e4e3dbb3bcc36c61b81f5b8abeddaceec8320ab491/langsmith-0.4.38-py3-none-any.whl", hash = "sha256:326232a24b1c6dd308a3188557cc023adf8fb14144263b2982c115a6be5141e7", size = 397341, upload-time = "2025-10-23T22:28:18.333Z" },
1479
  ]
1480
 
1481
  [[package]]
 
3481
 
3482
  [[package]]
3483
  name = "ruff"
3484
+ version = "0.14.2"
3485
+ source = { registry = "https://pypi.org/simple" }
3486
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/34/8218a19b2055b80601e8fd201ec723c74c7fe1ca06d525a43ed07b6d8e85/ruff-0.14.2.tar.gz", hash = "sha256:98da787668f239313d9c902ca7c523fe11b8ec3f39345553a51b25abc4629c96", size = 5539663, upload-time = "2025-10-23T19:37:00.956Z" }
3487
+ wheels = [
3488
+ { url = "https://files.pythonhosted.org/packages/16/dd/23eb2db5ad9acae7c845700493b72d3ae214dce0b226f27df89216110f2b/ruff-0.14.2-py3-none-linux_armv6l.whl", hash = "sha256:7cbe4e593505bdec5884c2d0a4d791a90301bc23e49a6b1eb642dd85ef9c64f1", size = 12533390, upload-time = "2025-10-23T19:36:18.044Z" },
3489
+ { url = "https://files.pythonhosted.org/packages/5a/8c/5f9acff43ddcf3f85130d0146d0477e28ccecc495f9f684f8f7119b74c0d/ruff-0.14.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8d54b561729cee92f8d89c316ad7a3f9705533f5903b042399b6ae0ddfc62e11", size = 12887187, upload-time = "2025-10-23T19:36:22.664Z" },
3490
+ { url = "https://files.pythonhosted.org/packages/99/fa/047646491479074029665022e9f3dc6f0515797f40a4b6014ea8474c539d/ruff-0.14.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c8753dfa44ebb2cde10ce5b4d2ef55a41fb9d9b16732a2c5df64620dbda44a3", size = 11925177, upload-time = "2025-10-23T19:36:24.778Z" },
3491
+ { url = "https://files.pythonhosted.org/packages/15/8b/c44cf7fe6e59ab24a9d939493a11030b503bdc2a16622cede8b7b1df0114/ruff-0.14.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0bbeffb8d9f4fccf7b5198d566d0bad99a9cb622f1fc3467af96cb8773c9e3", size = 12358285, upload-time = "2025-10-23T19:36:26.979Z" },
3492
+ { url = "https://files.pythonhosted.org/packages/45/01/47701b26254267ef40369aea3acb62a7b23e921c27372d127e0f3af48092/ruff-0.14.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7047f0c5a713a401e43a88d36843d9c83a19c584e63d664474675620aaa634a8", size = 12303832, upload-time = "2025-10-23T19:36:29.192Z" },
3493
+ { url = "https://files.pythonhosted.org/packages/2d/5c/ae7244ca4fbdf2bee9d6405dcd5bc6ae51ee1df66eb7a9884b77b8af856d/ruff-0.14.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bf8d2f9aa1602599217d82e8e0af7fd33e5878c4d98f37906b7c93f46f9a839", size = 13036995, upload-time = "2025-10-23T19:36:31.861Z" },
3494
+ { url = "https://files.pythonhosted.org/packages/27/4c/0860a79ce6fd4c709ac01173f76f929d53f59748d0dcdd662519835dae43/ruff-0.14.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1c505b389e19c57a317cf4b42db824e2fca96ffb3d86766c1c9f8b96d32048a7", size = 14512649, upload-time = "2025-10-23T19:36:33.915Z" },
3495
+ { url = "https://files.pythonhosted.org/packages/7f/7f/d365de998069720a3abfc250ddd876fc4b81a403a766c74ff9bde15b5378/ruff-0.14.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a307fc45ebd887b3f26b36d9326bb70bf69b01561950cdcc6c0bdf7bb8e0f7cc", size = 14088182, upload-time = "2025-10-23T19:36:36.983Z" },
3496
+ { url = "https://files.pythonhosted.org/packages/6c/ea/d8e3e6b209162000a7be1faa41b0a0c16a133010311edc3329753cc6596a/ruff-0.14.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61ae91a32c853172f832c2f40bd05fd69f491db7289fb85a9b941ebdd549781a", size = 13599516, upload-time = "2025-10-23T19:36:39.208Z" },
3497
+ { url = "https://files.pythonhosted.org/packages/fa/ea/c7810322086db68989fb20a8d5221dd3b79e49e396b01badca07b433ab45/ruff-0.14.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1967e40286f63ee23c615e8e7e98098dedc7301568bd88991f6e544d8ae096", size = 13272690, upload-time = "2025-10-23T19:36:41.453Z" },
3498
+ { url = "https://files.pythonhosted.org/packages/a9/39/10b05acf8c45786ef501d454e00937e1b97964f846bf28883d1f9619928a/ruff-0.14.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2877f02119cdebf52a632d743a2e302dea422bfae152ebe2f193d3285a3a65df", size = 13496497, upload-time = "2025-10-23T19:36:43.61Z" },
3499
+ { url = "https://files.pythonhosted.org/packages/59/a1/1f25f8301e13751c30895092485fada29076e5e14264bdacc37202e85d24/ruff-0.14.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e681c5bc777de5af898decdcb6ba3321d0d466f4cb43c3e7cc2c3b4e7b843a05", size = 12266116, upload-time = "2025-10-23T19:36:45.625Z" },
3500
+ { url = "https://files.pythonhosted.org/packages/5c/fa/0029bfc9ce16ae78164e6923ef392e5f173b793b26cc39aa1d8b366cf9dc/ruff-0.14.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e21be42d72e224736f0c992cdb9959a2fa53c7e943b97ef5d081e13170e3ffc5", size = 12281345, upload-time = "2025-10-23T19:36:47.618Z" },
3501
+ { url = "https://files.pythonhosted.org/packages/a5/ab/ece7baa3c0f29b7683be868c024f0838770c16607bea6852e46b202f1ff6/ruff-0.14.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8264016f6f209fac16262882dbebf3f8be1629777cf0f37e7aff071b3e9b92e", size = 12629296, upload-time = "2025-10-23T19:36:49.789Z" },
3502
+ { url = "https://files.pythonhosted.org/packages/a4/7f/638f54b43f3d4e48c6a68062794e5b367ddac778051806b9e235dfb7aa81/ruff-0.14.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ca36b4cb4db3067a3b24444463ceea5565ea78b95fe9a07ca7cb7fd16948770", size = 13371610, upload-time = "2025-10-23T19:36:51.882Z" },
3503
+ { url = "https://files.pythonhosted.org/packages/8d/35/3654a973ebe5b32e1fd4a08ed2d46755af7267da7ac710d97420d7b8657d/ruff-0.14.2-py3-none-win32.whl", hash = "sha256:41775927d287685e08f48d8eb3f765625ab0b7042cc9377e20e64f4eb0056ee9", size = 12415318, upload-time = "2025-10-23T19:36:53.961Z" },
3504
+ { url = "https://files.pythonhosted.org/packages/71/30/3758bcf9e0b6a4193a6f51abf84254aba00887dfa8c20aba18aa366c5f57/ruff-0.14.2-py3-none-win_amd64.whl", hash = "sha256:0df3424aa5c3c08b34ed8ce099df1021e3adaca6e90229273496b839e5a7e1af", size = 13565279, upload-time = "2025-10-23T19:36:56.578Z" },
3505
+ { url = "https://files.pythonhosted.org/packages/2e/5d/aa883766f8ef9ffbe6aa24f7192fb71632f31a30e77eb39aa2b0dc4290ac/ruff-0.14.2-py3-none-win_arm64.whl", hash = "sha256:ea9d635e83ba21569fbacda7e78afbfeb94911c9434aff06192d9bc23fd5495a", size = 12554956, upload-time = "2025-10-23T19:36:58.714Z" },
3506
  ]
3507
 
3508
  [[package]]