kcheng0816 commited on
Commit
ce282d0
·
1 Parent(s): 40fe4f8

Add application file

Browse files
Files changed (5) hide show
  1. Dockerfile +31 -0
  2. app.py +200 -0
  3. data/bible_genesis_en.html +0 -0
  4. pyproject.toml +20 -0
  5. uv.lock +0 -0
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Get a distribution that has uv already installed
3
+ FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
4
+
5
+ # Add user - this is the user that will run the app
6
+ # If you do not set user, the app will run as root (undesirable)
7
+ RUN useradd -m -u 1000 user
8
+ USER user
9
+
10
+ # Set the home directory and path
11
+ ENV HOME=/home/user \
12
+ PATH=/home/user/.local/bin:$PATH
13
+
14
+ ENV UVICORN_WS_PROTOCOL=websockets
15
+
16
+
17
+ # Set the working directory
18
+ WORKDIR $HOME/app
19
+
20
+ # Copy the app to the container
21
+ COPY --chown=user . $HOME/app
22
+
23
+ # Install the dependencies
24
+ # RUN uv sync --frozen
25
+ RUN uv sync
26
+
27
+ # Expose the port
28
+ EXPOSE 7860
29
+
30
+ # Run the app
31
+ CMD ["uv", "run", "chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from chainlit.types import AskFileResponse
4
+ import chainlit as cl
5
+
6
+ import pandas as pd
7
+ from langchain_community.vectorstores import FAISS
8
+ from langchain_core.documents import Document
9
+ from langchain_community.document_loaders import DirectoryLoader
10
+ from langchain_community.document_loaders import BSHTMLLoader
11
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
12
+ from langchain_huggingface import HuggingFaceEmbeddings
13
+ from langchain_qdrant import QdrantVectorStore
14
+ from qdrant_client import QdrantClient
15
+ from qdrant_client.http.models import Distance, VectorParams
16
+ from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
17
+ from langchain_cohere import CohereRerank
18
+ from langchain.prompts import ChatPromptTemplate
19
+ from langchain_openai import ChatOpenAI
20
+ from langchain.chat_models import init_chat_model
21
+ from langchain_core.rate_limiters import InMemoryRateLimiter
22
+ from langgraph.graph import START, StateGraph, END
23
+ from typing_extensions import List, TypedDict
24
+ from langchain_core.documents import Document
25
+ from langchain_core.messages import HumanMessage
26
+ from langchain_core.tools import tool
27
+ from langgraph.prebuilt import ToolNode
28
+
29
+
30
+ #Load API Keys
31
+ load_dotenv()
32
+
33
+ #Load downloaded html pages of the book Genesis in Bible
34
+ path = "data/"
35
+ loader = DirectoryLoader(path, glob="*.html")
36
+ docs = loader.load()
37
+
38
+ #Building RAG Graph with LangGraph
39
+ text_splitter = RecursiveCharacterTextSplitter(
40
+ chunk_size = 750,
41
+ chunk_overlap = 100
42
+ )
43
+
44
+ split_documents = text_splitter.split_documents(docs)
45
+ len(split_documents)
46
+ #fine tuned embedding model
47
+ huggingface_embeddings = HuggingFaceEmbeddings(model_name="kcheng0816/finetuned_arctic_genesis")
48
+
49
+ client = QdrantClient(":memory:")
50
+ client.create_collection(
51
+ collection_name="genesis_bible",
52
+ vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
53
+ )
54
+
55
+ vector_store = QdrantVectorStore(
56
+ client=client,
57
+ collection_name="genesis_bible",
58
+ embedding=huggingface_embeddings,
59
+ )
60
+
61
+ _ = vector_store.add_documents(documents=split_documents)
62
+ #Retrieve
63
+ retriever = vector_store.as_retriever(search_kwargs={"k": 5})
64
+
65
+
66
+ def retrieve_adjusted(state):
67
+ compressor = CohereRerank(model="rerank-v3.5")
68
+ compression_retriever = ContextualCompressionRetriever(
69
+ base_compressor=compressor, base_retriever=retriever, search_kwargs={"k": 5}
70
+ )
71
+ retrieved_docs = compression_retriever.invoke(state["question"])
72
+ return {"context" : retrieved_docs}
73
+
74
+
75
+ RAG_PROMPT = """\
76
+ You are a helpful assistant who answers questions based on provided context. You must only use the provided context, and cannot use your own knowledge.
77
+
78
+ ### Question
79
+ {question}
80
+
81
+ ### Context
82
+ {context}
83
+ """
84
+ rag_prompt = ChatPromptTemplate.from_template(RAG_PROMPT)
85
+
86
+
87
+ #llm
88
+ rate_limiter = InMemoryRateLimiter(
89
+ requests_per_second=1, # <-- make a request once every 1 seconds!!
90
+ check_every_n_seconds=0.1, # Wake up every 100 ms to check whether allowed to make a request,
91
+ max_bucket_size=10, # Controls the maximum burst size.
92
+ )
93
+ llm = init_chat_model("gpt-4o-mini", rate_limiter=rate_limiter)
94
+
95
+
96
+ def generate(state):
97
+ docs_content = "\n\n".join(doc.page_content for doc in state["context"])
98
+ messages = rag_prompt.format_messages(question=state["question"], context=docs_content)
99
+ response = llm.invoke(messages)
100
+ return {"response" : response.content}
101
+
102
+
103
+
104
+
105
+ class State(TypedDict):
106
+ question: str
107
+ context: List[Document]
108
+ response: str
109
+
110
+ graph_builder = StateGraph(State).add_sequence([retrieve_adjusted, generate])
111
+ graph_builder.add_edge(START, "retrieve_adjusted")
112
+ graph = graph_builder.compile()
113
+
114
+
115
+
116
+
117
+ @tool
118
+ def ai_rag_tool(question: str) -> str:
119
+ """Useful for when you need to answer questions about Bible """
120
+ response = graph.invoke({"question": question})
121
+ return {
122
+ "message": [HumanMessage(content=response["response"])],
123
+ "context": response["context"]
124
+ }
125
+
126
+ tool_belt = [
127
+ ai_rag_tool
128
+ ]
129
+
130
+
131
+ llm = init_chat_model("gpt-4o", temperature=0, rate_limiter=rate_limiter)
132
+ llm_with_tools = llm.bind_tools(tool_belt)
133
+
134
+
135
+ from langgraph.graph import END
136
+ from langchain_core.messages import AnyMessage
137
+ from langgraph.graph.message import add_messages
138
+ from typing import TypedDict, Annotated
139
+ from langchain_core.documents import Document
140
+
141
+
142
+ class AgentState(TypedDict):
143
+ messages: Annotated[list[AnyMessage], add_messages]
144
+ context:List[Document]
145
+
146
+
147
+
148
+ def call_mode(state):
149
+ messages = state["messages"]
150
+ response = llm_with_tools.invoke(messages)
151
+ return {
152
+ "messages": [response],
153
+ "context": state.get("context",[])
154
+ }
155
+
156
+ tool_node = ToolNode(tool_belt)
157
+
158
+ def should_continue(state):
159
+ last_message = state["messages"][-1]
160
+
161
+ if last_message.tool_calls:
162
+ return "action"
163
+
164
+ return END
165
+
166
+
167
+ #
168
+ uncompiled_graph = StateGraph(AgentState)
169
+
170
+ uncompiled_graph.add_node("agent", call_mode)
171
+ uncompiled_graph.add_node("action", tool_node)
172
+
173
+ uncompiled_graph.set_entry_point("agent")
174
+
175
+ uncompiled_graph.add_conditional_edges(
176
+ "agent",
177
+ should_continue
178
+ )
179
+
180
+ uncompiled_graph.add_edge("action", "agent")
181
+
182
+ # Compile and display the graph for a visual overview
183
+ compiled_graph = uncompiled_graph.compile()
184
+
185
+
186
+
187
+
188
+
189
+ @cl.on_chat_start
190
+ async def on_chat_start():
191
+ cl.user_session.set("graph", compiled_graph)
192
+
193
+
194
+
195
+ @cl.on_message
196
+ async def handle(message: cl.Message):
197
+ graph = cl.user_session.get("graph")
198
+ state = {"messages": [HumanMessage(content=message.content)]}
199
+ response = await graph.ainvoke(state)
200
+ await cl.Message(content=response["messages"][-1].content).send()
data/bible_genesis_en.html ADDED
The diff for this file is too large to render. See raw diff
 
pyproject.toml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "biblestudy-agentic-rag"
3
+ version = "0.1.0"
4
+ description = "AIE5 midterm"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "jupyter>=1.1.1",
9
+ "langchain>=0.3.15",
10
+ "langchain-community>=0.3.15",
11
+ "langchain-openai>=0.3.2",
12
+ "langgraph>=0.2.67",
13
+ "chainlit>=2.0.4",
14
+ "pandas>=2.2.3",
15
+ "langchain-qdrant>=0.2.0",
16
+ "langchain-cohere>=0.4.2",
17
+ "unstructured>=0.14.8",
18
+ "langchain-huggingface>=0.1.2",
19
+ "websockets>=15.0",
20
+ ]
uv.lock ADDED
The diff for this file is too large to render. See raw diff