Pushkar02-n commited on
Commit
d7434b7
·
1 Parent(s): f69a6fa

Final changes in local

Browse files
.gitignore CHANGED
@@ -2,4 +2,5 @@ __pycache__
2
  .venv
3
  .env
4
  data
5
- .gradio
 
 
2
  .venv
3
  .env
4
  data
5
+ .gradio
6
+ supabase_dump.sql
pyproject.toml CHANGED
@@ -13,6 +13,7 @@ dependencies = [
13
  "pandas>=2.3.3",
14
  "psycopg2-binary>=2.9.11",
15
  "python-dotenv>=1.2.1",
 
16
  "requests>=2.32.5",
17
  "sentence-transformers>=5.2.0",
18
  "sqlmodel>=0.0.37",
 
13
  "pandas>=2.3.3",
14
  "psycopg2-binary>=2.9.11",
15
  "python-dotenv>=1.2.1",
16
+ "qdrant-client>=1.17.0",
17
  "requests>=2.32.5",
18
  "sentence-transformers>=5.2.0",
19
  "sqlmodel>=0.0.37",
src/api/main.py CHANGED
@@ -21,7 +21,7 @@ async def lifespan(app: FastAPI):
21
  Replaces lazy global initialization with app state.
22
  """
23
  print("Initializing Anime RAG Pipeline...")
24
- app.state.pipeline = AnimeRAGPipeline(retriever_k=10)
25
 
26
  yield
27
  print("Shutting down... Cleaning up resources.")
@@ -79,7 +79,7 @@ async def get_recommendations(request: RecommendationRequest, fastapi_req: Reque
79
 
80
  if request.genre_filter:
81
  filters["genre_filter"] = request.genre_filter
82
-
83
  if request.anime_type:
84
  filters["anime_type"] = request.anime_type
85
 
 
21
  Replaces lazy global initialization with app state.
22
  """
23
  print("Initializing Anime RAG Pipeline...")
24
+ app.state.pipeline = AnimeRAGPipeline(retriever_k=50)
25
 
26
  yield
27
  print("Shutting down... Cleaning up resources.")
 
79
 
80
  if request.genre_filter:
81
  filters["genre_filter"] = request.genre_filter
82
+
83
  if request.anime_type:
84
  filters["anime_type"] = request.anime_type
85
 
src/llm/anime_reranker.py CHANGED
@@ -87,7 +87,7 @@ if __name__ == "__main__":
87
  reranker_service = AnimeReranker()
88
  retriever = AnimeRetriever()
89
 
90
- user_query = "Modern Romantic Comedy Anime"
91
  vector_db_results = retriever.search(query=user_query,
92
  n_results=50)
93
 
@@ -100,7 +100,7 @@ if __name__ == "__main__":
100
  # 2. Call the `.process()` method during runtime (e.g., inside an API endpoint)
101
  df = reranker_service.process(user_query,
102
  vector_db_results,
103
- top_k=10)
104
  df_dict_list = df.to_dict(orient="records") if not df.empty else []
105
  print("\nAFTER RERANKING")
106
  print("--------------------------------------------------")
 
87
  reranker_service = AnimeReranker()
88
  retriever = AnimeRetriever()
89
 
90
+ user_query = "Racing Anime"
91
  vector_db_results = retriever.search(query=user_query,
92
  n_results=50)
93
 
 
100
  # 2. Call the `.process()` method during runtime (e.g., inside an API endpoint)
101
  df = reranker_service.process(user_query,
102
  vector_db_results,
103
+ top_k=15)
104
  df_dict_list = df.to_dict(orient="records") if not df.empty else []
105
  print("\nAFTER RERANKING")
106
  print("--------------------------------------------------")
src/retrieval/rag_pipeline.py CHANGED
@@ -76,7 +76,8 @@ class AnimeRAGPipeline:
76
  return {
77
  "query": user_query,
78
  "recommendations": "Sorry, I'm having trouble processing your query. Can you be more clear and try again?",
79
- "retrieved_count": 0,
 
80
  "retrieved_animes": []
81
  }
82
  if not initial_response.tool_calls:
@@ -84,7 +85,8 @@ class AnimeRAGPipeline:
84
  "[2/5] No search needed. Returning conversational response.")
85
  return {
86
  "query": user_query,
87
- "retrieved_count": 0,
 
88
  "recommendations": initial_response.content,
89
  "retrieved_animes": []
90
  }
