Spaces:
YZ03
/
Runtime error

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()