Spaces:
Sleeping
Sleeping
Tried out an app version; the chat playground has browser issues, and the default playground is not ideal. Langsmith seems best for inspecting post-facto.
Browse files- app_notebook.ipynb +69 -0
- serve.py +205 -0
app_notebook.ipynb
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 1,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [],
|
8 |
+
"source": [
|
9 |
+
"from langserve import RemoteRunnable\n",
|
10 |
+
"\n",
|
11 |
+
"remote_runnable = RemoteRunnable('http://localhost:8000/')"
|
12 |
+
]
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"cell_type": "code",
|
16 |
+
"execution_count": 3,
|
17 |
+
"metadata": {},
|
18 |
+
"outputs": [
|
19 |
+
{
|
20 |
+
"ename": "HTTPStatusError",
|
21 |
+
"evalue": "Server error '500 Internal Server Error' for url 'http://localhost:8000/invoke'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 for Internal Server Error",
|
22 |
+
"output_type": "error",
|
23 |
+
"traceback": [
|
24 |
+
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
25 |
+
"\u001b[1;31mHTTPStatusError\u001b[0m Traceback (most recent call last)",
|
26 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\langserve\\client.py:157\u001b[0m, in \u001b[0;36m_raise_for_status\u001b[1;34m(response)\u001b[0m\n\u001b[0;32m 156\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 157\u001b[0m \u001b[43mresponse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mraise_for_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 158\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m httpx\u001b[38;5;241m.\u001b[39mHTTPStatusError \u001b[38;5;28;01mas\u001b[39;00m e:\n",
|
27 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\httpx\\_models.py:761\u001b[0m, in \u001b[0;36mResponse.raise_for_status\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 760\u001b[0m message \u001b[38;5;241m=\u001b[39m message\u001b[38;5;241m.\u001b[39mformat(\u001b[38;5;28mself\u001b[39m, error_type\u001b[38;5;241m=\u001b[39merror_type)\n\u001b[1;32m--> 761\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m HTTPStatusError(message, request\u001b[38;5;241m=\u001b[39mrequest, response\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m)\n",
|
28 |
+
"\u001b[1;31mHTTPStatusError\u001b[0m: Server error '500 Internal Server Error' for url 'http://localhost:8000/invoke'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500",
|
29 |
+
"\nDuring handling of the above exception, another exception occurred:\n",
|
30 |
+
"\u001b[1;31mHTTPStatusError\u001b[0m Traceback (most recent call last)",
|
31 |
+
"Cell \u001b[1;32mIn[3], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# remote_runnable.invoke(dict(input = 'Do you know the tragedy of Darth Plagueis the Wise?'))\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m \u001b[43mremote_runnable\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mDo you know the tragedy of Darth Plagueis the Wise?\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n",
|
32 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\langserve\\client.py:356\u001b[0m, in \u001b[0;36mRemoteRunnable.invoke\u001b[1;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[0;32m 354\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m kwargs:\n\u001b[0;32m 355\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mNotImplementedError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkwargs not implemented yet.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m--> 356\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_with_config\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_invoke\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\n",
|
33 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\langchain_core\\runnables\\base.py:1625\u001b[0m, in \u001b[0;36mRunnable._call_with_config\u001b[1;34m(self, func, input, config, run_type, **kwargs)\u001b[0m\n\u001b[0;32m 1621\u001b[0m context \u001b[38;5;241m=\u001b[39m copy_context()\n\u001b[0;32m 1622\u001b[0m context\u001b[38;5;241m.\u001b[39mrun(var_child_runnable_config\u001b[38;5;241m.\u001b[39mset, child_config)\n\u001b[0;32m 1623\u001b[0m output \u001b[38;5;241m=\u001b[39m cast(\n\u001b[0;32m 1624\u001b[0m Output,\n\u001b[1;32m-> 1625\u001b[0m \u001b[43mcontext\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 1626\u001b[0m \u001b[43m \u001b[49m\u001b[43mcall_func_with_variable_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[0;32m 1627\u001b[0m \u001b[43m \u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[0;32m 1628\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[0;32m 1629\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1630\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1631\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1632\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m,\n\u001b[0;32m 1633\u001b[0m )\n\u001b[0;32m 1634\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m 1635\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_error(e)\n",
|
34 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\langchain_core\\runnables\\config.py:347\u001b[0m, in \u001b[0;36mcall_func_with_variable_args\u001b[1;34m(func, input, config, run_manager, **kwargs)\u001b[0m\n\u001b[0;32m 345\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m accepts_run_manager(func):\n\u001b[0;32m 346\u001b[0m kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_manager\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m run_manager\n\u001b[1;32m--> 347\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
|
35 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\langserve\\client.py:343\u001b[0m, in \u001b[0;36mRemoteRunnable._invoke\u001b[1;34m(self, input, run_manager, config, **kwargs)\u001b[0m\n\u001b[0;32m 334\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Invoke the runnable with the given input and config.\"\"\"\u001b[39;00m\n\u001b[0;32m 335\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msync_client\u001b[38;5;241m.\u001b[39mpost(\n\u001b[0;32m 336\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m/invoke\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 337\u001b[0m json\u001b[38;5;241m=\u001b[39m{\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 341\u001b[0m },\n\u001b[0;32m 342\u001b[0m )\n\u001b[1;32m--> 343\u001b[0m output, callback_events \u001b[38;5;241m=\u001b[39m \u001b[43m_decode_response\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 344\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_lc_serializer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mresponse\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mis_batch\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[0;32m 345\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 347\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_use_server_callback_events \u001b[38;5;129;01mand\u001b[39;00m callback_events:\n\u001b[0;32m 348\u001b[0m handle_callbacks(run_manager, callback_events)\n",
|
36 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\langserve\\client.py:230\u001b[0m, in \u001b[0;36m_decode_response\u001b[1;34m(serializer, response, is_batch)\u001b[0m\n\u001b[0;32m 223\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_decode_response\u001b[39m(\n\u001b[0;32m 224\u001b[0m serializer: Serializer,\n\u001b[0;32m 225\u001b[0m response: httpx\u001b[38;5;241m.\u001b[39mResponse,\n\u001b[0;32m 226\u001b[0m \u001b[38;5;241m*\u001b[39m,\n\u001b[0;32m 227\u001b[0m is_batch: \u001b[38;5;28mbool\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[0;32m 228\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tuple[Any, Union[List[CallbackEventDict], List[List[CallbackEventDict]]]]:\n\u001b[0;32m 229\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Decode the response.\"\"\"\u001b[39;00m\n\u001b[1;32m--> 230\u001b[0m \u001b[43m_raise_for_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresponse\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 231\u001b[0m obj \u001b[38;5;241m=\u001b[39m response\u001b[38;5;241m.\u001b[39mjson()\n\u001b[0;32m 232\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(obj, \u001b[38;5;28mdict\u001b[39m):\n",
|
37 |
+
"File \u001b[1;32mc:\\Users\\Dr-Lo\\miniconda3\\envs\\ML11\\Lib\\site-packages\\langserve\\client.py:165\u001b[0m, in \u001b[0;36m_raise_for_status\u001b[1;34m(response)\u001b[0m\n\u001b[0;32m 162\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m e\u001b[38;5;241m.\u001b[39mresponse\u001b[38;5;241m.\u001b[39mtext:\n\u001b[0;32m 163\u001b[0m message \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m for \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;241m.\u001b[39mresponse\u001b[38;5;241m.\u001b[39mtext\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m--> 165\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m httpx\u001b[38;5;241m.\u001b[39mHTTPStatusError(\n\u001b[0;32m 166\u001b[0m message\u001b[38;5;241m=\u001b[39mmessage,\n\u001b[0;32m 167\u001b[0m request\u001b[38;5;241m=\u001b[39m_sanitize_request(e\u001b[38;5;241m.\u001b[39mrequest),\n\u001b[0;32m 168\u001b[0m response\u001b[38;5;241m=\u001b[39me\u001b[38;5;241m.\u001b[39mresponse,\n\u001b[0;32m 169\u001b[0m )\n",
|
38 |
+
"\u001b[1;31mHTTPStatusError\u001b[0m: Server error '500 Internal Server Error' for url 'http://localhost:8000/invoke'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 for Internal Server Error"
|
39 |
+
]
|
40 |
+
}
|
41 |
+
],
|
42 |
+
"source": [
|
43 |
+
"# remote_runnable.invoke(dict(input = 'Do you know the tragedy of Darth Plagueis the Wise?'))\n",
|
44 |
+
"remote_runnable.invoke('Do you know the tragedy of Darth Plagueis the Wise?')"
|
45 |
+
]
|
46 |
+
}
|
47 |
+
],
|
48 |
+
"metadata": {
|
49 |
+
"kernelspec": {
|
50 |
+
"display_name": "ML11",
|
51 |
+
"language": "python",
|
52 |
+
"name": "python3"
|
53 |
+
},
|
54 |
+
"language_info": {
|
55 |
+
"codemirror_mode": {
|
56 |
+
"name": "ipython",
|
57 |
+
"version": 3
|
58 |
+
},
|
59 |
+
"file_extension": ".py",
|
60 |
+
"mimetype": "text/x-python",
|
61 |
+
"name": "python",
|
62 |
+
"nbconvert_exporter": "python",
|
63 |
+
"pygments_lexer": "ipython3",
|
64 |
+
"version": "3.11.7"
|
65 |
+
}
|
66 |
+
},
|
67 |
+
"nbformat": 4,
|
68 |
+
"nbformat_minor": 2
|
69 |
+
}
|
serve.py
ADDED
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_openai import ChatOpenAI#, OpenAIEmbeddings # No need to pay for using embeddings as well when have free alternatives
|
2 |
+
|
3 |
+
# Data
|
4 |
+
from langchain_community.document_loaders import DirectoryLoader, TextLoader, WebBaseLoader
|
5 |
+
# from langchain_chroma import Chroma # The documentation uses this one, but it is extremely recent, and the same functionality is available in langchain_community and langchain (which imports community)
|
6 |
+
from langchain_community.vectorstores import Chroma # This has documentation on-hover, while the indirect import through non-community does not
|
7 |
+
from langchain_community.embeddings.sentence_transformer import SentenceTransformerEmbeddings # The free alternative (also the default in docs, with model_name = 'all-MiniLM-L6-v2')
|
8 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter#, TextSplitter # Recursive to better keep related bits contiguous (also recommended in docs: https://python.langchain.com/docs/modules/data_connection/document_transformers/)
|
9 |
+
|
10 |
+
# Chains
|
11 |
+
from langchain.prompts import PromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate, ChatPromptTemplate, MessagesPlaceholder
|
12 |
+
from langchain_core.output_parsers import StrOutputParser
|
13 |
+
from langchain.chains.combine_documents import create_stuff_documents_chain
|
14 |
+
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
|
15 |
+
from langchain.tools.retriever import create_retriever_tool
|
16 |
+
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
|
17 |
+
|
18 |
+
# Agents
|
19 |
+
from langchain import hub
|
20 |
+
from langchain.agents import create_tool_calling_agent, AgentExecutor
|
21 |
+
|
22 |
+
# To manually create inputs to test pipelines
|
23 |
+
from langchain_core.messages import HumanMessage, AIMessage
|
24 |
+
from langchain_core.documents import Document
|
25 |
+
|
26 |
+
# To serve the app
|
27 |
+
from fastapi import FastAPI
|
28 |
+
from langchain.pydantic_v1 import BaseModel, Field
|
29 |
+
from langchain_core.messages import BaseMessage
|
30 |
+
from langserve import add_routes, CustomUserType
|
31 |
+
|
32 |
+
import requests
|
33 |
+
from bs4 import BeautifulSoup
|
34 |
+
|
35 |
+
import os
|
36 |
+
import shutil
|
37 |
+
import re
|
38 |
+
|
39 |
+
import dotenv
|
40 |
+
dotenv.load_dotenv()
|
41 |
+
|
42 |
+
|
43 |
+
## Vector stores
|
44 |
+
|
45 |
+
script_db = Chroma(embedding_function = SentenceTransformerEmbeddings(model_name = 'all-MiniLM-L6-v2'), persist_directory = r'scripts\db')
|
46 |
+
woo_db = Chroma(embedding_function = SentenceTransformerEmbeddings(model_name = 'all-MiniLM-L6-v2'), persist_directory = r'wookieepedia_db')
|
47 |
+
|
48 |
+
|
49 |
+
|
50 |
+
## Wookieepedia functions
|
51 |
+
|
52 |
+
def first_wookieepedia_result(query: str) -> str:
|
53 |
+
'''Get the url of the first result when searching Wookieepedia for a query
|
54 |
+
(best for simple names as queries, ideally generated by the llm for something like
|
55 |
+
"Produce a input consisting of the name of the most important element in the query so that its article can be looked up")
|
56 |
+
'''
|
57 |
+
search_results = requests.get(f'https://starwars.fandom.com/wiki/Special:Search?query={"+".join(query.split(" "))}')
|
58 |
+
soup = BeautifulSoup(search_results.content, 'html.parser')
|
59 |
+
first_res = soup.find('a', class_ = 'unified-search__result__link')
|
60 |
+
return first_res['href']
|
61 |
+
|
62 |
+
|
63 |
+
def get_new_wookieepedia_chunks(query: str, previous_sources: set[str]) -> list[Document]:
|
64 |
+
'''Retrieve and return chunks of the content of the first result of query on Wookieepedia, then return the closest matches for.
|
65 |
+
'''
|
66 |
+
url = first_wookieepedia_result(query)
|
67 |
+
|
68 |
+
if url in previous_sources: return []
|
69 |
+
else:
|
70 |
+
doc = WebBaseLoader(url).load()[0] # Only one url passed in => only one Document out; no need to assert
|
71 |
+
|
72 |
+
# There probably is a very long preamble before the real content, however, if more than one gap then ignore and proceed with full document
|
73 |
+
trimmed = parts[1] if len(parts := doc.page_content.split('\n\n\n\n\n\n\n\n\n\n\n\n\n\n \xa0 \xa0')) == 2 else doc.page_content
|
74 |
+
doc.page_content = re.sub(r'[\n\t]{2,}', '\n', trimmed) # And remove excessive spacing
|
75 |
+
|
76 |
+
return RecursiveCharacterTextSplitter(chunk_size = 800, chunk_overlap = 100).split_documents([doc])
|
77 |
+
|
78 |
+
def get_wookieepedia_context(original_query: str, simple_query: str, wdb: Chroma) -> list[Document]:
|
79 |
+
try:
|
80 |
+
new_chunks = get_new_wookieepedia_chunks(simple_query, previous_sources = set(md.get('source') for md in wdb.get()['metadatas']))
|
81 |
+
if new_chunks: wdb.add_documents(new_chunks)
|
82 |
+
except: return []
|
83 |
+
|
84 |
+
return wdb.similarity_search(original_query, k = 10)
|
85 |
+
|
86 |
+
|
87 |
+
|
88 |
+
# Chains
|
89 |
+
|
90 |
+
llm = ChatOpenAI(model = 'gpt-3.5-turbo-0125', temperature = 0)
|
91 |
+
|
92 |
+
|
93 |
+
document_prompt_system_text = '''
|
94 |
+
You are very knowledgeable about Star Wars and your job is to answer questions about its plot, characters, etc.
|
95 |
+
Use the context below to produce your answers with as much detail as possible.
|
96 |
+
If you do not know an answer, say so; do not make up information not in the context.
|
97 |
+
|
98 |
+
<context>
|
99 |
+
{context}
|
100 |
+
</context>
|
101 |
+
'''
|
102 |
+
document_prompt = ChatPromptTemplate.from_messages([
|
103 |
+
('system', document_prompt_system_text),
|
104 |
+
MessagesPlaceholder(variable_name = 'chat_history', optional = True),
|
105 |
+
('user', '{input}')
|
106 |
+
])
|
107 |
+
document_chain = create_stuff_documents_chain(llm, document_prompt)
|
108 |
+
|
109 |
+
|
110 |
+
script_retriever_prompt = ChatPromptTemplate.from_messages([
|
111 |
+
MessagesPlaceholder(variable_name = 'chat_history'),
|
112 |
+
('user', '{input}'),
|
113 |
+
('user', '''Given the above conversation, generate a search query to look up relevant information in a database containing the full scripts from the Star Wars films (i.e. just dialogue and brief scene descriptions).
|
114 |
+
The query need not be a proper sentence, but a list of keywords likely to be in dialogue or scene descriptions''')
|
115 |
+
])
|
116 |
+
script_retriever_chain = create_history_aware_retriever(llm, script_db.as_retriever(), script_retriever_prompt) # Essentially just: prompt | llm | StrOutputParser() | retriever
|
117 |
+
|
118 |
+
|
119 |
+
woo_retriever_prompt = ChatPromptTemplate.from_messages([
|
120 |
+
MessagesPlaceholder(variable_name = 'chat_history'),
|
121 |
+
('user', '{input}'),
|
122 |
+
('user', 'Given the above conversation, generate a search query to find a relevant page in the Star Wars fandom wiki; the query should be something simple, such as the name of a character, place, event, item, etc.')
|
123 |
+
])
|
124 |
+
woo_retriever_chain = create_history_aware_retriever(llm, woo_db.as_retriever(), woo_retriever_prompt) # Essentially just: prompt | llm | StrOutputParser() | retriever
|
125 |
+
|
126 |
+
|
127 |
+
full_chain = create_retrieval_chain(script_retriever_chain, document_chain)
|
128 |
+
|
129 |
+
|
130 |
+
|
131 |
+
## Agent version
|
132 |
+
|
133 |
+
script_tool = create_retriever_tool(
|
134 |
+
script_db.as_retriever(search_kwargs = dict(k = 4)),
|
135 |
+
'search_film_scripts',
|
136 |
+
'''Search the Star Wars film scripts. This tool should be the first choice for Star Wars related questions.
|
137 |
+
Queries passed to this tool should be lists of keywords likely to be in dialogue or scene descriptions, and should not include film titles.'''
|
138 |
+
)
|
139 |
+
wookieepedia_tool = create_retriever_tool(
|
140 |
+
woo_db.as_retriever(search_kwargs = dict(k = 4)),
|
141 |
+
'search_wookieepedia',
|
142 |
+
'Search the Star Wars fandom wiki. This tool should be used for queries about details of a particular character, location, event, weapon, etc., and the query should be something simple, such as the name of a character, place, event, item, etc.',
|
143 |
+
)
|
144 |
+
tools = [script_tool, wookieepedia_tool]
|
145 |
+
|
146 |
+
agent_system_text = '''
|
147 |
+
You are a helpful agent who is very knowledgeable about Star Wars and your job is to answer questions about its plot, characters, etc.
|
148 |
+
Use the context provided in the exchanges to come to produce your answers with as much detail as possible.
|
149 |
+
If you do not know an answer, say so; do not make up information.
|
150 |
+
'''
|
151 |
+
agent_prompt = ChatPromptTemplate.from_messages([
|
152 |
+
('system', agent_system_text),
|
153 |
+
MessagesPlaceholder('chat_history', optional = True), # Using this form since not clear how to have optional = True in the tuple form
|
154 |
+
('human', '{input}'),
|
155 |
+
('placeholder', '{agent_scratchpad}') # Required for chat history and the agent's intermediate processing values
|
156 |
+
])
|
157 |
+
agent = create_tool_calling_agent(llm, tools, agent_prompt)
|
158 |
+
agent_executor = AgentExecutor(agent = agent, tools = tools, verbose = True)
|
159 |
+
|
160 |
+
|
161 |
+
|
162 |
+
|
163 |
+
class StrInput(BaseModel):
|
164 |
+
input: str
|
165 |
+
|
166 |
+
class Input(BaseModel):
|
167 |
+
input: str
|
168 |
+
chat_history: list[BaseMessage] = Field(
|
169 |
+
...,
|
170 |
+
extra = {'widget': {'type': 'chat', 'input': 'location'}},
|
171 |
+
)
|
172 |
+
|
173 |
+
class Output(BaseModel):
|
174 |
+
output: str
|
175 |
+
|
176 |
+
|
177 |
+
|
178 |
+
|
179 |
+
## App definition
|
180 |
+
|
181 |
+
app = FastAPI(
|
182 |
+
title = 'LangChain Server',
|
183 |
+
version = '1.0',
|
184 |
+
description = 'Simple version of a Star Wars expert',
|
185 |
+
)
|
186 |
+
|
187 |
+
|
188 |
+
|
189 |
+
## Adding chain route
|
190 |
+
|
191 |
+
# add_routes(app, script_db.as_retriever())
|
192 |
+
add_routes(app, full_chain.with_types(input_type = StrInput, output_type = Output), playground_type = 'default')
|
193 |
+
|
194 |
+
# NOTE: The chat playground type has a web page issue (flashes and becomes white, hence non-interactable; this was supposedly solved in an issue late last year)
|
195 |
+
|
196 |
+
# add_routes(app, agent_executor, playground_type = 'chat')
|
197 |
+
# add_routes(app, agent_executor.with_types(input_type = StrInput, output_type = Output))
|
198 |
+
|
199 |
+
|
200 |
+
if __name__ == '__main__':
|
201 |
+
import uvicorn
|
202 |
+
|
203 |
+
uvicorn.run(app, host = 'localhost', port = 8000)
|
204 |
+
|
205 |
+
|