@@ -111,15 +113,12 @@ class AnimeRAGPipeline:
111
  reranked_df = self.reranker.process(
112
  user_query=optimized_query,
113
  retrieved_anime=retrieved_animes,
114
- top_k=15
115
  )
116
 
117
  top_animes_list = reranked_df.to_dict(
118
  orient="records") if not reranked_df.empty else []
119
- print("FIRST IN TOP ANIMES LIST AFTER RERANKING \n",
120
- top_animes_list[0])
121
- print("SECOND IN TOP ANIMES LIST AFTER RERANKING \n",
122
- top_animes_list[1])
123
  print(
124
  f"After reranking, fetched {len(top_animes_list)} top animes....")
125
 
@@ -145,7 +144,7 @@ class AnimeRAGPipeline:
145
 
146
  return {
147
  "query": user_query,
148
- "retrieved_count": len(retrieved_animes),
149
  "reranked_count": len(top_animes_list),
150
  "recommendations": recommendations,
151
  "retrieved_animes": top_animes_list
@@ -173,7 +172,8 @@ if __name__ == "__main__":
173
 
174
  test_queries = [
175
  "Anime similar to Death Note but lighter in tone",
176
- "A really obscure and weird sci-fi mecha from the 90s", # Test the hidden gem / passion rate
 
177
  "A generic isekai with an overpowered main character" # Test the mainstream math
178
  ]
179
 
@@ -189,28 +189,28 @@ if __name__ == "__main__":
189
  # We need to reach into the pipeline's retriever to see what the raw ChromaDB output was,
190
  # since the pipeline only returns the *reranked* list.
191
  # Note: If the LLM chose NOT to search, result["retrieved_count"] will be 0.
192
-
193
  diagnostic_data = {
194
  "1_initial_user_query": result["query"],
195
  "2_llm_routing_decision": "Searched" if result["retrieved_count"] > 0 else "Chatted",
196
  "3_total_retrieved_from_chroma": result["retrieved_count"],
197
  "4_total_survived_reranking": result.get("reranked_count", 0),
198
  "5_final_llm_recommendation_text": result["recommendations"],
199
-
200
  # The "Before" state: We want to see what ChromaDB found just using embeddings
201
  "6_raw_chroma_results_before_reranking": [],
202
-
203
  # The "After" state: We want to see the exact math scores for the top survivors
204
  "7_reranked_results_with_math": []
205
  }
206
 
207
  # If the LLM actually performed a search, let's build the detailed lists
208
  if result["retrieved_count"] > 0:
209
-
210
  # We need to get the optimized query that the LLM generated for the tool call.
211
  # (In a real app, you might want to return `optimized_query` in the `result` dict from `recommend()`)
212
  # For this test, we will just assume it's the original query if we can't easily grab it here.
213
-
214
  # Let's format the top 15 Reranked items with all their math exposed
215
  for rank, anime in enumerate(result["retrieved_animes"]):
216
  diagnostic_data["7_reranked_results_with_math"].append({
@@ -221,13 +221,14 @@ if __name__ == "__main__":
221
  "quality_score_raw": round(anime.get("raw_quality_score", 0), 4),
222
  "bayesian_average": round(anime.get("bayesian_score", 0), 4),
223
  "passion_rate": round(anime.get("passion_rate", 0), 5),
224
- "chroma_distance": round(1 - anime.get("relevance_score", 1), 4) # Showing original vector distance
 
225
  })
226
 
227
  # 3. Save the diagnostic report
228
  safe_filename = f"diagnostic_{query[:20].replace(' ', '_').lower()}.json"
229
  filepath = os.path.join("data", safe_filename)
230
-
231
  with open(filepath, "w") as f:
232
  json.dump(diagnostic_data, f, indent=2)
233
 
@@ -236,4 +237,4 @@ if __name__ == "__main__":
236
 
237
  # Pause to respect Groq API rate limits
238
  import time
239
- time.sleep(3)
 
76
  return {
77
  "query": user_query,
78
  "recommendations": "Sorry, I'm having trouble processing your query. Can you be more clear and try again?",
79
+ "retrieved_count_from_DB": 0,
80
+ "reranked_count": 0,
81
  "retrieved_animes": []
82
  }
83
  if not initial_response.tool_calls:
 
85
  "[2/5] No search needed. Returning conversational response.")
