project: add C-RAG project
Browse files- .chainlit/config.toml +84 -0
- Dockerfile +17 -0
- README.md +13 -0
- app.py +32 -0
- chainlit.md +5 -0
- crag.ipynb +6 -15
- graph.py +10 -53
- nodes/__init__.py +0 -0
- generator.py β nodes/generator.py +5 -8
- grader.py β nodes/grader.py +6 -9
- retriever.py β nodes/retriever.py +8 -7
- rewriter.py β nodes/rewriter.py +5 -10
- requirements.txt +11 -0
- server.py +3 -3
- utils/__init__.py +0 -0
- chat_types.py β utils/chat_types.py +0 -0
- config.py β utils/config.py +4 -0
- utils/prompts.py +17 -0
.chainlit/config.toml
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[project]
|
2 |
+
# Whether to enable telemetry (default: true). No personal data is collected.
|
3 |
+
enable_telemetry = true
|
4 |
+
|
5 |
+
# List of environment variables to be provided by each user to use the app.
|
6 |
+
user_env = []
|
7 |
+
|
8 |
+
# Duration (in seconds) during which the session is saved when the connection is lost
|
9 |
+
session_timeout = 3600
|
10 |
+
|
11 |
+
# Enable third parties caching (e.g LangChain cache)
|
12 |
+
cache = false
|
13 |
+
|
14 |
+
# Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317)
|
15 |
+
# follow_symlink = false
|
16 |
+
|
17 |
+
[features]
|
18 |
+
# Show the prompt playground
|
19 |
+
prompt_playground = true
|
20 |
+
|
21 |
+
# Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript)
|
22 |
+
unsafe_allow_html = false
|
23 |
+
|
24 |
+
# Process and display mathematical expressions. This can clash with "$" characters in messages.
|
25 |
+
latex = false
|
26 |
+
|
27 |
+
# Authorize users to upload files with messages
|
28 |
+
multi_modal = true
|
29 |
+
|
30 |
+
# Allows user to use speech to text
|
31 |
+
[features.speech_to_text]
|
32 |
+
enabled = false
|
33 |
+
# See all languages here https://github.com/JamesBrill/react-speech-recognition/blob/HEAD/docs/API.md#language-string
|
34 |
+
# language = "en-US"
|
35 |
+
|
36 |
+
[UI]
|
37 |
+
# Name of the app and chatbot.
|
38 |
+
name = "10-Q Bot"
|
39 |
+
|
40 |
+
# Show the readme while the conversation is empty.
|
41 |
+
show_readme_as_default = true
|
42 |
+
|
43 |
+
# Description of the app and chatbot. This is used for HTML tags.
|
44 |
+
description = "This is a 10-Q Bot, designed to assist with SEC filings and financial data queries."
|
45 |
+
|
46 |
+
# Large size content are by default collapsed for a cleaner ui
|
47 |
+
default_collapse_content = true
|
48 |
+
|
49 |
+
# The default value for the expand messages settings.
|
50 |
+
default_expand_messages = false
|
51 |
+
|
52 |
+
# Hide the chain of thought details from the user in the UI.
|
53 |
+
hide_cot = false
|
54 |
+
|
55 |
+
# Link to your github repo. This will add a github button in the UI's header.
|
56 |
+
# github = "https://github.com/lakshyaag/"
|
57 |
+
|
58 |
+
# Specify a CSS file that can be used to customize the user interface.
|
59 |
+
# The CSS file can be served from the public directory or via an external link.
|
60 |
+
# custom_css = "/public/test.css"
|
61 |
+
|
62 |
+
# Override default MUI light theme. (Check theme.ts)
|
63 |
+
[UI.theme.light]
|
64 |
+
#background = "#FAFAFA"
|
65 |
+
#paper = "#FFFFFF"
|
66 |
+
|
67 |
+
[UI.theme.light.primary]
|
68 |
+
#main = "#F80061"
|
69 |
+
#dark = "#980039"
|
70 |
+
#light = "#FFE7EB"
|
71 |
+
|
72 |
+
# Override default MUI dark theme. (Check theme.ts)
|
73 |
+
[UI.theme.dark]
|
74 |
+
#background = "#FAFAFA"
|
75 |
+
#paper = "#FFFFFF"
|
76 |
+
|
77 |
+
[UI.theme.dark.primary]
|
78 |
+
#main = "#F80061"
|
79 |
+
#dark = "#980039"
|
80 |
+
#light = "#FFE7EB"
|
81 |
+
|
82 |
+
|
83 |
+
[meta]
|
84 |
+
generated_by = "0.7.700"
|
Dockerfile
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.10
|
2 |
+
RUN pip install --upgrade pip
|
3 |
+
|
4 |
+
RUN useradd -m -u 1000 user
|
5 |
+
USER user
|
6 |
+
ENV HOME=/home/user \
|
7 |
+
PATH=/home/user/.local/bin:$PATH
|
8 |
+
|
9 |
+
WORKDIR $HOME/app
|
10 |
+
|
11 |
+
COPY --chown=user ./requirements.txt $HOME/app/requirements.txt
|
12 |
+
RUN pip install -r $HOME/app/requirements.txt
|
13 |
+
|
14 |
+
COPY --chown=user . $HOME/app
|
15 |
+
RUN chown user -R ${HOME}/app/
|
16 |
+
|
17 |
+
CMD ["chainlit", "run", "app.py", "--port", "7860"]
|
README.md
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Airbnb 10-Q Chatbot
|
3 |
+
emoji: π
|
4 |
+
colorFrom: red
|
5 |
+
colorTo: green
|
6 |
+
sdk: docker
|
7 |
+
pinned: false
|
8 |
+
app_port: 7860
|
9 |
+
---
|
10 |
+
|
11 |
+
This chatbot is designed to answer questions about Airbnb's 10-Q financial statement for the quarter ended March 31, 2024.
|
12 |
+
|
13 |
+
It uses corrective RAG as the architecture to retrieve information based on the user's input, grading the relevance of the retrieved documents, and optionally querying Wikipedia for additional information, if necessary.
|
app.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import chainlit as cl
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
from graph import create_graph
|
5 |
+
from langchain_core.runnables import RunnableConfig
|
6 |
+
|
7 |
+
|
8 |
+
load_dotenv()
|
9 |
+
|
10 |
+
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
|
11 |
+
graph = create_graph()
|
12 |
+
|
13 |
+
@cl.on_message
|
14 |
+
async def main(message: cl.Message):
|
15 |
+
"""
|
16 |
+
This function will be called every time a message is recieved from a session.
|
17 |
+
"""
|
18 |
+
|
19 |
+
msg = cl.Message(content="")
|
20 |
+
|
21 |
+
async for event in graph.astream_events(
|
22 |
+
{"question": message.content},
|
23 |
+
config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()]),
|
24 |
+
version="v1",
|
25 |
+
):
|
26 |
+
if (
|
27 |
+
event["event"] == "on_chat_model_stream"
|
28 |
+
and event["metadata"]["langgraph_node"] == "generate"
|
29 |
+
):
|
30 |
+
await msg.stream_token(event["data"]["chunk"].content)
|
31 |
+
|
32 |
+
await msg.send()
|
chainlit.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Airbnb 10-Q Chatbot
|
2 |
+
|
3 |
+
This chatbot is designed to answer questions about Airbnb's 10-Q financial statement for the quarter ended March 31, 2024.
|
4 |
+
|
5 |
+
It uses corrective RAG as the architecture to retrieve information based on the user's input, grading the relevance of the retrieved documents, and optionally querying Wikipedia for additional information, if necessary.
|
crag.ipynb
CHANGED
@@ -2,18 +2,9 @@
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
-
"execution_count":
|
6 |
"metadata": {},
|
7 |
-
"outputs": [
|
8 |
-
{
|
9 |
-
"name": "stdout",
|
10 |
-
"output_type": "stream",
|
11 |
-
"text": [
|
12 |
-
"The autoreload extension is already loaded. To reload it, use:\n",
|
13 |
-
" %reload_ext autoreload\n"
|
14 |
-
]
|
15 |
-
}
|
16 |
-
],
|
17 |
"source": [
|
18 |
"%load_ext autoreload\n",
|
19 |
"%autoreload 2"
|
@@ -21,7 +12,7 @@
|
|
21 |
},
|
22 |
{
|
23 |
"cell_type": "code",
|
24 |
-
"execution_count":
|
25 |
"metadata": {},
|
26 |
"outputs": [],
|
27 |
"source": [
|
@@ -32,17 +23,17 @@
|
|
32 |
},
|
33 |
{
|
34 |
"cell_type": "code",
|
35 |
-
"execution_count":
|
36 |
"metadata": {},
|
37 |
"outputs": [
|
38 |
{
|
39 |
"data": {
|
40 |
-
"image/jpeg": "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",
|
41 |
"text/plain": [
|
42 |
"<IPython.core.display.Image object>"
|
43 |
]
|
44 |
},
|
45 |
-
"execution_count":
|
46 |
"metadata": {},
|
47 |
"output_type": "execute_result"
|
48 |
}
|
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
+
"execution_count": 1,
|
6 |
"metadata": {},
|
7 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
"source": [
|
9 |
"%load_ext autoreload\n",
|
10 |
"%autoreload 2"
|
|
|
12 |
},
|
13 |
{
|
14 |
"cell_type": "code",
|
15 |
+
"execution_count": 8,
|
16 |
"metadata": {},
|
17 |
"outputs": [],
|
18 |
"source": [
|
|
|
23 |
},
|
24 |
{
|
25 |
"cell_type": "code",
|
26 |
+
"execution_count": 9,
|
27 |
"metadata": {},
|
28 |
"outputs": [
|
29 |
{
|
30 |
"data": {
|
31 |
+
"image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAIqANsDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAYHBAUIAwECCf/EAFcQAAEEAQIDAwgECQkEBwYHAAEAAgMEBQYRBxIhEzFVCBQVFiJBlNEXMlGTIzdUYXF1kbTSCSQ2OEJSVoGzdoKh4TNEc3SissQlRWJykrFGU1djg5XB/8QAGwEBAAMBAQEBAAAAAAAAAAAAAAECAwUEBgf/xAA2EQACAQICCAUDAgYDAQAAAAAAAQIDERNREhQhMUFSkaEEFWFx8GKx0TLBIjNTY4HhBbLxQ//aAAwDAQACEQMRAD8A/qmiIgCIiAIiIAiIgCIiALys2oaUDprE0cELfrSSuDWjrt1JXqoZxejbLoeVj2h7HX8e1zXDcEeewbghXglKSTLRjpSUczfetWE8YofFM+aetWE8YofFM+arz1fxfhtP7hvyT1fxfhtP7hvyXG818PyS6o6/l31diw/WrCeMUPimfNPWrCeMUPimfNV56v4vw2n9w35J6v4vw2n9w35J5r4fkl1Q8u+rsWH61YTxih8Uz5p61YTxih8Uz5qvPV/F+G0/uG/JPV/F+G0/uG/JPNfD8kuqHl31diw/WrCeMUPimfNPWrCeMUPimfNV56v4vw2n9w35J6v4vw2n9w35J5r4fkl1Q8u+rsWH61YTxih8Uz5p61YTxih8Uz5qvPV/F+G0/uG/JPV/F+G0/uG/JPNfD8kuqHl31di1ILEVqFksMjJonjdr43BzXD7QQvRRLhO0M4d4RrQA0QkAD3e25S1dqpHQm4rgzjtWbQREWZAREQBERAEREAREQBERAEREAUO4tf0Kf+sMf++wKYqHcWv6FP8A1hj/AN9gWlP9cfc1pfzI+6NQiwc1nMdpvGTZHLZCri8fBt2tu7M2GKPcho5nuIA3JA6nvIUXZxw4cSHZnEDSzjsTsMzWPQDcn6/2BfnqjKW1I+sckt7JbksjXxGOtX7cnY1KsT55pCCeVjQXOOw+wAqoM95Qk9jg7qjWWB0nnYDQxYyOPky1SNkNpj2kxzN2m9qMAc7huHhvc3cgGXnjJoPLNdSxusNMZjI2QYq2PjzFdzrUjujYgA47lxIHce/uVRYHhJq7J6d4i4OrgZNCaZzWAkpUdP3Mqy7DDkHiQOlg5C4QwkOaC0bdeoaO5emnCK21FbdvMJyb2Q9S1KnFO6zQWOz1nRWppbll7IfRdWtBLZcSzm7XZsxY2M7HYuePcCASFrbXlGaaqaRx+ffQzRFrNDT78a2mDdr3va/BSRc3fu3b2S7fmbtuD0ieqcHrjW2jtHR5PRFsUsVdDMzpZmXrtdk4m1i1jxI2QMdG2Uhxje5vMGjce5aHSnBzVWJpUaY0pXw1WHiLBqSOpTuQvhr0DX5SB1aeaNw5S0N6k+zzDqtFTpWvJ9yjnUvZfYmuZ455+jxL0hg4tCZ1tPLY+5asVpI6vnbHRysjbsfOeQNaHF7upO0ke255gLpVVcT8JqWnxK0brHT2BOpY8bUv4+3QitxVpWifsXMka6UhpAMJBG+/tAgFSGxxr4e055K9rXWmatmJxjlgmzFZr43g7Oa4c/Qg7ghYTjpKLgunuaxlotqT+WJoig/06cNv/wBQtK//AN1W/jUwoX62Vo17tKxFcp2I2zQ2IHh8crHDdrmuHQggggjod1i4yjvRqpJ7mSThT+L3C/8AZH/zuUsUT4U/i9wv/ZH/AM7lLF+iV/5svd/c+Rn+phERYlAiIgCIiAIiIAiIgCIiAIiIAodxa/oU/wDWGP8A32BTFazUmnquqcPLjbjpWQSPjk5oH8j2uY9r2kH3bOaCr02lNN7rl4PRkm+BCHxtlYWvaHtPeHDcFePmFX8mh/8AoC3X0U0fGM38b/yT6KaPjGb+N/5L57yh/wBVdGdzX6WTNM2lXY4ObXia4HcEMG4Xutl9FNHxjN/G/wDJPopo+MZv43/knlH91dGPMKWTNaii/HXTDtAcGtaakxOay7MnisVYt1nS2udgkYwlu7duo3Hcttw10NHqjhzpXM3szmHXcjiqtucst8rTJJC17tht0G5PRPJ/7q6MnzClkzZLwNGs4kmvESepJYFufopo+MZv43/kn0U0fGM38b/yTyj+6ujI8wpZM0vo+qP+rQ/dhe7WhjQ1oDWgbADuC2f0U0fGM38b/wAk+imj4xm/jf8AknlD/qroxr9LJmVwp/F7hf8Asj/53KWLX4HCVtOYerjanP5tWZyMMjuZxG+/U+/vWwX0VWSlUlJbm2cKTu2wiIsyoREQBERAEREAREQBERAEREAREQBERAEREBVXlVf1bOJf6guf6RUh4J/ia0F+oKH7vGo95VX9WziX+oLn+kVIeCf4mtBfqCh+7xoCaIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiICqvKq/q2cS/1Bc/0ipDwT/E1oL9QUP3eNVt5W3FTRWP4LcStMWtYYGtqR2EsRDDTZOBlwvfDzMb2Jdz7uDmkDbqCNu9b3gBxc0Lm+Hug9PY7Wmnr+f8AQlOH0VVysElrnZWaXt7Jry7doY4kbbjlO/cUBcCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiwczm6Wn6Lrl+cQQAho2aXue49zWtaC5zj7mtBJ+xQu1xEzVx5OMwUNev/ZlyloskP/8AHG123+bgfzfZrGnKSvuXrsNYUp1P0q5YSKtTrLVu/Svhdv0zJ65au/J8L+2ZWwlzLqb6pWyOEP5UXgnJgNd43iZRjc6hnWso5B2+4jtxR7Rn8wfEwAAe+FxPet5/Jb8CzayOX4p5SsRFV5sbhi9vfI4fh5Rv9jSIwR0PPIO8LqLi7pvI8a9AZPSGo6uL9GXw3mlqvkbNC9rg5r43ODgHAj3gjbcEEEhZnDehluFWhcLpPA0sRFisVXEEPaulL39SXPeQAC5zi5ziABu49B3JhLmXUapWyLvRVr65au/J8L+2ZBrLVu/Wvhdv0zJhLmXUjVK2RZSKv6vEbK03A5XBskg/tT4uczOb198bmtJHv9kuP2D7Zri8rUzdGO5RsMs1pN+WRh94OxB+wgggg9QQQeoVJU5RV+HptMZ0p0/1KxloiLMyCIiAIiIAiIgCIiAIiIAiIgCIiAL45wY0ucQ1oG5J7gvqjfEqxLU4d6omhcWSx4uy5rh3tIid1/y71pThiTUM3YlK7sQmPIv1Xd9OT7mJ+4oRE7tigJ9l4H9942cT3gEN93XMX4hiZBDHHGA2NjQ1oHcAO5VBxggyOZ4t8MMDXzuVw2MyDcq69Hi7b67rDY4YnNaXNO46n6w9oAnYgndZ1J6cm+HD2Pp0lSgkkXEsOTM0IsxDin3IG5OaF9mOoXjtXRNc1rnhvfygvaN/tcFzblK+tta8SNX6bwlvIeYaTjp0KbRq2fGzt56zZPOJuWvM6y5xJ9qRxHsfVJJJzKmicnk+POghq/K3fWNmjp5LsuHyk9eCWxFZrA8oYWDkduS5mwa47bjoNsiuK3uXzcdJLV6Z1NjdY4WDLYiwbePnc9scxjfHuWPcx3suAI2c1w6j3fYqg4Zabt651RxIu5fU+onwUdTXMdSpVstPBDXiNaIHYMcCf+l3aD0YWgtAJJNa4TWGstQaJ4RaWpZK9bnzrMtPbt2M5LRtXPNpiGReeCOWQbNcXHYBxDAOYAHdYh1bbbfLpHXqLnC3T4h8M9M1dS6hy0zsdp/PR2X1GZeW+52JmY2KwyxI6OLtjE5xmY57SWhpG523WhzGsNZZ2tpubH5C4yHiLn7c1Vj8q+gIMdBAfNYIphHJ2BmbGJSWM5nFxG4J3CxLrW3o6tWPXyrtI5VmUjPLQnkbHkYt9mFp2aJ9v7zOm597Nwd+Vm1d8F9O62007PV9U2Gy4t8kT8XDLl5MpYg9kiZr7EkMbnN3DC3mBI3cN9tlPNRRRz6fycUoBifVla8OG42LDvuFvRlozSe57H7EyiqsGpIt5FrdNWJrenMVPY3M8tSJ8nMevMWAn/itkklotrI+YCIiqAiIgCIiAIiIAiIgCIiAIiIAvC/SiyVGxUnbzwWI3RSN+1rhsR+wr3RSnZ3QKiw7Z6cLsZdJ8/x583mLj1kA6Nl/Q9oDh+kjvBXjkNK4vK6gxGbtVe1ymJbM2lP2jx2QlaGyeyDyu3DQPaB226bKw9UaQi1ByWYJhQysLeWK4I+f2e/kkbuOdm/XbcEHqCCoVap6jxTyy1p+a60f9Zxc0ckbv917mPB/NsQPtPv1lTxXpQtt4bul+B3qPioTilN2ZENZcFtGa/y7Mpm8MLGRbD5u6zBZmrPli337OQxPb2je/wBl+46rJ1Vwn0rrVuJGWxfauxILaUtezLWkgaQA5ofE9ruUhrd2k7HYbhb438gCR6uZo/oqj+JfPSGQ/wAN5v4UfxKur1cjfTo5o8sBpXF6Ykyr8ZV82dlLr8jcPaPf2th7Wtc/2idtwxo2Gw6d3eo7d4J6KyGkMfpifBsfhsfM6xTiFiUS1pC5zi+OYP7Rrt3u6hw79u7otvqDWA0rg72Yy+Gy9DGUYXWLNmWr7EUbRu5x69wC98XqOXN4ypkaOCzFmlbhZYgnZV9mSN7Q5rh7XcQQU1erkTiUXsujExHDrTuC0la0zTxrW4S0yVlitLLJKZhKCJOd73Fzi4E7kndfdScOdNau0xBp7LYiC3h6/Z+b193M7AxjZjo3NIcxzR0DmkEfatn6QyH+G838KP4l9F/IEgerea+FH8SjV6uQxKVrXRgaN0LhOH+MloYKo6pXmmNiXtLEk8kkhAaXOkkc5zjs1o3JPQBZ2WpyZ0xYKuXCfJbxvcx2zoq/QTS/m2a7YH+89g6b7rLq47UeXcGVcK7GtI62cpIwNb190bHOc47ddjyj849040zpWvpuKV/aOuX7GxsXJQA9+2+zQB9Vjdzs0d25J3c5zjeEMFqcntW5b+vz8nlreKhCOjT3m5YxsbGsY0Na0bBoGwA+xfpEWJwwiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgKq8qr+rZxL/UFz/SKkPBP8TWgv1BQ/d41HvKq/q2cS/1Bc/0ipDwT/E1oL9QUP3eNATRERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAVV5VX9WziX+oLn+kVIeCf4mtBfqCh+7xqPeVV/Vs4l/qC5/pFSHgn+JrQX6gofu8aAmiIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIsO5mKGPdy2r1as77JpmsP8AxKlJvYgZiLVetWE8YofFM+aetWE8YofFM+avhz5WTZm1Rar1qwnjFD4pnzT1qwnjFD4pnzTDnysWZtUWq9asJ4xQ+KZ809asJ4xQ+KZ80w58rFmcC+Xr5VvEPQWsdW8LTg8G3SWZxbGVb09eY25a80IbK9rxMGbiUTNHsf2RuD3nc+Qt5V3EzjFrHDaGmwen49I4DFBt2/XrWG2GRRRdlCOd0zmc7n9nuOXq0P2A26TT+UN4TY/jFwqr5zT81bI6r05L2sNapI2Se1WeQJYmtad3OB5XgdT7LgBu5bzyDuFuI4H8Fq82VuU6mqdQubfyMc8zGSwt2IhgcCQQWtJJaRuHSPB7kw58rFmdTItV61YTxih8Uz5p61YTxih8Uz5phz5WLM2qLVetWE8YofFM+aetWE8YofFM+aYc+VizNqi1XrVhPGKHxTPmnrVhPGKHxTPmmHPlYszaosSnlaWRJFW5XskdT2MrX/8A2Ky1RprYyAiIoAREQBERAEREAXnPPHVgkmmkZDDG0vfJI4Na1oG5JJ7gB716KD8UbRlr4jDgjs8ha3sNP9qGNpeW/wCbhGCO4guB+w6U46crPd+y2svCLnJRXE0+Vzt7WJ52zWMZhXbGKvETDPYb/eld9ZgPujGx2+udyWN10GlsPVbtHiqbftPYNJPXfqSNz1+1bNVRqjyjcHprJX67MJncrUoZCLFWsrSrR+ZQ23uY0QukfI3YgyMBdtygkAuBVZVpvZF2WSPo4wp0Y2LJ9AYzw6p9w35J6Axnh1T7hvyUOxXGSlndb5bTGNwOcu2sTdbSv3I4IhVrl0bZGvL3SAlpDttmgu3B3aAQT56Z41UM/qqpp+5p7UWmr16OWWg7OUWwMuCMAvEZD3EOAIdyvDTt7lTEnzM00ok19AYzw6p9w35J6Axnh1T7hvyVIa88op9wYePSNHNNpT6noYp2o/MI3Y6yw2mxTxxvcS4gjnbzhgG4PK7fZS7M+UHgcLcyLn4rO2cFjbDqt/UVakH4+rI13LIHO5g8hjujnNY5rSDuehTEnzMriQLB9AYzw6p9w35J6Axnh1T7hvyWcCCAQdwfeFWXEXjXT0peyuDxuKzWezVKgbdo4am2dmOa5rjG+YucACeUkMHM4gb8uyYk+Zl5OMVdk/8AQGM8OqfcN+SegMZ4dU+4b8lU2kuPHYaD0Ey/QzGrtX5jT9bL2qmEqRulEbo2887wXRsY0vJAAI3O4aOi3ljygdNPw+nrmJrZTUdvPMkko4rFVee2WxHlmL2vc1sYY72Xc7h7XQbpiT5mUU4NXJ76Axnh1T7hvyT0BjPDqn3Dfko1iOKVHK6gwWElxOWxeSy9CzkI4MhXbE6FkEjI3tkHOSHEyNI23BHXfu30drygsHDLBBDic3ft2M1ewEFWpXjfJLZq83abfhAAw8h2c4gbdXco3KYk82W0olg+gMZ4dU+4b8k9AYzw6p9w35KrqvlNYKeCazPpzU1CjTyDcXk7lqjG2LGWS9rAyYiUk9XsPNGHtAe3cjdbnVHHLFabyuVqRYTPZyDD7elb+IpCavQPIHlsji8FzgxzXFsYeQCNwmJPNkacN9ycegMZ4dU+4b8k9AYzw6p9w35Kvct5QWFo5LIU8dhc7qM0cfXys02HqxyRCpMxz2ShzpG79GE8v1j/AGWu2O2rznHa5FxF0PjcDgL+oNPahw82Vbaoxw9pK3eLs3MMkzNmtbJu8Eb+2zl39oBiT5n1Ic4Is+xpTDWiHSYupzt6tkbC1r2n7WuA3B/OCtviNSXdJSAXLNjJYQnZxnPaTUx/eDvrPjHv5t3DvBIGygWnuL9DVOsr+BxuEzdmCjcmx1jNCq3zBlmJvNJGX8/OCPq7lnKSQAeqnZAcCCNwe8FaRrTWybusn82FZ06daNmWYx7ZGNexwcxw3DmncEfav0obwtuF2Ct4wncYi26kzv6RcrJIm9f7scrG/wC6pkpnHQk189D5ycXCTi+AREWZQIiIAiIgCgXE2uYslpq+Qezjsy1XkD6vaRktJ/NzRtb+lwU9WBncLW1FibOOthxgmaAXMOzmOBBa9p9zmuAcD7iAtaclGV3u2rqrGlOeHNSyIAuOdfXotIaw1pXzIuycOH6hhy2Qx+LyONmc6cGGQ8zXSiw0Olax7oWs36eydnLrt0tjD3WYzMckN/ujmaC2G4P70RPv+1m5c0/a3lc7V2eH2lrmeZnLGmsPPmmEObkpKETrLSO4iQt5t/8ANYyg6btI+hklWinBkR0Lo7PYbIcU5nRtxs2bzD7eLtOcx4LTTgjbIQCSAHsd0cAendsQqo4f8INS4rWnDnMWtCejruHNiHUGcmy0Nm3kpZqr4zYDuYudGHnm2cQ4c4DWdCupEWZd0k7en5ucv0NAcRMZoHSHDx+j2WaenM/j5xqCDJV2w2acFxsvaCJzhIH8n1mkd4OxJIC9NO8CjpfOZHEZXhFgtbVLWYmtQaosS1WuFWaYyETtkBlMkYc4DlBDtmjcLpxEuVwY5/Yhk/GXh5jZpKk2udM1Zq7jE+CTL12Ojc07FpaX7ggjbb3KubmM1Vh9Xa5y+lcBX1tp3XFWvZrZCllIIhWlbVEGzi8+3G4Na4OZvtuRsVeZo1nEk14iT1JLAvVrQxoa0BrR0AA2AQu4uW9nO2idC634Uu0lm6WljqKydHUcBk8VDkIIZ6lmvu5rg97uRzD2jmnlcSC0Ec26xtHcKNccJ8np3VtTD19UZWWlkKuZw9W4yB0BtXPOw6u+XlY4MceRwJbv3j83SaJcpgpcSm85S1nLrTRmvI9HPntVqF/G5DBQZKuZ67ZpInRSNke5sb/+hHMA7pz9ObZRfQ3DDWdbU+lMplsJHRdX1lnczdZFcilZBBagnbE4HcF4LpGjoObruWt67dGIly2Em7t/Nn4OfdQ8K9UXuGHFrEQYvnyGd1Q7I46HziIdvX56h59y7ZvSKTo4g+z3dRvrMnwWsYLXmsLVjhTheJNTO5F2Tp5S3PWilqF7Gh8E3bDm5GuaXNMYf0cem66VRLkOjF/Pf8lUYHh7fwnEDiFZrYuKngr+CxmOxbYXRtYTBHZa6NrAd2BokjA3AHXp3HaHYPQetdDYzg7lqumjm7+ndPzYbKYmK9BFLE+SODZ7Xvd2bg10JB2d7wRuuiEQnDXz3uUfV0jqX6cqubxWk5tJ491ud2ayMeWifUzFfsXNiLqrTuJ+fszzloIAcC526vBFi1+31HdkxuIkY+VjuS1bB5mVB799u+TY9GfoJ2C0hB1HZf8AgbjSi5Nkh4WV3GtnrxBEdzJvMW423bHFHCf/ABxPU3WJiMVWweLq4+mzs61aNsUbSdzsBt1PvP2n3lZa2qSU5XW79lsR81UlpycswiIsigREQBERAEREBi5LF08zTfVv1YblZ+xdFOwPaSO47H3j7VFpOFGG7q1nKUmdwjgyEpaP0Bxdt/kpmi1jVnBWi9heM5R/S7EI+ifH+L5v40/JPonx/i+b+NPyU3RWx6mZfGqczIR9E+P8Xzfxp+Sg2Qq4C/r7I8PMVm9TRarixTsh54+OWWlU5vZiMj9mtJcdyGhw3EbhuD0Wfk8lkPKEwOudKY06p4dQ4/INxvrCIGQyXmtd+H825jzBpAcztBt9ZpBPtNVsY+n6Po1qxmlsuhiZEZ7DuaWTlG3M87Ddx7yftJTHqZjGqczK60nwYnxmnaNbPasy+bzDGfzm9FKK7JX77nljAPKB3Abk9OpW3+ifH+L5v40/JTdEx6mYxqnMyEfRPj/F838afkn0T4/xfN/Gn5KbomPUzGNU5mQj6J8f4vm/jT8lotbcFb2W05Zr6a1jlcBmjs6C7YcLUQIPVr4zy7gjp0II7/zG1ETHqZjGqczKVxcOmLfEq9w/ky+qG6ko4+PIPfKJY61mJxAc+KQtLXBri0Hr0LtgSWu2mf0T4/xfN/Gn5KSamwbdTaeyeKdctY7z6rJVNyhII7EAe0tL43kHlcN9wduhAVd4nUWR4Ov4eaEy8epdcyZJslJ2rjWY9rJWDmYLPKd27sDvbO/SPclx5iGPUzGNU5mSD6J8f4vm/jT8k+ifH+L5v40/JTdEx6mYxqnMyGR8J8IXA2pclkG//l2chLyH9LWuAd+gghSyjQrYypHVp14qtaIcrIYWBjGj7AB0C90VJVJzVpMpKcpfqdwiIsygREQBERAEREAREQBERAYWSycePikDWG1cEEk8VGFzRNOGAbhgcQCd3NG5IALhuRuqtwumrvHnT+itT62w2d0NkcTkH5OLTkWULGyEOPm7rIj2LiAGP5Dylri4EbEg/jhXiMJxO1bPxVyOj72n9XVO3wEAycsjiyvFK4drGxwaG9oHfWA7um/U73EgCIiAIiIAiIgCIiAIiICmr+BveTlpDK3dE4DUPEBuQzgvWcRLlO2lpwyn8Mawk6uAdu7k3JLpCS7bci3q92GzJJEyRhni5e1hDwXxEjcBwBOx2Xuqc1th8Jwk4k47XmG0fey+pNZZShprKT46WQMggeXfzyaMBzSIxG0FxA6bDmHXcC40REAREQBERAEREAREQBERAFHdXcR9J8P/ADT1o1RhdN+d8/m/pfIQ1e25OXn5O0cObl5m77d3MPtCkS5A/lLuDVjiHwap6qx7Xy5DSEsll8TT9apKGCcgfa0xxP39zWvQFkcKPKD05U05aZr3i5w/vZk5Cw6GShn6YYKpf+BadnN9oN7+n+ZV7r+NnkIcCzxp45Y+a9XM2nNOluTyBI3Y9zXfgYT/APO8bkHvax6/smgCIiAIiIAiIgCIiAIiIAoXxMq62tDSvqVcpUzHnqkma89APa4sc3nEce7XfhD7G22x6HqFNFV3HjFaXyg4e+tGobWnvN9YY6xi/Nt/59fb2nY1X7Nd7D93b931R1CAtFERAEREAREQBRDN8RIadqaniqUmZuQuLJHMeIq8bgdi10pB9oHcENDiNjuAV58QM5O2SrgqUz689xjpbM8L+SSGuOh5SOrXPcQ0EdQOcgggFaCtWhpV44K8TIII2hjIomhrWNHcAB0AWv8ADTScldvh+To+G8Kqq057jKfrXVj3EspYaJu/RrpZXn9vK3/7L8+uWrvyfC/tmXmijHfKuh0dUo5Hp65au/J8L+2ZPXLV35Phf2zLzRMd8q6DVKOR6euWrvyfC/tmXhf1JqXKUbNK5RwNmpZjdDNDKJXMkY4EOaR7wQSNlomcQNPOZlXuysMEeLvNxtt9gOibHZdycsYLwA4ntYwOXcEuAHXopAmO+VdCF4Wi9yK14C8KXeTnp7I4jStejLFkLZtz2MjM+SZx2Aazma1vsNAOw239okkkqzvXLV35Phf2zLzRMd8q6E6pRyPT1y1d+T4X9syeuWrvyfC/tmXmiY75V0GqUcj09ctXfk+F/bMnrlq78nwv7Zl5omO+VdBqlHIzqvEbJ0yDl8I10G27p8XOZnN6++JzWuI9/slx/MptjcnVzFGK5SnZZrSglsjD0Ox2I/MQQQQeoIIPUKu14UsmdIZhmQjPJj7crIshFvszrs1s+399p5Q4+9nfvyM2tFxrPRSs+FuPp+PtxPJX8HFR0qZaqIixOQEREAVbca8rRxQ0J57ot+s/OdV0K8HJFz+ipXc/LfPsu2EWx69Nuf6wVkqF8TKutrQ0r6lXKVMx56pJmvPQD2uLHN5xHHu134Q+xttseh6hATRERAEREAREQFXZp7peImcL++OrUjZ/8n4R3/mc79i+WSW15SDsQ07EfoWx1/QdjM/TzYB80sxNoWnb7CNwcTC8/mJe9m/2uYP0YLmh7S1w3BGxC0r7WpLc0uysz6LwslKkrcDkrEZrUmk/Jb0/rSPU2Zymp87FSoS38plJDBUjnssYZA1wexjmt9ntixzt3bnm7lJXcNuK9bDaprwZa5jKk2GkfUYdU2MrbbkY3tkhdHK+CJzGP5XsezmLSHDp3g3dT4dabpaFj0azEwyaZjreZtx1gumZ2X90l5JP6Sd/zrG0Nwq0xw4fafp/HyVJLLGRyyT257Lyxm/IwOle4ho5nbNGwG5XnuSqT2XfA5zs+UxqKaG7rynDK7SOaxvoPC1Q1xIzLYRKx2x7g+WWeEn3mu38ymei9HZe5xbyGmsvrHUdmlgNOYd74octPH5zbJnD5nvDuY8xjO432duOYHlbtckfD7TkOCo4ZmIrtxdG2y9WrAHlinbN2zZG9dwRJu7/ADI7jssyppXF0dS5DUEFXky+QghrWbHaPPaRxF5jbyk8o2Mj+oAJ3677BCVSldOTucr61xk2T4acQMffzWbt18VxIpV6sljK2HSRQufRHJzl+/K0zPc0b7Nds4bEAqb8TPWRnEPSvDfTtrISY1uGsZN7p9ST0LV17ZmsDTc7OaV/IHFxYNiQ4Eu2bsbct8LdLX8PqPFWcRHYx+obTruTglke4WJi1jefq7dh2ij25Nti0EbHqtdkeB2i8vp/GYa5iZbFTGSPlpSvv2POq7nkl5ZY7TtRvv19vr0+wJch0pbbfNp94OYjV2C0rYpaxtRW7kd2XzN7bpuSNqHlMbJZjHGZHtPOOblBIDd9zutDxwyuTn1Bw/0nTzFrT1HUmTmgvZOjJ2VgRxV3ythjk/sOkc0N5h16HbvUgOjM3pXGUMToO3hMFh6zH718nj7F15e55cXB4sxnqXEnm5iSSd152+HVrXeEs4niMcLqOkZYp6rMZRnougkbze3zmxI4O6jZzC0j2u/fpBo09HRRSGfyWb0blNdYirqjPWaeL1JpivWlu5GSWWKGeSJ0zDITuWu5yDvvuNgSVuuPfEzUGgdZ63s4fISt8y0ZRmgrvlJgrzS5KSF1jkO7Q5rHAlxaejBvuBspnpLyfMRhMlxCo3aVa5pPUjqRhoy2Zp5h2UQa8yPeebm7QczXB5I2B3BAUmwnBPReAtZKzWwolnyVL0fekvWZrbrUG5PJKZXu5/rEbu3O2w32AAkyw5tbNnxlV4zSvErSMWWyU2QmhwPoO6bTbWqpsvM+YRF0M8BfWi7JwcDvyu5SHDZoICx62MzuluAWleIsWptRZnM06eMzuUjtZOaSK1VEYNmLsubk27KV7t+XdzomOcS7qra0zwP0Vo+G/FisQ+Bl2o6hMJLtibau7viZ2kjuzb+Zm3cFk6o0pkavDlumNGw4yq1lRmNhZlnSyQwVgzs/du57mt22DiN/e5C2G0tvz7Gi4OZ2zrnM601a3Iz2sBcyIx+Gh7Zzq/m9VvZvmjbvy/hJjNu4DqGM6nYKd6pjZLpnLMk25DUlDtxuNuQrC4faLp8OtEYTTOPJdUxdSOs2QjYyFo9p5H2uO7j+clbHJ0n56WDBQbmTIHlmLDsYq427V5+zoeUf/E9q2oK9WPv9jS+hTvItDCTy2cNQmm3E0leN79/7xaCf+KzV8a0NaAAAB0AHuX1Q3dtny4REUAKruPGK0vlBw99aNQ2tPeb6wx1jF+bb/wA+vt7Tsar9mu9h+7t+76o6hWiq2415WjihoTz3Rb9Z+c6roV4OSLn9FSu5+W+fZdsItj16bc/1ggLJREQBERAEREB5WqsF6rNWswx2K0zDHLDK0OY9pGxa4HoQQdiCq+yWjMxgnOOJAzOPA9irYm5bUfX6okcdpBt0HOQ7p1c4ndWMi0jPRVmrrI1p1Z0neLKjkt5OFxbJpnMtcD/ZhY8fta8hfn0hkP8ADeb+FH8St5FbSpcndns16pkiofSGQ/w3m/hR/EnpDIf4bzfwo/iVvImlS5O416pkiofSGQ/w3m/hR/EnpDIf4bzfwo/iVvImlS5O416pkiiNJ8Q6mucdLfwONymUpxWJKr5oKu7Wyxnlezqe8Hotz6QyH+G838KP4lFfIZ/FHnP9qst+8FdEJpUuTuNeqZIqH0hkP8N5v4UfxJ6QyH+G838KP4lbyJpUuTuNeqZIqH0hkP8ADeb+FH8S++kMgf8A8N5r4UfxK3UTSpcnca9UyRVlXGaky5DK2GOLYR1s5SRmzevujjc5zunuJb+lTnTOlq2moZnNe61esEGzcl+vJtvytH91jdzytHQbk9XOc47pFDns0YqyPNV8ROrsk9gREWR5wiIgChfEyrra0NK+pVylTMeeqSZrz0A9rixzecRx7td+EPsbbbHoeoU0VXceMVpfKDh760ahtae831hjrGL823/n19vadjVfs13sP3dv3fVHUIC0UREAREQBERAEREAREQBERAEREBzv5DP4o85/tVlv3grohc7+Qz+KPOf7VZb94K6IQBERAEREAREQBERAEREAVbca8rRxQ0J57ot+s/OdV0K8HJFz+ipXc/LfPsu2EWx69Nuf6wVkqF8TKutrQ0r6lXKVMx56pJmvPQD2uLHN5xHHu134Q+xttseh6hATRERAEREAREQBERAEREAREQBEXL/lx+UtrjyasVpHJaUw+JyNDJzWa96xloJpGwyNbG6FrTHKzYuHbHrvvydNtjuBuPIZ/FHnP9qst+8FdEL+Tnkv+WHxVoZrGcOtH4PTNybP5qWwJL1SzI6N88nPK48k7fwbBzO7tw1p3JX9Y0AREQBERAEREAREQBERAFV3HjFaXyg4e+tGobWnvN9YY6xi/Nt/59fb2nY1X7Nd7D93b931R1CtFVtxrytHFDQnnui36z851XQrwckXP6Kldz8t8+y7YRbHr025/rBAWSiIgCIiAIiiOrtXT1LfonE8hyBaHz2ZBzR1GHu6f2pHf2W9wG7ndOVr7xi5MvCEqktGO8lU9iKrGZJpWQxjvdI4NA/zK1/rVhR/74ofFM+arF+mqNubt8jGcvbI2NnI7TPPXfoCOVo/M0AfmXr6Axg/921PuG/JWvRXFs6S8A7bZFketWE8YofFM+aetWE8YofFM+arj0BjPDqn3DfknoDGeHVPuG/JNKj69i2ofUWP61YTxih8Uz5p61YTxih8Uz5quPQGM8OqfcN+SegMZ4dU+4b8k0qPr2GofUWP61YTxih8Uz5qt/KJ0Tprjrwh1BpGfL4xtqzD2tCeSyzaC0z2on777gc3snb+y5w96++gMZ4dU+4b8k9AYzw6p9w35JpUfXsNQ+o5Q/k3+BTdC5TP671pE3C5iIuxmMp5IiGVjdgZpw1x32d7LGuHeO07wV3t61YTxih8Uz5quPQGM8OqfcN+SegMZ4dU+4b8k0qPr2GofUWP61YTxih8Uz5p61YTxih8Uz5quPQGM8OqfcN+SegMZ4dU+4b8k0qPr2GofUWP61YTxih8Uz5p61YTxih8Uz5quPQGM8OqfcN+SegMZ4dU+4b8k0qPr2GofUWP61YTxih8Uz5rJp5ihkXctW7Wsu+yGVrz/wACqv8AQGM8OqfcN+S8Z9K4ayPwmLpk+5whaHD9BA3H+SXo+vYjUPqLiRVhitQ3tHvBmnsZPCb/AIWOZxknqt/vxu+s9o7yxxLtty0nYMdZkM0diJksT2yRPaHMew7tcD1BB94USjZaSd0c+rRlRdpH7REWZiFC+JlXW1oaV9SrlKmY89UkzXnoB7XFjm84jj3a78IfY222PQ9Qpoqu48YrS+UHD31o1Da095vrDHWMX5tv/Pr7e07Gq/ZrvYfu7fu+qOoQFooiIAiIgPOxOyrXlmkO0cbS9x/MBuVUOmnyW8VHkZ9jbyR89ncN+rpACB19zW8rR+ZoVs5GoMhj7VUnYTxOjJ+zcEf/AOqptKyOfpzGh7XMljgbDKxw2LXsHK8H9DmkLX/4u2a/c6vgEtKT4mk4g8TaHD2XDVZsdkszlMxO+CjjcTC2SeYsYZHkc7mtAa0Eklw/NusDN8YqWIOJrQ6f1Blc1kKYv+haNNpt1oCduecPe1sftezsXbkggA7HbF44afl1Jg6FWPRc+sOScytfSycePt4+QNPJPDK9zdnbnbo4Hb3EdFUc/BXVcWV0xqXV2j8bxWyD9PxYnK0rM1cT1rEcr3smjfNsx4LZCx+xBJHMN99l5ToTlNNpFwv41ULWl8dm8Lp3UWpI7ks0DqmLog2KskTuWVk7ZHsEbmuBbsTuSDtusKz5Q+nGYjSN+nRzGVGqJZ69CtTqAziaEO7SKRjnN5HBzHMPuBBJIaC5QvVHC/Ivh0V5tw6q2NLV4bT8jojGX4IIIrkhYY5n83JHO1oEgI9xduA5YnC/hJqvTVrhjDewMNCDTuazs9rza3FJDHBZjmMDo+ocWkyhm3KHDlJLQOqFdKpe3zh/ssXKccqePyMGLr6X1Jls55nHeu4rG1IpZsdHJvyCw7tRG152dsxr3OOx2BCi13jrf03xR1jTuYfP5rC0sTjchBRxmOY+SiHtndO+UktO/sx+wXOd7J5W9HLYXcRrHh5xW1bqHBaWGsMVqiOpI5kGQhqzUp4Iuy5XCUgOjc0NO7SSDzeyd1nYLSWfPEPiFm7uMbUgzeDxlesG2GSB08bLPaxggg+yZWDmIAO+49+0ktzb/wA5e5Y+CzdLUuEx+Xxs4s4+/XjtV5mggPje0Oa7r9oIWnu8QsXjdbjTFxs9W0cVJl2W5Q0VnwxyBkjQ7m352czXEbbbOB379oTww1jpvhXw00lpTV2p8Dp/UeLxNWvcx17LV2SwvETehHP+wjoR1C1/GHRVfj5idNX9G5+lcZVyElG9fxtuOVhx1iMxXYg5pI5iwsIG++7QoLub0brfkSatx30/lNP6RyuIq5DNjVLJn46pSjj7dwhifJLzB72tby8nIfa+u5o7juIFprjVckfwxyN7UM/q/laGdu5SzlKVeq9zaz2dmXtjLwzswXD2He0BudydhIdAcFJ9G8Zs/nQ5jdMMqn0FSZty1JbLw+6Gt/s7vgjcPzSkDuKrmp5NWS1fp/hZgNWYqeLFYqtnGZZte8xpifPK11fcsfu/cjnHLuAWjm+xTsMpOr/n/a/3/gs/N8XZLsfDa1VqZ/AVNS5ZkTXz0K7xJHySObDOHS80IlaO0a5oc4BmxDSdl+WeUnggye5YwGoqeBrZOXE2M9NUi8ygmZMYSXlspeGF4A5+TlG43IO4GrsaU13qDT/DWtnMeLOX03qqKW/ebYi5bVSGCxG24Bz7+3zxks+sHF3s7DdVxgcRrDiLwv1XoLFaZDMRl9TZSGxqWe/CIa8HpB7pSId+0dIAHNaANtyDze5A5zX/AJ7F4ao45YrTeVytSLCZ7OQYfb0rfxFITV6B5A8tkcXgucGOa4tjDyARuFgaz4+4nGMt08BQzGp7zMWMk+fB1GzxUoZIy6GSUuc0e0BzBoDnEDfl2VfZLgrPgNd6vsT8KcNxJp53IHJ0spbnrRy1C9jQ+CbtgXcjXNLmlgf0cem63lvRmrOHerNbx6X0fDndO6np1m1hUvQVPRkkNUVhE5khG8XKxhaWb8vUbITpVOP2Mrh5xUy2ducIqWTt5EW87pl2UtSto1zUyMvYxOfvIHh8T4y7m5WR8p7UDfp0lumeNVDP6qqafuae1Fpq9ejlloOzlFsDLgjALxGQ9xDgCHcrw07e5QbT/DDVtCtwZayq2hb09pW9jb1h00bxStyVqrIgQHHn9uN/VnMPZ7+o3jPDvhFqbDa34b5qzoMY+9hnWItQZyfLQ2beSllrPjNgHmLnR9oebZxDhzgBmwKEKVRWVsv2OoluOFtosxuSxBI5MXbMMAG/swvY2Rjevubzlo/M0LTracMK5fNqO/sRHPdbDGSNuYRRNa4j/f5x/ur0Uv0TT3WXW6/a5l41LD25k6REWZwgq2415WjihoTz3Rb9Z+c6roV4OSLn9FSu5+W+fZdsItj16bc/1grJUL4mVdbWhpX1KuUqZjz1STNeegHtcWObziOPdrvwh9jbbY9D1CAmiIiAIiIAq61VgZdOZGzlakLpsVbf2tyOIbvrS7AGUN98btva26td7WxDnFtiorxlo3T2pmtKpKlLSiVbWsw3YGT15WTwyDmZJE4Oa4faCOhXopPk+G+BydmSyK0tGzId3zY+xJXLzvuS4MIDjv7yCtf9E+P8XzXxp+Sth0nulb3X+zrLx0LbUzUItv8ARPj/ABfN/Gn5J9E+P8Xzfxp+SYVPn7E69TyZqEW3+ifH+L5v40/JPonx/i+b+NPyTCp8/Ya9TyZo5KkEri58Mb3H3uYCV+44mQt5Y2NY3v2aNgtz9E+P8Xzfxp+SfRPj/F838afkmFT5+w16nkzUIq48l+lb4q8Psnl8/m8pJcgzl+gwwWOzb2UUpawbAd+3vVvfRPj/ABfN/Gn5JhU+fsNep5M1CxMbiKOGgfDj6VejDJK+d8daJsbXSPcXPeQ0DdznEknvJJJUi+ifH+L5v40/JPonx/i+b+NPyTCp8/Ya9TyZqEW3+ifH+L5v40/JPonx/i+b+NPyTCp8/Ya9TyZqEW3+ifH+L5v40/JekXCjC7nzmxlL7D3x2MhLyH9LWkA/5ph0+M+w16nkyMxSWM5edjMMWS2w7lnsn2oqQ97pPtdt9WPvcdt9m8zhZeEw9bT+Jq46m0tr12BjeY7ud9rnH3uJ3JPvJJXrj8bUxNRlWlWiqVmfVihYGtH29AslRKStoQ3fc5levKs9u4IiLM8wVXceMVpfKDh760ahtae831hjrGL823/n19vadjVfs13sP3dv3fVHUK0VW3GvK0cUNCee6LfrPznVdCvByRc/oqV3Py3z7LthFsevTbn+sEBZKIiAIiIAiIgCIiAIiIAiIgCIiA528hf8UOb/ANqct+8FdErnbyF/xQ5v/anLfvBXRKAIiIAiIgCIiAIiIAiIgChfEyrra0NK+pVylTMeeqSZrz0A9rixzecRx7td+EPsbbbHoeoU0VXceMVpfKDh760ahtae831hjrGL823/AJ9fb2nY1X7Nd7D93b931R1CAtFERAEREAREQBERAEREAREQBERAcl6R1Hl/IvzGRwGtqIu8MMxlp71DWePicRj5p5OYw3YxuWDcgCQdO7v3PJ1Zj8jVy9Cvdo2YblKxG2WGxXkD45WEbtc1w6EEdQQvzk8ZTzWOs0MhVhvUbMbop61iMSRysI2LXNPQgj3Fc0X+G+tPJUvz5vhfXs6u4bSPdNkdAySl9mhud3S457tyfeTCd9+u25ILQOoUUO4WcWtL8ZtKw5/SuSZfpuPJNE4cs9WT3xTRnqx4+w9/eCQQTMUAREQBERAEREARFHdW8RNM6Dnw8GoM3TxM+Ytso0IrMmz7MziAGsb3nq5u57huNyN0Bs87ncdpjD28rl71fG4ypGZbFu1II4omDvLnHoAoJlaeW11xLx1TJaRwmT4fYyGHM0M9ZtCad2SBPYvgiAIb2bef2j39o0td0IOPW0FneJMer8VxXxunstpSfJxvwmKqxveW14nczJJ3kjdziGktA2HtAkh2ws2rVhpVoq9eJkFeFgjjiiaGsY0DYNAHQADpsEB6oiIAiIgCIiAIiIAiIgCIiAIiIAiIgOVvKq0dheCVTIcbNK6jg4fawgIZPCWF1PUjzuW1Zq7fryP2cQ9o3aA5ziA3nZxfwe8s3UDfK2g4h6syMnonMyHF3arpN4aOPe/8FGwdAGQuLX7gbu2eTu57if6OeUBpPRHFrAN0hqjD2dSmCw26ypSsPgNaYRua175WuaGnlkds125IdvynvHIWd/k4tPZGzJNisnewjH9WwTXm2xGfsB7Bh2/ST+lbKk7XbS938ZvChUqK8Uf0ZBDgCCCD1BC+qk+Hc+tNC6GwenZ7uLzT8XVZUbfstlbLMxg5WFwB+sGhoJ9+2/vUi9ctXfk+F/bMpwlzLqa6pWyLKRVr65au/J8L+2ZPXLV35Phf2zJhLmXUapWyLKXjcuQY+pPatTMr1oGOllmkdytYxo3LiT3AAE7qu/XLV35Phf2zKIcXaGsOKvDjO6SGQx2CZloPNpbtNsjpGxFw7RgDumz2hzD+ZxTCXMuo1Stkfz01D5b+qsf5U2b4o6fdDZpvjfh6uOyEZdE/GBwLI9ujoyXNbN7JGzyd92lzT/S7gPANbcL9I6k1DqHD8R8sTLkKmoqlFsbIjK5wLIQQHMLATEdwx3sEOa0ghciaZ/k69K4SxFYzE17UoZsXVRkhTjf9o9mBzv8AxD9I7111wD0poXhfgJdKaQws+l+1ndemx12eSZ80pYxj5WSOe8PGzGbhh6dN2tJUOlK11Z+z+MynQqU1eSLXREWJgEREAREQBERAEREAREQBERAEREAUZ13qGfCY6CvRcG5K/L2EDyAeyG275Nj0PK0EgEEF3KD0KkyrjXT3P17iWO+pHjbDmbj3mWIOP7A39q2pJOV3wTfQ3oQU6iizAoUIcbWbDCDy7lznPcXPe4ndz3OPVzidyXHqSSSshFyVgcvntT6vwLXZ/U8+uItYSR57AsnsR46pjoppHN9hu0bYxG2BzXb7yF2x5gSF5W3JtvefRSmoWVjrVFybpM8V+KeLn1hhLvmuTflLDIO31PLFUqshsuj83kx4quYRyM2Jc8vPNzcw3AGXqyxnZNMcbdVw6tz9XIaVzU5xNeHIPbVhbFWry8joh7MjXF7gWv3aB9UAkkxYzxtl7HU6LmTN39dcVeJ+s8diZbFepgG04a1epqeXEOiM1ZsvbvZHWl7bmc4gc55QGbcu+5Ozx+E1Zqfi3hdN6t1Rk60lfRUNrJQaeyMtWGzcFp8ZlDmcpbuOp5Q3foPqjYicW+5HRCw62ZoXMndx0FyCa/SbG6zWjkBfCJASznH9nmDSRv37Ll7Xmrs9DqS7rLS9vULcRjNUVsRZnyGeIpzO86jrzwxUAwtdHu5w7Rzmu3BI3AUt0tpyCh5QnFzOR2cvPbxtfH24qbMnOIZ3PrT7sfFzcr2gjZjXAhv9kBLDFu7JfNv4OgF4XaTb0QaXyQyMdzxTwu5ZInjuc0+4j/juQdwSFy/wmqcU9b43R+uK2TD/AElPBdvzTanlmqy1nP8Aw0LaHmojiIbzNbyv5muaN3O679Tqybi7reXhJVFu2Eu0TqGTUWGc6yGMyFSV1W2xncJGgEOH2BzXMeB7g8BSBV5w5e5urdTxN/6LsKUxH/7h7Zp/z5WM/wCCsNemqkpbOKT6pM+crQUKjigiIsTEIiIAiIgCIiAIiIAiIgCIiAKE8S8c+OKhnImuf6Oc9thrT/1eTlD3f7hax5/M1ymyK8JaErl4TcJKS4FYNcHtDmkOaRuCDuCFzzHwB1ZDr5mTx9jFabotzJyTruKzGTM0sJmMr4TTe81wZAS1x+r7TiGjuXSuV0FdxD3Sad7CSkSXHFTuLBGfshf1DW7/ANgjYb9C0ABaSSxlq7uWfTGXY/39myOUf5FjypwJPbTd136fF6ndVajWSbdiIy8DdDy6sdqT0E2PLPtNvPfFZmjhksNILZnQteI3PBAPMWk7jfdbOzwx0zcw+p8VNjeehqWaSxlYu3lHnMj42RuO4duzdsbBswgdPtJW49IZD/Deb+FH8SekMh/hvN/Cj+JNXq5GunRzRFtWcENFa3yMN/MYQT3Yq4q+cQWpq75IR3RyGJ7e0b/8L9wt/U0ZhqOoo85XpCLKR49uKbM2R+zazXl7Ywzfl6OO++2/u32XjqDWA0rg72Yy+Gy9DGUYXWLNmWr7EUbRu5x69wC98XqOXN4ypkaOCzFmlbhZYgnZV9mSN7Q5rh7XcQQU1erkTiUU73RFcz5PmgNQZDI3b+nxNNfmNmcC3OyPtztvMyNsgayXp/0jAH9/Xqt1kuGGmstrGpqqxjnDP1mMjZchsyxF7Wklokax4bIASducO23W59IZD/Deb+FH8SekMh/hvN/Cj+JNXq5EadHNEUw/A3Q+n9Ttz+OwTaeRZO+yzsrMwgjleCHvZBz9k1xDnAlrQepU4mmjrwvlle2KKNpc97zs1oHUkk9wWNHNmLPs19MZWR57u1bFC0fpL3j/AIbrf4PQlu5Yjt6hdXMcbg+PF1yZIg4dzpXuA5yO8NADQf7xAIYDW2o7L329PiM5+IpUl/C+hmcN8TNVxtzJ2onw2cpP24ik6OihDQyJpHuJa3nIPUGRw9yl6IonLTlc4EpOcnJ8QiIqFQiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiICqvKq/q2cS/wBQXP8ASKkPBP8AE1oL9QUP3eNR7yqv6tnEv9QXP9IqQ8E/xNaC/UFD93jQE0REQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQFVeVV/Vs4l/qC5/pFSHgn+JrQX6gofu8aj3lVf1bOJf6guf6RUh4J/ia0F+oKH7vGgJoiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAItdZ1FiqU7obGTpwTM+tHLYY1w/SCV5+tmE8Zx/xTPmtMOb2pMmxtUWq9bMJ4zj/imfNPWzCeM4/wCKZ80w58rFmcC+Xr5VvEPQWsdW8LTg8G3SWZxbGVb09eY25a80IbK9rxMGbiUTNHsf2RuD3nc+Qt5V3EzjDrHDaGmwen49I4DFBt2/XrWG2GRRRdlCOd0zmc7n9nuOXq0P2A26TT+UM4TY7jJwrr5vT89XI6r05KZYa1SVsk9qs/YSxNa07ucDyvA6n2XADdy3vkH8MMRwQ4KVpMrdpVNUagcMhkY55mMlgbttDA4EggtaSS09Q6R49yYc+VizOpUWq9bMJ4zj/imfNPWzCeM4/wCKZ80w58rFmbVFqvWzCeM4/wCKZ817VM/i784hq5KpZmO5EcU7XOP+QKh05ra0xYz0RFQgIiIAiIgCIiAIiIAiIgCIiAIiIAiIgKe9FUruqdWSWKdeeT0ntzSxNcdvN4em5CyfV/F+G0/uG/Jfav8ASXVn60/9PCs9crxtSarySk+HH0R8D4+cl4qdnxNf6v4vw2n9w35J6v4vw2n9w35LYKHa24nUtGZOlio8XldRZq3E+xHjMLA2WZsLSA6V5e5jGMBcBu5w3J2APVeJVKr2KT6nii6knZNkh9X8X4bT+4b8k9X8X4bT+4b8lXr/ACh9PvrYE08XnMlezM9unBjK1MC1FZrbdtBKx7m9m5vXqTy7AkuA2J9/p/02zRcmoJq2UhljyRwpwrqodkHXw7bzZsbXEOee8bOLeXrvsraVbN9TXQr+pO/V/F+G0/uG/JPV/F+G0/uG/JVrwj4mZfXvEjiDSvU8jicfi2Y3zTFZWtHDPWdJHKZCSwu5g4taQedw+zbqFbSiVSrF2cn1KVMSnLRk8uOaua/1fxfhtP7hvyXlj8ZTo690o+tUgruM9gF0UYaSPNpPsC2qw4f6d6T/AO8WP3aVe3wNSbrpOT3S/wCrPb/x85PxUE38sWoiIuofeBERAEREAREQBERAEREAREQBERAEREBVFX+kurP1p/6eFaPNcWNEabyc2Ny+stP4vIQbdrUu5SCGWPcBw5mOcCNwQeo7iFvKv9JdWfrT/wBPCsmSnXleXPgje497nMBJXH8dbWJX9Psj4Hx2jrVTSzIceOXDhoBPEDSwDhuCc1W6j/61VnETRuP4i8RMXxBwWnsDxfwAxjsJax0dyrIa8jZTKyaJ8h7Mn23Nc0uB2LSN10F6Pq/k0P3YXrHEyFvLGxrG9+zRsF41JRd4nlhVVJ6UFt9/xYpfFcNLdPVfCzJYvRlHSWPxkuTsZPHY+WEx1HzV+zjJ5eXnc7Zu5YDt9uw3Ucv8KdZY/IZPUmOxEV3JY7Xs+oaWLmtxxjIU5Kcdd3K/ctY/q8t59urTvtuF0cinEZZeJmne3p3b/cpLROVt6R15r3WWvqtLQGMzvoyvR9L5artI+KGYPaXNfyh3v236juJ2O02HHPhuQduIOljt1P8A7ardP/GppLDHO0NkY2RoO+zhuvL0fV/JofuwoclJ3aKSqRm7yXT0VvU0GA4oaN1XkW0MJq3BZi85peKtDJQzylo7zyscTsFu4f6d6T/7xY/dpV7R1IIXc0cMbHfa1oBXjD/TvSf/AHix+7Sr2eBtrCtlL/qz2eAtrcNH5sLUREXYPvQiIgCIiAIiIAiIgCIiAIiIAiIgCIiAh+Q4YY2/lLt8XsnVluSCWVla0WMLuVrdwNunRo/YvH6KKHjGb+OPyU2Ra4knv+yMnSpyd3FP/CIT9FFDxjN/HH5J9FFDxjN/HH5KbIoxJenREYNLkXREJ+iih4xm/jj8k+iih4xm/jj8lNkTEl6dEMGlyLoiE/RRQ8Yzfxx+SfRRQ8Yzfxx+SmyJiS9OiGDS5F0RCfoooeMZv44/JZOJ4a47E5ipkhdyVuxVLjELVkvY0uaWk7bfY4qWopxJLd9kSqVOLuopP2QREWRqEREAREQH/9k=",
|
32 |
"text/plain": [
|
33 |
"<IPython.core.display.Image object>"
|
34 |
]
|
35 |
},
|
36 |
+
"execution_count": 9,
|
37 |
"metadata": {},
|
38 |
"output_type": "execute_result"
|
39 |
}
|
graph.py
CHANGED
@@ -1,16 +1,13 @@
|
|
1 |
from typing import List
|
2 |
|
3 |
-
import aiosqlite
|
4 |
-
from generator import rag_chain
|
5 |
-
from grader import retrieval_grader
|
6 |
from langchain.schema import Document
|
7 |
-
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
|
8 |
from langgraph.graph import END, StateGraph
|
9 |
-
from
|
10 |
-
from
|
11 |
-
from
|
|
|
12 |
from tools.search_wikipedia import wikipedia
|
13 |
-
from typing_extensions import
|
14 |
|
15 |
|
16 |
# DEFINE STATE GRAPH
|
@@ -27,7 +24,7 @@ class GraphState(TypedDict):
|
|
27 |
|
28 |
# Update this to work with memory in a better way.
|
29 |
question: str
|
30 |
-
generation:
|
31 |
wiki_search: str
|
32 |
documents: List[str]
|
33 |
|
@@ -111,45 +108,14 @@ def search_wikipedia(state):
|
|
111 |
return {"question": question, "documents": documents}
|
112 |
|
113 |
|
114 |
-
def question_node(state):
|
115 |
-
question = state["question"]
|
116 |
-
generation = state.get("generation", None)
|
117 |
-
|
118 |
-
return {"question": question, "generation": generation}
|
119 |
-
|
120 |
-
|
121 |
-
def check_memory_or_db(state):
|
122 |
-
print("Checking memory or database...")
|
123 |
-
print(state)
|
124 |
-
|
125 |
-
if state.get("generation", None) is not None and len(state["generation"]) > 0:
|
126 |
-
question = state["question"]
|
127 |
-
generation = state["generation"]
|
128 |
-
|
129 |
-
perform_rag = retrieval_grader.invoke(
|
130 |
-
{"question": question, "document": generation}
|
131 |
-
)
|
132 |
-
|
133 |
-
if perform_rag.binary_score == "yes":
|
134 |
-
print("Memory not sufficient. Performing RAG.")
|
135 |
-
return "retrieve"
|
136 |
-
|
137 |
-
else:
|
138 |
-
print("Memory sufficient. Generating answer.")
|
139 |
-
return "generate"
|
140 |
-
|
141 |
-
else:
|
142 |
-
print("No memory found. Retrieving documents...")
|
143 |
-
return "retrieve"
|
144 |
-
|
145 |
-
|
146 |
# DEFINE CONDITIONAL EDGES
|
147 |
def generate_or_not(state):
|
148 |
print("Determining whether to query Wikipedia...")
|
149 |
|
150 |
wiki_search = state["wiki_search"]
|
|
|
151 |
|
152 |
-
if wiki_search:
|
153 |
print("Rewriting query and supplementing information from Wikipedia...")
|
154 |
return "rewrite_query"
|
155 |
|
@@ -162,18 +128,13 @@ def create_graph():
|
|
162 |
# DEFINE WORKFLOW
|
163 |
workflow = StateGraph(GraphState)
|
164 |
|
165 |
-
workflow.add_node("start", question_node)
|
166 |
-
|
167 |
workflow.add_node("retrieve", retrieve)
|
168 |
workflow.add_node("grade_documents", grade_documents)
|
169 |
workflow.add_node("rewrite_query", rewrite_query)
|
170 |
workflow.add_node("search_wikipedia", search_wikipedia)
|
171 |
workflow.add_node("generate", generate)
|
172 |
|
173 |
-
workflow.set_entry_point("
|
174 |
-
workflow.add_conditional_edges(
|
175 |
-
"start", check_memory_or_db, {"retrieve": "retrieve", "generate": "generate"}
|
176 |
-
)
|
177 |
workflow.add_edge("retrieve", "grade_documents")
|
178 |
workflow.add_conditional_edges(
|
179 |
"grade_documents",
|
@@ -185,11 +146,7 @@ def create_graph():
|
|
185 |
workflow.add_edge("search_wikipedia", "generate")
|
186 |
workflow.add_edge("generate", END)
|
187 |
|
188 |
-
# DEFINE MEMORY
|
189 |
-
checkpoints = aiosqlite.connect("./checkpoints/checkpoint.sqlite")
|
190 |
-
memory = AsyncSqliteSaver(checkpoints)
|
191 |
-
|
192 |
# COMPILE GRAPH
|
193 |
-
app = workflow.compile(
|
194 |
|
195 |
return app
|
|
|
1 |
from typing import List
|
2 |
|
|
|
|
|
|
|
3 |
from langchain.schema import Document
|
|
|
4 |
from langgraph.graph import END, StateGraph
|
5 |
+
from nodes.generator import rag_chain
|
6 |
+
from nodes.grader import retrieval_grader
|
7 |
+
from nodes.retriever import retriever
|
8 |
+
from nodes.rewriter import question_rewriter
|
9 |
from tools.search_wikipedia import wikipedia
|
10 |
+
from typing_extensions import TypedDict
|
11 |
|
12 |
|
13 |
# DEFINE STATE GRAPH
|
|
|
24 |
|
25 |
# Update this to work with memory in a better way.
|
26 |
question: str
|
27 |
+
generation: str
|
28 |
wiki_search: str
|
29 |
documents: List[str]
|
30 |
|
|
|
108 |
return {"question": question, "documents": documents}
|
109 |
|
110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
# DEFINE CONDITIONAL EDGES
|
112 |
def generate_or_not(state):
|
113 |
print("Determining whether to query Wikipedia...")
|
114 |
|
115 |
wiki_search = state["wiki_search"]
|
116 |
+
filtered_docs = state["documents"]
|
117 |
|
118 |
+
if len(filtered_docs) == 0 and wiki_search:
|
119 |
print("Rewriting query and supplementing information from Wikipedia...")
|
120 |
return "rewrite_query"
|
121 |
|
|
|
128 |
# DEFINE WORKFLOW
|
129 |
workflow = StateGraph(GraphState)
|
130 |
|
|
|
|
|
131 |
workflow.add_node("retrieve", retrieve)
|
132 |
workflow.add_node("grade_documents", grade_documents)
|
133 |
workflow.add_node("rewrite_query", rewrite_query)
|
134 |
workflow.add_node("search_wikipedia", search_wikipedia)
|
135 |
workflow.add_node("generate", generate)
|
136 |
|
137 |
+
workflow.set_entry_point("retrieve")
|
|
|
|
|
|
|
138 |
workflow.add_edge("retrieve", "grade_documents")
|
139 |
workflow.add_conditional_edges(
|
140 |
"grade_documents",
|
|
|
146 |
workflow.add_edge("search_wikipedia", "generate")
|
147 |
workflow.add_edge("generate", END)
|
148 |
|
|
|
|
|
|
|
|
|
149 |
# COMPILE GRAPH
|
150 |
+
app = workflow.compile()
|
151 |
|
152 |
return app
|
nodes/__init__.py
ADDED
File without changes
|
generator.py β nodes/generator.py
RENAMED
@@ -1,18 +1,15 @@
|
|
1 |
-
|
2 |
-
import
|
3 |
from langchain_openai import ChatOpenAI
|
4 |
-
from langchain_core.output_parsers import StrOutputParser
|
5 |
from langchain_core.prompts import ChatPromptTemplate
|
6 |
|
7 |
# Prompt
|
8 |
-
|
9 |
-
|
10 |
-
prompt = ChatPromptTemplate.from_messages([("human", agent_prompt)])
|
11 |
|
12 |
prompt
|
13 |
|
14 |
# Generator LLM
|
15 |
-
llm = ChatOpenAI(model_name=
|
16 |
|
17 |
|
18 |
# Post-processing
|
@@ -21,7 +18,7 @@ def format_docs(docs):
|
|
21 |
|
22 |
|
23 |
# Chain
|
24 |
-
rag_chain = prompt | llm
|
25 |
|
26 |
# generation = rag_chain.invoke({"context": docs, "question": question})
|
27 |
# print(generation)
|
|
|
1 |
+
from utils.prompts import GENERATOR_PROMPT
|
2 |
+
from utils.config import GENERATOR_MODEL
|
3 |
from langchain_openai import ChatOpenAI
|
|
|
4 |
from langchain_core.prompts import ChatPromptTemplate
|
5 |
|
6 |
# Prompt
|
7 |
+
prompt = ChatPromptTemplate.from_messages([("human", GENERATOR_PROMPT)])
|
|
|
|
|
8 |
|
9 |
prompt
|
10 |
|
11 |
# Generator LLM
|
12 |
+
llm = ChatOpenAI(model_name=GENERATOR_MODEL, temperature=0.7, streaming=True)
|
13 |
|
14 |
|
15 |
# Post-processing
|
|
|
18 |
|
19 |
|
20 |
# Chain
|
21 |
+
rag_chain = prompt | llm
|
22 |
|
23 |
# generation = rag_chain.invoke({"context": docs, "question": question})
|
24 |
# print(generation)
|
grader.py β nodes/grader.py
RENAMED
@@ -1,4 +1,5 @@
|
|
1 |
-
import
|
|
|
2 |
from langchain_core.prompts import ChatPromptTemplate
|
3 |
from langchain_core.pydantic_v1 import BaseModel, Field
|
4 |
from langchain_openai import ChatOpenAI
|
@@ -11,26 +12,22 @@ class DocumentGrade(BaseModel):
|
|
11 |
binary_score: str = Field(
|
12 |
description='Document is relevant to the question, "yes" or "no"'
|
13 |
)
|
|
|
14 |
|
15 |
|
16 |
# Grader Prompts
|
17 |
-
system = """You are a grader assessing relevance of a retrieved document to a user question. \n
|
18 |
-
If the document contains keyword(s) or semantic meaning related to the question, grade it as relevant. \n
|
19 |
-
Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question."""
|
20 |
-
|
21 |
grade_prompt = ChatPromptTemplate.from_messages(
|
22 |
[
|
23 |
-
("system",
|
24 |
-
("human",
|
25 |
]
|
26 |
)
|
27 |
|
28 |
|
29 |
# LLM with function call
|
30 |
-
llm = ChatOpenAI(model=
|
31 |
|
32 |
structured_llm_grader = llm.with_structured_output(DocumentGrade)
|
33 |
-
structured_llm_grader
|
34 |
|
35 |
|
36 |
retrieval_grader = grade_prompt | structured_llm_grader
|
|
|
1 |
+
from utils.prompts import GRADER_SYSTEM_PROMPT, GRADER_PROMPT
|
2 |
+
from utils.config import GRADER_MODEL
|
3 |
from langchain_core.prompts import ChatPromptTemplate
|
4 |
from langchain_core.pydantic_v1 import BaseModel, Field
|
5 |
from langchain_openai import ChatOpenAI
|
|
|
12 |
binary_score: str = Field(
|
13 |
description='Document is relevant to the question, "yes" or "no"'
|
14 |
)
|
15 |
+
reasoning: str = Field(description="Reasoning for the score")
|
16 |
|
17 |
|
18 |
# Grader Prompts
|
|
|
|
|
|
|
|
|
19 |
grade_prompt = ChatPromptTemplate.from_messages(
|
20 |
[
|
21 |
+
("system", GRADER_SYSTEM_PROMPT),
|
22 |
+
("human", GRADER_PROMPT),
|
23 |
]
|
24 |
)
|
25 |
|
26 |
|
27 |
# LLM with function call
|
28 |
+
llm = ChatOpenAI(model=GRADER_MODEL, temperature=0, streaming=True)
|
29 |
|
30 |
structured_llm_grader = llm.with_structured_output(DocumentGrade)
|
|
|
31 |
|
32 |
|
33 |
retrieval_grader = grade_prompt | structured_llm_grader
|
retriever.py β nodes/retriever.py
RENAMED
@@ -1,17 +1,18 @@
|
|
|
|
1 |
from langchain_community.document_loaders import PyPDFLoader
|
2 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
3 |
from langchain_community.vectorstores import Qdrant
|
4 |
from langchain_openai import OpenAIEmbeddings
|
5 |
import os
|
6 |
|
7 |
-
QDRANT_DATA_DIR =
|
|
|
8 |
|
9 |
if os.path.exists(QDRANT_DATA_DIR):
|
10 |
-
|
11 |
vectorstore = Qdrant.from_existing_collection(
|
12 |
embedding=OpenAIEmbeddings(),
|
13 |
path=QDRANT_DATA_DIR,
|
14 |
-
collection_name=
|
15 |
)
|
16 |
|
17 |
else:
|
@@ -31,11 +32,11 @@ else:
|
|
31 |
# Add to vectorDB with on-disk storage
|
32 |
vectorstore = Qdrant.from_documents(
|
33 |
documents=doc_splits,
|
34 |
-
collection_name=
|
35 |
-
path=
|
36 |
embedding=OpenAIEmbeddings(),
|
37 |
)
|
38 |
|
39 |
-
retriever = vectorstore.as_retriever()
|
40 |
|
41 |
-
# print(retriever.invoke("What is the average length of stay for Airbnb guests?"))
|
|
|
1 |
+
from utils.config import QDRANT_DATA_DIR, COLLECTION_NAME
|
2 |
from langchain_community.document_loaders import PyPDFLoader
|
3 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
4 |
from langchain_community.vectorstores import Qdrant
|
5 |
from langchain_openai import OpenAIEmbeddings
|
6 |
import os
|
7 |
|
8 |
+
QDRANT_DATA_DIR = QDRANT_DATA_DIR
|
9 |
+
COLLECTION_NAME = COLLECTION_NAME
|
10 |
|
11 |
if os.path.exists(QDRANT_DATA_DIR):
|
|
|
12 |
vectorstore = Qdrant.from_existing_collection(
|
13 |
embedding=OpenAIEmbeddings(),
|
14 |
path=QDRANT_DATA_DIR,
|
15 |
+
collection_name=COLLECTION_NAME,
|
16 |
)
|
17 |
|
18 |
else:
|
|
|
32 |
# Add to vectorDB with on-disk storage
|
33 |
vectorstore = Qdrant.from_documents(
|
34 |
documents=doc_splits,
|
35 |
+
collection_name=COLLECTION_NAME,
|
36 |
+
path=QDRANT_DATA_DIR, # Local mode with on-disk storage
|
37 |
embedding=OpenAIEmbeddings(),
|
38 |
)
|
39 |
|
40 |
+
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
|
41 |
|
42 |
+
# print(retriever.invoke("What is the average length of stay for Airbnb guests?"))
|
rewriter.py β nodes/rewriter.py
RENAMED
@@ -1,23 +1,18 @@
|
|
1 |
### Rewrite a question to be more optimized for Wikipedia search
|
2 |
-
import
|
|
|
3 |
from langchain_core.output_parsers import StrOutputParser
|
4 |
from langchain_core.prompts import ChatPromptTemplate
|
5 |
from langchain_openai import ChatOpenAI
|
6 |
|
7 |
# Question rewriter LLM
|
8 |
-
llm = ChatOpenAI(model=
|
9 |
|
10 |
# Prompt
|
11 |
-
system = """You are a question re-writer that converts an input question to a better version that is optimized \n
|
12 |
-
for searching on Wikipedia. Look at the input and try to reason about the underlying semantic intent / meaning, such that the new question is more optimized for search."""
|
13 |
-
|
14 |
rewrite_prompt = ChatPromptTemplate.from_messages(
|
15 |
[
|
16 |
-
("system",
|
17 |
-
(
|
18 |
-
"human",
|
19 |
-
"Here is the initial question: \n\n {question} \n Formulate an improved question.",
|
20 |
-
),
|
21 |
]
|
22 |
)
|
23 |
|
|
|
1 |
### Rewrite a question to be more optimized for Wikipedia search
|
2 |
+
from utils.config import REWRITER_MODEL
|
3 |
+
from utils.prompts import QUERY_REWRITE_PROMPT, QUERY_REWRITE_SYSTEM_PROMPT
|
4 |
from langchain_core.output_parsers import StrOutputParser
|
5 |
from langchain_core.prompts import ChatPromptTemplate
|
6 |
from langchain_openai import ChatOpenAI
|
7 |
|
8 |
# Question rewriter LLM
|
9 |
+
llm = ChatOpenAI(model=REWRITER_MODEL, temperature=0, streaming=True)
|
10 |
|
11 |
# Prompt
|
|
|
|
|
|
|
12 |
rewrite_prompt = ChatPromptTemplate.from_messages(
|
13 |
[
|
14 |
+
("system", QUERY_REWRITE_SYSTEM_PROMPT),
|
15 |
+
("human", QUERY_REWRITE_PROMPT),
|
|
|
|
|
|
|
16 |
]
|
17 |
)
|
18 |
|
requirements.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
chainlit==0.7.700
|
2 |
+
langchain==0.2.5
|
3 |
+
langchain_core==0.2.9
|
4 |
+
langchain_community==0.2.5
|
5 |
+
langchain_openai==0.1.8
|
6 |
+
langchain_text_splitters==0.2.1
|
7 |
+
langgraph==0.0.69
|
8 |
+
qdrant-client==1.9.1
|
9 |
+
python-dotenv==1.0.1
|
10 |
+
typing_extensions==4.12.2
|
11 |
+
wikipedia
|
server.py
CHANGED
@@ -4,15 +4,15 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
4 |
from langserve import add_routes
|
5 |
|
6 |
from graph import create_graph
|
7 |
-
from chat_types import ChatInputType
|
8 |
|
9 |
# Load environment variables from .env file
|
10 |
load_dotenv()
|
11 |
|
12 |
app = FastAPI(
|
13 |
-
title="
|
14 |
version="1.0",
|
15 |
-
description="
|
16 |
)
|
17 |
|
18 |
# Configure CORS
|
|
|
4 |
from langserve import add_routes
|
5 |
|
6 |
from graph import create_graph
|
7 |
+
from utils.chat_types import ChatInputType
|
8 |
|
9 |
# Load environment variables from .env file
|
10 |
load_dotenv()
|
11 |
|
12 |
app = FastAPI(
|
13 |
+
title="CRAG Backend",
|
14 |
version="1.0",
|
15 |
+
description="Backend to run agent performing corrective RAG over annual reports",
|
16 |
)
|
17 |
|
18 |
# Configure CORS
|
utils/__init__.py
ADDED
File without changes
|
chat_types.py β utils/chat_types.py
RENAMED
File without changes
|
config.py β utils/config.py
RENAMED
@@ -1,3 +1,7 @@
|
|
1 |
GENERATOR_MODEL = "gpt-4o"
|
2 |
REWRITER_MODEL = "gpt-4o"
|
3 |
GRADER_MODEL = "gpt-4o"
|
|
|
|
|
|
|
|
|
|
1 |
GENERATOR_MODEL = "gpt-4o"
|
2 |
REWRITER_MODEL = "gpt-4o"
|
3 |
GRADER_MODEL = "gpt-4o"
|
4 |
+
MEMORY_MODEL = "gpt-4o"
|
5 |
+
|
6 |
+
QDRANT_DATA_DIR = "./qdrant_data"
|
7 |
+
COLLECTION_NAME='10k-docs'
|
utils/prompts.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
QUERY_REWRITE_SYSTEM_PROMPT = """You are a question re-writer that converts an input question to a better version that is optimized for searching on Wikipedia.
|
2 |
+
Look at the input and try to reason about the underlying semantic intent / meaning, such that the new question is more optimized for search."""
|
3 |
+
|
4 |
+
QUERY_REWRITE_PROMPT = (
|
5 |
+
"Here is the initial question: \n\n {question} \n Formulate an improved question."
|
6 |
+
)
|
7 |
+
|
8 |
+
GRADER_SYSTEM_PROMPT = """You are a grader assessing relevance of a retrieved document to a user question.
|
9 |
+
If the document contains keyword(s) or semantic meaning related to the question, grade it as relevant.
|
10 |
+
Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. Provide reasoning for your answer."""
|
11 |
+
|
12 |
+
GRADER_PROMPT = "Retrieved document: \n\n {document} \n\n User question: {question}"
|
13 |
+
|
14 |
+
GENERATOR_PROMPT = """You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question.
|
15 |
+
If you don't know the answer, just say that you don't know.
|
16 |
+
Use three sentences maximum and keep the answer concise.
|
17 |
+
Question: {question} \nContext: {context} \nAnswer:"""
|