Spaces:
Sleeping
Sleeping
Migrated RAG to its package
Browse files- .gitignore +2 -1
- notebooks/transcript_rag.ipynb +76 -273
- pstuts_rag/pstuts_rag/prompt_templates.py +53 -0
- pstuts_rag/pstuts_rag/rag.py +131 -38
- pyproject.toml +37 -34
- uv.lock +159 -0
.gitignore
CHANGED
|
@@ -3,4 +3,5 @@ __pycache__/
|
|
| 3 |
.venv/
|
| 4 |
.env
|
| 5 |
.chainlit/
|
| 6 |
-
.files/
|
|
|
|
|
|
| 3 |
.venv/
|
| 4 |
.env
|
| 5 |
.chainlit/
|
| 6 |
+
.files/
|
| 7 |
+
.vscode/
|
notebooks/transcript_rag.ipynb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
"cells": [
|
| 3 |
{
|
| 4 |
"cell_type": "code",
|
| 5 |
-
"execution_count":
|
| 6 |
"metadata": {},
|
| 7 |
"outputs": [],
|
| 8 |
"source": [
|
|
@@ -15,7 +15,7 @@
|
|
| 15 |
},
|
| 16 |
{
|
| 17 |
"cell_type": "code",
|
| 18 |
-
"execution_count":
|
| 19 |
"metadata": {},
|
| 20 |
"outputs": [],
|
| 21 |
"source": [
|
|
@@ -24,7 +24,7 @@
|
|
| 24 |
},
|
| 25 |
{
|
| 26 |
"cell_type": "code",
|
| 27 |
-
"execution_count":
|
| 28 |
"metadata": {},
|
| 29 |
"outputs": [
|
| 30 |
{
|
|
@@ -43,7 +43,7 @@
|
|
| 43 |
},
|
| 44 |
{
|
| 45 |
"cell_type": "code",
|
| 46 |
-
"execution_count":
|
| 47 |
"metadata": {},
|
| 48 |
"outputs": [],
|
| 49 |
"source": [
|
|
@@ -59,7 +59,7 @@
|
|
| 59 |
},
|
| 60 |
{
|
| 61 |
"cell_type": "code",
|
| 62 |
-
"execution_count":
|
| 63 |
"metadata": {},
|
| 64 |
"outputs": [],
|
| 65 |
"source": [
|
|
@@ -87,10 +87,11 @@
|
|
| 87 |
},
|
| 88 |
{
|
| 89 |
"cell_type": "code",
|
| 90 |
-
"execution_count":
|
| 91 |
"metadata": {},
|
| 92 |
"outputs": [],
|
| 93 |
"source": [
|
|
|
|
| 94 |
"import json\n",
|
| 95 |
"filename = \"../data/test.json\"\n",
|
| 96 |
"\n",
|
|
@@ -99,17 +100,21 @@
|
|
| 99 |
},
|
| 100 |
{
|
| 101 |
"cell_type": "code",
|
| 102 |
-
"execution_count":
|
| 103 |
"metadata": {},
|
| 104 |
-
"outputs": [
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
},
|
| 114 |
{
|
| 115 |
"cell_type": "markdown",
|
|
@@ -127,26 +132,14 @@
|
|
| 127 |
},
|
| 128 |
{
|
| 129 |
"cell_type": "code",
|
| 130 |
-
"execution_count":
|
| 131 |
"metadata": {},
|
| 132 |
"outputs": [],
|
| 133 |
"source": [
|
| 134 |
-
"from
|
| 135 |
-
"from qdrant_client import QdrantClient\n",
|
| 136 |
-
"from qdrant_client.http.models import Distance, VectorParams\n",
|
| 137 |
-
"\n",
|
| 138 |
-
"qdrantclient = QdrantClient(\":memory:\")\n",
|
| 139 |
"\n",
|
| 140 |
-
"
|
| 141 |
-
"
|
| 142 |
-
" collection_name=f\"{params.filename}_qdrant\",\n",
|
| 143 |
-
" embeddings=embeddings,\n",
|
| 144 |
-
")\n",
|
| 145 |
-
"\n",
|
| 146 |
-
"_ = vectorstore.add_documents(documents=docs_chunks_semantic)\n",
|
| 147 |
-
"retriever =vectorstore.as_retriever(\n",
|
| 148 |
-
" search_kwargs={\"k\": params.n_context_docs}\n",
|
| 149 |
-
")"
|
| 150 |
]
|
| 151 |
},
|
| 152 |
{
|
|
@@ -159,301 +152,111 @@
|
|
| 159 |
]
|
| 160 |
},
|
| 161 |
{
|
| 162 |
-
"cell_type": "
|
| 163 |
-
"execution_count": 139,
|
| 164 |
"metadata": {},
|
| 165 |
-
"outputs": [],
|
| 166 |
"source": [
|
| 167 |
-
"
|
| 168 |
-
"\n",
|
| 169 |
-
"prompt_template = ChatPromptTemplate.from_messages([\n",
|
| 170 |
-
" (\"system\", \"\"\"\\\n",
|
| 171 |
-
"You are a helpful and friendly Photoshop expert.\n",
|
| 172 |
-
"\n",
|
| 173 |
-
"Your job is to answer user questions based **only** on transcript excerpts from training videos. These transcripts include **timestamps** that indicate when in the video the information was spoken.\n",
|
| 174 |
-
"\n",
|
| 175 |
-
"The transcript is from **spoken audio**, so it may include informal phrasing, filler words, or fragmented sentences. You may interpret meaning **only to the extent it is clearly implied**, but you must not add new information or invent details.\n",
|
| 176 |
-
"\n",
|
| 177 |
-
"✅ Your Responsibilities\n",
|
| 178 |
-
"\n",
|
| 179 |
-
"1. Use **only** the transcript to answer.\n",
|
| 180 |
-
"2. If a clear answer is **not** present in the transcript, respond exactly: \n",
|
| 181 |
-
" \"I don't know. This isn’t covered in the training videos.\"\n",
|
| 182 |
-
"3. When appropriate, include the **timestamp** of relevant information in your answer to help the user locate it in the original video.\n",
|
| 183 |
-
"4. Do **not** make assumptions or draw on outside knowledge.\n",
|
| 184 |
-
"\n",
|
| 185 |
-
"💡 Style & Formatting Tips\n",
|
| 186 |
-
"\n",
|
| 187 |
-
"- Use a step-by-step format when explaining procedures 📋.\n",
|
| 188 |
-
"- Add relevant emojis for clarity and friendliness 🎨🖱️🔧.\n",
|
| 189 |
-
"- Keep your answers short, clear, and conversational.\n",
|
| 190 |
-
"- The input timestamps will be in seconds. When reporting timestamps, convert them into minute:seconds format.\n",
|
| 191 |
-
"\n",
|
| 192 |
-
"⛔ Never Do This\n",
|
| 193 |
-
"\n",
|
| 194 |
-
"- ❌ Don't guess or summarize from general knowledge.\n",
|
| 195 |
-
"- ❌ Don’t fabricate steps, names, or features not in the transcript.\n",
|
| 196 |
-
"- ❌ Don’t omit the fallback response when required.\n",
|
| 197 |
-
"\"\"\"),\n",
|
| 198 |
-
" (\"user\",\"\"\"\\\n",
|
| 199 |
-
"\n",
|
| 200 |
-
"### Question\n",
|
| 201 |
-
"{question}\n",
|
| 202 |
-
"\n",
|
| 203 |
-
"NEVER invent the explanation. ALWAYS use ONLY the context information.\n",
|
| 204 |
-
"\n",
|
| 205 |
-
"### Context\n",
|
| 206 |
-
"{context}\n",
|
| 207 |
"\n",
|
| 208 |
-
"
|
| 209 |
]
|
| 210 |
},
|
| 211 |
{
|
| 212 |
"cell_type": "code",
|
| 213 |
-
"execution_count":
|
| 214 |
"metadata": {},
|
| 215 |
"outputs": [],
|
| 216 |
"source": [
|
| 217 |
-
"
|
| 218 |
-
"
|
| 219 |
-
"
|
| 220 |
-
" for doc in context\n",
|
| 221 |
-
" ] \n",
|
| 222 |
-
" print(type(references))\n",
|
| 223 |
-
" return json.dumps(references,indent=2)\n"
|
| 224 |
]
|
| 225 |
},
|
| 226 |
{
|
| 227 |
-
"cell_type": "
|
|
|
|
| 228 |
"metadata": {},
|
|
|
|
| 229 |
"source": [
|
| 230 |
-
"
|
| 231 |
"\n",
|
| 232 |
-
"
|
| 233 |
]
|
| 234 |
},
|
| 235 |
{
|
| 236 |
"cell_type": "code",
|
| 237 |
-
"execution_count":
|
| 238 |
"metadata": {},
|
| 239 |
"outputs": [],
|
| 240 |
"source": [
|
| 241 |
-
"from langchain_openai import ChatOpenAI\n",
|
| 242 |
"\n",
|
| 243 |
-
"
|
|
|
|
| 244 |
]
|
| 245 |
},
|
| 246 |
{
|
| 247 |
"cell_type": "code",
|
| 248 |
-
"execution_count":
|
| 249 |
"metadata": {},
|
| 250 |
-
"outputs": [
|
| 251 |
-
{
|
| 252 |
-
"name": "stdout",
|
| 253 |
-
"output_type": "stream",
|
| 254 |
-
"text": [
|
| 255 |
-
"<class 'list'>\n",
|
| 256 |
-
"('Layers are the building blocks of any image in Photoshop CC. You can think '\n",
|
| 257 |
-
" 'of layers like separate flat panes of glass stacked on top of each other. '\n",
|
| 258 |
-
" 'Each layer contains separate pieces of content. Some parts of a layer can be '\n",
|
| 259 |
-
" 'transparent, allowing you to see through to the layers below. This setup '\n",
|
| 260 |
-
" 'lets you edit parts of an image independently without affecting the rest of '\n",
|
| 261 |
-
" 'the image. You manage and work with layers in the Layers panel, where you '\n",
|
| 262 |
-
" 'can toggle their visibility on and off using the Eye icon. (See explanation '\n",
|
| 263 |
-
" 'around 0:28 to 1:00 and 1:25 to 2:32) 🎨🖼️\\n'\n",
|
| 264 |
-
" 'References:\\n'\n",
|
| 265 |
-
" '[\\n'\n",
|
| 266 |
-
" ' {\\n'\n",
|
| 267 |
-
" ' \"title\": \"Understand layers\",\\n'\n",
|
| 268 |
-
" ' \"source\": '\n",
|
| 269 |
-
" '\"https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4\",\\n'\n",
|
| 270 |
-
" ' \"start\": 0.47,\\n'\n",
|
| 271 |
-
" ' \"stop\": 62.14\\n'\n",
|
| 272 |
-
" ' },\\n'\n",
|
| 273 |
-
" ' {\\n'\n",
|
| 274 |
-
" ' \"title\": \"Understand layers\",\\n'\n",
|
| 275 |
-
" ' \"source\": '\n",
|
| 276 |
-
" '\"https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4\",\\n'\n",
|
| 277 |
-
" ' \"start\": 85.75,\\n'\n",
|
| 278 |
-
" ' \"stop\": 152.97\\n'\n",
|
| 279 |
-
" ' }\\n'\n",
|
| 280 |
-
" ']')\n"
|
| 281 |
-
]
|
| 282 |
-
}
|
| 283 |
-
],
|
| 284 |
"source": [
|
| 285 |
-
"
|
| 286 |
-
"from langchain.schema.output_parser import StrOutputParser\n",
|
| 287 |
-
"from langchain_core.runnables import RunnableLambda\n",
|
| 288 |
-
"\n",
|
| 289 |
-
"form_context = RunnableLambda(itemgetter(\"question\")) | {\n",
|
| 290 |
-
" \"context\": retriever, \n",
|
| 291 |
-
" \"question\": RunnablePassthrough() \n",
|
| 292 |
-
" } \n",
|
| 293 |
-
"\n",
|
| 294 |
-
"answer_chain = prompt_template | llm | StrOutputParser()\n",
|
| 295 |
-
"\n",
|
| 296 |
-
"get_videos = form_context | \\\n",
|
| 297 |
-
" {\"input\":RunnablePassthrough(),\"answer\": answer_chain} |\\\n",
|
| 298 |
-
" RunnableLambda( lambda d: \n",
|
| 299 |
-
" {**d[\"input\"], \"answer\": d[\"answer\"] + \n",
|
| 300 |
-
" \"\\nReferences:\\n\" +\n",
|
| 301 |
-
" compile_references(d[\"input\"][\"context\"]) \n",
|
| 302 |
-
" } )\n",
|
| 303 |
-
" \n",
|
| 304 |
-
"\n",
|
| 305 |
-
"\n",
|
| 306 |
-
"val = get_videos.invoke({\"question\":\"What are layers\"})\n",
|
| 307 |
-
"pp(val[\"answer\"])"
|
| 308 |
]
|
| 309 |
},
|
| 310 |
{
|
| 311 |
"cell_type": "code",
|
| 312 |
-
"execution_count":
|
| 313 |
"metadata": {},
|
| 314 |
"outputs": [
|
| 315 |
{
|
| 316 |
-
"
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
" 'References:\\n'\n",
|
| 328 |
-
" '[\\n'\n",
|
| 329 |
-
" ' {\\n'\n",
|
| 330 |
-
" ' \"title\": \"Understand layers\",\\n'\n",
|
| 331 |
-
" ' \"source\": '\n",
|
| 332 |
-
" '\"https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4\",\\n'\n",
|
| 333 |
-
" ' \"start\": 0.47,\\n'\n",
|
| 334 |
-
" ' \"stop\": 62.14\\n'\n",
|
| 335 |
-
" ' },\\n'\n",
|
| 336 |
-
" ' {\\n'\n",
|
| 337 |
-
" ' \"title\": \"Understand layers\",\\n'\n",
|
| 338 |
-
" ' \"source\": '\n",
|
| 339 |
-
" '\"https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4\",\\n'\n",
|
| 340 |
-
" ' \"start\": 85.75,\\n'\n",
|
| 341 |
-
" ' \"stop\": 152.97\\n'\n",
|
| 342 |
-
" ' }\\n'\n",
|
| 343 |
-
" ']')\n"
|
| 344 |
-
]
|
| 345 |
}
|
| 346 |
],
|
| 347 |
"source": [
|
| 348 |
-
"
|
| 349 |
]
|
| 350 |
},
|
| 351 |
{
|
| 352 |
"cell_type": "code",
|
| 353 |
-
"execution_count":
|
| 354 |
"metadata": {},
|
| 355 |
"outputs": [
|
| 356 |
{
|
| 357 |
"name": "stdout",
|
| 358 |
"output_type": "stream",
|
| 359 |
"text": [
|
| 360 |
-
"
|
| 361 |
-
"
|
| 362 |
-
"
|
| 363 |
-
"
|
| 364 |
-
"
|
| 365 |
-
"
|
| 366 |
-
"
|
| 367 |
-
"
|
| 368 |
-
"
|
| 369 |
-
"
|
| 370 |
-
"
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
"
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
{
|
| 379 |
-
"cell_type": "code",
|
| 380 |
-
"execution_count": 145,
|
| 381 |
-
"metadata": {},
|
| 382 |
-
"outputs": [
|
| 383 |
-
{
|
| 384 |
-
"ename": "NameError",
|
| 385 |
-
"evalue": "name 'generate' is not defined",
|
| 386 |
-
"output_type": "error",
|
| 387 |
-
"traceback": [
|
| 388 |
-
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
| 389 |
-
"\u001b[31mNameError\u001b[39m Traceback (most recent call last)",
|
| 390 |
-
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[145]\u001b[39m\u001b[32m, line 13\u001b[39m\n\u001b[32m 10\u001b[39m context: List[Document]\n\u001b[32m 11\u001b[39m response: \u001b[38;5;28mstr\u001b[39m\n\u001b[32m---> \u001b[39m\u001b[32m13\u001b[39m graph_builder = StateGraph(State).add_sequence([retrieve, \u001b[43mgenerate\u001b[49m ])\n\u001b[32m 14\u001b[39m graph_builder.add_edge(START, \u001b[33m\"\u001b[39m\u001b[33mretrieve\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 15\u001b[39m graph = graph_builder.compile()\n",
|
| 391 |
-
"\u001b[31mNameError\u001b[39m: name 'generate' is not defined"
|
| 392 |
]
|
| 393 |
}
|
| 394 |
],
|
| 395 |
"source": [
|
| 396 |
-
"
|
| 397 |
-
"from typing_extensions import List, TypedDict,Annotated\n",
|
| 398 |
-
"from langchain_core.documents import Document\n",
|
| 399 |
-
"from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n",
|
| 400 |
-
"from langchain_openai.chat_models import ChatOpenAI\n",
|
| 401 |
-
"import operator\n",
|
| 402 |
-
"\n",
|
| 403 |
-
"class State(TypedDict):\n",
|
| 404 |
-
" question: str\n",
|
| 405 |
-
" context: List[Document]\n",
|
| 406 |
-
" response: str\n",
|
| 407 |
-
" \n",
|
| 408 |
-
"graph_builder = StateGraph(State).add_sequence([retrieve, generate ])\n",
|
| 409 |
-
"graph_builder.add_edge(START, \"retrieve\")\n",
|
| 410 |
-
"graph = graph_builder.compile()"
|
| 411 |
-
]
|
| 412 |
-
},
|
| 413 |
-
{
|
| 414 |
-
"cell_type": "code",
|
| 415 |
-
"execution_count": 53,
|
| 416 |
-
"metadata": {},
|
| 417 |
-
"outputs": [],
|
| 418 |
-
"source": [
|
| 419 |
-
"from langchain.schema.output_parser import StrOutputParser\n",
|
| 420 |
-
"response = graph.invoke({\"question\" : \"What is the layer in Photoshop\"})"
|
| 421 |
-
]
|
| 422 |
-
},
|
| 423 |
-
{
|
| 424 |
-
"cell_type": "code",
|
| 425 |
-
"execution_count": null,
|
| 426 |
-
"metadata": {},
|
| 427 |
-
"outputs": [],
|
| 428 |
-
"source": [
|
| 429 |
-
"response.keys()"
|
| 430 |
-
]
|
| 431 |
-
},
|
| 432 |
-
{
|
| 433 |
-
"cell_type": "code",
|
| 434 |
-
"execution_count": null,
|
| 435 |
-
"metadata": {},
|
| 436 |
-
"outputs": [],
|
| 437 |
-
"source": [
|
| 438 |
-
"type(response)"
|
| 439 |
-
]
|
| 440 |
-
},
|
| 441 |
-
{
|
| 442 |
-
"cell_type": "code",
|
| 443 |
-
"execution_count": null,
|
| 444 |
-
"metadata": {},
|
| 445 |
-
"outputs": [],
|
| 446 |
-
"source": [
|
| 447 |
-
"pp(response)"
|
| 448 |
-
]
|
| 449 |
-
},
|
| 450 |
-
{
|
| 451 |
-
"cell_type": "code",
|
| 452 |
-
"execution_count": null,
|
| 453 |
-
"metadata": {},
|
| 454 |
-
"outputs": [],
|
| 455 |
-
"source": [
|
| 456 |
-
"response.keys()"
|
| 457 |
]
|
| 458 |
},
|
| 459 |
{
|
|
|
|
| 2 |
"cells": [
|
| 3 |
{
|
| 4 |
"cell_type": "code",
|
| 5 |
+
"execution_count": 9,
|
| 6 |
"metadata": {},
|
| 7 |
"outputs": [],
|
| 8 |
"source": [
|
|
|
|
| 15 |
},
|
| 16 |
{
|
| 17 |
"cell_type": "code",
|
| 18 |
+
"execution_count": 10,
|
| 19 |
"metadata": {},
|
| 20 |
"outputs": [],
|
| 21 |
"source": [
|
|
|
|
| 24 |
},
|
| 25 |
{
|
| 26 |
"cell_type": "code",
|
| 27 |
+
"execution_count": 11,
|
| 28 |
"metadata": {},
|
| 29 |
"outputs": [
|
| 30 |
{
|
|
|
|
| 43 |
},
|
| 44 |
{
|
| 45 |
"cell_type": "code",
|
| 46 |
+
"execution_count": 12,
|
| 47 |
"metadata": {},
|
| 48 |
"outputs": [],
|
| 49 |
"source": [
|
|
|
|
| 59 |
},
|
| 60 |
{
|
| 61 |
"cell_type": "code",
|
| 62 |
+
"execution_count": 13,
|
| 63 |
"metadata": {},
|
| 64 |
"outputs": [],
|
| 65 |
"source": [
|
|
|
|
| 87 |
},
|
| 88 |
{
|
| 89 |
"cell_type": "code",
|
| 90 |
+
"execution_count": 14,
|
| 91 |
"metadata": {},
|
| 92 |
"outputs": [],
|
| 93 |
"source": [
|
| 94 |
+
"from ast import Dict\n",
|
| 95 |
"import json\n",
|
| 96 |
"filename = \"../data/test.json\"\n",
|
| 97 |
"\n",
|
|
|
|
| 100 |
},
|
| 101 |
{
|
| 102 |
"cell_type": "code",
|
| 103 |
+
"execution_count": null,
|
| 104 |
"metadata": {},
|
| 105 |
+
"outputs": [
|
| 106 |
+
{
|
| 107 |
+
"data": {
|
| 108 |
+
"text/plain": [
|
| 109 |
+
"dict_values"
|
| 110 |
+
]
|
| 111 |
+
},
|
| 112 |
+
"execution_count": 18,
|
| 113 |
+
"metadata": {},
|
| 114 |
+
"output_type": "execute_result"
|
| 115 |
+
}
|
| 116 |
+
],
|
| 117 |
+
"source": []
|
| 118 |
},
|
| 119 |
{
|
| 120 |
"cell_type": "markdown",
|
|
|
|
| 132 |
},
|
| 133 |
{
|
| 134 |
"cell_type": "code",
|
| 135 |
+
"execution_count": 20,
|
| 136 |
"metadata": {},
|
| 137 |
"outputs": [],
|
| 138 |
"source": [
|
| 139 |
+
"from pstuts_rag.rag import RetrieverFactory\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
"\n",
|
| 141 |
+
"retriever_factory = RetrieverFactory()\n",
|
| 142 |
+
"retriever_factory.add_docs(raw_docs=data)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
]
|
| 144 |
},
|
| 145 |
{
|
|
|
|
| 152 |
]
|
| 153 |
},
|
| 154 |
{
|
| 155 |
+
"cell_type": "markdown",
|
|
|
|
| 156 |
"metadata": {},
|
|
|
|
| 157 |
"source": [
|
| 158 |
+
"## Generation\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
"\n",
|
| 160 |
+
"We will use a 4.1-nano to generate answers."
|
| 161 |
]
|
| 162 |
},
|
| 163 |
{
|
| 164 |
"cell_type": "code",
|
| 165 |
+
"execution_count": 25,
|
| 166 |
"metadata": {},
|
| 167 |
"outputs": [],
|
| 168 |
"source": [
|
| 169 |
+
"from langchain_openai import ChatOpenAI\n",
|
| 170 |
+
"\n",
|
| 171 |
+
"llm = ChatOpenAI(model=\"gpt-4.1-mini\",temperature=0)"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
]
|
| 173 |
},
|
| 174 |
{
|
| 175 |
+
"cell_type": "code",
|
| 176 |
+
"execution_count": 26,
|
| 177 |
"metadata": {},
|
| 178 |
+
"outputs": [],
|
| 179 |
"source": [
|
| 180 |
+
"from pstuts_rag.rag import RAGChainFactory\n",
|
| 181 |
"\n",
|
| 182 |
+
"rag_factory = RAGChainFactory(retriever=retriever_factory.get_retriever())\n"
|
| 183 |
]
|
| 184 |
},
|
| 185 |
{
|
| 186 |
"cell_type": "code",
|
| 187 |
+
"execution_count": 49,
|
| 188 |
"metadata": {},
|
| 189 |
"outputs": [],
|
| 190 |
"source": [
|
|
|
|
| 191 |
"\n",
|
| 192 |
+
"get_videos = rag_factory.get_rag_chain(llm)\n",
|
| 193 |
+
" \n"
|
| 194 |
]
|
| 195 |
},
|
| 196 |
{
|
| 197 |
"cell_type": "code",
|
| 198 |
+
"execution_count": 50,
|
| 199 |
"metadata": {},
|
| 200 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
"source": [
|
| 202 |
+
"val = get_videos.invoke({\"question\":\"What are layers\"})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
]
|
| 204 |
},
|
| 205 |
{
|
| 206 |
"cell_type": "code",
|
| 207 |
+
"execution_count": 52,
|
| 208 |
"metadata": {},
|
| 209 |
"outputs": [
|
| 210 |
{
|
| 211 |
+
"data": {
|
| 212 |
+
"text/plain": [
|
| 213 |
+
"{'refusal': None,\n",
|
| 214 |
+
" 'context': [Document(metadata={'video_id': 19172, 'title': 'Understand layers', 'desc': 'Learn what layers are and why they are so useful.', 'length': '00:04:44.75', 'source': 'https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4', 'speech_start_stop_times': [[0.47, 3.41], [3.81, 9.13], [9.309999, 15.01], [15.299999, 20.57], [20.88, 23.3], [23.83, 27.93], [29.38, 32.79], [32.96, 33.92], [34.43, 40.21], [41.91, 45.37], [45.88, 49.01], [49.54, 55.130001], [55.72, 58.49], [58.72, 62.14]], 'start': 0.47, 'stop': 62.14, '_id': '9124467014f5417eb5f90a7f350a7f4e', '_collection_name': '2f1798fb-3bc9-4ece-9bc5-1f465fc4b821_qdrant'}, page_content=\"Layers are the building blocks of any image in Photoshop CC. So, it's important to understand, what layers are and why to use them - which we'll cover in this video. If you're following along, open this layered image from the downloadable practice files for this tutorial. You might think of layers like separate flat pints of glass, stacked one on top of the other. Each layer contains separate pieces of content. To get a sense of how layers are constructed, let's take a look at this Layers panel. I've closed my other panels, so that we can focus on the Layers panel. But you can skip that. By the way: If your Layers panel isn't showing, go up to the Window menu and choose Layers from there. The Layers panel is where you go to select and work with layers. In this image there are 4 layers, each with separate content. If you click the Eye icon to the left of a layer, you can toggle the visibility of that layer off and on. So, I'm going to turn off the visibility of the tailor layer. And keep your eye on the image, so you can see what's on that layer.\"),\n",
|
| 215 |
+
" Document(metadata={'video_id': 19172, 'title': 'Understand layers', 'desc': 'Learn what layers are and why they are so useful.', 'length': '00:04:44.75', 'source': 'https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4', 'speech_start_stop_times': [[85.75, 88.659999], [89.42, 100.11], [101.469999, 108.64], [109.09, 117.459999], [117.75, 129.45], [129.97, 133.37], [133.73, 143.98], [144.76, 152.97]], 'start': 85.75, 'stop': 152.97, '_id': '2b41e3b5ed894393a4d50b55820888c9', '_collection_name': '2f1798fb-3bc9-4ece-9bc5-1f465fc4b821_qdrant'}, page_content=\"Now let's take a look at just one layer, the tailor layer. A quick way to turn off all the layers except the tailor layer, is to hold down the Option key on the Mac, or the ALT key on the PC, and click on the Eye icon to the left of the tailor layer. In the Document window, you can see that this layer contains just the one small photo surrounded by a gray and white checkerboard pattern. That pattern represents transparent pixels, which allow us to see down through the corresponding part of this layer to the content of the layers below. So, let's turn that content back on by going back to the Layers panel, again holding the Option key on the Mac or the ALT key on the PC and clicking on the Eye icon to the left of the tailor layer. And all the other layers and their Eye icons come back into view. So again: You might think of layers like a stack of pints of glass, each with its own artwork and in some cases transparent areas that let you see down through to the layers below. The biggest benefit of having items on separate layers like this, is that you'll be able to edit pieces of an image independently without affecting the rest of the image.\")],\n",
|
| 216 |
+
" 'question': 'What are layers'}"
|
| 217 |
+
]
|
| 218 |
+
},
|
| 219 |
+
"execution_count": 52,
|
| 220 |
+
"metadata": {},
|
| 221 |
+
"output_type": "execute_result"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
}
|
| 223 |
],
|
| 224 |
"source": [
|
| 225 |
+
"val.additional_kwargs"
|
| 226 |
]
|
| 227 |
},
|
| 228 |
{
|
| 229 |
"cell_type": "code",
|
| 230 |
+
"execution_count": 53,
|
| 231 |
"metadata": {},
|
| 232 |
"outputs": [
|
| 233 |
{
|
| 234 |
"name": "stdout",
|
| 235 |
"output_type": "stream",
|
| 236 |
"text": [
|
| 237 |
+
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
| 238 |
+
"\n",
|
| 239 |
+
"Layers are the building blocks of any image in Photoshop CC. You can think of layers like separate flat panes of glass stacked one on top of the other. Each layer contains separate pieces of content. Some parts of a layer can be transparent, allowing you to see through to the layers below. This setup lets you edit parts of an image independently without affecting the rest of the image. You work with layers in the Layers panel, where you can toggle their visibility on and off to see what each layer contains (like turning off the tailor layer to see what’s on it). The transparent areas are shown as a gray and white checkerboard pattern. This concept is explained around 0:47 to 3:41 and 85:75 to 152:97 in the video. 🎨🖼️\n",
|
| 240 |
+
"**References**:\n",
|
| 241 |
+
"[\n",
|
| 242 |
+
" {\n",
|
| 243 |
+
" \"title\": \"Understand layers\",\n",
|
| 244 |
+
" \"source\": \"https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4\",\n",
|
| 245 |
+
" \"start\": 0.47,\n",
|
| 246 |
+
" \"stop\": 62.14\n",
|
| 247 |
+
" },\n",
|
| 248 |
+
" {\n",
|
| 249 |
+
" \"title\": \"Understand layers\",\n",
|
| 250 |
+
" \"source\": \"https://images-tv.adobe.com/avp/vr/b758b4c4-2a74-41f4-8e67-e2f2eab83c6a/f810fc5b-2b04-4e23-8fa4-5c532e7de6f8/e268fe4d-e5c7-415c-9f5c-d34d024b14d8_20170727011753.1280x720at2400_h264.mp4\",\n",
|
| 251 |
+
" \"start\": 85.75,\n",
|
| 252 |
+
" \"stop\": 152.97\n",
|
| 253 |
+
" }\n",
|
| 254 |
+
"]\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
]
|
| 256 |
}
|
| 257 |
],
|
| 258 |
"source": [
|
| 259 |
+
"val.pretty_print()"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
]
|
| 261 |
},
|
| 262 |
{
|
pstuts_rag/pstuts_rag/prompt_templates.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Tuple
|
| 2 |
+
|
| 3 |
+
RAG_PROMPT_TEMPLATES: List[Tuple[str, str]] = []
|
| 4 |
+
|
| 5 |
+
RAG_PROMPT_TEMPLATES.append(
|
| 6 |
+
(
|
| 7 |
+
"system",
|
| 8 |
+
"""\
|
| 9 |
+
You are a helpful and friendly Photoshop expert.
|
| 10 |
+
|
| 11 |
+
Your job is to answer user questions based **only** on transcript excerpts from training videos. These transcripts include **timestamps** that indicate when in the video the information was spoken.
|
| 12 |
+
|
| 13 |
+
The transcript is from **spoken audio**, so it may include informal phrasing, filler words, or fragmented sentences. You may interpret meaning **only to the extent it is clearly implied**, but you must not add new information or invent details.
|
| 14 |
+
|
| 15 |
+
✅ Your Responsibilities
|
| 16 |
+
|
| 17 |
+
1. Use **only** the transcript to answer.
|
| 18 |
+
2. If a clear answer is **not** present in the transcript, respond exactly:
|
| 19 |
+
"I don't know. This isn’t covered in the training videos."
|
| 20 |
+
3. When appropriate, include the **timestamp** of relevant information in your answer to help the user locate it in the original video.
|
| 21 |
+
4. Do **not** make assumptions or draw on outside knowledge.
|
| 22 |
+
|
| 23 |
+
💡 Style & Formatting Tips
|
| 24 |
+
|
| 25 |
+
- Use a step-by-step format when explaining procedures 📋.
|
| 26 |
+
- Add relevant emojis for clarity and friendliness 🎨🖱️🔧.
|
| 27 |
+
- Keep your answers short, clear, and conversational.
|
| 28 |
+
- The input timestamps will be in seconds. When reporting timestamps, convert them into minute:seconds format.
|
| 29 |
+
|
| 30 |
+
⛔ Never Do This
|
| 31 |
+
|
| 32 |
+
- ❌ Don't guess or summarize from general knowledge.
|
| 33 |
+
- ❌ Don’t fabricate steps, names, or features not in the transcript.
|
| 34 |
+
- ❌ Don’t omit the fallback response when required.
|
| 35 |
+
""",
|
| 36 |
+
)
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
RAG_PROMPT_TEMPLATES.append(
|
| 40 |
+
(
|
| 41 |
+
"user",
|
| 42 |
+
"""\
|
| 43 |
+
### Question
|
| 44 |
+
{question}
|
| 45 |
+
|
| 46 |
+
NEVER invent the explanation. ALWAYS use ONLY the context information.
|
| 47 |
+
|
| 48 |
+
### Context
|
| 49 |
+
{context}
|
| 50 |
+
|
| 51 |
+
""",
|
| 52 |
+
)
|
| 53 |
+
)
|
pstuts_rag/pstuts_rag/rag.py
CHANGED
|
@@ -1,67 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from typing import List, Optional, Union
|
| 2 |
-
from langchain_experimental.text_splitter import SemanticChunker
|
| 3 |
-
from langchain_openai.embeddings import OpenAIEmbeddings
|
| 4 |
-
|
| 5 |
-
from langchain_qdrant import QdrantVectorStore
|
| 6 |
-
from qdrant_client import QdrantClient
|
| 7 |
-
from qdrant_client.http.models import Distance, VectorParams
|
| 8 |
-
|
| 9 |
-
from .datastore import transcripts_load, initialize_vectorstore
|
| 10 |
|
| 11 |
-
from
|
| 12 |
-
from langchain_openai.embeddings import OpenAIEmbeddings
|
| 13 |
from langchain_core.documents import Document
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
from
|
| 16 |
-
|
| 17 |
from langchain_qdrant import QdrantVectorStore
|
| 18 |
from qdrant_client import QdrantClient
|
| 19 |
-
from qdrant_client.http.models import Distance, VectorParams
|
| 20 |
|
| 21 |
-
from
|
|
|
|
|
|
|
| 22 |
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
import
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
-
class
|
| 28 |
|
| 29 |
embeddings: OpenAIEmbeddings
|
| 30 |
docs: List[Document]
|
| 31 |
-
|
| 32 |
name: str
|
| 33 |
-
|
| 34 |
-
|
| 35 |
|
| 36 |
def __init__(
|
| 37 |
self,
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
| 42 |
) -> None:
|
| 43 |
|
| 44 |
self.embeddings = embeddings
|
| 45 |
self.name = name
|
| 46 |
-
self.
|
| 47 |
-
self.
|
| 48 |
-
client=self.
|
| 49 |
collection_name=f"{self.name}_qdrant",
|
| 50 |
embeddings=self.embeddings,
|
| 51 |
)
|
| 52 |
self.docs = []
|
| 53 |
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
self.docs.
|
| 59 |
-
_ = self.
|
| 60 |
-
|
| 61 |
-
def clear() -> bool:
|
| 62 |
self.docs = []
|
| 63 |
-
return self.
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import uuid
|
| 3 |
+
from operator import itemgetter
|
| 4 |
from typing import List, Optional, Union
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
from langchain.schema.output_parser import StrOutputParser
|
|
|
|
| 7 |
from langchain_core.documents import Document
|
| 8 |
+
from langchain_core.runnables import (
|
| 9 |
+
Runnable,
|
| 10 |
+
RunnableLambda,
|
| 11 |
+
RunnablePassthrough,
|
| 12 |
+
)
|
| 13 |
|
| 14 |
+
from langchain_openai.embeddings import OpenAIEmbeddings
|
|
|
|
| 15 |
from langchain_qdrant import QdrantVectorStore
|
| 16 |
from qdrant_client import QdrantClient
|
|
|
|
| 17 |
|
| 18 |
+
from langchain.prompts import ChatPromptTemplate
|
| 19 |
+
from langchain_core.vectorstores import VectorStoreRetriever
|
| 20 |
+
from langchain_openai import ChatOpenAI
|
| 21 |
|
| 22 |
+
from .datastore import initialize_vectorstore, transcripts_load
|
| 23 |
+
from .prompt_templates import RAG_PROMPT_TEMPLATES
|
| 24 |
|
| 25 |
+
from langchain_core.language_models.base import BaseLanguageModel
|
| 26 |
+
from langchain_core.messages import AIMessage
|
| 27 |
|
| 28 |
|
| 29 |
+
class RetrieverFactory:
|
| 30 |
|
| 31 |
embeddings: OpenAIEmbeddings
|
| 32 |
docs: List[Document]
|
| 33 |
+
qdrant_client: QdrantClient
|
| 34 |
name: str
|
| 35 |
+
vector_store: QdrantVectorStore
|
|
|
|
| 36 |
|
| 37 |
def __init__(
|
| 38 |
self,
|
| 39 |
+
embeddings: OpenAIEmbeddings = OpenAIEmbeddings(
|
| 40 |
+
model="text-embedding-3-small"
|
| 41 |
+
),
|
| 42 |
+
qdrant_client: QdrantClient = QdrantClient(location=":memory:"),
|
| 43 |
+
name: str = str(object=uuid.uuid4()),
|
| 44 |
) -> None:
|
| 45 |
|
| 46 |
self.embeddings = embeddings
|
| 47 |
self.name = name
|
| 48 |
+
self.qdrant_client = qdrant_client
|
| 49 |
+
self.vector_store = initialize_vectorstore(
|
| 50 |
+
client=self.qdrant_client,
|
| 51 |
collection_name=f"{self.name}_qdrant",
|
| 52 |
embeddings=self.embeddings,
|
| 53 |
)
|
| 54 |
self.docs = []
|
| 55 |
|
| 56 |
+
def add_docs(self, raw_docs) -> None:
|
| 57 |
|
| 58 |
+
docs: List[Document] = transcripts_load(
|
| 59 |
+
json_transcripts=raw_docs, embeddings=self.embeddings
|
| 60 |
+
)
|
| 61 |
+
self.docs.extend(docs)
|
| 62 |
+
_ = self.vector_store.add_documents(documents=docs)
|
| 63 |
+
|
| 64 |
+
def clear(self) -> bool:
|
| 65 |
self.docs = []
|
| 66 |
+
return True if self.vector_store.delete() else False
|
| 67 |
+
|
| 68 |
+
def get_retriever(self, n_context_docs: int = 2) -> VectorStoreRetriever:
|
| 69 |
+
return self.vector_store.as_retriever(
|
| 70 |
+
search_kwargs={"k": n_context_docs}
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class RAGChainFactory:
|
| 75 |
+
|
| 76 |
+
format_query = RunnableLambda(itemgetter("question"))
|
| 77 |
+
retriever: VectorStoreRetriever
|
| 78 |
+
add_context_to_query: Runnable
|
| 79 |
+
prompt_template: Runnable
|
| 80 |
+
answer_chain: Runnable
|
| 81 |
+
llm: ChatOpenAI
|
| 82 |
+
|
| 83 |
+
rag_chain: Runnable
|
| 84 |
+
|
| 85 |
+
@staticmethod
|
| 86 |
+
def compile_references(context: List[Document]) -> str:
|
| 87 |
+
references = [
|
| 88 |
+
{k: doc.metadata[k] for k in ("title", "source", "start", "stop")}
|
| 89 |
+
for doc in context
|
| 90 |
+
]
|
| 91 |
+
return json.dumps(references, indent=2)
|
| 92 |
+
|
| 93 |
+
pack_references: RunnableLambda = RunnableLambda(
|
| 94 |
+
lambda d: {
|
| 95 |
+
**d["input"],
|
| 96 |
+
"answer": d["answer"]
|
| 97 |
+
+ "\nReferences:\n"
|
| 98 |
+
+ RAGChainFactory.compile_references(
|
| 99 |
+
context=d["input"]["context"]
|
| 100 |
+
),
|
| 101 |
+
}
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
@staticmethod
|
| 105 |
+
def pack_references2(msg_dict: Dict[str, Any]) -> AIMessage:
|
| 106 |
+
|
| 107 |
+
answer: AIMessage = msg_dict["answer"]
|
| 108 |
+
input = msg_dict["input"]
|
| 109 |
+
|
| 110 |
+
references = RAGChainFactory.compile_references(
|
| 111 |
+
context=input["context"]
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
text_w_references = f"{answer.content}\n**References**:\n{references}"
|
| 115 |
+
|
| 116 |
+
output: AIMessage = answer.model_copy(
|
| 117 |
+
update={
|
| 118 |
+
"content": text_w_references,
|
| 119 |
+
"additional_kwargs": {
|
| 120 |
+
**answer.additional_kwargs,
|
| 121 |
+
"context": input["context"],
|
| 122 |
+
"question": input["question"],
|
| 123 |
+
},
|
| 124 |
+
}
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
return output
|
| 128 |
+
|
| 129 |
+
def __init__(
|
| 130 |
+
self,
|
| 131 |
+
retriever: VectorStoreRetriever,
|
| 132 |
+
) -> None:
|
| 133 |
+
|
| 134 |
+
self.retriever = retriever
|
| 135 |
+
|
| 136 |
+
self.prepare_query = {
|
| 137 |
+
"context": retriever,
|
| 138 |
+
"question": RunnablePassthrough(),
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
self.prompt_template = ChatPromptTemplate.from_messages(
|
| 142 |
+
RAG_PROMPT_TEMPLATES
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
def get_rag_chain(
|
| 146 |
+
self,
|
| 147 |
+
llm: BaseLanguageModel = ChatOpenAI(
|
| 148 |
+
model="gpt-4.1-mini", temperature=0
|
| 149 |
+
),
|
| 150 |
+
) -> Runnable:
|
| 151 |
+
|
| 152 |
+
self.answer_chain = self.prompt_template | llm
|
| 153 |
+
self.rag_chain = (
|
| 154 |
+
self.format_query
|
| 155 |
+
| self.prepare_query
|
| 156 |
+
| {"input": RunnablePassthrough(), "answer": self.answer_chain}
|
| 157 |
+
| self.pack_references2
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
return self.rag_chain
|
pyproject.toml
CHANGED
|
@@ -33,6 +33,9 @@ dependencies = [
|
|
| 33 |
"unstructured>=0.17.2",
|
| 34 |
"uvicorn>=0.25.0,<0.26.0",
|
| 35 |
"websockets==14.2",
|
|
|
|
|
|
|
|
|
|
| 36 |
]
|
| 37 |
authors = [{ name = "Marko Budisic", email = "mbudisic@gmail.com" }]
|
| 38 |
license = "MIT"
|
|
@@ -44,44 +47,44 @@ build-backend = "hatchling.build"
|
|
| 44 |
[tool.hatch.build.targets.wheel]
|
| 45 |
packages = ["pstuts_rag/pstuts_rag"]
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
|
|
|
| 33 |
"unstructured>=0.17.2",
|
| 34 |
"uvicorn>=0.25.0,<0.26.0",
|
| 35 |
"websockets==14.2",
|
| 36 |
+
"langchain-core>=0.3.59",
|
| 37 |
+
"isort>=6.0.1",
|
| 38 |
+
"pylint-venv>=3.0.4",
|
| 39 |
]
|
| 40 |
authors = [{ name = "Marko Budisic", email = "mbudisic@gmail.com" }]
|
| 41 |
license = "MIT"
|
|
|
|
| 47 |
[tool.hatch.build.targets.wheel]
|
| 48 |
packages = ["pstuts_rag/pstuts_rag"]
|
| 49 |
|
| 50 |
+
[project.optional-dependencies]
|
| 51 |
+
dev = [
|
| 52 |
+
"pytest>=7.0.0",
|
| 53 |
+
"black>=22.0.0",
|
| 54 |
+
"flake8>=4.0.0",
|
| 55 |
+
"mypy>=0.900",
|
| 56 |
+
]
|
| 57 |
|
| 58 |
+
[tool.ruff]
|
| 59 |
+
line-length = 79
|
| 60 |
+
target-version = "py313"
|
| 61 |
+
select = ["E", "F", "I", "N", "W"]
|
| 62 |
+
ignore = []
|
| 63 |
|
| 64 |
+
[tool.ruff.isort]
|
| 65 |
+
known-first-party = ["src"]
|
| 66 |
|
| 67 |
+
[tool.black]
|
| 68 |
+
line-length = 79
|
| 69 |
+
target-version = ["py313"]
|
| 70 |
|
| 71 |
+
[tool.mypy]
|
| 72 |
+
python_version = "3.13"
|
| 73 |
+
warn_return_any = true
|
| 74 |
+
warn_unused_configs = true
|
| 75 |
+
disallow_untyped_defs = true
|
| 76 |
+
mypy_path = ["pstuts_rag/pstuts_rag"]
|
| 77 |
+
namespace_packages = true
|
| 78 |
+
explicit_package_bases = true
|
| 79 |
|
| 80 |
+
[tool.flake8]
|
| 81 |
+
application-import-names = "pstuts_rag"
|
| 82 |
+
extend-ignore = "E203,W503"
|
| 83 |
|
| 84 |
+
[tool.pylint.MASTER]
|
| 85 |
+
load-plugins = "pylint_venv" # optional but handy
|
| 86 |
+
source-roots = "pstuts_rag"
|
| 87 |
+
extension-pkg-allow-list = "numpy, torch" # compiled deps that astroid cannot parse
|
| 88 |
|
| 89 |
+
[tool.pylint.TYPECHECK]
|
| 90 |
+
ignored-modules = "pkg_resources" # suppress noisy vendored imports
|
uv.lock
CHANGED
|
@@ -235,6 +235,26 @@ wheels = [
|
|
| 235 |
{ url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764 },
|
| 236 |
]
|
| 237 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
[[package]]
|
| 239 |
name = "bleach"
|
| 240 |
version = "6.2.0"
|
|
@@ -613,6 +633,20 @@ wheels = [
|
|
| 613 |
{ url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 },
|
| 614 |
]
|
| 615 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
[[package]]
|
| 617 |
name = "fqdn"
|
| 618 |
version = "1.5.1"
|
|
@@ -885,6 +919,15 @@ wheels = [
|
|
| 885 |
{ url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 },
|
| 886 |
]
|
| 887 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
[[package]]
|
| 889 |
name = "ipykernel"
|
| 890 |
version = "6.29.5"
|
|
@@ -970,6 +1013,15 @@ wheels = [
|
|
| 970 |
{ url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 },
|
| 971 |
]
|
| 972 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 973 |
[[package]]
|
| 974 |
name = "jedi"
|
| 975 |
version = "0.19.2"
|
|
@@ -1615,6 +1667,15 @@ wheels = [
|
|
| 1615 |
{ url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 },
|
| 1616 |
]
|
| 1617 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1618 |
[[package]]
|
| 1619 |
name = "mistune"
|
| 1620 |
version = "3.1.3"
|
|
@@ -1683,6 +1744,25 @@ wheels = [
|
|
| 1683 |
{ url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 },
|
| 1684 |
]
|
| 1685 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1686 |
[[package]]
|
| 1687 |
name = "mypy-extensions"
|
| 1688 |
version = "1.1.0"
|
|
@@ -2094,6 +2174,15 @@ wheels = [
|
|
| 2094 |
{ url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 },
|
| 2095 |
]
|
| 2096 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2097 |
[[package]]
|
| 2098 |
name = "pexpect"
|
| 2099 |
version = "4.9.0"
|
|
@@ -2124,6 +2213,15 @@ wheels = [
|
|
| 2124 |
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 },
|
| 2125 |
]
|
| 2126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2127 |
[[package]]
|
| 2128 |
name = "portalocker"
|
| 2129 |
version = "2.10.1"
|
|
@@ -2224,10 +2322,12 @@ dependencies = [
|
|
| 2224 |
{ name = "httpx" },
|
| 2225 |
{ name = "ipykernel" },
|
| 2226 |
{ name = "ipywidgets" },
|
|
|
|
| 2227 |
{ name = "jq" },
|
| 2228 |
{ name = "jupyter" },
|
| 2229 |
{ name = "langchain" },
|
| 2230 |
{ name = "langchain-community" },
|
|
|
|
| 2231 |
{ name = "langchain-experimental" },
|
| 2232 |
{ name = "langchain-openai" },
|
| 2233 |
{ name = "langchain-qdrant" },
|
|
@@ -2236,6 +2336,7 @@ dependencies = [
|
|
| 2236 |
{ name = "openai" },
|
| 2237 |
{ name = "pip" },
|
| 2238 |
{ name = "pydantic" },
|
|
|
|
| 2239 |
{ name = "python-multipart" },
|
| 2240 |
{ name = "ragas" },
|
| 2241 |
{ name = "requests" },
|
|
@@ -2247,27 +2348,42 @@ dependencies = [
|
|
| 2247 |
{ name = "websockets" },
|
| 2248 |
]
|
| 2249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2250 |
[package.metadata]
|
| 2251 |
requires-dist = [
|
| 2252 |
{ name = "aiohttp", specifier = ">=3.8.0" },
|
|
|
|
| 2253 |
{ name = "chainlit", specifier = "==2.0.4" },
|
| 2254 |
{ name = "dotenv", specifier = ">=0.9.9" },
|
| 2255 |
{ name = "fastapi", specifier = ">=0.115.3,<0.116" },
|
|
|
|
| 2256 |
{ name = "httpx", specifier = "==0.27.0" },
|
| 2257 |
{ name = "ipykernel", specifier = ">=6.29.5" },
|
| 2258 |
{ name = "ipywidgets", specifier = ">=8.1.7" },
|
|
|
|
| 2259 |
{ name = "jq", specifier = ">=1.8.0" },
|
| 2260 |
{ name = "jupyter", specifier = ">=1.1.1" },
|
| 2261 |
{ name = "langchain", specifier = ">=0.3.25" },
|
| 2262 |
{ name = "langchain-community", specifier = ">=0.3.23" },
|
|
|
|
| 2263 |
{ name = "langchain-experimental", specifier = ">=0.3.4" },
|
| 2264 |
{ name = "langchain-openai" },
|
| 2265 |
{ name = "langchain-qdrant", specifier = ">=0.2.0" },
|
| 2266 |
{ name = "langgraph", specifier = ">=0.4.3" },
|
|
|
|
| 2267 |
{ name = "numpy", specifier = "==2.2.2" },
|
| 2268 |
{ name = "openai", specifier = "==1.59.9" },
|
| 2269 |
{ name = "pip", specifier = ">=25.0.1" },
|
| 2270 |
{ name = "pydantic", specifier = "==2.10.1" },
|
|
|
|
|
|
|
| 2271 |
{ name = "python-multipart", specifier = ">=0.0.18,<0.0.19" },
|
| 2272 |
{ name = "ragas", specifier = ">=0.2.15" },
|
| 2273 |
{ name = "requests", specifier = ">=2.31.0" },
|
|
@@ -2278,6 +2394,7 @@ requires-dist = [
|
|
| 2278 |
{ name = "uvicorn", specifier = ">=0.25.0,<0.26.0" },
|
| 2279 |
{ name = "websockets", specifier = "==14.2" },
|
| 2280 |
]
|
|
|
|
| 2281 |
|
| 2282 |
[[package]]
|
| 2283 |
name = "psutil"
|
|
@@ -2338,6 +2455,15 @@ wheels = [
|
|
| 2338 |
{ url = "https://files.pythonhosted.org/packages/37/40/ad395740cd641869a13bcf60851296c89624662575621968dcfafabaa7f6/pyarrow-20.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:82f1ee5133bd8f49d31be1299dc07f585136679666b502540db854968576faf9", size = 25944982 },
|
| 2339 |
]
|
| 2340 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2341 |
[[package]]
|
| 2342 |
name = "pycparser"
|
| 2343 |
version = "2.22"
|
|
@@ -2400,6 +2526,15 @@ wheels = [
|
|
| 2400 |
{ url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 },
|
| 2401 |
]
|
| 2402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2403 |
[[package]]
|
| 2404 |
name = "pygments"
|
| 2405 |
version = "2.19.1"
|
|
@@ -2418,6 +2553,15 @@ wheels = [
|
|
| 2418 |
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 },
|
| 2419 |
]
|
| 2420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2421 |
[[package]]
|
| 2422 |
name = "pypdf"
|
| 2423 |
version = "5.5.0"
|
|
@@ -2427,6 +2571,21 @@ wheels = [
|
|
| 2427 |
{ url = "https://files.pythonhosted.org/packages/a1/4e/931b90b51e3ebc69699be926b3d5bfdabae2d9c84337fd0c9fb98adbf70c/pypdf-5.5.0-py3-none-any.whl", hash = "sha256:2f61f2d32dde00471cd70b8977f98960c64e84dd5ba0d070e953fcb4da0b2a73", size = 303371 },
|
| 2428 |
]
|
| 2429 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2430 |
[[package]]
|
| 2431 |
name = "python-dateutil"
|
| 2432 |
version = "2.9.0.post0"
|
|
|
|
| 235 |
{ url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764 },
|
| 236 |
]
|
| 237 |
|
| 238 |
+
[[package]]
|
| 239 |
+
name = "black"
|
| 240 |
+
version = "25.1.0"
|
| 241 |
+
source = { registry = "https://pypi.org/simple" }
|
| 242 |
+
dependencies = [
|
| 243 |
+
{ name = "click" },
|
| 244 |
+
{ name = "mypy-extensions" },
|
| 245 |
+
{ name = "packaging" },
|
| 246 |
+
{ name = "pathspec" },
|
| 247 |
+
{ name = "platformdirs" },
|
| 248 |
+
]
|
| 249 |
+
sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 }
|
| 250 |
+
wheels = [
|
| 251 |
+
{ url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 },
|
| 252 |
+
{ url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 },
|
| 253 |
+
{ url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 },
|
| 254 |
+
{ url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 },
|
| 255 |
+
{ url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 },
|
| 256 |
+
]
|
| 257 |
+
|
| 258 |
[[package]]
|
| 259 |
name = "bleach"
|
| 260 |
version = "6.2.0"
|
|
|
|
| 633 |
{ url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 },
|
| 634 |
]
|
| 635 |
|
| 636 |
+
[[package]]
|
| 637 |
+
name = "flake8"
|
| 638 |
+
version = "7.2.0"
|
| 639 |
+
source = { registry = "https://pypi.org/simple" }
|
| 640 |
+
dependencies = [
|
| 641 |
+
{ name = "mccabe" },
|
| 642 |
+
{ name = "pycodestyle" },
|
| 643 |
+
{ name = "pyflakes" },
|
| 644 |
+
]
|
| 645 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177 }
|
| 646 |
+
wheels = [
|
| 647 |
+
{ url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786 },
|
| 648 |
+
]
|
| 649 |
+
|
| 650 |
[[package]]
|
| 651 |
name = "fqdn"
|
| 652 |
version = "1.5.1"
|
|
|
|
| 919 |
{ url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 },
|
| 920 |
]
|
| 921 |
|
| 922 |
+
[[package]]
|
| 923 |
+
name = "iniconfig"
|
| 924 |
+
version = "2.1.0"
|
| 925 |
+
source = { registry = "https://pypi.org/simple" }
|
| 926 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
| 927 |
+
wheels = [
|
| 928 |
+
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
| 929 |
+
]
|
| 930 |
+
|
| 931 |
[[package]]
|
| 932 |
name = "ipykernel"
|
| 933 |
version = "6.29.5"
|
|
|
|
| 1013 |
{ url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 },
|
| 1014 |
]
|
| 1015 |
|
| 1016 |
+
[[package]]
|
| 1017 |
+
name = "isort"
|
| 1018 |
+
version = "6.0.1"
|
| 1019 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1020 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955 }
|
| 1021 |
+
wheels = [
|
| 1022 |
+
{ url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186 },
|
| 1023 |
+
]
|
| 1024 |
+
|
| 1025 |
[[package]]
|
| 1026 |
name = "jedi"
|
| 1027 |
version = "0.19.2"
|
|
|
|
| 1667 |
{ url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 },
|
| 1668 |
]
|
| 1669 |
|
| 1670 |
+
[[package]]
|
| 1671 |
+
name = "mccabe"
|
| 1672 |
+
version = "0.7.0"
|
| 1673 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1674 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 }
|
| 1675 |
+
wheels = [
|
| 1676 |
+
{ url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 },
|
| 1677 |
+
]
|
| 1678 |
+
|
| 1679 |
[[package]]
|
| 1680 |
name = "mistune"
|
| 1681 |
version = "3.1.3"
|
|
|
|
| 1744 |
{ url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 },
|
| 1745 |
]
|
| 1746 |
|
| 1747 |
+
[[package]]
|
| 1748 |
+
name = "mypy"
|
| 1749 |
+
version = "1.15.0"
|
| 1750 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1751 |
+
dependencies = [
|
| 1752 |
+
{ name = "mypy-extensions" },
|
| 1753 |
+
{ name = "typing-extensions" },
|
| 1754 |
+
]
|
| 1755 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 }
|
| 1756 |
+
wheels = [
|
| 1757 |
+
{ url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 },
|
| 1758 |
+
{ url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 },
|
| 1759 |
+
{ url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 },
|
| 1760 |
+
{ url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 },
|
| 1761 |
+
{ url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 },
|
| 1762 |
+
{ url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 },
|
| 1763 |
+
{ url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 },
|
| 1764 |
+
]
|
| 1765 |
+
|
| 1766 |
[[package]]
|
| 1767 |
name = "mypy-extensions"
|
| 1768 |
version = "1.1.0"
|
|
|
|
| 2174 |
{ url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 },
|
| 2175 |
]
|
| 2176 |
|
| 2177 |
+
[[package]]
|
| 2178 |
+
name = "pathspec"
|
| 2179 |
+
version = "0.12.1"
|
| 2180 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2181 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
|
| 2182 |
+
wheels = [
|
| 2183 |
+
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
|
| 2184 |
+
]
|
| 2185 |
+
|
| 2186 |
[[package]]
|
| 2187 |
name = "pexpect"
|
| 2188 |
version = "4.9.0"
|
|
|
|
| 2213 |
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 },
|
| 2214 |
]
|
| 2215 |
|
| 2216 |
+
[[package]]
|
| 2217 |
+
name = "pluggy"
|
| 2218 |
+
version = "1.5.0"
|
| 2219 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2220 |
+
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
| 2221 |
+
wheels = [
|
| 2222 |
+
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
| 2223 |
+
]
|
| 2224 |
+
|
| 2225 |
[[package]]
|
| 2226 |
name = "portalocker"
|
| 2227 |
version = "2.10.1"
|
|
|
|
| 2322 |
{ name = "httpx" },
|
| 2323 |
{ name = "ipykernel" },
|
| 2324 |
{ name = "ipywidgets" },
|
| 2325 |
+
{ name = "isort" },
|
| 2326 |
{ name = "jq" },
|
| 2327 |
{ name = "jupyter" },
|
| 2328 |
{ name = "langchain" },
|
| 2329 |
{ name = "langchain-community" },
|
| 2330 |
+
{ name = "langchain-core" },
|
| 2331 |
{ name = "langchain-experimental" },
|
| 2332 |
{ name = "langchain-openai" },
|
| 2333 |
{ name = "langchain-qdrant" },
|
|
|
|
| 2336 |
{ name = "openai" },
|
| 2337 |
{ name = "pip" },
|
| 2338 |
{ name = "pydantic" },
|
| 2339 |
+
{ name = "pylint-venv" },
|
| 2340 |
{ name = "python-multipart" },
|
| 2341 |
{ name = "ragas" },
|
| 2342 |
{ name = "requests" },
|
|
|
|
| 2348 |
{ name = "websockets" },
|
| 2349 |
]
|
| 2350 |
|
| 2351 |
+
[package.optional-dependencies]
|
| 2352 |
+
dev = [
|
| 2353 |
+
{ name = "black" },
|
| 2354 |
+
{ name = "flake8" },
|
| 2355 |
+
{ name = "mypy" },
|
| 2356 |
+
{ name = "pytest" },
|
| 2357 |
+
]
|
| 2358 |
+
|
| 2359 |
[package.metadata]
|
| 2360 |
requires-dist = [
|
| 2361 |
{ name = "aiohttp", specifier = ">=3.8.0" },
|
| 2362 |
+
{ name = "black", marker = "extra == 'dev'", specifier = ">=22.0.0" },
|
| 2363 |
{ name = "chainlit", specifier = "==2.0.4" },
|
| 2364 |
{ name = "dotenv", specifier = ">=0.9.9" },
|
| 2365 |
{ name = "fastapi", specifier = ">=0.115.3,<0.116" },
|
| 2366 |
+
{ name = "flake8", marker = "extra == 'dev'", specifier = ">=4.0.0" },
|
| 2367 |
{ name = "httpx", specifier = "==0.27.0" },
|
| 2368 |
{ name = "ipykernel", specifier = ">=6.29.5" },
|
| 2369 |
{ name = "ipywidgets", specifier = ">=8.1.7" },
|
| 2370 |
+
{ name = "isort", specifier = ">=6.0.1" },
|
| 2371 |
{ name = "jq", specifier = ">=1.8.0" },
|
| 2372 |
{ name = "jupyter", specifier = ">=1.1.1" },
|
| 2373 |
{ name = "langchain", specifier = ">=0.3.25" },
|
| 2374 |
{ name = "langchain-community", specifier = ">=0.3.23" },
|
| 2375 |
+
{ name = "langchain-core", specifier = ">=0.3.59" },
|
| 2376 |
{ name = "langchain-experimental", specifier = ">=0.3.4" },
|
| 2377 |
{ name = "langchain-openai" },
|
| 2378 |
{ name = "langchain-qdrant", specifier = ">=0.2.0" },
|
| 2379 |
{ name = "langgraph", specifier = ">=0.4.3" },
|
| 2380 |
+
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=0.900" },
|
| 2381 |
{ name = "numpy", specifier = "==2.2.2" },
|
| 2382 |
{ name = "openai", specifier = "==1.59.9" },
|
| 2383 |
{ name = "pip", specifier = ">=25.0.1" },
|
| 2384 |
{ name = "pydantic", specifier = "==2.10.1" },
|
| 2385 |
+
{ name = "pylint-venv", specifier = ">=3.0.4" },
|
| 2386 |
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" },
|
| 2387 |
{ name = "python-multipart", specifier = ">=0.0.18,<0.0.19" },
|
| 2388 |
{ name = "ragas", specifier = ">=0.2.15" },
|
| 2389 |
{ name = "requests", specifier = ">=2.31.0" },
|
|
|
|
| 2394 |
{ name = "uvicorn", specifier = ">=0.25.0,<0.26.0" },
|
| 2395 |
{ name = "websockets", specifier = "==14.2" },
|
| 2396 |
]
|
| 2397 |
+
provides-extras = ["dev"]
|
| 2398 |
|
| 2399 |
[[package]]
|
| 2400 |
name = "psutil"
|
|
|
|
| 2455 |
{ url = "https://files.pythonhosted.org/packages/37/40/ad395740cd641869a13bcf60851296c89624662575621968dcfafabaa7f6/pyarrow-20.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:82f1ee5133bd8f49d31be1299dc07f585136679666b502540db854968576faf9", size = 25944982 },
|
| 2456 |
]
|
| 2457 |
|
| 2458 |
+
[[package]]
|
| 2459 |
+
name = "pycodestyle"
|
| 2460 |
+
version = "2.13.0"
|
| 2461 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2462 |
+
sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312 }
|
| 2463 |
+
wheels = [
|
| 2464 |
+
{ url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424 },
|
| 2465 |
+
]
|
| 2466 |
+
|
| 2467 |
[[package]]
|
| 2468 |
name = "pycparser"
|
| 2469 |
version = "2.22"
|
|
|
|
| 2526 |
{ url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 },
|
| 2527 |
]
|
| 2528 |
|
| 2529 |
+
[[package]]
|
| 2530 |
+
name = "pyflakes"
|
| 2531 |
+
version = "3.3.2"
|
| 2532 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2533 |
+
sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175 }
|
| 2534 |
+
wheels = [
|
| 2535 |
+
{ url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164 },
|
| 2536 |
+
]
|
| 2537 |
+
|
| 2538 |
[[package]]
|
| 2539 |
name = "pygments"
|
| 2540 |
version = "2.19.1"
|
|
|
|
| 2553 |
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 },
|
| 2554 |
]
|
| 2555 |
|
| 2556 |
+
[[package]]
|
| 2557 |
+
name = "pylint-venv"
|
| 2558 |
+
version = "3.0.4"
|
| 2559 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2560 |
+
sdist = { url = "https://files.pythonhosted.org/packages/48/ac/88e53ad1362392e68a36a4f08ca82c76e6cb6321d8ecba901cca5a70dcc1/pylint_venv-3.0.4.tar.gz", hash = "sha256:4c71c2ad14fc0549699440bcc460994ffddc1481b9078404b4c43d2806ef0f59", size = 5772 }
|
| 2561 |
+
wheels = [
|
| 2562 |
+
{ url = "https://files.pythonhosted.org/packages/a6/10/040e1928236e3d34b26639e3427df88c7249a85aadc621cea2158589b4f8/pylint_venv-3.0.4-py3-none-any.whl", hash = "sha256:31006a3df398f58f962c9e5620e756b284e8b2bc490594ce5ee5da41920cb32c", size = 5312 },
|
| 2563 |
+
]
|
| 2564 |
+
|
| 2565 |
[[package]]
|
| 2566 |
name = "pypdf"
|
| 2567 |
version = "5.5.0"
|
|
|
|
| 2571 |
{ url = "https://files.pythonhosted.org/packages/a1/4e/931b90b51e3ebc69699be926b3d5bfdabae2d9c84337fd0c9fb98adbf70c/pypdf-5.5.0-py3-none-any.whl", hash = "sha256:2f61f2d32dde00471cd70b8977f98960c64e84dd5ba0d070e953fcb4da0b2a73", size = 303371 },
|
| 2572 |
]
|
| 2573 |
|
| 2574 |
+
[[package]]
|
| 2575 |
+
name = "pytest"
|
| 2576 |
+
version = "8.3.5"
|
| 2577 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2578 |
+
dependencies = [
|
| 2579 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 2580 |
+
{ name = "iniconfig" },
|
| 2581 |
+
{ name = "packaging" },
|
| 2582 |
+
{ name = "pluggy" },
|
| 2583 |
+
]
|
| 2584 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
|
| 2585 |
+
wheels = [
|
| 2586 |
+
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
|
| 2587 |
+
]
|
| 2588 |
+
|
| 2589 |
[[package]]
|
| 2590 |
name = "python-dateutil"
|
| 2591 |
version = "2.9.0.post0"
|