kingabzpro commited on
Commit
9cbb2e3
Β·
verified Β·
1 Parent(s): f3913f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -150
app.py CHANGED
@@ -1,189 +1,130 @@
1
- """
2
- Doc-Q&A app (Gradio 5.x + Llama-Index 0.12.x, June 2025)
3
-
4
- Key upgrades
5
- ------------
6
- β–ͺ Gradio 5.34β€”new event system (`upload`, `clear` etc.)
7
- β–ͺ Llama-Index 0.12.42β€”`VectorStoreIndex.from_documents` signature unchanged
8
- β–ͺ MixedbreadAIEmbedding 0.3.0 ➜ supports `batch_size`, `timeout`
9
- β–ͺ Tenacity for exponential-back-off when MXBAI returns 5xx / rate limits
10
- """
11
-
12
- from __future__ import annotations
13
-
14
  import os
15
- from pathlib import Path
16
- from typing import List
17
 
18
  import gradio as gr
19
- from tenacity import retry, wait_exponential, stop_after_attempt
20
- from mixedbread_ai.core.api_error import ApiError
21
-
22
  from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
23
  from llama_index.embeddings.mixedbreadai import MixedbreadAIEmbedding
24
  from llama_index.llms.groq import Groq
25
  from llama_parse import LlamaParse
26
 
27
- # ──────────────────────────────────────────────────────────────────
28
- # 1. Environment variables (fail-fast if missing)
29
- # ──────────────────────────────────────────────────────────────────
30
- LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
31
- GROQ_API_KEY = os.getenv("GROQ_API_KEY")
32
- MXBAI_API_KEY = os.getenv("MXBAI_API_KEY")
33
-
34
- if not all([LLAMA_CLOUD_API_KEY, GROQ_API_KEY, MXBAI_API_KEY]):
35
- raise EnvironmentError(
36
- "LLAMA_CLOUD_API_KEY, GROQ_API_KEY and MXBAI_API_KEY must be set in the env."
37
  )
38
 
39
- # ──────────────────────────────────────────────────────────────────
40
- # 2. Models & parsers (latest defaults - June 2025)
41
- # ──────────────────────────────────────────────────────────────────
42
- LLM_MODEL = "llama-3.1-70b-versatile" # Groq’s best for Q&A
43
- EMBED_MODEL = "mixedbread-ai/mxbai-embed-large-v1" # 1024-dim
44
-
45
- parser = LlamaParse(api_key=LLAMA_CLOUD_API_KEY, result_type="markdown")
46
-
47
- SUPPORTED_EXTS = (
48
- ".pdf", ".docx", ".doc", ".txt", ".csv", ".xlsx",
49
- ".pptx", ".html", ".jpg", ".jpeg", ".png", ".webp", ".svg",
50
- )
51
- file_extractor = {ext: parser for ext in SUPPORTED_EXTS}
52
-
53
- embed_model = MixedbreadAIEmbedding(
54
- api_key = MXBAI_API_KEY,
55
- model_name = EMBED_MODEL,
56
- batch_size = 8, # keep requests < 100 KB
57
- timeout = 60, # generous server-side processing window
58
- )
59
-
60
- llm = Groq(model=LLM_MODEL, api_key=GROQ_API_KEY)
61
-
62
- # A simple global cache (could be swapped for Redis, etc.)
63
- vector_index: VectorStoreIndex | None = None
64
-
65
-
66
- # ──────────────────────────────────────────────────────────────────
67
- # 3. Helper wrappers
68
- # ──────────────────────────────────────────────────────────────────
69
- @retry(
70
- wait=wait_exponential(multiplier=2, min=4, max=32),
71
- stop=stop_after_attempt(4),
72
- retry_error_callback=lambda retry_state: None, # bubble up as None
73
- reraise=False,
74
- )
75
- def _safe_build_index(docs) -> VectorStoreIndex | None:
76
- """Retry MXBAI 503 / 429 transparently."""
77
- try:
78
- return VectorStoreIndex.from_documents(docs, embed_model=embed_model)
79
- except ApiError as e:
80
- # Tenacity will catch and retry unless non-5xx / non-429
81
- if e.status_code not in (429, 500, 502, 503, 504):
82
- raise
83
- raise # trigger retry
84
-
85
-
86
- def load_files(file: Path | None) -> str:
87
- """Parse uploaded file and build vector index (with retries)."""
88
- global vector_index
89
- if file is None:
90
- return "⚠️ No file selected."
91
-
92
- if file.suffix.lower() not in SUPPORTED_EXTS:
93
- allow = ", ".join(SUPPORTED_EXTS)
94
- return f"⚠️ Unsupported file type. Allowed: {allow}"
95
 
