Spaces:
Sleeping
Sleeping
Switched to Gradio; much more convenient AND much better results.
Browse files- Dockerfile +0 -17
- README.md +6 -3
- app.py +21 -58
- app_notebook.ipynb +0 -69
- explore.ipynb +164 -7
- ingredients.py +60 -38
- langserve_app.py +71 -0
- requirements.txt +5 -3
- scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/data_level0.bin +1 -1
- scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/header.bin +1 -1
- scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/index_metadata.pickle +1 -1
- scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/length.bin +1 -1
- scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/link_lists.bin +1 -1
- scripts/db/chroma.sqlite3 +2 -2
- testing.ipynb +132 -0
Dockerfile
DELETED
@@ -1,17 +0,0 @@
|
|
1 |
-
FROM python:3.11.7
|
2 |
-
|
3 |
-
# Set up a new user named "user" with user ID 1000
|
4 |
-
RUN useradd -m -u 1000 user
|
5 |
-
|
6 |
-
# Switch to the "user" user
|
7 |
-
USER user
|
8 |
-
|
9 |
-
COPY . .
|
10 |
-
|
11 |
-
WORKDIR /
|
12 |
-
|
13 |
-
RUN pip install --no-cache-dir --upgrade -r /requirements.txt
|
14 |
-
|
15 |
-
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
@@ -1,15 +1,18 @@
|
|
1 |
---
|
2 |
title: Star-Wars-Expert
|
3 |
colorFrom: yellow
|
4 |
-
colorTo:
|
5 |
-
sdk:
|
6 |
app_file: app.py
|
7 |
pinned: false
|
|
|
8 |
---
|
9 |
|
10 |
# Star-Wars-Expert
|
11 |
-
A LLM with RAG making it knowledgeable about Star Wars plot and data.
|
12 |
|
|
|
|
|
|
|
13 |
|
14 |
|
15 |
## Data
|
|
|
1 |
---
|
2 |
title: Star-Wars-Expert
|
3 |
colorFrom: yellow
|
4 |
+
colorTo: blue
|
5 |
+
sdk: gradio
|
6 |
app_file: app.py
|
7 |
pinned: false
|
8 |
+
license: mit
|
9 |
---
|
10 |
|
11 |
# Star-Wars-Expert
|
|
|
12 |
|
13 |
+
A LLM with RAG making it knowledgeable about Star Wars lore (built with LangChain, GPT-3.5 and Gradio).
|
14 |
+
|
15 |
+
Chat with it [on its Huggingface space](https://huggingface.co/spaces/T-Flet/Star-Wars-Expert) (or clone the repo and `python app.py`).
|
16 |
|
17 |
|
18 |
## Data
|
app.py
CHANGED
@@ -1,71 +1,34 @@
|
|
1 |
-
|
2 |
-
from langchain_core.pydantic_v1 import BaseModel, Field
|
3 |
|
4 |
-
|
5 |
-
from fastapi import FastAPI
|
6 |
-
from langchain_core.messages import BaseMessage
|
7 |
-
from langserve import add_routes, CustomUserType
|
8 |
|
|
|
9 |
import dotenv
|
10 |
dotenv.load_dotenv()
|
11 |
|
|
|
12 |
from ingredients import script_db, woo_db, full_chain, compound_chain, agent_executor
|
13 |
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
-
|
17 |
|
18 |
-
class StrInput(BaseModel):
|
19 |
-
input: str
|
20 |
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
)
|
27 |
-
|
28 |
-
|
29 |
-
output: str
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
## App definition
|
34 |
-
# 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)
|
35 |
-
|
36 |
-
app = FastAPI(
|
37 |
-
title = 'Star Wars Expert',
|
38 |
-
version = '1.0',
|
39 |
-
description = 'A Star Wars expert chatbot',
|
40 |
-
)
|
41 |
-
|
42 |
-
|
43 |
-
# Basic retriever versions
|
44 |
-
|
45 |
-
# add_routes(app, script_db.as_retriever())
|
46 |
-
# add_routes(app, woo_db.as_retriever())
|
47 |
-
|
48 |
-
|
49 |
-
# History-aware retriever version
|
50 |
-
# add_routes(app, full_chain.with_types(input_type = StrInput, output_type = Output), playground_type = 'default')
|
51 |
-
|
52 |
-
|
53 |
-
# Agent version
|
54 |
-
|
55 |
-
# add_routes(app, agent_executor, playground_type = 'chat')
|
56 |
-
# add_routes(app, agent_executor.with_types(input_type = StrInput, output_type = Output))
|
57 |
-
|
58 |
-
|
59 |
-
# Non-agent chain-logic version
|
60 |
-
|
61 |
-
add_routes(app, compound_chain.with_types(input_type = StrInput))
|
62 |
-
# add_routes(app, compound_chain.with_types(input_type = Input), playground_type = 'chat')
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
if __name__ == '__main__':
|
67 |
-
import uvicorn
|
68 |
-
|
69 |
-
uvicorn.run(app, host = 'localhost', port = 8000)
|
70 |
|
71 |
|
|
|
1 |
+
from langchain_core.messages import HumanMessage, AIMessage
|
|
|
2 |
|
3 |
+
import gradio as gr
|
|
|
|
|
|
|
4 |
|
5 |
+
# For local testing; not used in the Huggingface space
|
6 |
import dotenv
|
7 |
dotenv.load_dotenv()
|
8 |
|
9 |
+
# The available backends to use in the app
|
10 |
from ingredients import script_db, woo_db, full_chain, compound_chain, agent_executor
|
11 |
|
12 |
|
13 |
+
def chat(message, history):
|
14 |
+
formatted_history = []
|
15 |
+
for human, ai in history:
|
16 |
+
formatted_history.append(HumanMessage(content = human))
|
17 |
+
formatted_history.append(AIMessage(content = ai))
|
18 |
+
|
19 |
+
# Yes, the context chat entries are not fed back to the system, but that is probably for the best due to input size limit
|
20 |
+
response = compound_chain.invoke(dict(input = HumanMessage(content = message), chat_history = formatted_history))
|
21 |
|
22 |
+
return response['answer']
|
23 |
|
|
|
|
|
24 |
|
25 |
+
gr.ChatInterface(
|
26 |
+
chat,
|
27 |
+
textbox = gr.Textbox(placeholder = 'Ask something about Star Wars', container = False, scale = 7),
|
28 |
+
title = 'Star Wars Expert', description = 'I am knowledgeable about Star Wars; ask me about it',
|
29 |
+
examples = ['Do you know the tragedy of Darth Plagueis the Wise?', 'What power source did the Death Star use?', "Who participates in Han's rescue from Jabba? And where is the palace?"],
|
30 |
+
cache_examples = False, # This would avoid invoking the chatbot for the example queries (it would invokes it on them on startup instead)
|
31 |
+
theme = 'soft', retry_btn = None, undo_btn = 'Delete Previous', clear_btn = 'Clear'
|
32 |
+
).launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
|
app_notebook.ipynb
DELETED
@@ -1,69 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"cells": [
|
3 |
-
{
|
4 |
-
"cell_type": "code",
|
5 |
-
"execution_count": 5,
|
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": 7,
|
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[7], line 1\u001b[0m\n\u001b[1;32m----> 1\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\u001b[0;32m 2\u001b[0m \u001b[38;5;66;03m# remote_runnable.invoke(dict(input = 'Do you know the tragedy of Darth Plagueis the Wise?', chat_history = []))\u001b[39;00m\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?', chat_history = []))\n",
|
44 |
-
"# remote_runnable.invoke(dict(input = 'What power source did the Death Star use?', chat_history = []))"
|
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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
explore.ipynb
CHANGED
@@ -61,6 +61,7 @@
|
|
61 |
"\n",
|
62 |
"import os\n",
|
63 |
"import shutil\n",
|
|
|
64 |
"import re\n",
|
65 |
"\n",
|
66 |
"import dotenv\n",
|
@@ -91,7 +92,8 @@
|
|
91 |
"name": "stdout",
|
92 |
"output_type": "stream",
|
93 |
"text": [
|
94 |
-
"
|
|
|
95 |
]
|
96 |
}
|
97 |
],
|
@@ -106,7 +108,7 @@
|
|
106 |
"\n",
|
107 |
"REGENERATE_SCRIPT_DATABASE = False\n",
|
108 |
"\n",
|
109 |
-
"if (db_exists := os.path.exists(db_dir :=
|
110 |
" if REGENERATE_SCRIPT_DATABASE:\n",
|
111 |
" print('Deleting the previous database and creating a new one (because otherwise content is duplicated in the db every time this block is run)')\n",
|
112 |
" shutil.rmtree(db_dir)\n",
|
@@ -114,7 +116,7 @@
|
|
114 |
"\n",
|
115 |
"if not db_exists or (db_exists and REGENERATE_SCRIPT_DATABASE): # Unfortunate disjoining of the two conditional blocks\n",
|
116 |
" scripts = DirectoryLoader('scripts', glob = '*.txt', loader_cls = TextLoader).load()\n",
|
117 |
-
" for s in scripts: s.page_content = re.sub(r'\\t
|
118 |
"\n",
|
119 |
" script_chunks = RecursiveCharacterTextSplitter(chunk_size = 1000, chunk_overlap = 200, separators = ['\\n\\n\\n', '\\n\\n', '\\n']).split_documents(scripts)\n",
|
120 |
" # Why not some overlap for extra context just in case?\n",
|
@@ -588,7 +590,7 @@
|
|
588 |
"source": [
|
589 |
"REGENERATE_WOOKIEEPEDIA_DATABASE = False\n",
|
590 |
"\n",
|
591 |
-
"if (db_exists := os.path.exists(db_dir :=
|
592 |
" if REGENERATE_WOOKIEEPEDIA_DATABASE:\n",
|
593 |
" print('Deleting the previous database and creating a new one (because otherwise content is duplicated in the db every time this block is run)')\n",
|
594 |
" shutil.rmtree(db_dir)\n",
|
@@ -732,9 +734,164 @@
|
|
732 |
},
|
733 |
{
|
734 |
"cell_type": "code",
|
735 |
-
"execution_count":
|
736 |
"metadata": {},
|
737 |
-
"outputs": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
738 |
"source": [
|
739 |
"# from sentence_transformers import SentenceTransformer\n",
|
740 |
"\n",
|
@@ -1196,7 +1353,7 @@
|
|
1196 |
"source": [
|
1197 |
"compound_chain = create_retrieval_chain(compound_retriever, document_chain)\n",
|
1198 |
"\n",
|
1199 |
-
"compound_chain.invoke(dict(input = 'Do you know the tragedy of Darth Plagueis the Wise?'))"
|
1200 |
]
|
1201 |
}
|
1202 |
],
|
|
|
61 |
"\n",
|
62 |
"import os\n",
|
63 |
"import shutil\n",
|
64 |
+
"from pathlib import Path\n",
|
65 |
"import re\n",
|
66 |
"\n",
|
67 |
"import dotenv\n",
|
|
|
92 |
"name": "stdout",
|
93 |
"output_type": "stream",
|
94 |
"text": [
|
95 |
+
"Deleting the previous database and creating a new one (because otherwise content is duplicated in the db every time this block is run)\n",
|
96 |
+
"The script database contains 1260 chunks, with mean length of 892 characters\n"
|
97 |
]
|
98 |
}
|
99 |
],
|
|
|
108 |
"\n",
|
109 |
"REGENERATE_SCRIPT_DATABASE = False\n",
|
110 |
"\n",
|
111 |
+
"if (db_exists := os.path.exists(db_dir := str(Path('scripts') / 'db')):\n",
|
112 |
" if REGENERATE_SCRIPT_DATABASE:\n",
|
113 |
" print('Deleting the previous database and creating a new one (because otherwise content is duplicated in the db every time this block is run)')\n",
|
114 |
" shutil.rmtree(db_dir)\n",
|
|
|
116 |
"\n",
|
117 |
"if not db_exists or (db_exists and REGENERATE_SCRIPT_DATABASE): # Unfortunate disjoining of the two conditional blocks\n",
|
118 |
" scripts = DirectoryLoader('scripts', glob = '*.txt', loader_cls = TextLoader).load()\n",
|
119 |
+
" for s in scripts: s.page_content = re.sub(r'^[\\t ]+', '', s.page_content, flags = re.MULTILINE) # Spacing to centre text noise\n",
|
120 |
"\n",
|
121 |
" script_chunks = RecursiveCharacterTextSplitter(chunk_size = 1000, chunk_overlap = 200, separators = ['\\n\\n\\n', '\\n\\n', '\\n']).split_documents(scripts)\n",
|
122 |
" # Why not some overlap for extra context just in case?\n",
|
|
|
590 |
"source": [
|
591 |
"REGENERATE_WOOKIEEPEDIA_DATABASE = False\n",
|
592 |
"\n",
|
593 |
+
"if (db_exists := os.path.exists(db_dir := str(Path('wookieepedia') / 'db'))):\n",
|
594 |
" if REGENERATE_WOOKIEEPEDIA_DATABASE:\n",
|
595 |
" print('Deleting the previous database and creating a new one (because otherwise content is duplicated in the db every time this block is run)')\n",
|
596 |
" shutil.rmtree(db_dir)\n",
|
|
|
734 |
},
|
735 |
{
|
736 |
"cell_type": "code",
|
737 |
+
"execution_count": 7,
|
738 |
"metadata": {},
|
739 |
+
"outputs": [
|
740 |
+
{
|
741 |
+
"data": {
|
742 |
+
"application/vnd.jupyter.widget-view+json": {
|
743 |
+
"model_id": "da3292f4ae9e4d6c912f226b91a4412e",
|
744 |
+
"version_major": 2,
|
745 |
+
"version_minor": 0
|
746 |
+
},
|
747 |
+
"text/plain": [
|
748 |
+
"modules.json: 0%| | 0.00/349 [00:00<?, ?B/s]"
|
749 |
+
]
|
750 |
+
},
|
751 |
+
"metadata": {},
|
752 |
+
"output_type": "display_data"
|
753 |
+
},
|
754 |
+
{
|
755 |
+
"data": {
|
756 |
+
"application/vnd.jupyter.widget-view+json": {
|
757 |
+
"model_id": "2f3dcddcf3ce4fbbac71999124541281",
|
758 |
+
"version_major": 2,
|
759 |
+
"version_minor": 0
|
760 |
+
},
|
761 |
+
"text/plain": [
|
762 |
+
"config_sentence_transformers.json: 0%| | 0.00/116 [00:00<?, ?B/s]"
|
763 |
+
]
|
764 |
+
},
|
765 |
+
"metadata": {},
|
766 |
+
"output_type": "display_data"
|
767 |
+
},
|
768 |
+
{
|
769 |
+
"data": {
|
770 |
+
"application/vnd.jupyter.widget-view+json": {
|
771 |
+
"model_id": "70c10212924a487d878d8f87c43bdd90",
|
772 |
+
"version_major": 2,
|
773 |
+
"version_minor": 0
|
774 |
+
},
|
775 |
+
"text/plain": [
|
776 |
+
"README.md: 0%| | 0.00/10.7k [00:00<?, ?B/s]"
|
777 |
+
]
|
778 |
+
},
|
779 |
+
"metadata": {},
|
780 |
+
"output_type": "display_data"
|
781 |
+
},
|
782 |
+
{
|
783 |
+
"data": {
|
784 |
+
"application/vnd.jupyter.widget-view+json": {
|
785 |
+
"model_id": "8d04da9721ec4e86b01068d16a8588fa",
|
786 |
+
"version_major": 2,
|
787 |
+
"version_minor": 0
|
788 |
+
},
|
789 |
+
"text/plain": [
|
790 |
+
"sentence_bert_config.json: 0%| | 0.00/53.0 [00:00<?, ?B/s]"
|
791 |
+
]
|
792 |
+
},
|
793 |
+
"metadata": {},
|
794 |
+
"output_type": "display_data"
|
795 |
+
},
|
796 |
+
{
|
797 |
+
"data": {
|
798 |
+
"application/vnd.jupyter.widget-view+json": {
|
799 |
+
"model_id": "8dfd7c22b7a44ea9a16fbd6573635186",
|
800 |
+
"version_major": 2,
|
801 |
+
"version_minor": 0
|
802 |
+
},
|
803 |
+
"text/plain": [
|
804 |
+
"config.json: 0%| | 0.00/612 [00:00<?, ?B/s]"
|
805 |
+
]
|
806 |
+
},
|
807 |
+
"metadata": {},
|
808 |
+
"output_type": "display_data"
|
809 |
+
},
|
810 |
+
{
|
811 |
+
"data": {
|
812 |
+
"application/vnd.jupyter.widget-view+json": {
|
813 |
+
"model_id": "f5fabe250679451da00677c57d08abf1",
|
814 |
+
"version_major": 2,
|
815 |
+
"version_minor": 0
|
816 |
+
},
|
817 |
+
"text/plain": [
|
818 |
+
"model.safetensors: 0%| | 0.00/90.9M [00:00<?, ?B/s]"
|
819 |
+
]
|
820 |
+
},
|
821 |
+
"metadata": {},
|
822 |
+
"output_type": "display_data"
|
823 |
+
},
|
824 |
+
{
|
825 |
+
"data": {
|
826 |
+
"application/vnd.jupyter.widget-view+json": {
|
827 |
+
"model_id": "0e978ece6930478b9e7106020bfa1ebf",
|
828 |
+
"version_major": 2,
|
829 |
+
"version_minor": 0
|
830 |
+
},
|
831 |
+
"text/plain": [
|
832 |
+
"tokenizer_config.json: 0%| | 0.00/350 [00:00<?, ?B/s]"
|
833 |
+
]
|
834 |
+
},
|
835 |
+
"metadata": {},
|
836 |
+
"output_type": "display_data"
|
837 |
+
},
|
838 |
+
{
|
839 |
+
"data": {
|
840 |
+
"application/vnd.jupyter.widget-view+json": {
|
841 |
+
"model_id": "db27fca31ec844e888ed7f6f6aecbe2b",
|
842 |
+
"version_major": 2,
|
843 |
+
"version_minor": 0
|
844 |
+
},
|
845 |
+
"text/plain": [
|
846 |
+
"vocab.txt: 0%| | 0.00/232k [00:00<?, ?B/s]"
|
847 |
+
]
|
848 |
+
},
|
849 |
+
"metadata": {},
|
850 |
+
"output_type": "display_data"
|
851 |
+
},
|
852 |
+
{
|
853 |
+
"data": {
|
854 |
+
"application/vnd.jupyter.widget-view+json": {
|
855 |
+
"model_id": "d1570b2dd7d7413ba72777799fa7e2a5",
|
856 |
+
"version_major": 2,
|
857 |
+
"version_minor": 0
|
858 |
+
},
|
859 |
+
"text/plain": [
|
860 |
+
"tokenizer.json: 0%| | 0.00/466k [00:00<?, ?B/s]"
|
861 |
+
]
|
862 |
+
},
|
863 |
+
"metadata": {},
|
864 |
+
"output_type": "display_data"
|
865 |
+
},
|
866 |
+
{
|
867 |
+
"data": {
|
868 |
+
"application/vnd.jupyter.widget-view+json": {
|
869 |
+
"model_id": "2ec46385e4f44907991e3f498bc4b77d",
|
870 |
+
"version_major": 2,
|
871 |
+
"version_minor": 0
|
872 |
+
},
|
873 |
+
"text/plain": [
|
874 |
+
"special_tokens_map.json: 0%| | 0.00/112 [00:00<?, ?B/s]"
|
875 |
+
]
|
876 |
+
},
|
877 |
+
"metadata": {},
|
878 |
+
"output_type": "display_data"
|
879 |
+
},
|
880 |
+
{
|
881 |
+
"data": {
|
882 |
+
"application/vnd.jupyter.widget-view+json": {
|
883 |
+
"model_id": "0f1dddc6b2ee4ebf8eb37e356b9c3e41",
|
884 |
+
"version_major": 2,
|
885 |
+
"version_minor": 0
|
886 |
+
},
|
887 |
+
"text/plain": [
|
888 |
+
"1_Pooling/config.json: 0%| | 0.00/190 [00:00<?, ?B/s]"
|
889 |
+
]
|
890 |
+
},
|
891 |
+
"metadata": {},
|
892 |
+
"output_type": "display_data"
|
893 |
+
}
|
894 |
+
],
|
895 |
"source": [
|
896 |
"# from sentence_transformers import SentenceTransformer\n",
|
897 |
"\n",
|
|
|
1353 |
"source": [
|
1354 |
"compound_chain = create_retrieval_chain(compound_retriever, document_chain)\n",
|
1355 |
"\n",
|
1356 |
+
"# compound_chain.invoke(dict(input = 'Do you know the tragedy of Darth Plagueis the Wise?'))"
|
1357 |
]
|
1358 |
}
|
1359 |
],
|
ingredients.py
CHANGED
@@ -29,6 +29,7 @@ from bs4 import BeautifulSoup
|
|
29 |
|
30 |
import os
|
31 |
import shutil
|
|
|
32 |
import re
|
33 |
|
34 |
import dotenv
|
@@ -39,46 +40,22 @@ dotenv.load_dotenv()
|
|
39 |
|
40 |
## Vector stores
|
41 |
|
42 |
-
|
43 |
-
woo_db = Chroma(embedding_function = SentenceTransformerEmbeddings(model_name = 'all-MiniLM-L6-v2'), persist_directory = r'wookieepedia\db')
|
44 |
|
|
|
|
|
|
|
|
|
45 |
|
|
|
|
|
|
|
46 |
|
47 |
-
## Wookieepedia functions
|
48 |
-
|
49 |
-
def first_wookieepedia_result(query: str) -> str:
|
50 |
-
'''Get the url of the first result when searching Wookieepedia for a query
|
51 |
-
(best for simple names as queries, ideally generated by the llm for something like
|
52 |
-
"Produce a input consisting of the name of the most important element in the query so that its article can be looked up")
|
53 |
-
'''
|
54 |
-
search_results = requests.get(f'https://starwars.fandom.com/wiki/Special:Search?query={"+".join(query.split(" "))}')
|
55 |
-
soup = BeautifulSoup(search_results.content, 'html.parser')
|
56 |
-
first_res = soup.find('a', class_ = 'unified-search__result__link')
|
57 |
-
return first_res['href']
|
58 |
-
|
59 |
-
|
60 |
-
def get_new_wookieepedia_chunks(query: str, previous_sources: set[str]) -> list[Document]:
|
61 |
-
'''Retrieve and return chunks of the content of the first result of query on Wookieepedia, then return the closest matches for.
|
62 |
-
'''
|
63 |
-
url = first_wookieepedia_result(query)
|
64 |
-
|
65 |
-
if url in previous_sources: return []
|
66 |
-
else:
|
67 |
-
doc = WebBaseLoader(url).load()[0] # Only one url passed in => only one Document out; no need to assert
|
68 |
-
|
69 |
-
# There probably is a very long preamble before the real content, however, if more than one gap then ignore and proceed with full document
|
70 |
-
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
|
71 |
-
doc.page_content = re.sub(r'[\n\t]{2,}', '\n', trimmed) # And remove excessive spacing
|
72 |
-
|
73 |
-
return RecursiveCharacterTextSplitter(chunk_size = 800, chunk_overlap = 100).split_documents([doc])
|
74 |
|
75 |
-
|
76 |
-
try:
|
77 |
-
new_chunks = get_new_wookieepedia_chunks(simple_query, previous_sources = set(md.get('source') for md in wdb.get()['metadatas']))
|
78 |
-
if new_chunks: wdb.add_documents(new_chunks)
|
79 |
-
except: return []
|
80 |
|
81 |
-
|
|
|
82 |
|
83 |
|
84 |
|
@@ -105,7 +82,6 @@ document_prompt = ChatPromptTemplate.from_messages([
|
|
105 |
])
|
106 |
document_chain = create_stuff_documents_chain(llm, document_prompt)
|
107 |
|
108 |
-
|
109 |
script_retriever_prompt = ChatPromptTemplate.from_messages([
|
110 |
MessagesPlaceholder(variable_name = 'chat_history'),
|
111 |
('user', '{input}'),
|
@@ -114,7 +90,6 @@ script_retriever_prompt = ChatPromptTemplate.from_messages([
|
|
114 |
])
|
115 |
script_retriever_chain = create_history_aware_retriever(llm, script_db.as_retriever(), script_retriever_prompt) # Essentially just: prompt | llm | StrOutputParser() | retriever
|
116 |
|
117 |
-
|
118 |
woo_retriever_prompt = ChatPromptTemplate.from_messages([
|
119 |
MessagesPlaceholder(variable_name = 'chat_history'),
|
120 |
('user', '{input}'),
|
@@ -122,7 +97,6 @@ woo_retriever_prompt = ChatPromptTemplate.from_messages([
|
|
122 |
])
|
123 |
woo_retriever_chain = create_history_aware_retriever(llm, woo_db.as_retriever(), woo_retriever_prompt) # Essentially just: prompt | llm | StrOutputParser() | retriever
|
124 |
|
125 |
-
|
126 |
# full_chain = create_retrieval_chain(script_retriever_chain, document_chain)
|
127 |
full_chain = create_retrieval_chain(woo_retriever_chain, document_chain)
|
128 |
|
@@ -146,6 +120,7 @@ script_tool = create_retriever_tool(
|
|
146 |
'''Search the Star Wars film scripts. This tool should be the first choice for Star Wars related questions.
|
147 |
Queries passed to this tool should be lists of keywords likely to be in dialogue or scene descriptions, and should not include film titles.'''
|
148 |
)
|
|
|
149 |
woo_tool = create_retriever_tool(
|
150 |
woo_db.as_retriever(search_kwargs = dict(k = 4)),
|
151 |
'search_wookieepedia',
|
@@ -166,6 +141,7 @@ agent_prompt = ChatPromptTemplate.from_messages([
|
|
166 |
('placeholder', '{agent_scratchpad}') # Required for chat history and the agent's intermediate processing values
|
167 |
])
|
168 |
agent = create_tool_calling_agent(llm, tools, agent_prompt)
|
|
|
169 |
agent_executor = AgentExecutor(agent = agent, tools = tools, verbose = True)
|
170 |
|
171 |
|
@@ -204,3 +180,49 @@ def compound_retriever(question):
|
|
204 |
compound_chain = create_retrieval_chain(compound_retriever, document_chain)
|
205 |
|
206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
import os
|
31 |
import shutil
|
32 |
+
from pathlib import Path
|
33 |
import re
|
34 |
|
35 |
import dotenv
|
|
|
40 |
|
41 |
## Vector stores
|
42 |
|
43 |
+
# Non-persistent; build from documents
|
|
|
44 |
|
45 |
+
# scripts = DirectoryLoader('scripts', glob = '*.txt', loader_cls = TextLoader).load()
|
46 |
+
# for s in scripts: s.page_content = re.sub(r'^[\t ]+', '', s.page_content, flags = re.MULTILINE) # Spacing to centre text noise
|
47 |
+
# script_chunks = RecursiveCharacterTextSplitter(chunk_size = 1000, chunk_overlap = 200, separators = ['\n\n\n', '\n\n', '\n']).split_documents(scripts)
|
48 |
+
# script_db = Chroma.from_documents(script_chunks, SentenceTransformerEmbeddings(model_name = 'all-MiniLM-L6-v2'))
|
49 |
|
50 |
+
# pages = DirectoryLoader('wookieepedia', glob = '*.txt', loader_cls = TextLoader).load()
|
51 |
+
# page_chunks = RecursiveCharacterTextSplitter(chunk_size = 1000, chunk_overlap = 200, separators = ['\n\n\n', '\n\n', '\n']).split_documents(pages)
|
52 |
+
# woo_db = Chroma.from_documents(page_chunks, SentenceTransformerEmbeddings(model_name = 'all-MiniLM-L6-v2'))
|
53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
+
# # Load pre-built persistent ones
|
|
|
|
|
|
|
|
|
56 |
|
57 |
+
script_db = Chroma(embedding_function = SentenceTransformerEmbeddings(model_name = 'all-MiniLM-L6-v2'), persist_directory = str(Path('scripts') / 'db'))
|
58 |
+
woo_db = Chroma(embedding_function = SentenceTransformerEmbeddings(model_name = 'all-MiniLM-L6-v2'), persist_directory = str(Path('wookieepedia') / 'db'))
|
59 |
|
60 |
|
61 |
|
|
|
82 |
])
|
83 |
document_chain = create_stuff_documents_chain(llm, document_prompt)
|
84 |
|
|
|
85 |
script_retriever_prompt = ChatPromptTemplate.from_messages([
|
86 |
MessagesPlaceholder(variable_name = 'chat_history'),
|
87 |
('user', '{input}'),
|
|
|
90 |
])
|
91 |
script_retriever_chain = create_history_aware_retriever(llm, script_db.as_retriever(), script_retriever_prompt) # Essentially just: prompt | llm | StrOutputParser() | retriever
|
92 |
|
|
|
93 |
woo_retriever_prompt = ChatPromptTemplate.from_messages([
|
94 |
MessagesPlaceholder(variable_name = 'chat_history'),
|
95 |
('user', '{input}'),
|
|
|
97 |
])
|
98 |
woo_retriever_chain = create_history_aware_retriever(llm, woo_db.as_retriever(), woo_retriever_prompt) # Essentially just: prompt | llm | StrOutputParser() | retriever
|
99 |
|
|
|
100 |
# full_chain = create_retrieval_chain(script_retriever_chain, document_chain)
|
101 |
full_chain = create_retrieval_chain(woo_retriever_chain, document_chain)
|
102 |
|
|
|
120 |
'''Search the Star Wars film scripts. This tool should be the first choice for Star Wars related questions.
|
121 |
Queries passed to this tool should be lists of keywords likely to be in dialogue or scene descriptions, and should not include film titles.'''
|
122 |
)
|
123 |
+
|
124 |
woo_tool = create_retriever_tool(
|
125 |
woo_db.as_retriever(search_kwargs = dict(k = 4)),
|
126 |
'search_wookieepedia',
|
|
|
141 |
('placeholder', '{agent_scratchpad}') # Required for chat history and the agent's intermediate processing values
|
142 |
])
|
143 |
agent = create_tool_calling_agent(llm, tools, agent_prompt)
|
144 |
+
|
145 |
agent_executor = AgentExecutor(agent = agent, tools = tools, verbose = True)
|
146 |
|
147 |
|
|
|
180 |
compound_chain = create_retrieval_chain(compound_retriever, document_chain)
|
181 |
|
182 |
|
183 |
+
|
184 |
+
## Wookieepedia functions
|
185 |
+
|
186 |
+
def first_wookieepedia_result(query: str) -> str:
|
187 |
+
'''Get the url of the first result when searching Wookieepedia for a query
|
188 |
+
(best for simple names as queries, ideally generated by the llm for something like
|
189 |
+
"Produce a input consisting of the name of the most important element in the query so that its article can be looked up")
|
190 |
+
'''
|
191 |
+
search_results = requests.get(f'https://starwars.fandom.com/wiki/Special:Search?query={"+".join(query.split(" "))}')
|
192 |
+
soup = BeautifulSoup(search_results.content, 'html.parser')
|
193 |
+
first_res = soup.find('a', class_ = 'unified-search__result__link')
|
194 |
+
return first_res['href']
|
195 |
+
|
196 |
+
|
197 |
+
def get_wookieepedia_page_content(query: str, previous_sources: set[str]) -> Document | None:
|
198 |
+
'''Return cleaned content from a Wookieepedia page provided it was not already sourced
|
199 |
+
'''
|
200 |
+
url = first_wookieepedia_result(query)
|
201 |
+
|
202 |
+
if url in previous_sources: return None
|
203 |
+
else:
|
204 |
+
response = requests.get(url)
|
205 |
+
soup = BeautifulSoup(response.content, 'html.parser')
|
206 |
+
doc = soup.find('div', id = 'content').get_text()
|
207 |
+
|
208 |
+
# Cleaning
|
209 |
+
doc = doc.split('\n\n\n\n\n\n\n\n\n\n\n\n\n\n')[-1] # The (multiple) preambles are separated by these many newlines; no harm done if not present
|
210 |
+
doc = re.sub('\[\d*\]', '', doc) # References (and section title's "[]" suffixes) are noise
|
211 |
+
doc = doc.split('\nAppearances\n')[0] # Keep only content before these sections
|
212 |
+
doc = doc.split('\nSources\n')[0] # Technically no need to check this if successfully cut on appearances, but no harm done
|
213 |
+
doc = re.sub('Contents\n\n(?:[\d\.]+ [^\n]+\n+)+', '', doc) # Remove table of contents
|
214 |
+
|
215 |
+
return Document(page_content = doc, metadata = dict(source = url))
|
216 |
+
|
217 |
+
def get_wookieepedia_context(original_query: str, simple_query: str, wdb: Chroma) -> list[Document]:
|
218 |
+
try:
|
219 |
+
doc = get_wookieepedia_page_content(simple_query, previous_sources = set(md.get('source') for md in wdb.get()['metadatas']))
|
220 |
+
if doc is not None:
|
221 |
+
new_chunks = RecursiveCharacterTextSplitter(chunk_size = 1000, chunk_overlap = 200).split_documents([doc])
|
222 |
+
wdb.add_documents(new_chunks)
|
223 |
+
print(f"Added new chunks (for '{simple_query}' -> {doc['metadata']['source']}) to the Wookieepedia database.")
|
224 |
+
except: return []
|
225 |
+
|
226 |
+
return wdb.similarity_search(original_query, k = 10)
|
227 |
+
|
228 |
+
|
langserve_app.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Chains
|
2 |
+
from langchain_core.pydantic_v1 import BaseModel, Field
|
3 |
+
|
4 |
+
# To serve the app
|
5 |
+
from fastapi import FastAPI
|
6 |
+
from langchain_core.messages import BaseMessage
|
7 |
+
from langserve import add_routes, CustomUserType
|
8 |
+
|
9 |
+
import dotenv
|
10 |
+
dotenv.load_dotenv()
|
11 |
+
|
12 |
+
from ingredients import script_db, woo_db, full_chain, compound_chain, agent_executor
|
13 |
+
|
14 |
+
|
15 |
+
|
16 |
+
## Type specifications (with unusual class-scope fields)
|
17 |
+
|
18 |
+
class StrInput(BaseModel):
|
19 |
+
input: str
|
20 |
+
|
21 |
+
class Input(BaseModel):
|
22 |
+
input: str
|
23 |
+
chat_history: list[BaseMessage] = Field(
|
24 |
+
...,
|
25 |
+
extra = dict(widget = dict(type = 'chat', input = 'location')),
|
26 |
+
)
|
27 |
+
|
28 |
+
class Output(BaseModel):
|
29 |
+
output: str
|
30 |
+
|
31 |
+
|
32 |
+
|
33 |
+
## App definition
|
34 |
+
# 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)
|
35 |
+
|
36 |
+
app = FastAPI(
|
37 |
+
title = 'Star Wars Expert',
|
38 |
+
version = '1.0',
|
39 |
+
description = 'A Star Wars expert chatbot',
|
40 |
+
)
|
41 |
+
|
42 |
+
|
43 |
+
# Basic retriever versions
|
44 |
+
|
45 |
+
# add_routes(app, script_db.as_retriever())
|
46 |
+
# add_routes(app, woo_db.as_retriever())
|
47 |
+
|
48 |
+
|
49 |
+
# History-aware retriever version
|
50 |
+
# add_routes(app, full_chain.with_types(input_type = StrInput, output_type = Output), playground_type = 'default')
|
51 |
+
|
52 |
+
|
53 |
+
# Agent version
|
54 |
+
|
55 |
+
# add_routes(app, agent_executor, playground_type = 'chat')
|
56 |
+
# add_routes(app, agent_executor.with_types(input_type = StrInput, output_type = Output))
|
57 |
+
|
58 |
+
|
59 |
+
# Non-agent chain-logic version
|
60 |
+
|
61 |
+
add_routes(app, compound_chain.with_types(input_type = StrInput))
|
62 |
+
# add_routes(app, compound_chain.with_types(input_type = Input), playground_type = 'chat')
|
63 |
+
|
64 |
+
|
65 |
+
|
66 |
+
if __name__ == '__main__':
|
67 |
+
import uvicorn
|
68 |
+
|
69 |
+
uvicorn.run(app, host = 'localhost', port = 8000)
|
70 |
+
|
71 |
+
|
requirements.txt
CHANGED
@@ -1,12 +1,14 @@
|
|
1 |
beautifulsoup4>=4.12.3
|
2 |
-
fastapi>=0.110.2
|
3 |
langchain>=0.1.16
|
4 |
langchain_community>=0.0.34
|
5 |
langchain_core>=0.1.45
|
6 |
langchain_openai>=0.1.3
|
7 |
-
langserve>=0.1.0
|
8 |
sentence-transformers>=2.7.0
|
9 |
python-dotenv>=1.0.1
|
10 |
Requests>=2.31.0
|
11 |
-
uvicorn>=0.29.0
|
12 |
chromadb>=0.4.24
|
|
|
|
|
|
|
|
|
|
|
|
1 |
beautifulsoup4>=4.12.3
|
|
|
2 |
langchain>=0.1.16
|
3 |
langchain_community>=0.0.34
|
4 |
langchain_core>=0.1.45
|
5 |
langchain_openai>=0.1.3
|
|
|
6 |
sentence-transformers>=2.7.0
|
7 |
python-dotenv>=1.0.1
|
8 |
Requests>=2.31.0
|
|
|
9 |
chromadb>=0.4.24
|
10 |
+
gradio>=4.27.0
|
11 |
+
|
12 |
+
# fastapi>=0.110.2
|
13 |
+
# langserve>=0.1.0
|
14 |
+
# uvicorn>=0.29.0
|
scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/data_level0.bin
RENAMED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
size 1676000
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:b4907f424ad43eafe0706f355d0345cd82ee7798a135f9995233ca7e42bc937d
|
3 |
size 1676000
|
scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/header.bin
RENAMED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
size 100
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:bc26d853c2df1dbd2161639e83f53b98808b632c106c529341d0b31f3a0b3e2a
|
3 |
size 100
|
scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/index_metadata.pickle
RENAMED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
size 55974
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:7824365e98ba10f9c2112739ddbb7d87839f7c27a662e21ee2fe819677ce0e0e
|
3 |
size 55974
|
scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/length.bin
RENAMED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
size 4000
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:ac2283f2e028a485f5564c59ed1722b4e7c01c7dad86578d7572e39b4af15c74
|
3 |
size 4000
|
scripts/db/{ee161592-5f4b-453f-af5c-10f9ed7a57a0 β 9a364cee-0b3d-4004-86d6-7bac58ecc92c}/link_lists.bin
RENAMED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
size 8624
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:f3ace8c9f24593fdd1d5ea5618e39cac109d9663f598c4e0e5ab668534f3b62e
|
3 |
size 8624
|
scripts/db/chroma.sqlite3
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:08c0800eb8542363f5030d5fa2c1807251c5827bd9a8a1888f923d85cc564951
|
3 |
+
size 12173312
|
testing.ipynb
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "markdown",
|
5 |
+
"metadata": {},
|
6 |
+
"source": [
|
7 |
+
"# Testing of different versions"
|
8 |
+
]
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"cell_type": "markdown",
|
12 |
+
"metadata": {},
|
13 |
+
"source": [
|
14 |
+
"## Local environment"
|
15 |
+
]
|
16 |
+
},
|
17 |
+
{
|
18 |
+
"cell_type": "code",
|
19 |
+
"execution_count": null,
|
20 |
+
"metadata": {},
|
21 |
+
"outputs": [],
|
22 |
+
"source": [
|
23 |
+
"from ingredients import script_db, woo_db, full_chain, compound_chain, agent_executor"
|
24 |
+
]
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"cell_type": "code",
|
28 |
+
"execution_count": null,
|
29 |
+
"metadata": {},
|
30 |
+
"outputs": [],
|
31 |
+
"source": [
|
32 |
+
"compound_chain.invoke(dict(input = 'What power source did the Death Star use?', chat_history = []))"
|
33 |
+
]
|
34 |
+
},
|
35 |
+
{
|
36 |
+
"cell_type": "markdown",
|
37 |
+
"metadata": {},
|
38 |
+
"source": [
|
39 |
+
"## Gradio app"
|
40 |
+
]
|
41 |
+
},
|
42 |
+
{
|
43 |
+
"cell_type": "code",
|
44 |
+
"execution_count": 3,
|
45 |
+
"metadata": {},
|
46 |
+
"outputs": [
|
47 |
+
{
|
48 |
+
"name": "stdout",
|
49 |
+
"output_type": "stream",
|
50 |
+
"text": [
|
51 |
+
"Loaded as API: http://127.0.0.1:7860/ β\n"
|
52 |
+
]
|
53 |
+
}
|
54 |
+
],
|
55 |
+
"source": [
|
56 |
+
"from gradio_client import Client\n",
|
57 |
+
"\n",
|
58 |
+
"# client = Client('T-Flet/Star-Wars-Expert')\n",
|
59 |
+
"client = Client('http://127.0.0.1:7860')"
|
60 |
+
]
|
61 |
+
},
|
62 |
+
{
|
63 |
+
"cell_type": "code",
|
64 |
+
"execution_count": 4,
|
65 |
+
"metadata": {},
|
66 |
+
"outputs": [
|
67 |
+
{
|
68 |
+
"data": {
|
69 |
+
"text/plain": [
|
70 |
+
"'Hello! How can I help you with Star Wars today?'"
|
71 |
+
]
|
72 |
+
},
|
73 |
+
"execution_count": 4,
|
74 |
+
"metadata": {},
|
75 |
+
"output_type": "execute_result"
|
76 |
+
}
|
77 |
+
],
|
78 |
+
"source": [
|
79 |
+
"client.predict(message = 'Do you know the tragedy of Darth Plagueis the Wise?', api_name = '/chat')"
|
80 |
+
]
|
81 |
+
},
|
82 |
+
{
|
83 |
+
"cell_type": "markdown",
|
84 |
+
"metadata": {},
|
85 |
+
"source": [
|
86 |
+
"## LangServe app"
|
87 |
+
]
|
88 |
+
},
|
89 |
+
{
|
90 |
+
"cell_type": "code",
|
91 |
+
"execution_count": 1,
|
92 |
+
"metadata": {},
|
93 |
+
"outputs": [],
|
94 |
+
"source": [
|
95 |
+
"from langserve import RemoteRunnable\n",
|
96 |
+
"\n",
|
97 |
+
"remote_runnable = RemoteRunnable('http://localhost:8000/')"
|
98 |
+
]
|
99 |
+
},
|
100 |
+
{
|
101 |
+
"cell_type": "code",
|
102 |
+
"execution_count": null,
|
103 |
+
"metadata": {},
|
104 |
+
"outputs": [],
|
105 |
+
"source": [
|
106 |
+
"# remote_runnable.invoke(dict(input = 'Do you know the tragedy of Darth Plagueis the Wise?', chat_history = []))\n",
|
107 |
+
"remote_runnable.invoke(dict(input = 'What power source did the Death Star use?', chat_history = []))"
|
108 |
+
]
|
109 |
+
}
|
110 |
+
],
|
111 |
+
"metadata": {
|
112 |
+
"kernelspec": {
|
113 |
+
"display_name": "ML11",
|
114 |
+
"language": "python",
|
115 |
+
"name": "python3"
|
116 |
+
},
|
117 |
+
"language_info": {
|
118 |
+
"codemirror_mode": {
|
119 |
+
"name": "ipython",
|
120 |
+
"version": 3
|
121 |
+
},
|
122 |
+
"file_extension": ".py",
|
123 |
+
"mimetype": "text/x-python",
|
124 |
+
"name": "python",
|
125 |
+
"nbconvert_exporter": "python",
|
126 |
+
"pygments_lexer": "ipython3",
|
127 |
+
"version": "3.11.7"
|
128 |
+
}
|
129 |
+
},
|
130 |
+
"nbformat": 4,
|
131 |
+
"nbformat_minor": 2
|
132 |
+
}
|