Spaces:
Sleeping
Sleeping
Commit
·
5757dad
1
Parent(s):
ab32d1c
added knowledge base
Browse files
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
|
23 |
-
from
|
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
|
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
|
53 |
|
54 |
-
He
|
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-
|
91 |
temperature=0.3,
|
92 |
-
openai_api_key=os.
|
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
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
109 |
-
- "is_out_of_scope": true if
|
110 |
-
- "justification": a short sentence explaining your decision
|
111 |
|
112 |
---
|
113 |
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
|
119 |
Examples:
|
120 |
|
121 |
-
Q: "
|
122 |
-
Chunks:
|
|
|
123 |
|
124 |
Output:
|
125 |
{{
|
126 |
-
"is_out_of_scope":
|
127 |
-
"justification": "
|
128 |
}}
|
129 |
|
130 |
-
Q: "What
|
131 |
-
Chunks:
|
|
|
132 |
|
133 |
Output:
|
134 |
{{
|
135 |
-
"is_out_of_scope":
|
136 |
-
"justification": "
|
137 |
}}
|
138 |
|
139 |
---
|
@@ -146,37 +178,69 @@ User Question:
|
|
146 |
Chunks:
|
147 |
{contents}
|
148 |
|
149 |
-
|
|
|
|
|
|
|
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 |
-
"
|
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()][:
|
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 |
-
|
|
|
|
|
363 |
for chunk in full_pipeline.stream(inputs):
|
364 |
-
if isinstance(chunk,
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|