File size: 8,102 Bytes
e857401 79d7111 8b1269f 200321b e857401 00fb51f 9ed82c2 da9143d 9da4968 9fbb15f 9ed82c2 8bec955 79d7111 a8e953d e857401 79d7111 e73b3ef a8e953d 9ed82c2 79d7111 4796bb7 bd06c7c da9143d e73b3ef e857401 9da4968 e857401 79d7111 e73b3ef 697bd0c e857401 79d7111 e73b3ef a8e953d 9f41d34 697bd0c 1795606 697bd0c 9f41d34 697bd0c bc3f295 697bd0c bc3f295 697bd0c bc3f295 697bd0c bc3f295 697bd0c bc3f295 0e2c7f3 ab39c80 0e2c7f3 697bd0c 0e2c7f3 697bd0c a8e953d 1795606 a8e953d 4796bb7 6988980 e857401 8c5264d 79d7111 8858c07 8bec955 8858c07 8bec955 8858c07 0ce7032 e73b3ef c662295 83b40ea 93ac62f 9ed82c2 bd06c7c 9ed82c2 bd06c7c 8858c07 9ed82c2 8858c07 3b5a961 8858c07 219d6a6 0ce7032 5b4906d 8858c07 bd06c7c 5b4906d 9ed82c2 bd06c7c 9ed82c2 8858c07 63c793a 8858c07 c08cd2a fcb0082 ab39c80 7440d01 6fdf7cb fcb0082 4244f7b 9ed82c2 12efb93 c662295 e73b3ef a75f1e2 b0ceb73 e93df42 118ac97 c16a8fc beb9aac 83b40ea 2d81b02 beb9aac 5b9536e bdf20ce beb9aac c2b0117 cb6079a 83b40ea bdf20ce 83b40ea c2b0117 83b40ea 79d7111 e73b3ef | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | import os
import gradio as gr
from gradio.components import ChatMessage
import openai
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_openai import OpenAIEmbeddings
from pinecone import Pinecone, ServerlessSpec
import subprocess
import re
import json
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import JsonOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# load .env in dev
if os.getenv("HF_SPACE", None) is None:
from dotenv import load_dotenv
load_dotenv()
# API Keys
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
embedding_model = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY)
PINECONE_API_KEY = os.environ["PINECONE_API_KEY"]
PINECONE_INDEX = os.environ["PINECONE_INDEX"]
pc = Pinecone(api_key=PINECONE_API_KEY)
index = pc.Index(PINECONE_INDEX)
#Run this when upserting new documents to the pinecone index by adding new files to the data or journal folders
#subprocess.run(["python", "pinecone_rag.py"])
# response structure
class ResearchResponse(BaseModel):
#topic: str
empathetic_response: str
informative_response: str
quran: str
question: str
# set up LLM and parser
llm = ChatOpenAI(openai_api_key=OPENAI_API_KEY, model="gpt-4o-mini")
parser = PydanticOutputParser(pydantic_object=ResearchResponse)
# custom prompt to API
prompt = ChatPromptTemplate.from_messages([
(
"system",
"""
You are an empathetic, culturally and religiously sensitive AI assistant specializing in mental health support for Muslim women. Your responses must always:
1. Start with a kind acknowledgment of the user's feelings or situation.
2. Offer a gentle validation grounded in shared experience or emotional context (e.g., "Many people feel this way" or "That makes sense given what you're going through").
3. Provide a concise, emotionally supportive response that includes both comforting reflection and practical suggestions, where appropriate, from the Relevant Islamic Background Information.
4. Provide a Quranic verse with a simple, relevant tafsir from Tafsir al-Mizan that directly supports the emotional or practical point being discussed.
5. Ask a short, open-ended, growth-oriented reflection question that encourages deeper thought or emotional clarity. This question should be sensitive to Muslim women’s lived experiences and needs (e.g., family, community, faith, privacy, modesty).
Optionally, if the topic is deep or complex, you may include a deeper analysis of the Quranic verse or tafsir snippet from Tafsir al-Mizan. However, only if it adds clarity, reassurance, or value to the user's experience.
Use accessible, conversational language that feels warm and human. Avoid overly academic or robotic phrasing.
Separate each sentence to be on a new line for readability.
Always end the chat with the reflection question.
**Important Output Instructions**
You **must** return a valid **JSON object** using the following format:
{format_instructions}
- Begin your output immediately with the JSON object.
- Do **not** wrap it in backticks or markdown.
- Do **not** include any explanation, greeting, or text before or after the JSON.
- Only the JSON should be returned. Strictly follow the schema.
If you do not follow this format, your response will be rejected and considered invalid.
"""
),
("placeholder", "{chat_history}"),
("human", "{query}"),
("placeholder", "{agent_scratchpad}"),
])
prompt = prompt.partial(format_instructions=parser.get_format_instructions())
# set up agent
tools = []
agent = create_tool_calling_agent(llm=llm, prompt=prompt, tools=tools)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=False)
history = ""
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
agent_with_chat_history = RunnableWithMessageHistory(
agent_executor,
get_session_history,
input_messages_key="query",
history_messages_key="chat_history",
)
# Gradio chat response function
def respond(message, history: list[dict], username):
# Step 1: Embed user message
try:
vectorized_input = embedding_model.embed_query(message)
except Exception as e:
print("Embedding failed:", e)
vectorized_input = []
# Step 2: Query Pinecone
TOP_K_GLOBAL = 3
context = ""
if vectorized_input:
try:
pinecone_response = index.query(
namespace="ns4",
vector=vectorized_input,
top_k=3,
include_metadata=True
)
matches = pinecone_response.get("matches", [])
context = "\n".join([match['metadata']['text'] for match in matches if 'metadata' in match])
print("From Pinecone:", context)
if username or username.strip().lower() == "Lina":
pinecone_journal_response = index.query(
namespace=username,
vector=vectorized_input,
top_k=3,
include_metadata=True
)
journal_matches = pinecone_journal_response.get("matches", [])
journal_context = "\n".join([match['metadata']['text'] for match in journal_matches if 'metadata' in match])
print("From Pinecone:", journal_context)
context_prefix = f"Relevant Islamic background information:\n{context}\nRelevant User previous Journal entry information:\n{journal_context}\n\nUser query:"
else:
context_prefix = f"Relevant Islamic background information:\n{context}\n\nUser query:"
except:
print("Pinecone query failed.")
else:
print("Vectorised input is empty.")
# Append context to the query
full_query = f"{context_prefix} {message}"
print("\n Full query sent to LLM:\n", full_query)
# Step 4: Agent invocation
agent_output = agent_with_chat_history.invoke(
{"query": full_query},
config={"configurable": {"session_id": "<foo>"}},
)
raw = agent_output["output"]
print("=== RAW OUTPUT FROM LLM ===")
print(raw)
out = parser.parse(raw)
assistant_text = "\n".join([
out.empathetic_response,
out.informative_response,
out.quran,
out.question,
])
response = {"role": "assistant", "content": assistant_text}
yield response
# launch ChatInterface
with gr.Blocks(fill_height=True) as demo:
username = gr.State()
with gr.Column(visible=True) as login_container:
username_input = gr.Textbox(label="Enter your username to start.\nIf you're new, enter 'Guest'")
submit_button = gr.Button("Start Chat")
# Wrap ChatInterface inside a column so we can control its visibility
with gr.Column(visible=False) as chatbot_container:
chatbot = gr.ChatInterface(
fn=respond,
title="YAQIN Chatbot",
description="Culturally Sensitive Chatbot for Muslim Women Wanting Mental Healthcare.\n\nWhat's on your mind?",
type="messages",
additional_inputs=[username],
)
def start_chatbot(name):
return gr.update(visible=False), gr.update(visible=True), name
submit_button.click(
fn=start_chatbot,
inputs=username_input,
outputs=[login_container, chatbot_container, username]
)
if __name__ == "__main__":
demo.launch()
|