shreyanshknayak commited on
Commit
d339ddb
·
verified ·
1 Parent(s): ab9f9fe

Upload 3 files

Browse files
Files changed (3) hide show
  1. main_2.py +239 -0
  2. pipeline_2.py +184 -0
  3. requirements.txt +219 -0
main_2.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # File: main.py
2
+ # (Modified to load embedding model at startup and await async pipeline run)
3
+
4
+ import os
5
+ import tempfile
6
+ import asyncio
7
+ import time
8
+ from typing import List, Dict, Any
9
+ from urllib.parse import urlparse, unquote
10
+ from pathlib import Path
11
+
12
+ import httpx
13
+ from fastapi import FastAPI, HTTPException
14
+ from pydantic import BaseModel, HttpUrl
15
+ from groq import AsyncGroq
16
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
17
+ import torch # Import torch to check for CUDA availability
18
+
19
+ from dotenv import load_dotenv
20
+
21
+ load_dotenv()
22
+
23
+ # Import the Pipeline class from the previous file
24
+ from pipeline_2 import Pipeline
25
+
26
+ # FastAPI application setup
27
+ app = FastAPI(
28
+ title="Llama-Index RAG with Groq",
29
+ description="An API to process a PDF from a URL and answer a list of questions using a Llama-Index RAG pipeline.",
30
+ )
31
+
32
+ # --- Pydantic Models for API Request and Response ---
33
+ class RunRequest(BaseModel):
34
+ documents: HttpUrl
35
+ questions: List[str]
36
+
37
+ class Answer(BaseModel):
38
+ question: str
39
+ answer: str
40
+
41
+ class RunResponse(BaseModel):
42
+ answers: List[Answer]
43
+ processing_time: float
44
+ step_timings: Dict[str, float]
45
+
46
+ # --- Global Configurations ---
47
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY", "gsk_...")
48
+ GROQ_MODEL_NAME = "llama3-70b-8192"
49
+ EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
50
+
51
+ # Global variable to hold the initialized embedding model
52
+ embed_model_instance: HuggingFaceEmbedding | None = None
53
+
54
+ if GROQ_API_KEY == "gsk_...":
55
+ print("WARNING: GROQ_API_KEY is not set. Please set it in your environment or main.py.")
56
+
57
+ @app.on_event("startup")
58
+ async def startup_event():
59
+ """
60
+ Loads the embedding model once when the application starts.
61
+ This prevents re-loading it on every API call.
62
+ """
63
+ global embed_model_instance
64
+ print(f"Loading embedding model '{EMBEDDING_MODEL_NAME}' at startup...")
65
+ # Check for GPU availability and use it if possible
66
+ # Assuming 16GB VRAM, a standard device check is sufficient
67
+ device = "cuda" if torch.cuda.is_available() else "cpu"
68
+ print(f"Using device: {device}")
69
+ embed_model_instance = await asyncio.to_thread(HuggingFaceEmbedding, model_name=EMBEDDING_MODEL_NAME, device=device)
70
+ print("Embedding model loaded successfully.")
71
+
72
+ # --- Async Groq Generation Function ---
73
+ async def generate_answer_with_groq(query: str, retrieved_results: List[dict], groq_api_key: str) -> str:
74
+ """
75
+ Generates an answer using the Groq API based on the query and retrieved chunks' content.
76
+ """
77
+ if not groq_api_key:
78
+ return "Error: Groq API key is not set. Cannot generate answer."
79
+
80
+ client = AsyncGroq(api_key=groq_api_key)
81
+
82
+ context_parts = []
83
+ for i, res in enumerate(retrieved_results):
84
+ content = res.get("content", "")
85
+ metadata = res.get("document_metadata", {})
86
+
87
+ section_heading = metadata.get("section_heading", metadata.get("file_name", "N/A"))
88
+
89
+ context_parts.append(
90
+ f"--- Context Chunk {i+1} ---\n"
91
+ f"Document Part: {section_heading}\n"
92
+ f"Content: {content}\n"
93
+ f"-------------------------"
94
+ )
95
+ context = "\n\n".join(context_parts)
96
+
97
+ prompt = (
98
+ f"You are a specialized document analyzer assistant. Your task is to answer the user's question "
99
+ f"solely based on the provided context. If the answer cannot be found in the provided context, "
100
+ f"clearly state that you do not have enough information.\n\n"
101
+ f"Context:\n{context}\n\n"
102
+ f"Question: {query}\n\n"
103
+ f"Answer:"
104
+ )
105
+
106
+ try:
107
+ chat_completion = await client.chat.completions.create(
108
+ messages=[
109
+ {
110
+ "role": "user",
111
+ "content": prompt,
112
+ }
113
+ ],
114
+ model=GROQ_MODEL_NAME,
115
+ temperature=0.7,
116
+ max_tokens=500,
117
+ )
118
+ answer = chat_completion.choices[0].message.content
119
+ return answer
120
+ except Exception as e:
121
+ print(f"An error occurred during Groq API call: {e}")
122
+ return "Could not generate an answer due to an API error."
123
+
124
+
125
+ # --- FastAPI Endpoint ---
126
+ @app.post("/rag/run", response_model=RunResponse)
127
+ async def run_rag_pipeline(request: RunRequest):
128
+ """
129
+ Runs the RAG pipeline for a given PDF document URL and a list of questions.
130
+ The PDF is downloaded, processed, and then the questions are answered.
131
+ """
132
+ pdf_url = request.documents
133
+ questions = request.questions
134
+ local_pdf_path = None
135
+ step_timings = {}
136
+
137
+ start_time_total = time.perf_counter()
138
+
139
+ if not embed_model_instance:
140
+ raise HTTPException(
141
+ status_code=500,
142
+ detail="Embedding model not loaded. Application startup failed."
143
+ )
144
+
145
+ if not GROQ_API_KEY or GROQ_API_KEY == "gsk_...":
146
+ raise HTTPException(
147
+ status_code=500,
148
+ detail="Groq API key is not configured. Please set the GROQ_API_KEY environment variable."
149
+ )
150
+
151
+ try:
152
+ # 1. Download PDF
153
+ start_time = time.perf_counter()
154
+ async with httpx.AsyncClient() as client:
155
+ try:
156
+ response = await client.get(str(pdf_url), timeout=30.0, follow_redirects=True)
157
+ response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
158
+ doc_bytes = response.content
159
+ print("Download successful.")
160
+ except httpx.HTTPStatusError as e:
161
+ raise HTTPException(status_code=e.response.status_code, detail=f"HTTP error downloading PDF: {e.response.status_code} - {e.response.text}")
162
+ except httpx.RequestError as e:
163
+ raise HTTPException(status_code=400, detail=f"Network error downloading PDF: {e}")
164
+ except Exception as e:
165
+ raise HTTPException(status_code=500, detail=f"An unexpected error occurred during download: {e}")
166
+
167
+ # Determine a temporary local filename
168
+ parsed_path = urlparse(str(pdf_url)).path
169
+ filename = unquote(os.path.basename(parsed_path))
170
+ if not filename or not filename.lower().endswith(".pdf"):
171
+ # If the URL doesn't provide a valid PDF filename, create a generic one.
172
+ filename = "downloaded_document.pdf"
173
+
174
+ # Use tempfile to create a secure temporary file
175
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as temp_pdf_file:
176
+ temp_pdf_file.write(doc_bytes)
177
+ local_pdf_path = temp_pdf_file.name
178
+
179
+ end_time = time.perf_counter()
180
+ step_timings["download_pdf"] = end_time - start_time
181
+ print(f"PDF download took {step_timings['download_pdf']:.2f} seconds.")
182
+
183
+ # 2. Initialize and Run the Pipeline (Parsing, Node Creation, Embeddings)
184
+ start_time = time.perf_counter()
185
+ # The Pipeline's run() method is now async, so await it directly
186
+ pipeline = Pipeline(groq_api_key=GROQ_API_KEY, pdf_path=local_pdf_path, embed_model=embed_model_instance)
187
+ await pipeline.run() # Changed from asyncio.to_thread(pipeline.run)
188
+ end_time = time.perf_counter()
189
+ step_timings["pipeline_setup"] = end_time - start_time
190
+ print(f"Pipeline setup took {step_timings['pipeline_setup']:.2f} seconds.")
191
+
192
+ # 3. Concurrent Retrieval Phase
193
+ start_time_retrieval = time.perf_counter()
194
+ print(f"\nStarting concurrent retrieval for {len(questions)} questions...")
195
+
196
+ retrieval_tasks = [asyncio.to_thread(pipeline.retrieve_nodes, q) for q in questions]
197
+ all_retrieved_results = await asyncio.gather(*retrieval_tasks)
198
+
199
+ end_time_retrieval = time.perf_counter()
200
+ step_timings["retrieval"] = end_time_retrieval - start_time_retrieval
201
+ print(f"Retrieval phase completed in {step_timings['retrieval']:.2f} seconds.")
202
+
203
+ # 4. Concurrent Generation Phase
204
+ start_time_generation = time.perf_counter()
205
+ print(f"\nStarting concurrent answer generation for {len(questions)} questions...")
206
+
207
+ generation_tasks = [
208
+ generate_answer_with_groq(q, retrieved_results, GROQ_API_KEY)
209
+ for q, retrieved_results in zip(questions, all_retrieved_results)
210
+ ]
211
+
212
+ all_answer_texts = await asyncio.gather(*generation_tasks)
213
+
214
+ end_time_generation = time.perf_counter()
215
+ step_timings["generation"] = end_time_generation - start_time_generation
216
+ print(f"Generation phase completed in {step_timings['generation']:.2f} seconds.")
217
+
218
+ end_time_total = time.perf_counter()
219
+ total_processing_time = end_time_total - start_time_total
220
+
221
+ answers = [Answer(question=q, answer=a) for q, a in zip(questions, all_answer_texts)]
222
+
223
+ return RunResponse(
224
+ answers=answers,
225
+ processing_time=total_processing_time,
226
+ step_timings=step_timings,
227
+ )
228
+
229
+ except HTTPException as e:
230
+ raise e
231
+ except Exception as e:
232
+ print(f"An unhandled error occurred: {e}")
233
+ raise HTTPException(
234
+ status_code=500, detail=f"An internal server error occurred: {e}"
235
+ )
236
+ finally:
237
+ if local_pdf_path and os.path.exists(local_pdf_path):
238
+ os.unlink(local_pdf_path)
239
+ print(f"Cleaned up temporary PDF file: {local_pdf_path}")
pipeline_2.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # File: pipeline.py
2
+ # (Modified to accept a pre-initialized embedding model and generate embeddings concurrently)
3
+
4
+ import time
5
+ from pathlib import Path
6
+ from typing import List, Any
7
+ import asyncio # Import asyncio for concurrent operations
8
+
9
+ from llama_index.core import Document, StorageContext, VectorStoreIndex, Settings
10
+ from llama_index.core.node_parser import HierarchicalNodeParser, get_leaf_nodes, get_root_nodes
11
+ from llama_index.core.retrievers import AutoMergingRetriever, BaseRetriever
12
+ from llama_index.core.storage.docstore import SimpleDocumentStore
13
+ from llama_index.readers.file import PyMuPDFReader
14
+ from llama_index.llms.groq import Groq
15
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
16
+
17
+
18
+ class Pipeline:
19
+ """
20
+ A pipeline to process a PDF, create nodes, and generate embeddings.
21
+ It exposes a retriever to fetch nodes for a given query,
22
+ but does not handle the answer generation itself. The embedding
23
+ model is now passed in, not initialized internally.
24
+ """
25
+
26
+ def __init__(self, groq_api_key: str, pdf_path: str, embed_model: HuggingFaceEmbedding):
27
+ """
28
+ Initializes the pipeline with API keys, file path, and a pre-initialized embedding model.
29
+
30
+ Args:
31
+ groq_api_key (str): Your API key for Groq.
32
+ pdf_path (str): The path to the PDF file to be processed.
33
+ embed_model (HuggingFaceEmbedding): The pre-initialized embedding model.
34
+ """
35
+ self.groq_api_key = groq_api_key
36
+ self.pdf_path = Path(pdf_path)
37
+ self.embed_model = embed_model
38
+
39
+ # Configure Llama-Index LLM setting only
40
+ Settings.llm = Groq(model="llama3-70b-8192", api_key=self.groq_api_key)
41
+
42
+ # Initialize components
43
+ self.documents: List[Document] = []
44
+ self.nodes: List[Any] = []
45
+ self.storage_context: StorageContext | None = None
46
+ self.index: VectorStoreIndex | None = None
47
+ self.retriever: BaseRetriever | None = None
48
+ self.leaf_nodes: List[Any] = []
49
+ self.root_nodes: List[Any] = []
50
+
51
+
52
+ def _parse_pdf(self) -> None:
53
+ """Parses the PDF file into Llama-Index Document objects."""
54
+ print(f"Parsing PDF at: {self.pdf_path}")
55
+ start_time = time.perf_counter()
56
+ loader = PyMuPDFReader()
57
+ docs = loader.load(file_path=self.pdf_path)
58
+ # Concatenate all document parts into a single document for simpler processing
59
+ # Adjust this if you need to maintain per-page document context
60
+ doc_text = "\n\n".join([d.get_content() for d in docs])
61
+ self.documents = [Document(text=doc_text)]
62
+ end_time = time.perf_counter()
63
+ print(f"PDF parsing completed in {end_time - start_time:.2f} seconds.")
64
+
65
+ def _create_nodes(self) -> None:
66
+ """Creates hierarchical nodes from the parsed documents."""
67
+ print("Creating nodes from documents...")
68
+ start_time = time.perf_counter()
69
+ node_parser = HierarchicalNodeParser.from_defaults()
70
+ self.nodes = node_parser.get_nodes_from_documents(self.documents)
71
+ self.leaf_nodes = get_leaf_nodes(self.nodes)
72
+ self.root_nodes = get_root_nodes(self.nodes)
73
+ end_time = time.perf_counter()
74
+ print(f"Node creation completed in {end_time - start_time:.2f} seconds.")
75
+
76
+ async def _generate_embeddings_concurrently(self) -> None:
77
+ """
78
+ Generates embeddings for leaf nodes concurrently using asyncio.to_thread
79
+ and then builds the VectorStoreIndex.
80
+ """
81
+ print("Generating embeddings for leaf nodes concurrently...")
82
+ start_time_embeddings = time.perf_counter()
83
+
84
+ # Define a batch size for sending texts to the embedding model.
85
+ # For a 16GB VRAM GPU, a batch size of 300 for 'all-MiniLM-L6-v2' is a reasonable starting point.
86
+ # You might be able to increase this further depending on the exact model and GPU utilization.
87
+ BATCH_SIZE = 300
88
+
89
+ embedding_tasks = []
90
+ # Extract text content from leaf nodes
91
+ node_texts = [node.get_content() for node in self.leaf_nodes]
92
+
93
+ # Create batches of texts and schedule embedding generation in separate threads
94
+ for i in range(0, len(node_texts), BATCH_SIZE):
95
+ batch_texts = node_texts[i : i + BATCH_SIZE]
96
+ # Use asyncio.to_thread to run the synchronous embedding model call in a separate thread
97
+ # This prevents blocking the main event loop
98
+ embedding_tasks.append(asyncio.to_thread(self.embed_model.get_text_embedding_batch, texts=batch_texts, show_progress=False))
99
+
100
+ # Wait for all concurrent embedding tasks to complete
101
+ all_embeddings_batches = await asyncio.gather(*embedding_tasks)
102
+
103
+ # Flatten the list of lists of embeddings into a single list
104
+ flat_embeddings = [emb for sublist in all_embeddings_batches for emb in sublist]
105
+
106
+ # Assign the generated embeddings back to their respective leaf nodes
107
+ for i, node in enumerate(self.leaf_nodes):
108
+ node.embedding = flat_embeddings[i]
109
+
110
+ end_time_embeddings = time.perf_counter()
111
+ print(f"Embeddings generated for {len(self.leaf_nodes)} nodes in {end_time_embeddings - start_time_embeddings:.2f} seconds.")
112
+
113
+ # Now, build the VectorStoreIndex using the nodes that now have pre-computed embeddings
114
+ print("Building VectorStoreIndex...")
115
+ start_time_index_build = time.perf_counter()
116
+
117
+ # Add all nodes (root and leaf) to the document store
118
+ docstore = SimpleDocumentStore()
119
+ docstore.add_documents(self.nodes)
120
+
121
+ self.storage_context = StorageContext.from_defaults(docstore=docstore)
122
+
123
+ # When nodes already have embeddings, VectorStoreIndex will use them
124
+ self.index = VectorStoreIndex(
125
+ self.leaf_nodes, # Pass leaf nodes which now contain their embeddings
126
+ storage_context=self.storage_context,
127
+ embed_model=self.embed_model # Still pass the embed_model, though it won't re-embed if nodes have embeddings
128
+ )
129
+ end_time_index_build = time.perf_counter()
130
+ print(f"VectorStoreIndex built in {end_time_index_build - start_time_index_build:.2f} seconds.")
131
+ print(f"Total index generation and embedding process completed in {end_time_index_build - start_time_embeddings:.2f} seconds.")
132
+
133
+
134
+ def _setup_retriever(self) -> None:
135
+ """Sets up the retriever."""
136
+ print("Setting up retriever...")
137
+ base_retriever = self.index.as_retriever(similarity_top_k=6)
138
+ self.retriever = AutoMergingRetriever(
139
+ base_retriever, storage_context=self.storage_context, verbose=True
140
+ )
141
+
142
+ async def run(self) -> None:
143
+ """Runs the entire pipeline from parsing to retriever setup."""
144
+ if not self.pdf_path.exists():
145
+ raise FileNotFoundError(f"PDF file not found at: {self.pdf_path}")
146
+
147
+ self._parse_pdf()
148
+ self._create_nodes()
149
+ await self._generate_embeddings_concurrently() # Await the async embedding generation
150
+ self._setup_retriever()
151
+ print("Pipeline is ready for retrieval.")
152
+
153
+ def retrieve_nodes(self, query_str: str) -> List[dict]:
154
+ """
155
+ Retrieves relevant nodes for a given query and converts them to a
156
+ list of dictionaries for external use.
157
+
158
+ Args:
159
+ query_str (str): The query string.
160
+
161
+ Returns:
162
+ List[dict]: A list of dictionaries with node content and metadata.
163
+ """
164
+ if not self.retriever:
165
+ raise RuntimeError("Retriever is not initialized. Run the pipeline first.")
166
+
167
+ print(f"\nRetrieving nodes for query: '{query_str}'")
168
+ start_time = time.perf_counter()
169
+
170
+ # This is a synchronous call
171
+ nodes = self.retriever.retrieve(query_str)
172
+
173
+ end_time = time.perf_counter()
174
+ print(f"Retrieval completed in {end_time - start_time:.2f} seconds. Found {len(nodes)} nodes.")
175
+
176
+ # Convert the Llama-Index nodes to a dictionary format
177
+ retrieved_results = [
178
+ {
179
+ "content": n.text,
180
+ "document_metadata": n.metadata
181
+ }
182
+ for n in nodes
183
+ ]
184
+ return retrieved_results
requirements.txt ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ acres==0.5.0
2
+ aiofiles==24.1.0
3
+ aiohappyeyeballs==2.6.1
4
+ aiohttp==3.12.15
5
+ aiosignal==1.4.0
6
+ aiosqlite==0.21.0
7
+ annotated-types==0.7.0
8
+ anyio==4.10.0
9
+ appnope==0.1.4
10
+ argon2-cffi==25.1.0
11
+ argon2-cffi-bindings==25.1.0
12
+ arrow==1.3.0
13
+ asttokens==3.0.0
14
+ async-lru==2.0.5
15
+ asyncio==4.0.0
16
+ attrs==25.3.0
17
+ babel==2.17.0
18
+ banks==2.2.0
19
+ beautifulsoup4==4.13.4
20
+ bleach==6.2.0
21
+ certifi==2025.8.3
22
+ cffi==1.17.1
23
+ charset-normalizer==3.4.2
24
+ ci-info==0.3.0
25
+ click==8.2.1
26
+ cohere==5.16.2
27
+ colorama==0.4.6
28
+ comm==0.2.3
29
+ configobj==5.0.9
30
+ configparser==7.2.0
31
+ contourpy==1.3.3
32
+ cycler==0.12.1
33
+ dataclasses-json==0.6.7
34
+ debugpy==1.8.15
35
+ decorator==5.2.1
36
+ defusedxml==0.7.1
37
+ Deprecated==1.2.18
38
+ dirtyjson==1.0.8
39
+ distro==1.9.0
40
+ dotenv==0.9.9
41
+ etelemetry==0.3.1
42
+ executing==2.2.0
43
+ fastapi==0.116.1
44
+ fastavro==1.12.0
45
+ fastjsonschema==2.21.1
46
+ filelock==3.18.0
47
+ filetype==1.2.0
48
+ fitz==0.0.1.dev2
49
+ fonttools==4.59.0
50
+ fqdn==1.5.1
51
+ frontend==0.0.3
52
+ frozenlist==1.7.0
53
+ fsspec==2025.7.0
54
+ greenlet==3.2.3
55
+ griffe==1.9.0
56
+ groq==0.31.0
57
+ h11==0.16.0
58
+ hf-xet==1.1.5
59
+ httpcore==1.0.9
60
+ httplib2==0.22.0
61
+ httpx==0.28.1
62
+ httpx-sse==0.4.0
63
+ huggingface-hub==0.34.3
64
+ idna==3.10
65
+ ipykernel==6.30.1
66
+ ipython==9.4.0
67
+ ipython_pygments_lexers==1.1.1
68
+ ipywidgets==8.1.7
69
+ isoduration==20.11.0
70
+ itsdangerous==2.2.0
71
+ jedi==0.19.2
72
+ Jinja2==3.1.6
73
+ jiter==0.10.0
74
+ joblib==1.5.1
75
+ json5==0.12.0
76
+ jsonpointer==3.0.0
77
+ jsonschema==4.25.0
78
+ jsonschema-specifications==2025.4.1
79
+ jupyter==1.1.1
80
+ jupyter-console==6.6.3
81
+ jupyter-events==0.12.0
82
+ jupyter-lsp==2.2.6
83
+ jupyter_client==8.6.3
84
+ jupyter_core==5.8.1
85
+ jupyter_server==2.16.0
86
+ jupyter_server_terminals==0.5.3
87
+ jupyterlab==4.4.5
88
+ jupyterlab_pygments==0.3.0
89
+ jupyterlab_server==2.27.3
90
+ jupyterlab_widgets==3.0.15
91
+ kiwisolver==1.4.8
92
+ lark==1.2.2
93
+ llama-cloud==0.1.35
94
+ llama-cloud-services==0.6.54
95
+ llama-index==0.13.0
96
+ llama-index-cli==0.5.0
97
+ llama-index-core==0.13.0
98
+ llama-index-embeddings-cohere==0.6.0
99
+ llama-index-embeddings-huggingface==0.6.0
100
+ llama-index-embeddings-openai==0.5.0
101
+ llama-index-indices-managed-llama-cloud==0.9.0
102
+ llama-index-instrumentation==0.4.0
103
+ llama-index-llms-groq==0.4.0
104
+ llama-index-llms-openai==0.5.0
105
+ llama-index-llms-openai-like==0.5.0
106
+ llama-index-readers-file==0.5.0
107
+ llama-index-readers-llama-parse==0.5.0
108
+ llama-index-vector-stores-pinecone==0.7.0
109
+ llama-index-workflows==1.2.0
110
+ llama-parse==0.6.54
111
+ looseversion==1.3.0
112
+ lxml==6.0.0
113
+ MarkupSafe==3.0.2
114
+ marshmallow==3.26.1
115
+ matplotlib==3.10.5
116
+ matplotlib-inline==0.1.7
117
+ mistune==3.1.3
118
+ mpmath==1.3.0
119
+ multidict==6.6.3
120
+ mypy_extensions==1.1.0
121
+ nbclient==0.10.2
122
+ nbconvert==7.16.6
123
+ nbformat==5.10.4
124
+ nest-asyncio==1.6.0
125
+ networkx==3.5
126
+ nibabel==5.3.2
127
+ nipype==1.10.0
128
+ nltk==3.9.1
129
+ notebook==7.4.5
130
+ notebook_shim==0.2.4
131
+ numpy==2.3.2
132
+ openai==1.98.0
133
+ overrides==7.7.0
134
+ packaging==24.2
135
+ pandas==2.2.3
136
+ pandocfilters==1.5.1
137
+ parso==0.8.4
138
+ pathlib==1.0.1
139
+ pexpect==4.9.0
140
+ pillow==11.3.0
141
+ pinecone==7.3.0
142
+ pinecone-plugin-assistant==1.7.0
143
+ pinecone-plugin-interface==0.0.7
144
+ platformdirs==4.3.8
145
+ prometheus_client==0.22.1
146
+ prompt_toolkit==3.0.51
147
+ propcache==0.3.2
148
+ prov==2.1.1
149
+ psutil==7.0.0
150
+ ptyprocess==0.7.0
151
+ pure_eval==0.2.3
152
+ puremagic==1.30
153
+ pycparser==2.22
154
+ pydantic==2.11.7
155
+ pydantic_core==2.33.2
156
+ pydot==4.0.1
157
+ Pygments==2.19.2
158
+ PyMuPDF==1.26.3
159
+ pyparsing==3.2.3
160
+ pypdf==5.9.0
161
+ python-dateutil==2.9.0.post0
162
+ python-dotenv==1.1.1
163
+ python-json-logger==3.3.0
164
+ pytz==2025.2
165
+ pyxnat==1.6.3
166
+ PyYAML==6.0.2
167
+ pyzmq==27.0.1
168
+ rdflib==7.1.4
169
+ referencing==0.36.2
170
+ regex==2025.7.34
171
+ requests==2.32.4
172
+ rfc3339-validator==0.1.4
173
+ rfc3986-validator==0.1.1
174
+ rfc3987-syntax==1.1.0
175
+ rpds-py==0.26.0
176
+ safetensors==0.5.3
177
+ scikit-learn==1.7.1
178
+ scipy==1.16.1
179
+ Send2Trash==1.8.3
180
+ sentence-transformers==5.0.0
181
+ setuptools==80.9.0
182
+ simplejson==3.20.1
183
+ six==1.17.0
184
+ sniffio==1.3.1
185
+ soupsieve==2.7
186
+ SQLAlchemy==2.0.42
187
+ stack-data==0.6.3
188
+ starlette==0.47.2
189
+ striprtf==0.0.26
190
+ sympy==1.14.0
191
+ tenacity==9.1.2
192
+ terminado==0.18.1
193
+ threadpoolctl==3.6.0
194
+ tiktoken==0.9.0
195
+ tinycss2==1.4.0
196
+ tokenizers==0.21.4
197
+ torch==2.7.1
198
+ tornado==6.5.1
199
+ tqdm==4.67.1
200
+ traitlets==5.14.3
201
+ traits==7.0.2
202
+ transformers==4.54.1
203
+ types-python-dateutil==2.9.0.20250708
204
+ types-requests==2.32.4.20250611
205
+ typing==3.7.4.3
206
+ typing-inspect==0.9.0
207
+ typing-inspection==0.4.1
208
+ typing_extensions==4.14.1
209
+ tzdata==2025.2
210
+ uri-template==1.3.0
211
+ urllib3==2.5.0
212
+ uvicorn==0.35.0
213
+ wcwidth==0.2.13
214
+ webcolors==24.11.1
215
+ webencodings==0.5.1
216
+ websocket-client==1.8.0
217
+ widgetsnbextension==4.0.14
218
+ wrapt==1.17.2
219
+ yarl==1.20.1