Spaces:
Sleeping
Sleeping
Commit
·
236fcc8
1
Parent(s):
80733cd
Fix: Install system dependencies as root before switching user
Browse files- Dockerfile +5 -9
- app.py +162 -108
- pyproject.toml +24 -23
Dockerfile
CHANGED
@@ -1,10 +1,8 @@
|
|
1 |
# Get a distribution that has uv already installed
|
2 |
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
|
3 |
|
4 |
-
# Install required system dependencies for building packages (as root)
|
5 |
-
RUN apt-get update && apt-get install -y gcc g++ python3-dev
|
6 |
-
|
7 |
# Add user - this is the user that will run the app
|
|
|
8 |
RUN useradd -m -u 1000 user
|
9 |
USER user
|
10 |
|
@@ -20,14 +18,12 @@ WORKDIR $HOME/app
|
|
20 |
# Copy the app to the container
|
21 |
COPY --chown=user . $HOME/app
|
22 |
|
23 |
-
#
|
24 |
-
|
25 |
-
|
26 |
-
# Install dependencies
|
27 |
-
RUN uv pip install --system --requirements pyproject.toml
|
28 |
|
29 |
# Expose the port
|
30 |
EXPOSE 7860
|
31 |
|
32 |
# Run the app
|
33 |
-
CMD ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
1 |
# Get a distribution that has uv already installed
|
2 |
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
|
3 |
|
|
|
|
|
|
|
4 |
# Add user - this is the user that will run the app
|
5 |
+
# If you do not set user, the app will run as root (undesirable)
|
6 |
RUN useradd -m -u 1000 user
|
7 |
USER user
|
8 |
|
|
|
18 |
# Copy the app to the container
|
19 |
COPY --chown=user . $HOME/app
|
20 |
|
21 |
+
# Install the dependencies
|
22 |
+
# RUN uv sync --frozen
|
23 |
+
RUN uv sync
|
|
|
|
|
24 |
|
25 |
# Expose the port
|
26 |
EXPOSE 7860
|
27 |
|
28 |
# Run the app
|
29 |
+
CMD ["uv", "run", "chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
CHANGED
@@ -1,139 +1,193 @@
|
|
1 |
-
import
|
2 |
-
from
|
3 |
-
|
4 |
-
from
|
5 |
-
from aimakerspace.openai_utils.prompts import (
|
6 |
-
UserRolePrompt,
|
7 |
-
SystemRolePrompt,
|
8 |
-
AssistantRolePrompt,
|
9 |
-
)
|
10 |
-
from aimakerspace.openai_utils.embedding import EmbeddingModel
|
11 |
-
from aimakerspace.vectordatabase import VectorDatabase
|
12 |
-
from aimakerspace.openai_utils.chatmodel import ChatOpenAI
|
13 |
import chainlit as cl
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
system_template = """\
|
16 |
-
Use the following context to answer a users question. If you cannot find the answer in the context, say you don't know the answer."""
|
17 |
-
system_role_prompt = SystemRolePrompt(system_template)
|
18 |
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
21 |
{context}
|
22 |
|
23 |
-
Question:
|
24 |
-
{question}
|
25 |
-
"""
|
26 |
-
user_role_prompt = UserRolePrompt(user_prompt_template)
|
27 |
|
28 |
-
|
29 |
-
def __init__(self, llm: ChatOpenAI(), vector_db_retriever: VectorDatabase) -> None:
|
30 |
-
self.llm = llm
|
31 |
-
self.vector_db_retriever = vector_db_retriever
|
32 |
|
33 |
-
|
34 |
-
context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
|
35 |
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
39 |
|
40 |
-
|
|
|
|
|
|
|
41 |
|
42 |
-
|
|
|
|
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
47 |
|
48 |
-
|
49 |
|
50 |
-
|
|
|
|
|
|
|
51 |
|
|
|
52 |
|
53 |
-
|
54 |
-
import tempfile
|
55 |
-
import shutil
|
56 |
-
|
57 |
-
print(f"Processing file: {file.name}")
|
58 |
-
|
59 |
-
# Create a temporary file with the correct extension
|
60 |
-
suffix = f".{file.name.split('.')[-1]}"
|
61 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
62 |
-
# Copy the uploaded file content to the temporary file
|
63 |
-
shutil.copyfile(file.path, temp_file.name)
|
64 |
-
print(f"Created temporary file at: {temp_file.name}")
|
65 |
-
|
66 |
-
# Create appropriate loader
|
67 |
-
if file.name.lower().endswith('.pdf'):
|
68 |
-
loader = PDFLoader(temp_file.name)
|
69 |
-
else:
|
70 |
-
loader = TextFileLoader(temp_file.name)
|
71 |
-
|
72 |
-
try:
|
73 |
-
# Load and process the documents
|
74 |
-
documents = loader.load_documents()
|
75 |
-
texts = text_splitter.split_texts(documents)
|
76 |
-
return texts
|
77 |
-
finally:
|
78 |
-
# Clean up the temporary file
|
79 |
-
try:
|
80 |
-
os.unlink(temp_file.name)
|
81 |
-
except Exception as e:
|
82 |
-
print(f"Error cleaning up temporary file: {e}")
|
83 |
|
|
|
|
|
84 |
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
88 |
|
89 |
-
|
90 |
-
while files == None:
|
91 |
-
files = await cl.AskFileMessage(
|
92 |
-
content="Please upload a Text or PDF file to begin!",
|
93 |
-
accept=["text/plain", "application/pdf"],
|
94 |
-
max_size_mb=2,
|
95 |
-
timeout=180,
|
96 |
-
).send()
|
97 |
|
98 |
-
|
99 |
|
100 |
-
|
101 |
-
|
102 |
-
)
|
103 |
-
await msg.send()
|
104 |
|
105 |
-
|
106 |
-
texts = process_file(file)
|
107 |
|
108 |
-
|
|
|
|
|
109 |
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
-
# Create a chain
|
117 |
-
retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(
|
118 |
-
vector_db_retriever=vector_db,
|
119 |
-
llm=chat_openai
|
120 |
-
)
|
121 |
-
|
122 |
-
# Let the user know that the system is ready
|
123 |
-
msg.content = f"Processing `{file.name}` done. You can now ask questions!"
|
124 |
-
await msg.update()
|
125 |
|
126 |
-
cl.user_session.set("chain", retrieval_augmented_qa_pipeline)
|
127 |
|
128 |
|
|
|
|
|
|
|
|
|
129 |
@cl.on_message
|
130 |
-
async def
|
131 |
-
|
|
|
|
|
|
|
|
|
|
|
132 |
|
133 |
-
|
134 |
-
|
135 |
|
136 |
-
|
137 |
-
|
138 |
|
139 |
-
await
|
|
|
1 |
+
from typing import TypedDict, Annotated, List
|
2 |
+
from typing_extensions import List, TypedDict
|
3 |
+
|
4 |
+
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
import chainlit as cl
|
6 |
+
import operator
|
7 |
+
|
8 |
+
from langchain.prompts import ChatPromptTemplate
|
9 |
+
from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
|
10 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
11 |
+
from langchain_community.document_loaders import DirectoryLoader, BSHTMLLoader, PyPDFLoader
|
12 |
+
from sentence_transformers import SentenceTransformer
|
13 |
+
from langchain_community.tools.arxiv.tool import ArxivQueryRun
|
14 |
+
from langchain_community.tools.tavily_search import TavilySearchResults
|
15 |
+
from langchain_core.documents import Document
|
16 |
+
from langchain_core.messages import BaseMessage, HumanMessage
|
17 |
+
from langchain_core.tools import tool
|
18 |
+
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
|
19 |
+
from langchain_qdrant import QdrantVectorStore
|
20 |
+
from langgraph.graph import START, StateGraph, END
|
21 |
+
from langgraph.graph.message import add_messages
|
22 |
+
from langgraph.prebuilt import ToolNode
|
23 |
+
from qdrant_client import QdrantClient
|
24 |
+
from qdrant_client.http.models import Distance, VectorParams
|
25 |
+
from langchain_core.prompts import PromptTemplate
|
26 |
+
from langchain.embeddings import SentenceTransformerEmbeddings
|
27 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
28 |
+
|
29 |
+
load_dotenv()
|
30 |
+
|
31 |
+
openai_chat_model = ChatOpenAI(model="gpt-4o-mini")
|
32 |
+
|
33 |
+
path = "data/"
|
34 |
+
|
35 |
+
# Load HTML files
|
36 |
+
html_loader = DirectoryLoader(path, glob="*.html", loader_cls=BSHTMLLoader)
|
37 |
+
html_docs = html_loader.load()
|
38 |
+
|
39 |
+
# Load PDF files
|
40 |
+
pdf_loader = DirectoryLoader(path, glob="*.pdf", loader_cls=PyPDFLoader)
|
41 |
+
pdf_docs = pdf_loader.load()
|
42 |
+
|
43 |
+
# Combine both document lists
|
44 |
+
docs = html_docs + pdf_docs
|
45 |
+
|
46 |
+
# Split documents into chunks
|
47 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
48 |
+
chunk_size = 850,
|
49 |
+
chunk_overlap = 50,
|
50 |
+
length_function = len
|
51 |
+
)
|
52 |
+
split_documents = text_splitter.split_documents(docs)
|
53 |
+
|
54 |
+
|
55 |
+
# Load your fine-tuned model
|
56 |
+
|
57 |
+
finetune_embeddings = HuggingFaceEmbeddings(model_name="finetuned_caregiver_ft")
|
58 |
+
|
59 |
+
client = QdrantClient(":memory:")
|
60 |
+
|
61 |
+
client.create_collection(
|
62 |
+
collection_name="ai_across_years2",
|
63 |
+
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
|
64 |
+
)
|
65 |
+
|
66 |
+
vector_store = QdrantVectorStore(
|
67 |
+
client=client,
|
68 |
+
collection_name="ai_across_years2",
|
69 |
+
embedding=finetune_embeddings,
|
70 |
+
)
|
71 |
+
|
72 |
+
_ = vector_store.add_documents(documents=split_documents)
|
73 |
+
|
74 |
+
|
75 |
+
retriever = vector_store.as_retriever(search_kwargs={"k": 5})
|
76 |
+
|
77 |
+
|
78 |
+
def retrieve(state):
|
79 |
+
retrieved_docs = retriever.invoke(state["question"])
|
80 |
+
return {"context": retrieved_docs}
|
81 |
|
|
|
|
|
|
|
82 |
|
83 |
+
#####
|
84 |
+
|
85 |
+
RAG_template = """
|
86 |
+
Use the following pieces of context to answer the question at the end.
|
87 |
+
If you don't know the answer, just say "I don't know, would you like to talk to a care coach?", don't try to make up an answer.
|
88 |
+
Use three sentences maximum and keep the answer as concise as possible.
|
89 |
+
|
90 |
{context}
|
91 |
|
92 |
+
Question: {question}
|
|
|
|
|
|
|
93 |
|
94 |
+
Helpful Answer:"""
|
|
|
|
|
|
|
95 |
|
96 |
+
rag_prompt = PromptTemplate.from_template(RAG_template)
|
|
|
97 |
|
98 |
+
def generate(state):
|
99 |
+
docs_content = "\n\n".join([doc.page_content for doc in state["context"]])
|
100 |
+
messages = rag_prompt.format_prompt(context=docs_content, question=state["question"])
|
101 |
+
respose = openai_chat_model.invoke(messages)
|
102 |
+
return {"response": respose.content}
|
103 |
|
104 |
+
class State(TypedDict):
|
105 |
+
question: str
|
106 |
+
context: List[Document]
|
107 |
+
response: str
|
108 |
|
109 |
+
graph_builder = StateGraph(State).add_sequence([retrieve, generate])
|
110 |
+
graph_builder.add_edge(START, "retrieve");
|
111 |
+
graph = graph_builder.compile();
|
112 |
|
113 |
+
@tool
|
114 |
+
def ai_rag_tool(question: str) -> str:
|
115 |
+
"""Answer questions about ALS based on the retrieved documents. Input should be a fully formed question."""
|
116 |
+
response = graph.invoke({"question": question})
|
117 |
+
return{
|
118 |
+
"messages": [HumanMessage(content=response["response"])],
|
119 |
+
"context": response["context"],
|
120 |
+
}
|
121 |
|
122 |
+
tavily_tool = TavilySearchResults(max_results=5)
|
123 |
|
124 |
+
tool_belt = [
|
125 |
+
tavily_tool,
|
126 |
+
ai_rag_tool,
|
127 |
+
]
|
128 |
|
129 |
+
model = ChatOpenAI(model="gpt-4o", temperature=0)
|
130 |
|
131 |
+
model = model.bind_tools(tool_belt)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
|
133 |
+
class AgentState(TypedDict):
|
134 |
+
messages: Annotated[list, add_messages]
|
135 |
|
136 |
+
def call_model(state):
|
137 |
+
messages = state["messages"]
|
138 |
+
response = model.invoke(messages, config={"tool_choice": "auto"}) # Ensure it knows it can use tools
|
139 |
+
return {"messages": [response]}
|
140 |
|
141 |
+
tool_node = ToolNode(tool_belt)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
|
143 |
+
uncompiled_graph = StateGraph(AgentState)
|
144 |
|
145 |
+
uncompiled_graph.add_node("agent", call_model)
|
146 |
+
uncompiled_graph.add_node("action", tool_node)
|
|
|
|
|
147 |
|
148 |
+
uncompiled_graph.set_entry_point("agent")
|
|
|
149 |
|
150 |
+
def should_continue(state):
|
151 |
+
last_message = state["messages"][-1]
|
152 |
+
print(f"Checking if model wants to call a tool: {last_message}") # Debugging
|
153 |
|
154 |
+
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
|
155 |
+
print(f"Model wants to call a tool: {last_message.tool_calls}")
|
156 |
+
return "action"
|
157 |
+
|
158 |
+
print("No tool calls detected, ending execution.")
|
159 |
+
return END
|
160 |
+
|
161 |
+
|
162 |
+
### Add conditional edges to the graph
|
163 |
+
uncompiled_graph.add_conditional_edges(
|
164 |
+
"agent",
|
165 |
+
should_continue
|
166 |
+
)
|
167 |
+
|
168 |
+
uncompiled_graph.add_edge("action", "agent")
|
169 |
+
compiled_graph = uncompiled_graph.compile()
|
170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
|
|
172 |
|
173 |
|
174 |
+
@cl.on_chat_start
|
175 |
+
async def start():
|
176 |
+
cl.user_session.set("compiled_graph", compiled_graph)
|
177 |
+
|
178 |
@cl.on_message
|
179 |
+
async def handle(message: cl.Message):
|
180 |
+
"""Handle user messages, invoke the agent graph, and send responses."""
|
181 |
+
|
182 |
+
compiled_graph = cl.user_session.get("compiled_graph") # Retrieve the stored graph
|
183 |
+
|
184 |
+
# Initialize the state for the agent graph
|
185 |
+
state = {"messages": [HumanMessage(content=message.content)]}
|
186 |
|
187 |
+
# Invoke the agent graph asynchronously
|
188 |
+
response = await compiled_graph.ainvoke(state)
|
189 |
|
190 |
+
# Extract the model's response and send it to the user
|
191 |
+
final_message = response["messages"][-1].content # Extract last message content
|
192 |
|
193 |
+
await cl.Message(content=final_message).send()
|
pyproject.toml
CHANGED
@@ -1,30 +1,31 @@
|
|
1 |
[project]
|
2 |
name = "midterm"
|
3 |
version = "0.1.0"
|
4 |
-
description = "
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.13"
|
7 |
dependencies = [
|
8 |
-
"
|
9 |
-
"
|
10 |
-
"
|
11 |
-
"
|
12 |
-
"
|
13 |
-
"
|
14 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
"python-pptx==1.0.2",
|
16 |
-
"
|
17 |
-
"
|
18 |
-
"
|
19 |
-
"
|
20 |
-
|
21 |
-
"wandb",
|
22 |
-
"accelerate>=0.26.0",
|
23 |
-
"langgraph==0.1.6",
|
24 |
-
"arxiv",
|
25 |
-
"langchain-qdrant==0.1.4",
|
26 |
-
"tiktoken",
|
27 |
-
"xmltodict",
|
28 |
-
"rapidfuzz",
|
29 |
-
"chainlit>=0.7.0"
|
30 |
-
]
|
|
|
1 |
[project]
|
2 |
name = "midterm"
|
3 |
version = "0.1.0"
|
4 |
+
description = "Add your description here"
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.13"
|
7 |
dependencies = [
|
8 |
+
"arxiv==2.1.3",
|
9 |
+
"beautifulsoup4==4.13.3",
|
10 |
+
"chainlit==2.2.1",
|
11 |
+
"cohere==5.13.12",
|
12 |
+
"datasets==3.3.1",
|
13 |
+
"faiss-cpu==1.10.0",
|
14 |
+
"langchain-cohere==0.4.2",
|
15 |
+
"langchain-community==0.3.14",
|
16 |
+
"langchain-huggingface==0.1.2",
|
17 |
+
"langchain-openai==0.2.14",
|
18 |
+
"langchain-qdrant==0.2.0",
|
19 |
+
"langgraph==0.2.61",
|
20 |
+
"lxml==5.3.1",
|
21 |
+
"nltk==3.8.1",
|
22 |
+
"numpy==2.2.3",
|
23 |
+
"pyarrow==19.0.1",
|
24 |
+
"pymupdf==1.25.3",
|
25 |
+
"python-dotenv>=1.0.1",
|
26 |
"python-pptx==1.0.2",
|
27 |
+
"ragas==0.2.10",
|
28 |
+
"sentence-transformers==3.4.1",
|
29 |
+
"unstructured==0.14.8",
|
30 |
+
"websockets>=15.0",
|
31 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|