File size: 11,300 Bytes
5d99375
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import gradio as gr
import requests
import numpy as np
import time
import json
import os

# Import the utilities with proper error handling
try:
    from utils.encoding_input import encode_text
    from utils.retrieve_n_rerank import retrieve_and_rerank
    from utils.sentiment_analysis import get_sentiment
    from utils.coherence_bbscore import coherence_report
    from utils.loading_embeddings import get_vectorstore
    from utils.model_generation import build_messages
except ImportError as e:
    print(f"Import error: {e}")
    print("Make sure you're running from the correct directory and all dependencies are installed.")

API_KEY = os.getenv("API_KEY", "sk-do-8Hjf0liuGQCoPwglilL49xiqrthMECwjGP_kAjPM53OTOFQczPyfPK8xJc")
MODEL = "llama3.3-70b-instruct"

# Global settings for sentiment and coherence analysis
ENABLE_SENTIMENT = True
ENABLE_COHERENCE = True

def chat_response(message, history):
    """
    Generate response for chat interface.
    
    Args:
        message: Current user message
        history: List of [user_message, bot_response] pairs
    """
    
    try:
        # Initialize vectorstore when needed
        vectorstore = get_vectorstore()
        
        # Retrieve and rerank documents
        reranked_results = retrieve_and_rerank(
            query_text=message,
            vectorstore=vectorstore,
            k=50,  # number of initial documents to retrieve
            rerank_model="cross-encoder/ms-marco-MiniLM-L-6-v2",
            top_m=20,  # number of documents to return after reranking
            min_score=0.5,  # minimum score for reranked documents
            only_docs=False  # return both documents and scores
        )
        
        if not reranked_results:
            return "I'm sorry, I couldn't find any relevant information in the policy documents to answer your question. Could you try rephrasing your question or asking about a different topic?"
        
        top_docs = [doc for doc, score in reranked_results]

        # Perform sentiment and coherence analysis if enabled
        sentiment_rollup = get_sentiment(top_docs) if ENABLE_SENTIMENT else {}
        coherence_report_ = coherence_report(reranked_results=top_docs, input_text=message) if ENABLE_COHERENCE else ""

        # Build messages for the LLM, including conversation history
        messages = build_messages_with_history(
            query=message,
            history=history,
            top_docs=top_docs,
            task_mode="verbatim_sentiment",
            sentiment_rollup=sentiment_rollup,
            coherence_report=coherence_report_,
        )

        # Stream response from the API
        response = ""
        for chunk in stream_llm_response(messages):
            response += chunk
            yield response

    except Exception as e:
        error_msg = f"I encountered an error while processing your request: {str(e)}"
        yield error_msg

def build_messages_with_history(query, history, top_docs, task_mode, sentiment_rollup, coherence_report):
    """Build messages including conversation history for better context."""
    
    # System message
    system_msg = (
        "You are a compliance-grade policy analyst assistant specializing in Kenya policy documents. "
        "Your job is to return precise, fact-grounded responses based on the provided policy documents. "
        "Avoid hallucinations. Base everything strictly on the content provided. "
        "Maintain conversation context from previous exchanges when relevant. "
        "If sentiment or coherence analysis is not available, do not mention it in the response."
    )
    
    messages = [{"role": "system", "content": system_msg}]
    
    # Add conversation history (keep last 4 exchanges to maintain context without exceeding limits)
    recent_history = history[-4:] if len(history) > 4 else history
    for user_msg, bot_msg in recent_history:
        messages.append({"role": "user", "content": user_msg})
        messages.append({"role": "assistant", "content": bot_msg})
    
    # Build context from retrieved documents
    context_block = "\n\n".join([
        f"**Source: {getattr(doc, 'metadata', {}).get('source', 'Unknown')} "
        f"(Page {getattr(doc, 'metadata', {}).get('page', 'Unknown')})**\n"
        f"{doc.page_content}\n"
        for doc in top_docs[:10]  # Limit to top 10 docs to avoid token limits
    ])
    
    # Current user query with context
    current_query = f"""
Query: {query}

Based on the following policy documents, please provide:
1) **Quoted Policy Excerpts**: Quote key policy content directly. Cite the source using filename and page.
2) **Analysis**: Explain the policy implications in clear terms.
"""
    
    if sentiment_rollup:
        current_query += f"\n3) **Sentiment Summary**: {sentiment_rollup}"
    
    if coherence_report:
        current_query += f"\n4) **Coherence Assessment**: {coherence_report}"
        
    current_query += f"\n\nContext Sources:\n{context_block}"
    
    messages.append({"role": "user", "content": current_query})
    
    return messages