96
- docs = SimpleDirectoryReader(
97
- input_files=[str(file)],
98
- file_extractor=file_extractor,
99
- ).load_data()
100
 
101
- idx = _safe_build_index(docs)
102
- if idx is None:
103
- return "🚧 Embedding service busy. Please retry in ~1 minute."
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- vector_index = idx
106
- return f"βœ… Parsed **{file.name}** β€” you can start chatting!"
107
 
 
108
 
109
- def respond(message: str, history: List[List[str]]):
110
- """Stream answer chunks to the Chatbot."""
111
- if vector_index is None:
112
- yield "➑️ Please upload a document first."
113
- return
114
 
115
- query_engine = vector_index.as_query_engine(streaming=True, llm=llm)
116
- response = query_engine.query(message)
117
 
118
- partial = ""
119
- for token in response.response_gen:
120
- partial += token
121
- yield partial
122
-
123
-
124
- def clear():
125
- """Reset everything (file widget + status + index)."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  global vector_index
127
  vector_index = None
128
- return None, "", None # file_input, status_md, chatbot history
129
 
130
 
131
- # ──────────────────────────────────────────────────────────────────
132
- # 4. Gradio UI (5.x syntax)
133
- # ──────────────────────────────────────────────────────────────────
134
  with gr.Blocks(
135
  theme=gr.themes.Default(
136
  primary_hue="green",
137
  secondary_hue="blue",
138
  font=[gr.themes.GoogleFont("Poppins")],
139
  ),
140
- css="footer {visibility:hidden}",
141
  ) as demo:
142
-
143
- gr.Markdown("<h1 style='text-align:center'>DataCamp Doc Q&A πŸ€–πŸ“‘</h1>")
144
-
145
  with gr.Row():
146
  with gr.Column(scale=1):
147
  file_input = gr.File(
148
- label="Upload document",
149
- file_count="single",
150
- type="filepath",
151
- show_label=True,
152
  )
153
- status_md = gr.Markdown()
154
  with gr.Row():
155
- clear_btn = gr.Button("Reset πŸ”„", variant="secondary")
156
-
 
157
  with gr.Column(scale=3):
158
- chatbot = gr.Chatbot(height=340)
159
- txt_box = gr.Textbox(
160
- placeholder="Ask something about the uploaded document…",
161
- container=False,
162
- scale=7,
 
 
 
 
163
  )
164
- send_btn = gr.Button("Send", variant="primary")
165
 
166
- # events (v5 style)
167
- file_input.upload(
168
- fn=load_files,
169
- inputs=file_input,
170
- outputs=status_md,
171
- )
172
- send_btn.click(
173
- fn=respond,
174
- inputs=[txt_box, chatbot],
175
- outputs=chatbot,
176
  )
177
- clear_btn.click(
178
- fn=clear,
179
- outputs=[file_input, status_md, chatbot],
180
- )
181
-
182
- # optional: disable public OpenAPI schema (old crash guard)
183
- demo.queue(api_open=False)
184
 
185
- # ──────────────────────────────────────────────────────────────────
186
- # 5. Launch
187
- # ──────────────────────────────────────────────────────────────────
188
  if __name__ == "__main__":
189
- demo.launch(share=True, server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
 
2
 
3
  import gradio as gr
 
 
 
4
  from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
5
  from llama_index.embeddings.mixedbreadai import MixedbreadAIEmbedding
6
  from llama_index.llms.groq import Groq
7
  from llama_parse import LlamaParse
8
 
9
+ # API keys
10
+ llama_cloud_key = os.environ.get("LLAMA_CLOUD_API_KEY")
11
+ groq_key = os.environ.get("GROQ_API_KEY")
12
+ mxbai_key = os.environ.get("MXBAI_API_KEY")
13
+ if not (llama_cloud_key and groq_key and mxbai_key):
14
+ raise ValueError(
15
+ "API Keys not found! Ensure they are passed to the Docker container."
 
 
 
16
  )
17
 
18
+ # models name
19
+ llm_model_name = "llama-3.1-70b-versatile"
20
+ embed_model_name = "mixedbread-ai/mxbai-embed-large-v1"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ # Initialize the parser
23
+ parser = LlamaParse(api_key=llama_cloud_key, result_type="markdown")
 
 
24
 
25
+ # Define file extractor with various common extensions
26
+ file_extractor = {
27
+ ".pdf": parser,
28
+ ".docx": parser,
29
+ ".doc": parser,
30
+ ".txt": parser,
31
+ ".csv": parser,
32
+ ".xlsx": parser,
33
+ ".pptx": parser,
34
+ ".html": parser,
35
+ ".jpg": parser,
36
+ ".jpeg": parser,
37
+ ".png": parser,
38
+ ".webp": parser,
39
+ ".svg": parser,
40
+ }
41
 
42
+ # Initialize the embedding model
43
+ embed_model = MixedbreadAIEmbedding(api_key=mxbai_key, model_name=embed_model_name)
44
 
45
+ # Initialize the LLM
46
 
47
+ llm = Groq(model="llama-3.1-70b-versatile", api_key=groq_key)
 
 
 
 
48
 
 
 
49
 
50
+ # File processing function
51
+ def load_files(file_path: str):
52
+ global vector_index
53
+ if not file_path:
54
+ return "No file path provided. Please upload a file."
55
+
56
+ valid_extensions = ', '.join(file_extractor.keys())
57
+ if not any(file_path.endswith(ext) for ext in file_extractor):
58
+ return f"The parser can only parse the following file types: {valid_extensions}"
59
+
60
+ document = SimpleDirectoryReader(input_files=[file_path], file_extractor=file_extractor).load_data()
61
+ vector_index = VectorStoreIndex.from_documents(document, embed_model=embed_model)
62
+ print(f"Parsing completed for: {file_path}")
63
+ filename = os.path.basename(file_path)
64
+ return f"Ready to provide responses based on: {filename}"
65
+
66
+
67
+ # Respond function
68
+ def respond(message, history):
69
+ try:
70
+ # Use the preloaded LLM
71
+ query_engine = vector_index.as_query_engine(streaming=True, llm=llm)
72
+ streaming_response = query_engine.query(message)
73
+ partial_text = ""
74
+ for new_text in streaming_response.response_gen:
75
+ partial_text += new_text
76
+ # Yield an empty string to cleanup the message textbox and the updated conversation history
77
+ yield partial_text
78
+ except (AttributeError, NameError):
79
+ print("An error occurred while processing your request.")
80
+ yield "Please upload the file to begin chat."
81
+
82
+
83
+ # Clear function
84
+ def clear_state():
85
  global vector_index
86
  vector_index = None
87
+ return [None, None, None]
88
 
89
 
90
+ # UI Setup
 
 
91
  with gr.Blocks(
92
  theme=gr.themes.Default(
93
  primary_hue="green",
94
  secondary_hue="blue",
95
  font=[gr.themes.GoogleFont("Poppins")],
96
  ),
97
+ css="footer {visibility: hidden}",
98
  ) as demo:
99
+ gr.Markdown("# DataCamp Doc Q&A πŸ€–πŸ“ƒ")
 
 
100
  with gr.Row():
101
  with gr.Column(scale=1):
102
  file_input = gr.File(
103
+ file_count="single", type="filepath", label="Upload Document"
 
 
 
104
  )
 
105
  with gr.Row():
106
+ btn = gr.Button("Submit", variant="primary")
107
+ clear = gr.Button("Clear")
108
+ output = gr.Textbox(label="Status")
109
  with gr.Column(scale=3):
110
+ chatbot = gr.ChatInterface(
111
+ fn=respond,
112
+ chatbot=gr.Chatbot(height=300),
113
+ theme="soft",
114
+ show_progress="full",
115
+ textbox=gr.Textbox(
116
+ placeholder="Ask questions about the uploaded document!",
117
+ container=False,
118
+ ),
119
  )
 
120
 
121
+ # Set up Gradio interactions
122
+ btn.click(fn=load_files, inputs=file_input, outputs=output)
123
+ clear.click(
124
+ fn=clear_state, # Use the clear_state function
125
+ outputs=[file_input, output],
 
 
 
 
 
126
  )
 
 
 
 
 
 
 
127
 
128
+ # Launch the demo
 
 
129
  if __name__ == "__main__":
130
+ demo.launch()