krishnadhulipalla commited on
Commit
5757dad
·
1 Parent(s): ab32d1c

added knowledge base

Browse files
Files changed (1) hide show
  1. app.py +175 -48
app.py CHANGED
@@ -3,28 +3,29 @@ import json
3
  import re
4
  import hashlib
5
  import gradio as gr
6
- import time
7
  from functools import partial
8
  import concurrent.futures
9
  from collections import defaultdict
10
  from pathlib import Path
11
- from typing import List, Dict, Any
12
  import numpy as np
13
  from dotenv import load_dotenv
14
  from rich.console import Console
15
  from rich.style import Style
 
16
  from langchain_core.runnables import RunnableLambda
17
  from langchain_nvidia_ai_endpoints import ChatNVIDIA
18
  from langchain_core.output_parsers import StrOutputParser
19
  from langchain_core.prompts import ChatPromptTemplate
20
  from langchain.schema.runnable.passthrough import RunnableAssign
21
  from langchain.text_splitter import RecursiveCharacterTextSplitter
22
- from langchain_community.embeddings import HuggingFaceEmbeddings
23
- from langchain_community.vectorstores import FAISS
24
- from langchain_community.retrievers import BM25Retriever
25
  from langchain.docstore.document import Document
 
26
  from langchain_openai import ChatOpenAI
27
  from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
 
28
 
29
  #dotenv_path = os.path.join(os.getcwd(), ".env")
30
  #load_dotenv(dotenv_path)
@@ -45,13 +46,13 @@ if not Path(FAISS_PATH).exists():
45
  if not Path(CHUNKS_PATH).exists():
46
  raise FileNotFoundError(f"Chunks file not found at {CHUNKS_PATH}")
47
 
48
- KRISHNA_BIO = """Krishna Vamsi Dhulipalla is a graduate student in Computer Science at Virginia Tech (M.Eng, expected 2024), with over 3 years of experience across data engineering, machine learning research, and real-time analytics. He specializes in building scalable data systems and intelligent LLM-powered applications, with strong expertise in Python, PyTorch, Hugging Face Transformers, and end-to-end ML pipelines.
49
 
50
  He has led projects involving retrieval-augmented generation (RAG), feature selection for genomic classification, fine-tuning domain-specific LLMs (e.g., DNABERT, HyenaDNA), and real-time forecasting systems using Kafka, Spark, and Airflow. His cloud proficiency spans AWS (S3, SageMaker, ECS, CloudWatch), GCP (BigQuery, Cloud Composer), and DevOps tools like Docker, Kubernetes, and MLflow.
51
 
52
- Krishna’s academic focus areas include genomic sequence modeling, transformer optimization, MLOps automation, and cross-domain generalization. He has published research in bioinformatics and ML applications for circadian transcription prediction and transcription factor binding.
53
 
54
- He is certified in NVIDIA’s RAG Agents with LLMs, Google Cloud Data Engineering, AWS ML Specialization, and has a proven ability to blend research and engineering in real-world systems. Krishna is passionate about scalable LLM infra, data-centric AI, and domain-adaptive ML solutions."""
55
 
56
  def initialize_console():
57
  console = Console()
@@ -80,16 +81,30 @@ vectorstore, all_chunks, all_texts, metadatas = initialize_resources()
80
 
81
  bm25_retriever = BM25Retriever.from_texts(texts=all_texts, metadatas=metadatas)
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  # LLMs
84
- repharser_llm = ChatNVIDIA(model="mistralai/mistral-7b-instruct-v0.3") | StrOutputParser()
 
85
  instruct_llm = ChatNVIDIA(model="mistralai/mixtral-8x22b-instruct-v0.1") | StrOutputParser()
86
  relevance_llm = ChatNVIDIA(model="meta/llama3-70b-instruct") | StrOutputParser()
87
- if not os.environ.get("OPENAI_API_KEY"):
88
- raise RuntimeError("OPENAI_API_KEY not found in environment!")
89
  answer_llm = ChatOpenAI(
90
- model="gpt-4-1106-preview",
91
  temperature=0.3,
92
- openai_api_key=os.environ.get("OPENAI_API_KEY"),
93
  streaming=True,
94
  callbacks=[StreamingStdOutCallbackHandler()]
95
  ) | StrOutputParser()