def stream_llm_response(messages):
    """Stream response from the LLM API."""
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }

    data = {
        "model": MODEL,
        "messages": messages,
        "temperature": 0.2,
        "stream": True,
        "max_tokens": 2000
    }

    try:
        with requests.post("https://inference.do-ai.run/v1/chat/completions", 
                          headers=headers, json=data, stream=True, timeout=30) as r:
            if r.status_code != 200:
                yield f"[ERROR] API returned status {r.status_code}: {r.text}"
                return

            for line in r.iter_lines(decode_unicode=True):
                if not line or line.strip() == "data: [DONE]":
                    continue
                if line.startswith("data: "):
                    line = line[len("data: "):]

                try:
                    chunk = json.loads(line)
                    delta = chunk.get("choices", [{}])[0].get("delta", {}).get("content", "")
                    if delta:
                        yield delta
                        time.sleep(0.01)  # Small delay for smooth streaming
                except json.JSONDecodeError:
                    continue
                except Exception as e:
                    print(f"Streaming error: {e}")
                    continue
                    
    except requests.exceptions.RequestException as e:
        yield f"[ERROR] Network error: {str(e)}"
    except Exception as e:
        yield f"[ERROR] Unexpected error: {str(e)}"

def update_sentiment_setting(enable):
    """Update global sentiment analysis setting."""
    global ENABLE_SENTIMENT
    ENABLE_SENTIMENT = enable
    return f"βœ… Sentiment analysis {'enabled' if enable else 'disabled'}"

def update_coherence_setting(enable):
    """Update global coherence analysis setting."""
    global ENABLE_COHERENCE
    ENABLE_COHERENCE = enable
    return f"βœ… Coherence analysis {'enabled' if enable else 'disabled'}"

# Create the chat interface
with gr.Blocks(title="Kenya Policy Assistant - Chat", theme=gr.themes.Soft()) as demo:
    gr.Markdown("""
    # πŸ›οΈ Kenya Policy Assistant - Interactive Chat
    Ask questions about Kenya's policies and have a conversation! I can help you understand policy documents with sentiment and coherence analysis.
    """)
    
    with gr.Row():
        with gr.Column(scale=3):
            # Settings row at the top
            with gr.Row():
                sentiment_toggle = gr.Checkbox(
                    label="πŸ“Š Sentiment Analysis", 
                    value=True,
                    info="Analyze tone and sentiment of policy documents"
                )
                coherence_toggle = gr.Checkbox(
                    label="πŸ” Coherence Analysis", 
                    value=True,
                    info="Check coherence and consistency of retrieved documents"
                )
            
            # Main chat interface
            chatbot = gr.Chatbot(
                height=500,
                bubble_full_width=False,
                show_copy_button=True,
                show_share_button=True,
                avatar_images=("πŸ‘€", "πŸ€–")
            )
            
            msg = gr.Textbox(
                placeholder="Ask me about Kenya's policies... (e.g., 'What are the renewable energy regulations?')",
                label="Your Question",
                lines=2
            )
            
            with gr.Row():
                submit_btn = gr.Button("πŸ“€ Send", variant="primary")
                clear_btn = gr.Button("πŸ—‘οΈ Clear Chat")
        
        with gr.Column(scale=1):
            gr.Markdown("""
            ### πŸ’‘ Chat Tips
            - Ask specific questions about Kenya policies
            - Ask follow-up questions based on responses
            - Reference previous answers: *"What does this mean?"*
            - Request elaboration: *"Can you explain more?"*
            
            ### πŸ“ Example Questions
            - *"What are Kenya's renewable energy policies?"*
            - *"Tell me about water management regulations"*
            - *"What penalties exist for environmental violations?"*
            - *"How does this relate to what you mentioned earlier?"*
            
            ### βš™οΈ Analysis Features
            **Sentiment Analysis**: Understands the tone and intent of policy text
            
            **Coherence Analysis**: Checks if retrieved documents are relevant and consistent
            """)
            
            with gr.Accordion("πŸ“Š Analysis Status", open=False):
                sentiment_status = gr.Textbox(
                    value="βœ… Sentiment analysis enabled",
                    label="Sentiment Status",
                    interactive=False
                )
                coherence_status = gr.Textbox(
                    value="βœ… Coherence analysis enabled", 
                    label="Coherence Status",
                    interactive=False
                )

    # Chat functionality
    def respond(message, history):
        if message.strip():
            bot_message = chat_response(message, history)
            history.append([message, ""])
            
            for partial_response in bot_message:
                history[-1][1] = partial_response
                yield history, ""
        else:
            yield history, ""

    submit_btn.click(respond, [msg, chatbot], [chatbot, msg])
    msg.submit(respond, [msg, chatbot], [chatbot, msg])
    clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg])
    
    # Update settings when toggles change
    sentiment_toggle.change(
        fn=update_sentiment_setting,
        inputs=[sentiment_toggle],
        outputs=[sentiment_status]
    )
    
    coherence_toggle.change(
        fn=update_coherence_setting,
        inputs=[coherence_toggle],
        outputs=[coherence_status]
    )

if __name__ == "__main__":
    print("πŸš€ Starting Kenya Policy Assistant Chat...")
    demo.queue(max_size=20).launch(
        share=True, 
        debug=True,
        server_name="0.0.0.0",
        server_port=7860
    )