Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,30 +2,40 @@ import os
|
|
| 2 |
import json
|
| 3 |
import random
|
| 4 |
import logging
|
| 5 |
-
from typing import List
|
| 6 |
import streamlit as st
|
| 7 |
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
import autogen
|
| 9 |
from autogen import AssistantAgent, UserProxyAgent
|
| 10 |
|
|
|
|
| 11 |
from langchain_community.embeddings import HuggingFaceEmbeddings
|
| 12 |
from langchain_community.vectorstores import Chroma
|
| 13 |
-
from langchain.docstore.document import Document
|
| 14 |
|
|
|
|
| 15 |
load_dotenv()
|
| 16 |
|
|
|
|
| 17 |
logging.basicConfig(level=logging.INFO)
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
-
st.set_page_config(page_title="IT Support System (RAG)", layout="centered")
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
| 22 |
if "chat_history" not in st.session_state:
|
| 23 |
st.session_state.chat_history = []
|
| 24 |
|
| 25 |
if "workflow_logs" not in st.session_state:
|
| 26 |
st.session_state.workflow_logs = []
|
| 27 |
|
| 28 |
-
# Knowledge Base
|
| 29 |
kb_path = os.path.join(os.path.dirname(__file__), 'kb.json')
|
| 30 |
with open(kb_path, encoding='utf-8') as f:
|
| 31 |
kb_entries = json.load(f)
|
|
@@ -34,10 +44,7 @@ docs: List[Document] = []
|
|
| 34 |
for entry in kb_entries:
|
| 35 |
docs.append(Document(
|
| 36 |
page_content=entry['answer'],
|
| 37 |
-
metadata={
|
| 38 |
-
'id': entry.get('id'),
|
| 39 |
-
'question': entry.get('question')
|
| 40 |
-
}
|
| 41 |
))
|
| 42 |
|
| 43 |
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
|
@@ -64,6 +71,7 @@ def escalate_ticket(query: str, analysis: str = "") -> str:
|
|
| 64 |
logger.info(f"Escalating issue with ticket {ticket_id}: {description}")
|
| 65 |
return f"Escalated issue. Created ticket {ticket_id}. A support technician will contact you shortly."
|
| 66 |
|
|
|
|
| 67 |
llm_config = {
|
| 68 |
"config_list": [{
|
| 69 |
"model": "llama3",
|
|
@@ -74,10 +82,8 @@ llm_config = {
|
|
| 74 |
"temperature": 0.5,
|
| 75 |
}
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
llm_config=llm_config,
|
| 80 |
-
system_message="""
|
| 81 |
You are the Master Agent that orchestrates the IT support workflow:
|
| 82 |
1. First determine if the query is IT-related. If not, provide a direct response explaining your limitations.
|
| 83 |
2. For IT-related queries, pass to the Planning Agent for execution plan development
|
|
@@ -85,13 +91,9 @@ You are the Master Agent that orchestrates the IT support workflow:
|
|
| 85 |
4. Provide a comprehensive yet concise final response to the user
|
| 86 |
|
| 87 |
Only handle one query at a time through the complete workflow.
|
| 88 |
-
"""
|
| 89 |
-
)
|
| 90 |
|
| 91 |
-
planning_agent = AssistantAgent(
|
| 92 |
-
name="Planning",
|
| 93 |
-
llm_config=llm_config,
|
| 94 |
-
system_message="""
|
| 95 |
You are the Planning Agent responsible for:
|
| 96 |
1. Validating if the user query is clear and complete
|
| 97 |
2. Refining the query if needed for better processing
|
|
@@ -104,13 +106,9 @@ Provide your analysis as a structured output with sections for:
|
|
| 104 |
- Execution Plan
|
| 105 |
|
| 106 |
Always end your message with: "Forwarding to Analysis Agent"
|
| 107 |
-
"""
|
| 108 |
-
)
|
| 109 |
|
| 110 |
-
analysis_agent = AssistantAgent(
|
| 111 |
-
name="Analysis",
|
| 112 |
-
llm_config=llm_config,
|
| 113 |
-
system_message="""
|
| 114 |
You are the Analysis Agent responsible for:
|
| 115 |
1. Identifying key entities in the user query (devices, software, errors, etc.)
|
| 116 |
2. Determining severity level (Low, Medium, High, Critical)
|
|
@@ -124,8 +122,7 @@ Provide your analysis as structured output with sections for:
|
|
| 124 |
- Analysis Summary
|
| 125 |
|
| 126 |
Always end your message with: "Forwarding to Resolution Agent"
|
| 127 |
-
"""
|
| 128 |
-
)
|
| 129 |
|
| 130 |
resolution_agent = AssistantAgent(
|
| 131 |
name="Resolution",
|
|
@@ -166,7 +163,59 @@ Always include the ticket ID and expected follow-up timeframe.
|
|
| 166 |
function_map={"escalate_ticket": escalate_ticket}
|
| 167 |
)
|
| 168 |
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
query = query.strip()
|
| 171 |
if not query:
|
| 172 |
return "Please enter an IT question or issue.", {}
|
|
@@ -236,15 +285,25 @@ def handle_it_query(query: str) -> (str, dict):
|
|
| 236 |
logger.info(f"Final Master Agent Response: {final_response}")
|
| 237 |
workflow_logs["final_response"] = final_response
|
| 238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
return final_response, workflow_logs
|
| 240 |
|
| 241 |
except Exception as e:
|
| 242 |
logger.error(f"Error in workflow: {e}", exc_info=True)
|
| 243 |
-
|
|
|
|
|
|
|
| 244 |
|
|
|
|
|
|
|
| 245 |
|
| 246 |
-
st.
|
| 247 |
-
st.write("Ask any IT support question and our multi-agent system will assist you.")
|
| 248 |
|
| 249 |
with st.form(key="query_form", clear_on_submit=True):
|
| 250 |
user_input = st.text_area("Describe your IT issue:", height=100)
|
|
@@ -258,19 +317,19 @@ if submitted:
|
|
| 258 |
with st.spinner("Processing your request through our agent workflow..."):
|
| 259 |
response, logs = handle_it_query(user_input)
|
| 260 |
|
| 261 |
-
# Append to
|
| 262 |
st.session_state.chat_history.append({"user": user_input, "assistant": response})
|
| 263 |
if show_logs:
|
| 264 |
st.session_state.workflow_logs.append(logs)
|
| 265 |
|
| 266 |
-
#
|
| 267 |
st.markdown("## Conversation")
|
| 268 |
for chat in st.session_state.chat_history:
|
| 269 |
st.markdown(f"**User:** {chat['user']}")
|
| 270 |
st.markdown(f"**Assistant:** {chat['assistant']}")
|
| 271 |
st.markdown("---")
|
| 272 |
|
| 273 |
-
#
|
| 274 |
if show_logs and st.session_state.workflow_logs:
|
| 275 |
st.markdown("## Workflow Logs")
|
| 276 |
for i, log in enumerate(st.session_state.workflow_logs):
|
|
|
|
| 2 |
import json
|
| 3 |
import random
|
| 4 |
import logging
|
| 5 |
+
from typing import List, Dict, Any
|
| 6 |
import streamlit as st
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import faiss
|
| 11 |
+
import numpy as np
|
| 12 |
+
from sentence_transformers import SentenceTransformer
|
| 13 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
|
| 14 |
+
|
| 15 |
import autogen
|
| 16 |
from autogen import AssistantAgent, UserProxyAgent
|
| 17 |
|
| 18 |
+
from langchain.docstore.document import Document
|
| 19 |
from langchain_community.embeddings import HuggingFaceEmbeddings
|
| 20 |
from langchain_community.vectorstores import Chroma
|
|
|
|
| 21 |
|
| 22 |
+
# --- Load environment ---
|
| 23 |
load_dotenv()
|
| 24 |
|
| 25 |
+
# --- Logging ---
|
| 26 |
logging.basicConfig(level=logging.INFO)
|
| 27 |
logger = logging.getLogger(__name__)
|
|
|
|
| 28 |
|
| 29 |
+
st.set_page_config(page_title="AI Help Desk with Hybrid Open Source + Multi-Agent", layout="centered")
|
| 30 |
+
|
| 31 |
+
# --- Initialize session state ---
|
| 32 |
if "chat_history" not in st.session_state:
|
| 33 |
st.session_state.chat_history = []
|
| 34 |
|
| 35 |
if "workflow_logs" not in st.session_state:
|
| 36 |
st.session_state.workflow_logs = []
|
| 37 |
|
| 38 |
+
# --- Load Knowledge Base ---
|
| 39 |
kb_path = os.path.join(os.path.dirname(__file__), 'kb.json')
|
| 40 |
with open(kb_path, encoding='utf-8') as f:
|
| 41 |
kb_entries = json.load(f)
|
|
|
|
| 44 |
for entry in kb_entries:
|
| 45 |
docs.append(Document(
|
| 46 |
page_content=entry['answer'],
|
| 47 |
+
metadata={'id': entry.get('id'), 'question': entry.get('question')}
|
|
|
|
|
|
|
|
|
|
| 48 |
))
|
| 49 |
|
| 50 |
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
|
|
|
| 71 |
logger.info(f"Escalating issue with ticket {ticket_id}: {description}")
|
| 72 |
return f"Escalated issue. Created ticket {ticket_id}. A support technician will contact you shortly."
|
| 73 |
|
| 74 |
+
# --- OpenAI Multi-agent LLM Config ---
|
| 75 |
llm_config = {
|
| 76 |
"config_list": [{
|
| 77 |
"model": "llama3",
|
|
|
|
| 82 |
"temperature": 0.5,
|
| 83 |
}
|
| 84 |
|
| 85 |
+
# --- Define Agents ---
|
| 86 |
+
master_agent = AssistantAgent(name="Master", llm_config=llm_config, system_message="""
|
|
|
|
|
|
|
| 87 |
You are the Master Agent that orchestrates the IT support workflow:
|
| 88 |
1. First determine if the query is IT-related. If not, provide a direct response explaining your limitations.
|
| 89 |
2. For IT-related queries, pass to the Planning Agent for execution plan development
|
|
|
|
| 91 |
4. Provide a comprehensive yet concise final response to the user
|
| 92 |
|
| 93 |
Only handle one query at a time through the complete workflow.
|
| 94 |
+
""")
|
|
|
|
| 95 |
|
| 96 |
+
planning_agent = AssistantAgent(name="Planning", llm_config=llm_config, system_message="""
|
|
|
|
|
|
|
|
|
|
| 97 |
You are the Planning Agent responsible for:
|
| 98 |
1. Validating if the user query is clear and complete
|
| 99 |
2. Refining the query if needed for better processing
|
|
|
|
| 106 |
- Execution Plan
|
| 107 |
|
| 108 |
Always end your message with: "Forwarding to Analysis Agent"
|
| 109 |
+
""")
|
|
|
|
| 110 |
|
| 111 |
+
analysis_agent = AssistantAgent(name="Analysis", llm_config=llm_config, system_message="""
|
|
|
|
|
|
|
|
|
|
| 112 |
You are the Analysis Agent responsible for:
|
| 113 |
1. Identifying key entities in the user query (devices, software, errors, etc.)
|
| 114 |
2. Determining severity level (Low, Medium, High, Critical)
|
|
|
|
| 122 |
- Analysis Summary
|
| 123 |
|
| 124 |
Always end your message with: "Forwarding to Resolution Agent"
|
| 125 |
+
""")
|
|
|
|
| 126 |
|
| 127 |
resolution_agent = AssistantAgent(
|
| 128 |
name="Resolution",
|
|
|
|
| 163 |
function_map={"escalate_ticket": escalate_ticket}
|
| 164 |
)
|
| 165 |
|
| 166 |
+
# --- Initialize open-source fallback Mistral-7B model for AI fallback ---
|
| 167 |
+
quantization_config = BitsAndBytesConfig(
|
| 168 |
+
load_in_4bit=True,
|
| 169 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 170 |
+
bnb_4bit_use_double_quant=True,
|
| 171 |
+
bnb_4bit_quant_type="nf4"
|
| 172 |
+
)
|
| 173 |
+
model_name = "mistralai/Mistral-7B-Instruct-v0.1"
|
| 174 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 175 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 176 |
+
mistral_model = AutoModelForCausalLM.from_pretrained(
|
| 177 |
+
model_name,
|
| 178 |
+
quantization_config=quantization_config,
|
| 179 |
+
device_map="auto"
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
# --- Load FAQ Data for fallback ---
|
| 183 |
+
faq_data = [
|
| 184 |
+
# Populate with your actual FAQ data or import from file if needed
|
| 185 |
+
{"question": "What is Onfon Mobile?", "answer": "Onfon Mobile is a device financing company..."},
|
| 186 |
+
{"question": "How do I get a phone via Onfon Mobile?", "answer": "To get a smartphone, register with *797#..."},
|
| 187 |
+
# ... Add as needed ...
|
| 188 |
+
]
|
| 189 |
+
|
| 190 |
+
embed_model = SentenceTransformer("BAAI/bge-large-en")
|
| 191 |
+
faq_embeddings = np.array([embed_model.encode(f["question"], convert_to_numpy=True) for f in faq_data])
|
| 192 |
+
faiss_index = faiss.IndexFlatL2(faq_embeddings.shape[1])
|
| 193 |
+
faiss_index.add(faq_embeddings)
|
| 194 |
+
|
| 195 |
+
def find_top_faqs(user_input: str, top_k=3):
|
| 196 |
+
emb = embed_model.encode(user_input, convert_to_numpy=True).reshape(1, -1)
|
| 197 |
+
_, idxs = faiss_index.search(emb, top_k)
|
| 198 |
+
return [faq_data[i] for i in idxs[0]]
|
| 199 |
+
|
| 200 |
+
def generate_ai_faq_answer(user_input: str):
|
| 201 |
+
top_faqs = find_top_faqs(user_input, 3)
|
| 202 |
+
system_prompt = (
|
| 203 |
+
"You are an Onfon Mobile customer support assistant. Be kind and helpful to onfon mobile customers. "
|
| 204 |
+
"From the following FAQs, provide the best possible answer briefly:\n\n"
|
| 205 |
+
)
|
| 206 |
+
faq_context = "\n".join([f"Q: {f['question']}\nA: {f['answer']}" for f in top_faqs])
|
| 207 |
+
prompt = f"{system_prompt}{faq_context}\n\nUser Question: {user_input}\nAnswer:"
|
| 208 |
+
|
| 209 |
+
inputs = tokenizer(prompt, return_tensors="pt", truncation=True).to(device)
|
| 210 |
+
with torch.no_grad():
|
| 211 |
+
outputs = mistral_model.generate(**inputs, max_new_tokens=150)
|
| 212 |
+
answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 213 |
+
if "Answer:" in answer:
|
| 214 |
+
answer = answer.split("Answer:")[-1].strip()
|
| 215 |
+
return answer
|
| 216 |
+
|
| 217 |
+
# --- Main Query Handler ---
|
| 218 |
+
def handle_it_query(query: str) -> (str, Dict[str, Any]):
|
| 219 |
query = query.strip()
|
| 220 |
if not query:
|
| 221 |
return "Please enter an IT question or issue.", {}
|
|
|
|
| 285 |
logger.info(f"Final Master Agent Response: {final_response}")
|
| 286 |
workflow_logs["final_response"] = final_response
|
| 287 |
|
| 288 |
+
# Fallback to open-source model if no valid response or error
|
| 289 |
+
if not final_response or final_response.lower().startswith("an error occurred"):
|
| 290 |
+
logger.warning("Using fallback open-source Mistral-7B FAQ model due to empty or error response")
|
| 291 |
+
fallback_response = generate_ai_faq_answer(query)
|
| 292 |
+
workflow_logs["fallback_response"] = fallback_response
|
| 293 |
+
final_response = fallback_response
|
| 294 |
+
|
| 295 |
return final_response, workflow_logs
|
| 296 |
|
| 297 |
except Exception as e:
|
| 298 |
logger.error(f"Error in workflow: {e}", exc_info=True)
|
| 299 |
+
fallback_response = generate_ai_faq_answer(query)
|
| 300 |
+
workflow_logs["fallback_response"] = fallback_response
|
| 301 |
+
return f"An error occurred during processing, fallback answer provided.\n\n{fallback_response}", workflow_logs
|
| 302 |
|
| 303 |
+
# --- Streamlit UI ---
|
| 304 |
+
st.title("AI Help Desk (Hybrid Multi-Agent + Open Source)")
|
| 305 |
|
| 306 |
+
st.write("Ask any IT support question and our hybrid multi-agent + open-source system will assist you.")
|
|
|
|
| 307 |
|
| 308 |
with st.form(key="query_form", clear_on_submit=True):
|
| 309 |
user_input = st.text_area("Describe your IT issue:", height=100)
|
|
|
|
| 317 |
with st.spinner("Processing your request through our agent workflow..."):
|
| 318 |
response, logs = handle_it_query(user_input)
|
| 319 |
|
| 320 |
+
# Append conversation to history
|
| 321 |
st.session_state.chat_history.append({"user": user_input, "assistant": response})
|
| 322 |
if show_logs:
|
| 323 |
st.session_state.workflow_logs.append(logs)
|
| 324 |
|
| 325 |
+
# --- Show conversation ---
|
| 326 |
st.markdown("## Conversation")
|
| 327 |
for chat in st.session_state.chat_history:
|
| 328 |
st.markdown(f"**User:** {chat['user']}")
|
| 329 |
st.markdown(f"**Assistant:** {chat['assistant']}")
|
| 330 |
st.markdown("---")
|
| 331 |
|
| 332 |
+
# --- Show logs if requested ---
|
| 333 |
if show_logs and st.session_state.workflow_logs:
|
| 334 |
st.markdown("## Workflow Logs")
|
| 335 |
for i, log in enumerate(st.session_state.workflow_logs):
|