Chibueze-Kingsley commited on
Commit
2b9e3bc
·
verified ·
1 Parent(s): 20bf199

Update qwen_app.py

Browse files
Files changed (1) hide show
  1. qwen_app.py +212 -153
qwen_app.py CHANGED
@@ -1,153 +1,212 @@
1
- import os
2
- from typing import List, Any
3
- from chainlit.types import AskFileResponse
4
- import tempfile
5
- import shutil
6
-
7
- # Text processing
8
- from langchain.text_splitter import CharacterTextSplitter
9
- from langchain_community.document_loaders import TextLoader, PyPDFLoader
10
- from langchain.docstore.document import Document
11
-
12
- # Prompt templates
13
- from langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
14
-
15
- # Embeddings + VectorDB
16
- from langchain_community.embeddings import HuggingFaceEmbeddings
17
- from langchain_community.vectorstores import FAISS
18
-
19
- # OpenRouter (Qwen via OpenAI-compatible API)
20
- from langchain_openai import ChatOpenAI
21
-
22
- # UI framework
23
- import chainlit as cl
24
-
25
-
26
- # -------------------------
27
- # API Key Setup
28
- # -------------------------
29
- # Make sure you export in Colab / Terminal before running:
30
- # os.environ["OPENROUTER_API_KEY"] = "your_api_key_here"
31
- # os.environ["OPENAI_API_BASE"] = "https://openrouter.ai/api/v1"
32
-
33
-
34
- # -------------------------
35
- # File processing
36
- # -------------------------
37
- text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
38
-
39
- def process_file(file: AskFileResponse) -> List[Document]:
40
- """Load and split PDF or TXT into LangChain Documents."""
41
- suffix = f".{file.name.split('.')[-1]}"
42
- with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
43
- shutil.copyfile(file.path, temp_file.name)
44
-
45
- if file.name.lower().endswith(".pdf"):
46
- loader = PyPDFLoader(temp_file.name)
47
- else:
48
- loader = TextLoader(temp_file.name)
49
-
50
- try:
51
- docs = loader.load()
52
- texts = text_splitter.split_documents(docs)
53
- return texts
54
- finally:
55
- try:
56
- os.unlink(temp_file.name)
57
- except Exception as e:
58
- print(f"Cleanup error: {e}")
59
-
60
-
61
- # -------------------------
62
- # Retrieval QA Pipeline
63
- # -------------------------
64
- class RetrievalAugmentedQAPipeline:
65
- def __init__(self, llm: Any, vectorstore: FAISS) -> None:
66
- self.llm = llm
67
- self.vectorstore = vectorstore
68
-
69
- # Prompt definition
70
- system_template = (
71
- "You are a helpful assistant. "
72
- "Use the following context to answer a user's question. "
73
- "If the context does not contain the answer, reply with 'I don't know'."
74
- )
75
- self.prompt = ChatPromptTemplate.from_messages([
76
- SystemMessagePromptTemplate.from_template(system_template),
77
- HumanMessagePromptTemplate.from_template("Context:\n{context}\n\nQuestion:\n{question}")
78
- ])
79
-
80
- async def arun_pipeline(self, user_query: str):
81
- # Retrieve documents
82
- docs = self.vectorstore.similarity_search(user_query, k=4)
83
- context_text = "\n".join([doc.page_content for doc in docs])
84
-
85
- # Format the prompt
86
- messages = self.prompt.format_messages(context=context_text, question=user_query)
87
-
88
- # Stream response from Qwen
89
- async def generate_response():
90
- async for chunk in self.llm.astream(messages):
91
- yield chunk.content if chunk.content else ""
92
-
93
- return {"response": generate_response(), "context": docs}
94
-
95
-
96
- # -------------------------
97
- # Chainlit Handlers
98
- # -------------------------
99
- @cl.on_chat_start
100
- async def on_chat_start():
101
- files = None
102
-
103
- # Wait for user file
104
- while files is None:
105
- files = await cl.AskFileMessage(
106
- content="Please upload a Text or PDF file to begin!",
107
- accept=["text/plain", "application/pdf"],
108
- max_size_mb=5,
109
- timeout=180,
110
- ).send()
111
-
112
- file = files[0]
113
- msg = cl.Message(content=f"Processing `{file.name}`...")
114
- await msg.send()
115
-
116
- # Load & process file
117
- texts = process_file(file)
118
- print(f"Processing {len(texts)} chunks")
119
-
120
- # Create embeddings + vectorstore
121
- embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
122
- vectorstore = FAISS.from_documents(texts, embeddings)
123
-
124
- # Initialize Qwen via OpenRouter
125
- chat_llm = ChatOpenAI(
126
- model="qwen/qwen2.5-vl-72b-instruct", # you can swap with qwen-3 when available
127
- streaming=True,
128
- temperature=0,
129
- max_tokens=1024,
130
- openai_api_base=os.environ.get("OPENAI_API_BASE", "https://openrouter.ai/api/v1"),
131
- openai_api_key= "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"
132
- )
133
-
134
- # Create retrieval pipeline
135
- retrieval_qa = RetrievalAugmentedQAPipeline(llm=chat_llm, vectorstore=vectorstore)
136
-
137
- msg.content = f"Processing `{file.name}` done ✅. You can now ask questions!"
138
- await msg.update()
139
-
140
- cl.user_session.set("chain", retrieval_qa)
141
-
142
-
143
- @cl.on_message
144
- async def main(message: cl.Message):
145
- chain = cl.user_session.get("chain")
146
-
147
- msg = cl.Message(content="")
148
- result = await chain.arun_pipeline(message.content)
149
-
150
- async for stream_resp in result["response"]:
151
- await msg.stream_token(stream_resp)
152
-
153
- await msg.send()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import shutil
4
+ from typing import List, Any
5
+
6
+ from chainlit.types import AskFileResponse
7
+ import chainlit as cl
8
+
9
+ from langchain.text_splitter import CharacterTextSplitter
10
+ from langchain_community.document_loaders import TextLoader, PyPDFLoader
11
+ from langchain.docstore.document import Document
12
+
13
+ from langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
14
+ from langchain_community.embeddings import HuggingFaceEmbeddings
15
+ from langchain_community.vectorstores import FAISS
16
+ from langchain_openai import ChatOpenAI
17
+
18
+ from fastapi import FastAPI, UploadFile, Form
19
+ from fastapi.responses import JSONResponse
20
+ import uvicorn
21
+ import requests
22
+
23
+
24
+ # -------------------------
25
+ # File processing
26
+ # -------------------------
27
+ text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
28
+
29
+ def process_file_path(file_path: str) -> List[Document]:
30
+ """Load and split PDF or TXT into LangChain Documents."""
31
+ if file_path.lower().endswith(".pdf"):
32
+ loader = PyPDFLoader(file_path)
33
+ else:
34
+ loader = TextLoader(file_path)
35
+
36
+ docs = loader.load()
37
+ return text_splitter.split_documents(docs)
38
+
39
+
40
+ # -------------------------
41
+ # Retrieval QA Pipeline
42
+ # -------------------------
43
+ class RetrievalAugmentedQAPipeline:
44
+ def __init__(self, llm: Any, vectorstore: FAISS) -> None:
45
+ self.llm = llm
46
+ self.vectorstore = vectorstore
47
+
48
+ system_template = (
49
+ "You are a helpful assistant. "
50
+ "Use the following context to answer a user's question. "
51
+ "If the context does not contain the answer, reply with 'I don't know'."
52
+ )
53
+
54
+ self.prompt = ChatPromptTemplate.from_messages([
55
+ SystemMessagePromptTemplate.from_template(system_template),
56
+ HumanMessagePromptTemplate.from_template("Context:\n{context}\n\nQuestion:\n{question}")
57
+ ])
58
+
59
+ async def arun_pipeline(self, user_query: str):
60
+ docs = self.vectorstore.similarity_search(user_query, k=4)
61
+ context_text = "\n".join([doc.page_content for doc in docs])
62
+ messages = self.prompt.format_messages(context=context_text, question=user_query)
63
+
64
+ async def generate_response():
65
+ async for chunk in self.llm.astream(messages):
66
+ yield chunk.content if chunk.content else ""
67
+
68
+ return {"response": generate_response(), "context": docs}
69
+
70
+
71
+ # -------------------------
72
+ # Chainlit Handlers (UI)
73
+ # -------------------------
74
+ @cl.on_chat_start
75
+ async def on_chat_start():
76
+ files = None
77
+ while files is None:
78
+ files = await cl.AskFileMessage(
79
+ content="Please upload a Text or PDF file to begin!",
80
+ accept=["text/plain", "application/pdf"],
81
+ max_size_mb=5,
82
+ timeout=180,
83
+ ).send()
84
+
85
+ file = files[0]
86
+ msg = cl.Message(content=f"Processing `{file.name}`...")
87
+ await msg.send()
88
+
89
+ texts = process_file_path(file.path)
90
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
91
+ vectorstore = FAISS.from_documents(texts, embeddings)
92
+
93
+ chat_llm = ChatOpenAI(
94
+ model="qwen/qwen2.5-vl-72b-instruct",
95
+ streaming=True,
96
+ temperature=0,
97
+ max_tokens=1024,
98
+ openai_api_base=os.environ.get("OPENAI_API_BASE", "https://openrouter.ai/api/v1"),
99
+ openai_api_key= "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"
100
+ )
101
+
102
+ retrieval_qa = RetrievalAugmentedQAPipeline(llm=chat_llm, vectorstore=vectorstore)
103
+ msg.content = f"Processing `{file.name}` done ✅. You can now ask questions!"
104
+ await msg.update()
105
+ cl.user_session.set("chain", retrieval_qa)
106
+
107
+
108
+ @cl.on_message
109
+ async def main(message: cl.Message):
110
+ chain = cl.user_session.get("chain")
111
+ msg = cl.Message(content="")
112
+ result = await chain.arun_pipeline(message.content)
113
+
114
+ async for stream_resp in result["response"]:
115
+ await msg.stream_token(stream_resp)
116
+ await msg.send()
117
+
118
+
119
+ # -------------------------
120
+ # FastAPI (API Mode)
121
+ # -------------------------
122
+ app = FastAPI()
123
+ global_pipeline = None # Keep one pipeline in memory
124
+
125
+ @app.post("/upload/")
126
+ async def upload_file(file: UploadFile):
127
+ global global_pipeline
128
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file.filename.split('.')[-1]}") as tmp:
129
+ shutil.copyfileobj(file.file, tmp)
130
+ tmp_path = tmp.name
131
+
132
+ texts = process_file_path(tmp_path)
133
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
134
+ vectorstore = FAISS.from_documents(texts, embeddings)
135
+
136
+ chat_llm = ChatOpenAI(
137
+ model="qwen/qwen2.5-vl-72b-instruct",
138
+ streaming=True,
139
+ temperature=0,
140
+ max_tokens=1024,
141
+ openai_api_base=os.environ.get("OPENAI_API_BASE", "https://openrouter.ai/api/v1"),
142
+ openai_api_key= "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"
143
+ )
144
+
145
+ global_pipeline = RetrievalAugmentedQAPipeline(llm=chat_llm, vectorstore=vectorstore)
146
+ return JSONResponse({"status": "File uploaded and processed ✅", "filename": file.filename})
147
+
148
+ @app.post("/upload_url/")
149
+ async def upload_file_url(file_url: str = Form(...)):
150
+ global global_pipeline
151
+
152
+ # Download file from URL
153
+ response = requests.get(file_url, stream=True)
154
+ if response.status_code != 200:
155
+ return JSONResponse({"error": f"Failed to download file: {response.status_code}"}, status_code=400)
156
+
157
+ filename = file_url.split("/")[-1] or "downloaded_file.pdf"
158
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{filename.split('.')[-1]}") as tmp:
159
+ for chunk in response.iter_content(chunk_size=8192):
160
+ tmp.write(chunk)
161
+ tmp_path = tmp.name
162
+
163
+ texts = process_file_path(tmp_path)
164
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
165
+ vectorstore = FAISS.from_documents(texts, embeddings)
166
+
167
+ chat_llm = ChatOpenAI(
168
+ model="qwen/qwen2.5-vl-72b-instruct",
169
+ streaming=True,
170
+ temperature=0,
171
+ max_tokens=1024,
172
+ openai_api_base=os.environ.get("OPENAI_API_BASE", "https://openrouter.ai/api/v1"),
173
+ openai_api_key= "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"
174
+ )
175
+
176
+
177
+ global_pipeline = RetrievalAugmentedQAPipeline(llm=chat_llm, vectorstore=vectorstore)
178
+ return JSONResponse({"status": "File downloaded and processed ✅", "filename": filename})
179
+
180
+ @app.post("/ask/")
181
+ async def ask_question(question: str = Form(...)):
182
+ global global_pipeline
183
+ if not global_pipeline:
184
+ return JSONResponse({"error": "No file uploaded yet."}, status_code=400)
185
+
186
+ result = await global_pipeline.arun_pipeline(question)
187
+ response_text = ""
188
+ async for token in result["response"]:
189
+ response_text += token
190
+
191
+ return JSONResponse({"answer": response_text})
192
+
193
+
194
+ # -------------------------
195
+ # Run both Chainlit + API
196
+ # -------------------------
197
+ import os
198
+ os.environ["NGROK_AUTH_TOKEN"] = "2zuN63ZzFTYUM6ABGW4C1XJHe2x_7THDuvuKcg6fJY9h9bdCH"
199
+ # Start ngrok tunnel
200
+ import nest_asyncio
201
+ from pyngrok import ngrok
202
+ import uvicorn
203
+
204
+ # Allow nested event loops (needed in Colab)
205
+ nest_asyncio.apply()
206
+
207
+ # Expose port 8000
208
+ public_url = ngrok.connect(8000)
209
+ print("🚀 Public FastAPI URL:", public_url.public_url)
210
+
211
+ # Run app
212
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=False)