86
  return {
87
  "query": user_query,
88
+ "retrieved_count_from_DB": 0,
89
+ "reranked_count": 0,
90
  "recommendations": initial_response.content,
91
  "retrieved_animes": []
92
  }
 
113
  reranked_df = self.reranker.process(
114
  user_query=optimized_query,
115
  retrieved_anime=retrieved_animes,
116
+ top_k=10
117
  )
118
 
119
  top_animes_list = reranked_df.to_dict(
120
  orient="records") if not reranked_df.empty else []
121
+
 
 
 
122
  print(
123
  f"After reranking, fetched {len(top_animes_list)} top animes....")
124
 
 
144
 
145
  return {
146
  "query": user_query,
147
+ "retrieved_count_from_DB": len(retrieved_animes),
148
  "reranked_count": len(top_animes_list),
149
  "recommendations": recommendations,
150
  "retrieved_animes": top_animes_list
 
172
 
173
  test_queries = [
174
  "Anime similar to Death Note but lighter in tone",
175
+ # Test the hidden gem / passion rate
176
+ "A really obscure and weird sci-fi mecha from the 90s",
177
  "A generic isekai with an overpowered main character" # Test the mainstream math
178
  ]
179
 
 
189
  # We need to reach into the pipeline's retriever to see what the raw ChromaDB output was,
190
  # since the pipeline only returns the *reranked* list.
191
  # Note: If the LLM chose NOT to search, result["retrieved_count"] will be 0.
192
+
193
  diagnostic_data = {
194
  "1_initial_user_query": result["query"],
195
  "2_llm_routing_decision": "Searched" if result["retrieved_count"] > 0 else "Chatted",
196
  "3_total_retrieved_from_chroma": result["retrieved_count"],
197
  "4_total_survived_reranking": result.get("reranked_count", 0),
198
  "5_final_llm_recommendation_text": result["recommendations"],
199
+
200
  # The "Before" state: We want to see what ChromaDB found just using embeddings
201
  "6_raw_chroma_results_before_reranking": [],
202
+
203
  # The "After" state: We want to see the exact math scores for the top survivors
204
  "7_reranked_results_with_math": []
205
  }
206
 
207
  # If the LLM actually performed a search, let's build the detailed lists
208
  if result["retrieved_count"] > 0:
209
+
210
  # We need to get the optimized query that the LLM generated for the tool call.
211
  # (In a real app, you might want to return `optimized_query` in the `result` dict from `recommend()`)
212
  # For this test, we will just assume it's the original query if we can't easily grab it here.
213
+
214
  # Let's format the top 15 Reranked items with all their math exposed
215
  for rank, anime in enumerate(result["retrieved_animes"]):
216
  diagnostic_data["7_reranked_results_with_math"].append({
 
221
  "quality_score_raw": round(anime.get("raw_quality_score", 0), 4),
222
  "bayesian_average": round(anime.get("bayesian_score", 0), 4),
223
  "passion_rate": round(anime.get("passion_rate", 0), 5),
224
+ # Showing original vector distance
225
+ "chroma_distance": round(1 - anime.get("relevance_score", 1), 4)
226
  })
227
 
228
  # 3. Save the diagnostic report
229
  safe_filename = f"diagnostic_{query[:20].replace(' ', '_').lower()}.json"
230
  filepath = os.path.join("data", safe_filename)
231
+
232
  with open(filepath, "w") as f:
233
  json.dump(diagnostic_data, f, indent=2)
234
 
 
237
 
238
  # Pause to respect Groq API rate limits
239
  import time
240
+ time.sleep(3)
src/retrieval/vector_search.py CHANGED
@@ -131,9 +131,7 @@ class AnimeRetriever:
131
  "favorites": pg_anime.favorites,
132
  "images": pg_anime.images,
133
  "synopsis": pg_anime.synopsis,
134
- "searchable_text": pg_anime.searchable_text,
135
- # Clean rounded score
136
- "relevance_score": round(1 - distance, 3),
137
  }
138
  anime_list.append(anime_info)
139
 
 
131
  "favorites": pg_anime.favorites,
132
  "images": pg_anime.images,
133
  "synopsis": pg_anime.synopsis,
134
+ "searchable_text": pg_anime.searchable_text
 
 
135
  }
136
  anime_list.append(anime_info)
137
 
