rishisriv-bh's picture
Update app.py
e222dc9 verified
Raw
History Blame Contribute Delete
10.8 kB
import boto3
import gradio as gr
import requests
from huggingface_hub import InferenceClient
import uuid
from datetime import datetime
from urllib.parse import urlparse, parse_qs
import os
# Initialize DeepSeek via HuggingFace
hf_token = os.environ.get("HF_TOKEN", "")
client = InferenceClient(model="deepseek-ai/DeepSeek-V3", token=hf_token)
# Initialize AWS SES
ses = boto3.client("ses",
aws_access_key_id=os.environ.get("access_key"),
aws_secret_access_key=os.environ.get("secret_access_key"),
region_name="us-east-1")
def get_username_from_request(request: gr.Request):
if request and request.url:
parsed = urlparse(str(request.url))
params = parse_qs(parsed.query)
username = params.get("userName", ["UnknownUser"])[0]
email = params.get("userEmail", ["UnknownUser"])[0]
return username, email
return "UnknownUser", "unknown@email.com"
def fetch_google_doc_text(doc_id: str) -> str:
"""Fetch documentation from Google Docs"""
export_url = f"https://docs.google.com/document/d/{doc_id}/export?format=txt"
try:
response = requests.get(export_url, timeout=10)
if response.status_code == 200:
return response.text.strip()
else:
return f"Unable to fetch document. Status code: {response.status_code}"
except Exception as e:
return f"Error fetching document: {e}"
# Google Doc ID
GOOGLE_DOC_ID = "1u7wt-7Gp6ETH1OPh2o9FIgGPgm3dAWsQDM6DT3MCd6Q"
doc_context = fetch_google_doc_text(GOOGLE_DOC_ID)
# Store chat history for email logging
chat_history = []
def send_email_if_needed(username, message, bot_response, email):
"""Send email notification when bot cannot answer"""
trigger_phrases = [
"I don't have information",
"not found in the documentation"
]
should_send = (
any(phrase.lower() in bot_response.lower() for phrase in trigger_phrases) or
len(bot_response) < 50
)
if should_send:
sender_email = "process@documents.beiinghuman.com"
recipient_emails = ["rishi@beiinghuman.com", "support@beiinghuman.com", "ubaid@beiinghuman.com"]
session_id = str(uuid.uuid4())
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log = (
f"UNANSWERED QUESTION ALERT\n"
f"{'=' * 60}\n"
f"Session ID: {session_id}\n"
f"Timestamp: {timestamp}\n"
f"User: {username}\n"
f"Email: {email}\n"
f"{'=' * 60}\n\n"
f"CONVERSATION HISTORY:\n"
f"{'-' * 60}\n"
)
for i, (user_msg, bot_msg) in enumerate(chat_history, 1):
log += f"\n[{i}] {user_msg}\n"
log += f"Bot: {bot_msg}\n"
log += f"{'-' * 60}\n"
log += (
f"\n\nACTION REQUIRED:\n"
f"Please review this conversation and update the documentation.\n"
)
try:
ses.send_email(
Source=sender_email,
Destination={'ToAddresses': recipient_emails},
Message={
'Subject': {'Data': f"Unanswered FAQ - {username}"},
'Body': {'Text': {'Data': log}}
}
)
print(f"Email notification sent to {recipient_email}")
except Exception as e:
print(f"Error sending email: {e}")
finally:
chat_history.clear()
def respond(message, history, system_message, max_tokens, temperature, top_p, request: gr.Request):
"""Main chatbot response function using DeepSeek"""
try:
username, email = get_username_from_request(request)
# Enhanced system prompt with better instructions
system_prompt = f"""You are a knowledgeable support assistant for Beiing Human, an invoice processing and approval platform.
ROLE: Answer user questions accurately using ONLY the documentation provided below.
RESPONSE GUIDELINES:
1. ACCURACY: Base all answers strictly on the documentation. Never invent features or make assumptions.
2. CLARITY:
- Write in clear, professional language
- Use numbered steps for procedures
- Break complex topics into digestible sections
- Use bullet points for lists of features or options
3. RECOGNITION:
- Understand user intent even with different wording (e.g., "recall" = "pull back", "remove" = "delete")
- Match questions to relevant documentation sections
- Recognize abbreviated terms (PO = Purchase Order, DT = Delivery Ticket, HIL = Human-in-the-Loop)
4. STRUCTURE:
- Start with a direct answer
- Follow with step-by-step instructions if applicable
- Add relevant context or tips at the end
- Keep responses concise but complete
5. WHEN INFORMATION IS MISSING:
If the documentation doesn't contain the answer, respond with:
"I don't have information about that in the current documentation. Please contact support@beiinghuman.com for assistance with this specific question."
6. FORMATTING:
- Use **bold** for important terms or actions
- Use numbered lists (1. 2. 3.) for sequential steps
- Use bullet points (•) for non-sequential items
- Include relevant section references when helpful
DOCUMENTATION:
---
{doc_context}
---
Remember: Your goal is to help users quickly find accurate information. Be helpful, precise, and professional."""
# Build messages
messages = [
{"role": "system", "content": system_prompt}
]
# Add conversation history
for user_msg, assistant_msg in history:
messages.append({"role": "user", "content": user_msg})
messages.append({"role": "assistant", "content": assistant_msg})
# Add current message
messages.append({"role": "user", "content": message})
# Stream response from DeepSeek
response = ""
try:
for chunk in client.chat_completion(
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
stream=True
):
# Safe access with multiple checks
if hasattr(chunk, 'choices') and len(chunk.choices) > 0:
choice = chunk.choices[0]
if hasattr(choice, 'delta'):
delta = choice.delta
if hasattr(delta, 'content') and delta.content is not None:
token = delta.content
response += token
yield response
except IndexError as e:
print(f"IndexError in streaming: {e}")
if not response:
response = "I apologize, but I encountered an error processing your request. Please try again."
yield response
except Exception as stream_error:
print(f"Streaming error: {stream_error}")
if not response:
response = "I apologize, but I encountered an error processing your request. Please try again."
yield response
# Save to history and check if email notification needed
if response: # Only save if we got a response
chat_history.append((f"{username}: {message}", response))
send_email_if_needed(username, message, response, email)
except Exception as e:
print(f"Error in respond function: {str(e)}")
error_message = f"Error: {str(e)}\n\nPlease try again or contact support if the issue persists."
yield error_message
# Gradio UI Setup
demo = gr.ChatInterface(
respond,
additional_inputs=[
gr.Textbox(value="", label="System message", visible=False),
gr.Slider(minimum=64, maximum=32000, value=2000, step=64, label="Max tokens", visible=False),
gr.Slider(minimum=0.0, maximum=1.5, value=0.3, step=0.1, label="Temperature", visible=False),
gr.Slider(minimum=0.0, maximum=1.0, value=0.95, step=0.05, label="Top-p", visible=False),
],
title="💬 Beiing Human FAQ Chatbot",
description=(
"Ask questions on how to use Beiing Human App.\n\n"
"I can help you with:\n"
"• Document submission and processing\n"
"• Invoice matching and approval workflows\n"
"• User management and ERP integration\n"
"• Reports and advanced features\n"
"• Troubleshooting common issues\n\n"
),
examples=[
["How do I upload invoices?"],
["What does the INYA report show?"],
["How do I match an invoice to a PO?"],
["How can I invite new users?"],
["What are the different document statuses?"],
["How does the Q&A feature work?"],
["How do I use the pull back feature?"],
["What is a Sub Admin and what can they do?"],
["How do I integrate with Foundation ERP?"],
["What's the difference between Approved and Verified statuses?"],
["How do I attach a delivery ticket to an invoice?"],
],
css="""
/* Override Gradio's CSS variables to force dark theme */
:root {
--background-fill-primary: #141414 !important;
--background-fill-secondary: #1e1e1e !important;
--body-text-color: #ffffff !important;
--body-text-color-subdued: #cccccc !important;
--color-accent: #ffffff !important;
--color-accent-soft: #333333 !important;
--border-color-primary: #444444 !important;
--border-color-accent: #555555 !important;
--neutral-100: #141414 !important;
--neutral-200: #1e1e1e !important;
--neutral-300: #333333 !important;
--neutral-400: #444444 !important;
--neutral-500: #555555 !important;
--neutral-600: #666666 !important;
--neutral-700: #777777 !important;
--neutral-800: #888888 !important;
--neutral-900: #999999 !important;
}
/* Force dark mode styles */
body, html, .app, .gradio-container {
background-color: #141414 !important;
color: #ffffff !important;
}
textarea,
input,
.message,
.chatbot {
background-color: unset !important;
}
/* Only force text color on specific elements, not everything */
body, html, .gradio-container,
.chatbot, .message,
textarea, input,
label, p, span, div.block,
h1, h2, h3, h4, h5, h6 {
color: #ffffff !important;
}
/* Input styling */
textarea, input {
background-color: #1e1e1e !important;
color: #ffffff !important;
border: 1px solid #444444 !important;
}
"""
)
if __name__ == "__main__":
demo.launch()