T-Flet commited on
Commit
42a73b9
1 Parent(s): 2ab76e5

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
Files changed (2) hide show
  1. app_notebook.ipynb +69 -0
  2. 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
+