Spaces:
Build error
Build error
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,
|