@@ -97,43 +112,60 @@ answer_llm = ChatOpenAI(
97
 
98
  # Prompts
99
  repharser_prompt = ChatPromptTemplate.from_template(
100
- "Rewrite the question below in 4 diverse ways to retrieve semantically similar information.Ensure diversity in phrasings across style, voice, and abstraction:\n\nQuestion: {query}\n\nRewrites:"
 
 
 
 
 
101
  )
102
 
103
  relevance_prompt = ChatPromptTemplate.from_template("""
104
  You are Krishna's personal AI assistant classifier.
105
 
106
- Your job is to decide whether a user's question can be meaningfully answered using the provided document chunks.
107
 
108
- Think carefully and return a JSON object with:
109
- - "is_out_of_scope": true if none of the chunks contain information relevant to the question.
110
- - "justification": a short sentence explaining your decision.
111
 
112
  ---
113
 
114
- Rules:
115
- - Chunks are snippets from Krishna’s resume, project history, and personal background.
116
- - If none of the chunks contain evidence, examples, or details that directly help answer the question, mark it as out of scope.
117
- - Do NOT rely on keyword matches. Use reasoning to decide whether the content actually addresses the question.
 
 
 
 
 
 
 
 
 
 
118
 
119
  Examples:
120
 
121
- Q: "What are Krishna's favorite movies?"
122
- Chunks: Mostly about research, skills, and work experience.
 
123
 
124
  Output:
125
  {{
126
- "is_out_of_scope": true,
127
- "justification": "No chunk discusses Krishna's personal preferences like movies."
128
  }}
129
 
130
- Q: "What ML tools has Krishna used in projects?"
131
- Chunks: Mentions PyTorch, Kafka, Hugging Face, Spark.
 
132
 
133
  Output:
134
  {{
135
- "is_out_of_scope": false,
136
- "justification": "Chunks mention tools Krishna used directly in his work."
137
  }}
138
 
139
  ---
@@ -146,37 +178,69 @@ User Question:
146
  Chunks:
147
  {contents}
148
 
149
- Return only the JSON object.
 
 
 
150
  """)
151
 
152
 
153
  answer_prompt_relevant = ChatPromptTemplate.from_template(
154
- "You are Krishna's personal AI assistant. Your job is to answer the user’s question clearly and professionally using the provided context.\n"
155
  "Rather than copying sentences, synthesize relevant insights and explain them like a knowledgeable peer.\n\n"
 
156
  "Krishna's Background:\n{profile}\n\n"
157
- "Note: The context might include some unrelated or noisy information. Focus only on content that directly supports your answer.\n\n"
158
- "Make your response rich and informative by:\n"
159
- "- Combining relevant facts from multiple parts of the context\n"
160
- "- Using natural, human-style language (not just bullet points)\n"
161
- "- Expanding briefly on tools or skills when appropriate\n"
162
- "- Avoiding repetition, filler, or hallucinations\n\n"
163
  "Context:\n{context}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  "User Question:\n{query}\n\n"
165
  "Answer:"
166
  )
167
 
 
168
  answer_prompt_fallback = ChatPromptTemplate.from_template(
169
  "You are Krishna’s personal AI assistant. The user asked a question unrelated to Krishna’s background.\n"
170
  "Respond with a touch of humor, then guide the conversation back to Krishna’s actual skills, experiences, or projects.\n\n"
171
  "Make it clear that everything you mention afterward comes from Krishna's actual profile.\n\n"
172
  "Krishna's Background:\n{profile}\n\n"
 
173
  "User Question:\n{query}\n\n"
174
  "Your Answer:"
175
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  # Helper Functions
177
  def parse_rewrites(raw_response: str) -> list[str]:
178
  lines = raw_response.strip().split("\n")
179
- return [line.strip("0123456789. ").strip() for line in lines if line.strip()][:4]
180
 
181
  def hybrid_retrieve(inputs, exclude_terms=None):
182
  # if exclude_terms is None:
@@ -312,7 +376,8 @@ hybrid_chain = generate_rewrites_chain | retrieve_chain
312
  # Validation
313
  extract_validation_inputs = RunnableLambda(lambda x: {
314
  "query": x["query"],
315
- "contents": [c["content"] for c in x["chunks"]]
 
316
  })
317
 
318
  validation_chain = (
@@ -332,7 +397,8 @@ def prepare_answer_inputs(x: Dict) -> Dict:
332
  "query": x["query"],
333
  "profile": KRISHNA_BIO,
334
  "context": context,
335
- "use_fallback": x["validation"]["is_out_of_scope"]
 
336
  }
337
 
338
  select_and_prompt = RunnableLambda(lambda x:
@@ -345,6 +411,57 @@ answer_chain = (
345
  | relevance_llm
346
  )
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  # Full Pipeline
349
  full_pipeline = hybrid_chain | RunnableAssign({"validation": validation_chain}) | answer_chain
350
 
@@ -359,14 +476,24 @@ def chat_interface(message, history):
359
  "vectorstore": vectorstore,
360
  "bm25_retriever": bm25_retriever,
361
  }
362
- response = ""
 
 
363
  for chunk in full_pipeline.stream(inputs):
364
- if isinstance(chunk, str):
365
- response += chunk
366
- yield response
367
- elif isinstance(chunk, dict) and "answer" in chunk:
368
- response += chunk["answer"]
369
- yield response
 
 
 
 
 
 
 
 
370
 
371
  demo = gr.ChatInterface(
372
  fn=chat_interface,
 
3
  import re
4
  import hashlib
5
  import gradio as gr
 
6
  from functools import partial
7
  import concurrent.futures
8
  from collections import defaultdict
9
  from pathlib import Path
10
+ from typing import List, Dict, Any, Optional, List, Literal, Type
11
  import numpy as np
12
  from dotenv import load_dotenv
13
  from rich.console import Console
14
  from rich.style import Style
15
+ from pydantic import BaseModel, Field
16
  from langchain_core.runnables import RunnableLambda
17
  from langchain_nvidia_ai_endpoints import ChatNVIDIA
18
  from langchain_core.output_parsers import StrOutputParser
19
  from langchain_core.prompts import ChatPromptTemplate
20
  from langchain.schema.runnable.passthrough import RunnableAssign
21
  from langchain.text_splitter import RecursiveCharacterTextSplitter
22
+ from langchain_huggingface import HuggingFaceEmbeddings
23
+ from langchain.vectorstores import FAISS
 
24
  from langchain.docstore.document import Document
25
+ from langchain.retrievers import BM25Retriever
26
  from langchain_openai import ChatOpenAI
27
  from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
28
+ from langchain.output_parsers import PydanticOutputParser
29
 
30
  #dotenv_path = os.path.join(os.getcwd(), ".env")
31
  #load_dotenv(dotenv_path)
 
46
  if not Path(CHUNKS_PATH).exists():
47
  raise FileNotFoundError(f"Chunks file not found at {CHUNKS_PATH}")
48
 
49
+ KRISHNA_BIO = """Krishna Vamsi Dhulipalla is a 2024 graduate of the M.Eng program in Computer Science at Virginia Tech, with over 3 years of experience across data engineering, machine learning research, and real-time analytics. He specializes in building scalable data systems and intelligent LLM-powered applications, with strong expertise in Python, PyTorch, Hugging Face Transformers, and end-to-end ML pipelines.
50
 
51
  He has led projects involving retrieval-augmented generation (RAG), feature selection for genomic classification, fine-tuning domain-specific LLMs (e.g., DNABERT, HyenaDNA), and real-time forecasting systems using Kafka, Spark, and Airflow. His cloud proficiency spans AWS (S3, SageMaker, ECS, CloudWatch), GCP (BigQuery, Cloud Composer), and DevOps tools like Docker, Kubernetes, and MLflow.
52
 
53
+ Krishna’s research has focused on genomic sequence modeling, transformer optimization, MLOps automation, and cross-domain generalization. He has published work in bioinformatics and machine learning applications for circadian transcription prediction and transcription factor binding.
54
 
55
+ He holds certifications in NVIDIA’s RAG Agents with LLMs, Google Cloud Data Engineering, and AWS ML Specialization. Krishna is passionate about scalable LLM infrastructure, data-centric AI, and domain-adaptive ML solutions — combining deep technical expertise with real-world engineering impact."""
56
 
57
  def initialize_console():
58
  console = Console()
 
81
 
82
  bm25_retriever = BM25Retriever.from_texts(texts=all_texts, metadatas=metadatas)
83
 
84
+ # Define the KnowledgeBase model
85
+ class KnowledgeBase(BaseModel):
86
+ user_name: str = Field('unknown', description="The name of the user chatting with Krishna's assistant, or 'unknown' if not provided")
87
+ company: Optional[str] = Field(None, description="The company or organization the user is associated with, if mentioned")
88
+ last_input: str = Field("", description="The most recent user question or message")
89
+ last_output: str = Field("", description="The most recent assistant response to the user")
90
+ summary_history: List[str] = Field(default_factory=list, description="Summarized conversation history over turns")
91
+ recent_interests: List[str] = Field(default_factory=list, description="User's recurring interests or topics they ask about, e.g., 'LLMs', 'Krishna's research', 'career advice'")
92
+ last_followups: List[str] = Field(default_factory=list, description="List of follow-up suggestions from the last assistant response")
93
+ tone: Optional[Literal['formal', 'casual', 'playful', 'direct', 'uncertain']] = Field(None, description="Inferred tone or attitude from the user based on recent input")
94
+
95
+ # Initialize the knowledge base
96
+ knowledge_base = KnowledgeBase()
97
+
98
+
99
  # LLMs
100
+ # repharser_llm = ChatNVIDIA(model="mistralai/mistral-7b-instruct-v0.3") | StrOutputParser()
101
+ repharser_llm = ChatNVIDIA(model="microsoft/phi-3-mini-4k-instruct") | StrOutputParser()
102
  instruct_llm = ChatNVIDIA(model="mistralai/mixtral-8x22b-instruct-v0.1") | StrOutputParser()
103
  relevance_llm = ChatNVIDIA(model="meta/llama3-70b-instruct") | StrOutputParser()
 
 
104
  answer_llm = ChatOpenAI(
105
+ model="gpt-4o",
106
  temperature=0.3,
107
+ openai_api_key=os.getenv("OPENAI_API_KEY"),
108
  streaming=True,
109
  callbacks=[StreamingStdOutCallbackHandler()]
110
  ) | StrOutputParser()
 
112
 
113
  # Prompts
114
  repharser_prompt = ChatPromptTemplate.from_template(
115
+ "Rewrite the question below in 3 different ways to help retrieve related information. Vary tone, style, and phrasing, but keep the meaning the same."
116
+ "Question: {query}"
117
+ "\n\nRewrites:"
118
+ "1."
119
+ "2."
120
+ "3."
121
  )
122
 
123
  relevance_prompt = ChatPromptTemplate.from_template("""
124
  You are Krishna's personal AI assistant classifier.
125
 
126
+ Your job is to decide whether a user's question can be meaningfully answered using the provided document chunks **or** relevant user memory.
127
 
128
+ Return a JSON object:
129
+ - "is_out_of_scope": true if the chunks and memory cannot help answer the question
130
+ - "justification": a short sentence explaining your decision
131
 
132
  ---
133
 
134
+ Special instructions:
135
+
136
+ Treat short or vague queries like "yes", "tell me more", "go on", or "give me" as follow-up prompts.
137
+ Assume the user is asking for **continuation** of the previous assistant response or follow-ups stored in memory. Consider that context as *in-scope*.
138
+
139
+ ✅ Also consider if the user's question can be answered using stored memory (like their name, company, interests, or last follow-up topics).
140
+
141
+ Do NOT classify these types of queries as "out of scope".
142
+
143
+ Only mark as out-of-scope if the user asks something truly unrelated to both:
144
+ - Krishna's background
145
+ - Stored user memory
146
+
147
+ ---
148
 
149
  Examples:
150
 
151
+ Q: "Tell me more"
152
+ Chunks: previously retrieved info about Krishna's ML tools
153
+ Memory: User previously asked about PyTorch and ML pipelines
154
 
155
  Output:
156
  {{
157
+ "is_out_of_scope": false,
158
+ "justification": "User is requesting a follow-up to a valid context, based on prior conversation"
159
  }}
160
 
161
+ Q: "What is Krishna's Hogwarts house?"
162
+ Chunks: None about fiction
163
+ Memory: User hasn't mentioned fiction/fantasy
164
 
165
  Output:
166
  {{
167
+ "is_out_of_scope": true,
168
+ "justification": "The question is unrelated to Krishna or user context"
169
  }}
170
 
171
  ---
 
178
  Chunks:
179
  {contents}
180
 
181
+ User Memory (Knowledge Base):
182
+ {memory}
183
+
184
+ Return ONLY the JSON object.
185
  """)
186
 
187
 
188
  answer_prompt_relevant = ChatPromptTemplate.from_template(
189
+ "You are Krishna's personal AI assistant. Your job is to answer the user’s question clearly, thoroughly, and professionally using the provided context.\n"
190
  "Rather than copying sentences, synthesize relevant insights and explain them like a knowledgeable peer.\n\n"
191
+ "Use relevant memory about the user to personalize the answer where appropriate.\n\n"
192
  "Krishna's Background:\n{profile}\n\n"
193
+ "User Memory (Knowledge Base):\n{memory}\n\n"
 
 
 
 
 
194
  "Context:\n{context}\n\n"
195
+ "Instructions:\n"
196
+ "- Format your response in **Markdown** for readability.\n"
197
+ "- Use **section headings with emojis** to organize the answer when helpful (e.g., 🔍 Overview, 🛠️ Tools Used, 📈 Real-World Impact).\n"
198
+ "- Use bullet points or bold text to highlight tools, skills, or project names.\n"
199
+ "- Add paragraph breaks between major ideas.\n"
200
+ "- Keep the tone conversational and helpful — like a smart peer explaining something.\n"
201
+ "- If the user asks about Krishna’s work experience, provide a **chronological summary** of his roles and key contributions (e.g., UJR, Virginia Tech).\n"
202
+ "- You may use general knowledge to briefly explain tools (like PyTorch or Kafka), but **do not invent any new facts** about Krishna.\n"
203
+ "- Avoid filler phrases, repetition, or generic praise (e.g., strengths) unless directly asked.\n"
204
+ "- End with a friendly follow-up question (no subheading needed here).\n\n"
205
+ "Example:\n"
206
+ "**Q: What work experience does Krishna have?**\n"
207
+ "**A:**\n"
208
+ "**🔧 Work Experience Overview**\n"
209
+ "**1. UJR Technologies** – Migrated batch ETL to real-time (Kafka/Spark), Dockerized services, and optimized Snowflake queries.\n"
210
+ "**2. Virginia Tech** – Built real-time IoT forecasting pipeline (10K sensors, GPT-4), achieving 91% accuracy and 15% energy savings.\n\n"
211
+ "_Would you like to dive into Krishna’s cloud deployment work using SageMaker and MLflow?_\n\n"
212
+ "Now generate the answer for the following:\n\n"
213
  "User Question:\n{query}\n\n"
214
  "Answer:"
215
  )
216
 
217
+
218
  answer_prompt_fallback = ChatPromptTemplate.from_template(
219
  "You are Krishna’s personal AI assistant. The user asked a question unrelated to Krishna’s background.\n"
220
  "Respond with a touch of humor, then guide the conversation back to Krishna’s actual skills, experiences, or projects.\n\n"
221
  "Make it clear that everything you mention afterward comes from Krishna's actual profile.\n\n"
222
  "Krishna's Background:\n{profile}\n\n"
223
+ "User Memory (Knowledge Base):\n{memory}\n\n"
224
  "User Question:\n{query}\n\n"
225
  "Your Answer:"
226
  )
227
+
228
+ parser_prompt = ChatPromptTemplate.from_template(
229
+ "You are Krishna's personal AI assistant, and your task is to maintain a memory of the user you're chatting with.\n"
230
+ "You just received a new user message and provided a response.\n"
231
+ "Please update the knowledge base using the schema below.\n\n"
232
+ "{format_instructions}\n\n"
233
+ "Previous Knowledge Base:\n{know_base}\n\n"
234
+ "Latest Assistant Response:\n{output}\n\n"
235
+ "Latest User Message:\n{input}\n\n"
236
+ "Return ONLY the updated knowledge base JSON:\n"
237
+ "If the assistant’s response includes follow-up suggestions or continuation prompts (like 'Would you like to learn more about...'), store them in the `last_followups` field."
238
+ )
239
+
240
  # Helper Functions
241
  def parse_rewrites(raw_response: str) -> list[str]:
242
  lines = raw_response.strip().split("\n")
243
+ return [line.strip("0123456789. ").strip() for line in lines if line.strip()][:3]
244
 
245
  def hybrid_retrieve(inputs, exclude_terms=None):
246
  # if exclude_terms is None:
 
376
  # Validation
377
  extract_validation_inputs = RunnableLambda(lambda x: {
378
  "query": x["query"],
379
+ "contents": [c["content"] for c in x["chunks"]],
380
+ "memory": knowledge_base.json()
381
  })
382
 
383
  validation_chain = (
 
397
  "query": x["query"],
398
  "profile": KRISHNA_BIO,
399
  "context": context,
400
+ "use_fallback": x["validation"]["is_out_of_scope"],
401
+ "memory": knowledge_base.json()
402
  }
403
 
404
  select_and_prompt = RunnableLambda(lambda x:
 
411
  | relevance_llm
412
  )
413
 
414
+ def RExtract(pydantic_class: Type[BaseModel], llm, prompt):
415
+ """
416
+ Runnable Extraction module for updating Krishna Assistant's KnowledgeBase.
417
+ Fills in a structured schema using PydanticOutputParser.
418
+ """
419
+ parser = PydanticOutputParser(pydantic_object=pydantic_class)
420
+ instruct_merge = RunnableAssign({
421
+ 'format_instructions': lambda x: parser.get_format_instructions()
422
+ })
423
+
424
+ def preparse(raw: str):
425
+ # Clean malformed LLM outputs
426
+ if '{' not in raw: raw = '{' + raw
427
+ if '}' not in raw: raw = raw + '}'
428
+ return (raw
429
+ .replace("\\_", "_")
430
+ .replace("\n", " ")
431
+ .replace("\]", "]")
432
+ .replace("\[", "[")
433
+ )
434
+
435
+ return instruct_merge | prompt | llm | RunnableLambda(preparse) | parser
436
+
437
+ knowledge_extractor = RExtract(
438
+ pydantic_class=KnowledgeBase,
439
+ llm=relevance_llm,
440
+ prompt=parser_prompt
441
+ )
442
+
443
+ def update_kb_after_answer(data: dict):
444
+ try:
445
+ kb_input = {
446
+ "know_base": knowledge_base.json(),
447
+ "input": data["query"],
448
+ "output": data["answer"]
449
+ }
450
+
451
+ new_kb = knowledge_extractor.invoke(kb_input)
452
+ knowledge_base.__dict__.update(new_kb.__dict__) # update in place
453
+
454
+ # Optional: print or log updated KB
455
+ # print("✅ Knowledge base updated:", knowledge_base.dict())
456
+
457
+ except Exception as e:
458
+ print("❌ Failed to update knowledge base:", str(e))
459
+
460
+ return data # Return unchanged so answer can flow forward
461
+
462
+
463
+ update_kb_chain = RunnableLambda(update_kb_after_answer)
464
+
465
  # Full Pipeline
466
  full_pipeline = hybrid_chain | RunnableAssign({"validation": validation_chain}) | answer_chain
467
 
 
476
  "vectorstore": vectorstore,
477
  "bm25_retriever": bm25_retriever,
478
  }
479
+ full_response = ""
480
+ collected = None
481
+
482
  for chunk in full_pipeline.stream(inputs):
483
+ if isinstance(chunk, dict) and "answer" in chunk:
484
+ full_response += chunk["answer"]
485
+ collected = chunk # store result for memory update
486
+ yield full_response
487
+ elif isinstance(chunk, str):
488
+ full_response += chunk
489
+ yield full_response
490
+
491
+ # After yielding the full response, run knowledge update in background
492
+ if collected:
493
+ update_kb_after_answer({
494
+ "query": message,
495
+ "answer": full_response
496
+ })
497
 
498
  demo = gr.ChatInterface(
499
  fn=chat_interface,