Gbenga Awodokun commited on
Commit
d88d205
·
1 Parent(s): d33c335

Add application file

Browse files
Files changed (1) hide show
  1. app.py +601 -0
app.py ADDED
@@ -0,0 +1,601 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ # In[1]:
5
+
6
+
7
+ #!/usr/bin/env python
8
+ # coding: utf-8
9
+ import os
10
+ import json
11
+ import requests
12
+ import gradio as gr
13
+ from typing import Literal, List, Dict, Any
14
+ from pydantic import BaseModel, Field
15
+ from dotenv import load_dotenv
16
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
17
+ from langchain_community.document_loaders import WebBaseLoader
18
+ from langchain_community.vectorstores import Chroma
19
+ from langchain_community.embeddings import HuggingFaceEmbeddings
20
+ from langchain_community.tools.tavily_search import TavilySearchResults
21
+ from langchain.schema import Document
22
+ from langgraph.graph import END, StateGraph
23
+ from typing_extensions import TypedDict
24
+
25
+ # Load environment variables
26
+ load_dotenv()
27
+
28
+ # Configuration
29
+ BASE_URL = "https://api.llama.com/v1"
30
+ LLAMA_API_KEY = os.environ.get('LLAMA_API_KEY')
31
+
32
+ # Initialize global variables
33
+ vectorstore = None
34
+ retriever = None
35
+ web_search_tool = None
36
+ app = None
37
+
38
+ class RouteQuery(BaseModel):
39
+ """Route a user query to the most relevant datasource."""
40
+ datasource: Literal["vectorstore", "web_search"] = Field(
41
+ ...,
42
+ description="Given a user question choose to route it to web search or a vectorstore.",
43
+ )
44
+
45
+ class GraphState(TypedDict):
46
+ """Represents the state of our graph."""
47
+ question: str
48
+ generation: str
49
+ web_search: str
50
+ documents: List[str]
51
+
52
+ def initialize_system():
53
+ """Initialize the RAG system with vectorstore and workflow."""
54
+ global vectorstore, retriever, web_search_tool, app
55
+
56
+ try:
57
+ # Read configuration
58
+ with open('wragby.json', 'r') as file:
59
+ data = json.load(file)
60
+ urls = data["urls"]
61
+
62
+ # Build Index
63
+ docs = [WebBaseLoader(url).load() for url in urls]
64
+ docs_list = [item for sublist in docs for item in sublist]
65
+
66
+ text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
67
+ chunk_size=500, chunk_overlap=0
68
+ )
69
+ doc_splits = text_splitter.split_documents(docs_list)
70
+
71
+ vectorstore = Chroma.from_documents(
72
+ documents=doc_splits,
73
+ collection_name="rag-chroma",
74
+ embedding=HuggingFaceEmbeddings(),
75
+ )
76
+ retriever = vectorstore.as_retriever()
77
+
78
+ # Initialize web search
79
+ web_search_tool = TavilySearchResults(k=3)
80
+
81
+ # Build workflow
82
+ app = build_workflow()
83
+
84
+ return "✅ System initialized successfully!"
85
+
86
+ except Exception as e:
87
+ return f"❌ Error initializing system: {str(e)}"
88
+
89
+ def chat_completion(messages, model="Llama-4-Scout-17B-16E-Instruct-FP8", max_tokens=1024):
90
+ """Make API call to Llama."""
91
+ headers = {
92
+ "Content-Type": "application/json",
93
+ "Authorization": f"Bearer {LLAMA_API_KEY}",
94
+ }
95
+ payload = {
96
+ "messages": messages,
97
+ "model": model,
98
+ "max_tokens": max_tokens,
99
+ "stream": False,
100
+ }
101
+ response = requests.post("https://api.llama.com/v1/chat/completions", headers=headers, json=payload)
102
+ return response
103
+
104
+ def route_query(question: str) -> RouteQuery:
105
+ """Route a user question using Llama API with structured output."""
106
+ system_message = """You are an expert at routing a user question to a vectorstore or web search.
107
+ The vectorstore contains documents related to the business Wragby Solutions, their product information, and customer sales.
108
+ Use the vectorstore for questions on these topics. Otherwise, use web-search.
109
+
110
+ You must respond with a JSON object in this exact format:
111
+ {"datasource": "vectorstore"} or {"datasource": "web_search"}
112
+
113
+ Only respond with the JSON object, no additional text."""
114
+
115
+ messages = [
116
+ {"role": "system", "content": system_message},
117
+ {"role": "user", "content": question}
118
+ ]
119
+
120
+ try:
121
+ response = chat_completion(messages, max_tokens=50)
122
+ content = response.json()['completion_message']['content']['text'].strip()
123
+ route_data = json.loads(content)
124
+ return RouteQuery(**route_data)
125
+ except Exception as e:
126
+ print(f"Error parsing response: {e}")
127
+ return RouteQuery(datasource="web_search")
128
+
129
+ def format_docs(docs):
130
+ """Format a list of documents into a single string."""
131
+ if not docs:
132
+ return ""
133
+
134
+ formatted_docs = []
135
+ for doc in docs:
136
+ try:
137
+ if hasattr(doc, 'page_content'):
138
+ formatted_docs.append(doc.page_content)
139
+ elif isinstance(doc, dict) and 'content' in doc:
140
+ formatted_docs.append(doc['content'])
141
+ elif isinstance(doc, dict) and 'page_content' in doc:
142
+ formatted_docs.append(doc['page_content'])
143
+ elif isinstance(doc, str):
144
+ formatted_docs.append(doc)
145
+ else:
146
+ formatted_docs.append(str(doc))
147
+ except Exception as e:
148
+ print(f"Error processing document: {e}")
149
+ formatted_docs.append(str(doc))
150
+
151
+ return "\n\n".join(formatted_docs)
152
+
153
+ def rag_generate_answer(question: str, docs: list) -> str:
154
+ """Generate an answer using RAG."""
155
+ system_message = """You are an assistant for question-answering tasks.
156
+ Use the following pieces of retrieved context to answer the question.
157
+ If you don't know the answer, just say that you don't know.
158
+ Use three sentences maximum and keep the answer concise."""
159
+
160
+ context = format_docs(docs)
161
+ user_message = f"""Context: {context}
162
+
163
+ Question: {question}
164
+
165
+ Answer:"""
166
+
167
+ messages = [
168
+ {"role": "system", "content": system_message},
169
+ {"role": "user", "content": user_message}
170
+ ]
171
+
172
+ try:
173
+ response = chat_completion(messages, max_tokens=512)
174
+ answer = response.json()['completion_message']['content']['text'].strip()
175
+ return answer
176
+ except Exception as e:
177
+ print(f"Error generating RAG answer: {e}")
178
+ return "I apologize, but I encountered an error while generating an answer."
179
+
180
+ def grade_answer_quality(question: str, generation: str) -> dict:
181
+ """Grade whether an LLM generation addresses/resolves the user question."""
182
+ system_message = """You are a grader assessing whether an answer addresses / resolves a question.
183
+ Give a binary score 'yes' or 'no'. 'Yes' means that the answer resolves the question.
184
+
185
+ You must respond with exactly one word:
186
+ - yes (if the answer addresses and resolves the question)
187
+ - no (if the answer does not address or resolve the question)
188
+
189
+ Only respond with 'yes' or 'no', no additional text or explanation."""
190
+
191
+ user_message = f"User question: \n\n {question} \n\n LLM generation: {generation}"
192
+ messages = [
193
+ {"role": "system", "content": system_message},
194
+ {"role": "user", "content": user_message}
195
+ ]
196
+
197
+ try:
198
+ response = chat_completion(messages, max_tokens=10)
199
+ content = response.json()['completion_message']['content']['text'].strip().lower()
200
+
201
+ if "yes" in content:
202
+ score = "yes"
203
+ elif "no" in content:
204
+ score = "no"
205
+ else:
206
+ score = "no"
207
+
208
+ return {"score": score}
209
+ except Exception as e:
210
+ print(f"Error calling Llama API for answer grading: {e}")
211
+ return {"score": "no"}
212
+
213
+ def grade_hallucinations(documents: list, generation: str) -> dict:
214
+ """Grade whether an LLM generation is grounded in the provided documents."""
215
+ system_message = """You are a grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts.
216
+ Give a binary score 'yes' or 'no'. 'Yes' means that the answer is grounded in / supported by the set of facts.
217
+
218
+ You must respond with exactly one word:
219
+ - yes (if the generation is grounded in the facts)
220
+ - no (if the generation contains hallucinations or unsupported claims)
221
+
222
+ Only respond with 'yes' or 'no', no additional text or explanation."""
223
+
224
+ if isinstance(documents, list):
225
+ if documents and hasattr(documents[0], 'page_content'):
226
+ docs_text = format_docs(documents)
227
+ else:
228
+ docs_text = "\n\n".join(documents)
229
+ else:
230
+ docs_text = str(documents)
231
+
232
+ user_message = f"Set of facts: \n\n {docs_text} \n\n LLM generation: {generation}"
233
+ messages = [
234
+ {"role": "system", "content": system_message},
235
+ {"role": "user", "content": user_message}
236
+ ]
237
+
238
+ try:
239
+ response = chat_completion(messages, max_tokens=10)
240
+ content = response.json()['completion_message']['content']['text'].strip().lower()
241
+
242
+ if "yes" in content:
243
+ score = "yes"
244
+ elif "no" in content:
245
+ score = "no"
246
+ else:
247
+ score = "no"
248
+
249
+ return {"score": score}
250
+ except Exception as e:
251
+ print(f"Error calling Llama API for hallucination grading: {e}")
252
+ return {"score": "no"}
253
+
254
+ def grade_document_relevance(question: str, document: str) -> dict:
255
+ """Grade the relevance of a retrieved document to a user question."""
256
+ system_message = """You are a grader assessing relevance of a retrieved document to a user question.
257
+ If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant.
258
+ It does not need to be a stringent test. The goal is to filter out erroneous retrievals.
259
+ Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.
260
+
261
+ You must respond with exactly one word:
262
+ - yes (if document is relevant)
263
+ - no (if document is not relevant)
264
+
265
+ Only respond with 'yes' or 'no', no additional text or explanation."""
266
+
267
+ user_message = f"Retrieved document: \n\n {document} \n\n User question: {question}"
268
+ messages = [
269
+ {"role": "system", "content": system_message},
270
+ {"role": "user", "content": user_message}
271
+ ]
272
+
273
+ try:
274
+ response = chat_completion(messages)
275
+ content = response.json()['completion_message']['content']['text'].strip().lower()
276
+
277
+ if "yes" in content:
278
+ score = "yes"
279
+ elif "no" in content:
280
+ score = "no"
281
+ else:
282
+ score = "no"
283
+
284
+ return {"score": score}
285
+ except Exception as e:
286
+ print(f"Error calling Llama API for document grading: {e}")
287
+ return {"score": "no"}
288
+
289
+ # Workflow Nodes
290
+ def retrieve(state):
291
+ """Retrieve documents from vectorstore"""
292
+ print("---RETRIEVE---")
293
+ question = state["question"]
294
+ documents = retriever.invoke(question)
295
+ return {"documents": documents, "question": question}
296
+
297
+ def generate(state):
298
+ """Generate answer using RAG on retrieved documents"""
299
+ print("---GENERATE---")
300
+ question = state["question"]
301
+ documents = state["documents"]
302
+ generation = rag_generate_answer(question, documents)
303
+ return {"documents": documents, "question": question, "generation": generation}
304
+
305
+ def grade_documents(state):
306
+ """Determines whether the retrieved documents are relevant to the question"""
307
+ print("---CHECK DOCUMENT RELEVANCE TO QUESTION---")
308
+ question = state["question"]
309
+ documents = state["documents"]
310
+
311
+ filtered_docs = []
312
+ web_search = "No"
313
+ for d in documents:
314
+ score = grade_document_relevance(question, d.page_content)
315
+ grade = score["score"]
316
+ if grade.lower() == "yes":
317
+ print("---GRADE: DOCUMENT RELEVANT---")
318
+ filtered_docs.append(d)
319
+ else:
320
+ print("---GRADE: DOCUMENT NOT RELEVANT---")
321
+ web_search = "Yes"
322
+ continue
323
+ return {"documents": filtered_docs, "question": question, "web_search": web_search}
324
+
325
+ def web_search(state):
326
+ """Web search based on the question"""
327
+ print("---WEB SEARCH---")
328
+ question = state["question"]
329
+ documents = state["documents"]
330
+
331
+ docs = web_search_tool.invoke({"query": question})
332
+ web_results = "\n".join([d["content"] for d in docs])
333
+ web_results = Document(page_content=web_results)
334
+ if documents is not None:
335
+ documents.append(web_results)
336
+ else:
337
+ documents = [web_results]
338
+ return {"documents": documents, "question": question}
339
+
340
+ def route_question(state):
341
+ """Route question to web search or RAG."""
342
+ print("---ROUTE QUESTION---")
343
+ question = state["question"]
344
+ source = route_query(question)
345
+ if source.datasource == 'web_search':
346
+ print("---ROUTE QUESTION TO WEB SEARCH---")
347
+ return "websearch"
348
+ elif source.datasource == 'vectorstore':
349
+ print("---ROUTE QUESTION TO RAG---")
350
+ return "vectorstore"
351
+
352
+ def decide_to_generate(state):
353
+ """Determines whether to generate an answer, or add web search"""
354
+ print("---ASSESS GRADED DOCUMENTS---")
355
+ web_search = state["web_search"]
356
+
357
+ if web_search == "Yes":
358
+ print("---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, INCLUDE WEB SEARCH---")
359
+ return "websearch"
360
+ else:
361
+ print("---DECISION: GENERATE---")
362
+ return "generate"
363
+
364
+ def grade_generation_v_documents_and_question(state):
365
+ """Determines whether the generation is grounded in the document and answers question."""
366
+ print("---CHECK HALLUCINATIONS---")
367
+ question = state["question"]
368
+ documents = state["documents"]
369
+ generation = state["generation"]
370
+
371
+ score = grade_hallucinations(documents, generation)
372
+ grade = score["score"]
373
+
374
+ if grade == "yes":
375
+ print("---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---")
376
+ print("---GRADE GENERATION vs QUESTION---")
377
+ score = grade_answer_quality(question, generation)
378
+ grade = score["score"]
379
+ if grade == "yes":
380
+ print("---DECISION: GENERATION ADDRESSES QUESTION---")
381
+ return "useful"
382
+ else:
383
+ print("---DECISION: GENERATION DOES NOT ADDRESS QUESTION---")
384
+ return "not useful"
385
+ else:
386
+ print("---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---")
387
+ return "not supported"
388
+
389
+ def build_workflow():
390
+ """Build the RAG workflow graph."""
391
+ workflow = StateGraph(GraphState)
392
+
393
+ # Define the nodes
394
+ workflow.add_node("websearch", web_search)
395
+ workflow.add_node("retrieve", retrieve)
396
+ workflow.add_node("grade_documents", grade_documents)
397
+ workflow.add_node("generate", generate)
398
+
399
+ # Build graph
400
+ workflow.set_conditional_entry_point(
401
+ route_question,
402
+ {
403
+ "websearch": "websearch",
404
+ "vectorstore": "retrieve",
405
+ },
406
+ )
407
+
408
+ workflow.add_edge("retrieve", "grade_documents")
409
+ workflow.add_conditional_edges(
410
+ "grade_documents",
411
+ decide_to_generate,
412
+ {
413
+ "websearch": "websearch",
414
+ "generate": "generate",
415
+ },
416
+ )
417
+ workflow.add_edge("websearch", "generate")
418
+ workflow.add_conditional_edges(
419
+ "generate",
420
+ grade_generation_v_documents_and_question,
421
+ {
422
+ "not supported": "generate",
423
+ "useful": END,
424
+ "not useful": "websearch",
425
+ },
426
+ )
427
+
428
+ return workflow.compile().with_config({"run_name": "Wragby Solutions Assistant"})
429
+
430
+ def process_question(question: str, history: List[List[str]]) -> tuple:
431
+ """Process a question through the RAG system and return the answer with sources."""
432
+ if not question.strip():
433
+ return history, "Please enter a question."
434
+
435
+ if app is None:
436
+ return history, "❌ System not initialized. Please click 'Initialize System' first."
437
+
438
+ try:
439
+ # Process through the workflow
440
+ inputs = {"question": question}
441
+ final_state = None
442
+
443
+ for output in app.stream(inputs):
444
+ for key, value in output.items():
445
+ print(f"Finished running: {key}")
446
+ final_state = value
447
+
448
+ if final_state and "generation" in final_state:
449
+ answer = final_state["generation"]
450
+
451
+ # Get source information
452
+ sources = []
453
+ if "documents" in final_state and final_state["documents"]:
454
+ for i, doc in enumerate(final_state["documents"][:3]): # Show top 3 sources
455
+ if hasattr(doc, 'metadata') and 'source' in doc.metadata:
456
+ sources.append(f"Source {i+1}: {doc.metadata['source']}")
457
+ else:
458
+ sources.append(f"Source {i+1}: Retrieved document")
459
+
460
+ # Format response with sources
461
+ if sources:
462
+ full_response = f"{answer}\n\n**Sources:**\n" + "\n".join(sources)
463
+ else:
464
+ full_response = answer
465
+
466
+ # Update chat history
467
+ history.append([question, full_response])
468
+ return history, ""
469
+ else:
470
+ history.append([question, "I apologize, but I couldn't generate an answer for your question."])
471
+ return history, ""
472
+
473
+ except Exception as e:
474
+ error_msg = f"❌ Error processing question: {str(e)}"
475
+ history.append([question, error_msg])
476
+ return history, ""
477
+
478
+ def clear_chat():
479
+ """Clear the chat history."""
480
+ return [], ""
481
+
482
+ # Create Gradio Interface
483
+ def create_gradio_app():
484
+ """Create and configure the Gradio interface."""
485
+
486
+ # Custom CSS for better styling
487
+ css = """
488
+ .gradio-container {
489
+ max-width: 1200px !important;
490
+ margin: auto !important;
491
+ }
492
+ .chat-container {
493
+ height: 500px !important;
494
+ }
495
+ .title {
496
+ text-align: center;
497
+ color: #2D5AA0;
498
+ margin-bottom: 20px;
499
+ }
500
+ .description {
501
+ text-align: center;
502
+ color: #666;
503
+ margin-bottom: 30px;
504
+ }
505
+ """
506
+
507
+ with gr.Blocks(css=css, title="Wragby Solutions Q&A Assistant") as demo:
508
+ gr.HTML("""
509
+ <div class="title">
510
+ <h1>🤖 Wragby Solutions Q&A Assistant</h1>
511
+ </div>
512
+ <div class="description">
513
+ <p>Ask questions about Wragby Solutions products, services, and business information.
514
+ The system will search through company documents and the web to provide accurate answers.</p>
515
+ </div>
516
+ """)
517
+
518
+ with gr.Row():
519
+ with gr.Column(scale=3):
520
+ # Chat interface
521
+ chatbot = gr.Chatbot(
522
+ label="Chat History",
523
+ height=500,
524
+ show_label=True,
525
+ container=True,
526
+ elem_classes=["chat-container"]
527
+ )
528
+
529
+ with gr.Row():
530
+ question_input = gr.Textbox(
531
+ placeholder="Ask a question about Wragby Solutions...",
532
+ label="Your Question",
533
+ lines=2,
534
+ scale=4
535
+ )
536
+ submit_btn = gr.Button("Submit", variant="primary", scale=1)
537
+
538
+ with gr.Row():
539
+ clear_btn = gr.Button("Clear Chat", variant="secondary")
540
+
541
+ with gr.Column(scale=1):
542
+ # System controls
543
+ gr.HTML("<h3>System Controls</h3>")
544
+
545
+ init_btn = gr.Button("Initialize System", variant="primary")
546
+ init_status = gr.Textbox(
547
+ label="System Status",
548
+ value="Click 'Initialize System' to start",
549
+ interactive=False
550
+ )
551
+
552
+ gr.HTML("""
553
+ <div style="margin-top: 20px; padding: 15px; background-color: #f0f8ff; border-radius: 5px;">
554
+ <h4>💡 Sample Questions:</h4>
555
+ <ul>
556
+ <li>What are the types of solutions offered by Wbizmanager?</li>
557
+ <li>How can SMBs use Wbizmanager?</li>
558
+ <li>What SAP solutions are available?</li>
559
+ <li>Tell me about Wragby Solutions services</li>
560
+ </ul>
561
+ </div>
562
+ """)
563
+
564
+ # Event handlers
565
+ init_btn.click(
566
+ fn=initialize_system,
567
+ outputs=[init_status]
568
+ )
569
+
570
+ submit_btn.click(
571
+ fn=process_question,
572
+ inputs=[question_input, chatbot],
573
+ outputs=[chatbot, question_input]
574
+ )
575
+
576
+ question_input.submit(
577
+ fn=process_question,
578
+ inputs=[question_input, chatbot],
579
+ outputs=[chatbot, question_input]
580
+ )
581
+
582
+ clear_btn.click(
583
+ fn=clear_chat,
584
+ outputs=[chatbot, question_input]
585
+ )
586
+
587
+ return demo
588
+
589
+
590
+ # In[2]:
591
+
592
+
593
+ # Create and launch the Gradio app
594
+ demo = create_gradio_app()
595
+ demo.launch(
596
+ server_name="0.0.0.0",
597
+ server_port=7860,
598
+ share=True, # Set to True if you want to create a public link
599
+ debug=True
600
+ )
601
+