ui/gradio_app.py CHANGED
@@ -91,9 +91,8 @@ def build_retrieved_row(anime: dict, idx: int) -> str:
91
  kind = anime.get("type") or ""
92
  genres = (anime.get("genres") or [])[:2]
93
  url = anime.get("mal_url", "#")
94
- raw_score = anime.get("relevance_score", 0)
95
- normalized_score = (raw_score + 1) / 2
96
- rel_pct = int(normalized_score * 100)
97
  genre_str = " · ".join(genres)
98
 
99
  return (
 
91
  kind = anime.get("type") or ""
92
  genres = (anime.get("genres") or [])[:2]
93
  url = anime.get("mal_url", "#")
94
+ raw_score = anime.get("final_hybrid_score", 0)
95
+ rel_pct = max(5, int(raw_score * 100))
 
96
  genre_str = " · ".join(genres)
97
 
98
  return (
uv.lock CHANGED
@@ -35,6 +35,7 @@ dependencies = [
35
  { name = "pandas" },
36
  { name = "psycopg2-binary" },
37
  { name = "python-dotenv" },
 
38
  { name = "requests" },
39
  { name = "sentence-transformers" },
40
  { name = "sqlmodel" },
@@ -62,6 +63,7 @@ requires-dist = [
62
  { name = "pandas", specifier = ">=2.3.3" },
63
  { name = "psycopg2-binary", specifier = ">=2.9.11" },
64
  { name = "python-dotenv", specifier = ">=1.2.1" },
 
65
  { name = "requests", specifier = ">=2.32.5" },
66
  { name = "sentence-transformers", specifier = ">=5.2.0" },
67
  { name = "sqlmodel", specifier = ">=0.0.37" },
@@ -1024,6 +1026,19 @@ wheels = [
1024
  { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
1025
  ]
1026
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1027
  [[package]]
1028
  name = "hf-xet"
1029
  version = "1.2.0"
@@ -1046,6 +1061,15 @@ wheels = [
1046
  { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" },
1047
  ]
1048
 
 
 
 
 
 
 
 
 
 
1049
  [[package]]
1050
  name = "httpcore"
1051
  version = "1.0.9"
@@ -1103,6 +1127,11 @@ wheels = [
1103
  { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
1104
  ]
1105
 
 
 
 
 
 
1106
  [[package]]
1107
  name = "huggingface-hub"
1108
  version = "0.36.0"
@@ -1122,6 +1151,15 @@ wheels = [
1122
  { url = "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", hash = "sha256:7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d", size = 566094, upload-time = "2025-10-23T12:11:59.557Z" },
1123
  ]
1124
 
 
 
 
 
 
 
 
 
 
1125
  [[package]]
1126
  name = "idna"
1127
  version = "3.11"
@@ -2411,6 +2449,18 @@ wheels = [
2411
  { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
2412
  ]
2413
 
 
 
 
 
 
 
 
 
 
 
 
 
2414
  [[package]]
2415
  name = "posthog"
2416
  version = "5.4.0"
@@ -2872,6 +2922,22 @@ wheels = [
2872
  { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
2873
  ]
2874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2875
  [[package]]
2876
  name = "pywinpty"
2877
  version = "3.0.2"
@@ -2969,6 +3035,24 @@ wheels = [
2969
  { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" },
2970
  ]
2971
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2972
  [[package]]
2973
  name = "referencing"
2974
  version = "0.37.0"
 
35
  { name = "pandas" },
36
  { name = "psycopg2-binary" },
37
  { name = "python-dotenv" },
38
+ { name = "qdrant-client" },
39
  { name = "requests" },
40
  { name = "sentence-transformers" },
41
  { name = "sqlmodel" },
 
63
  { name = "pandas", specifier = ">=2.3.3" },
64
  { name = "psycopg2-binary", specifier = ">=2.9.11" },
65
  { name = "python-dotenv", specifier = ">=1.2.1" },
66
+ { name = "qdrant-client", specifier = ">=1.17.0" },
67
  { name = "requests", specifier = ">=2.32.5" },
68
  { name = "sentence-transformers", specifier = ">=5.2.0" },
69
  { name = "sqlmodel", specifier = ">=0.0.37" },
 
1026
  { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
1027
  ]
1028
 
1029
+ [[package]]
1030
+ name = "h2"
1031
+ version = "4.3.0"
1032
+ source = { registry = "https://pypi.org/simple" }
1033
+ dependencies = [
1034
+ { name = "hpack" },
1035
+ { name = "hyperframe" },
1036
+ ]
1037
+ sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" }
1038
+ wheels = [
1039
+ { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" },
1040
+ ]
1041
+
1042
  [[package]]
1043
  name = "hf-xet"
1044
  version = "1.2.0"
 
1061
  { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" },
1062
  ]
1063
 
1064
+ [[package]]
1065
+ name = "hpack"
1066
+ version = "4.1.0"
1067
+ source = { registry = "https://pypi.org/simple" }
1068
+ sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" }
1069
+ wheels = [
1070
+ { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" },
1071
+ ]
1072
+
1073
  [[package]]
1074
  name = "httpcore"
1075
  version = "1.0.9"
 
1127
  { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
1128
  ]
1129
 
1130
+ [package.optional-dependencies]
1131
+ http2 = [
1132
+ { name = "h2" },
1133
+ ]
1134
+
1135
  [[package]]
1136
  name = "huggingface-hub"
1137
  version = "0.36.0"
 
1151
  { url = "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", hash = "sha256:7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d", size = 566094, upload-time = "2025-10-23T12:11:59.557Z" },
1152
  ]
1153
 
1154
+ [[package]]
1155
+ name = "hyperframe"
1156
+ version = "6.1.0"
1157
+ source = { registry = "https://pypi.org/simple" }
1158
+ sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" }
1159
+ wheels = [
1160
+ { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" },
1161
+ ]
1162
+
1163
  [[package]]
1164
  name = "idna"
1165
  version = "3.11"
 
2449
  { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
2450
  ]
2451
 
2452
+ [[package]]
2453
+ name = "portalocker"
2454
+ version = "3.2.0"
2455
+ source = { registry = "https://pypi.org/simple" }
2456
+ dependencies = [
2457
+ { name = "pywin32", marker = "sys_platform == 'win32'" },
2458
+ ]
2459
+ sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" }
2460
+ wheels = [
2461
+ { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" },
2462
+ ]
2463
+
2464
  [[package]]
2465
  name = "posthog"
2466
  version = "5.4.0"
 
2922
  { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
2923
  ]
2924
 
2925
+ [[package]]
2926
+ name = "pywin32"
2927
+ version = "311"
2928
+ source = { registry = "https://pypi.org/simple" }
2929
+ wheels = [
2930
+ { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" },
2931
+ { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" },
2932
+ { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
2933
+ { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
2934
+ { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
2935
+ { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
2936
+ { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
2937
+ { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
2938
+ { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
2939
+ ]
2940
+
2941
  [[package]]
2942
  name = "pywinpty"
2943
  version = "3.0.2"
 
3035
  { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" },
3036
  ]
3037
 
3038
+ [[package]]
3039
+ name = "qdrant-client"
3040
+ version = "1.17.0"
3041
+ source = { registry = "https://pypi.org/simple" }
3042
+ dependencies = [
3043
+ { name = "grpcio" },
3044
+ { name = "httpx", extra = ["http2"] },
3045
+ { name = "numpy" },
3046
+ { name = "portalocker" },
3047
+ { name = "protobuf" },
3048
+ { name = "pydantic" },
3049
+ { name = "urllib3" },
3050
+ ]
3051
+ sdist = { url = "https://files.pythonhosted.org/packages/20/fb/c9c4cecf6e7fdff2dbaeee0de40e93fe495379eb5fe2775b184ea45315da/qdrant_client-1.17.0.tar.gz", hash = "sha256:47eb033edb9be33a4babb4d87b0d8d5eaf03d52112dca0218db7f2030bf41ba9", size = 344839, upload-time = "2026-02-19T16:03:17.069Z" }
3052
+ wheels = [
3053
+ { url = "https://files.pythonhosted.org/packages/c1/15/dfadbc9d8c9872e8ac45fa96f5099bb2855f23426bfea1bbcdc85e64ef6e/qdrant_client-1.17.0-py3-none-any.whl", hash = "sha256:f5b452c68c42b3580d3d266446fb00d3c6e3aae89c916e16585b3c704e108438", size = 390381, upload-time = "2026-02-19T16:03:15.486Z" },
3054
+ ]
3055
+
3056
  [[package]]
3057
  name = "referencing"
3058
  version = "0.37.0"