File size: 15,600 Bytes
b6f3de6
24e479b
 
2c8b701
168ecfe
b631e19
0b633df
9a31c8f
 
fdbaaaf
96ebd33
b6f3de6
9a31c8f
 
 
f8c2a86
 
9a31c8f
e631d2f
fccec79
d4e577f
 
 
f8c2a86
 
 
d4e577f
 
 
9a31c8f
 
2c8b701
 
 
 
 
 
 
 
 
 
 
 
 
168ecfe
 
 
 
 
 
2c8b701
 
 
 
 
 
 
 
 
 
 
 
 
 
20ff1e3
 
 
0b633df
20ff1e3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb9293b
20ff1e3
 
 
 
 
 
 
 
 
 
 
 
 
 
9a31c8f
24e479b
 
 
ccae886
20ff1e3
0b633df
 
 
 
 
 
 
 
 
20ff1e3
 
cc075da
 
 
 
 
 
 
 
4abecb0
cc075da
 
0b633df
 
 
 
 
 
 
 
 
 
2ef1794
12bd908
0b633df
 
 
 
 
 
 
20ff1e3
5fa5d29
 
1df6d5c
4abecb0
1df6d5c
e378954
1df6d5c
e378954
5fa5d29
773fd7c
0d66203
ccae886
 
 
4ac5005
 
 
0d66203
e378954
773fd7c
e378954
 
0d66203
 
 
 
 
24e479b
0b633df
24e479b
e4cab9a
24e479b
 
 
ccae886
20ff1e3
 
 
 
 
22d49db
20ff1e3
 
95d8fc2
96ebd33
 
0b633df
acfc3b8
0b633df
8f7067c
12bd908
 
acfc3b8
 
9a31c8f
 
1ec0707
 
 
 
 
fccec79
9a31c8f
24e479b
 
 
 
 
 
 
bf19733
24e479b
eb9293b
 
 
 
 
 
24e479b
eb9293b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a31c8f
 
 
 
 
0194da3
9a31c8f
 
 
 
0194da3
9a31c8f
22d49db
 
 
 
 
 
9a31c8f
2fc0926
 
96ee798
9a31c8f
5f18a18
 
 
 
 
 
 
a0baf23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773fd7c
a0baf23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb9293b
35f8dd7
5f18a18
eb9293b
 
5f18a18
 
eb9293b
 
 
 
 
e4cab9a
eb9293b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
import streamlit as st
import random
import time
import hmac
import os
import json
import requests
from llm_reasoner import LLMReasoner
from prompts import templates
from typing import Any
from string import Template

def safe_parse_json(model_answer):
    """.."""
    try:
        parsed_answer = json.loads(model_answer)
        return parsed_answer
    except json.JSONDecodeError as e:
        print("Failed to parse JSON: %s", e)
        print(f"Raw model answer is: {model_answer}")
        model_answer = model_answer.replace("```json", "")
        model_answer = model_answer.replace("```", "")
    try:
        parsed_answer = json.loads(model_answer)
        print(f"Hit parsing error but cleaned json as markdown code snippet and it can be parsed now")
        return parsed_answer
    except json.JSONDecodeError as e:
        print("Failed to parse JSON: %s", e)
        print(f"Tried to parse cleaned model answer which was: {model_answer}")
        return None

def check_password():
    """Returns `True` if the user had a correct password."""

    def login_form():
        """Form with widgets to collect user information"""
        with st.form("Credentials"):
            st.text_input("Username", key="username")
            st.text_input("Password", type="password", key="password")
            st.form_submit_button("Log in", on_click=password_entered)

    def password_entered():
        """Checks whether a password entered by the user is correct."""

        stored_password = os.getenv(st.session_state["username"])
        if stored_password == st.session_state["password"]:
            st.session_state["password_correct"] = True
            del st.session_state["password"]  # Remove credentials from session
            del st.session_state["username"]
            return

          # If authentication fails
        st.session_state["password_correct"] = False

    # Return True if the username + password is validated.
    if st.session_state.get("password_correct", False):
        return True

    # Show inputs for username + password.
    login_form()
    if "password_correct" in st.session_state:
        st.error("πŸ˜• User not known or password incorrect")
    return False

def select_models():
    """Returns only when a valid option is selected from both dropdowns."""

    retriever_options = ["Choose one...", "BM25 Retriever", "Off-the-shelf Retriever", "Finetuned Retriever", "No Retriever"]
    reasoner_options = ["Choose one...", "Claude Sonnet", "GPT-4o", "o3-mini"]

    #selectboxes

    retriever = st.selectbox(
        "Select the Retriever Model",
        retriever_options,
        key="retriever"
    )

    reasoner = st.selectbox(
        "Select the Reasoner Model",
        reasoner_options,
        key="reasoner"
    )

    # next button

    if st.button("Next"):
        # Check that both selections are not the placeholder.
        if retriever == "Choose one..." or reasoner == "Choose one...":
            st.info("Please select both a retriever and a reasoner.")
            return None, None
        else:
            # Store the valid selections in session state
            st.session_state["selected_models"] = (retriever, reasoner)
            return retriever, reasoner
    else:
        st.info("Click 'Next' once you have made your selections.")
        return None, None

def retriever(query: str, selected_retriever: str):
    """Simulate a 'retriever' step, searching for relevant information."""
    with st.chat_message("assistant"):
        placeholder = st.empty()
        text = ""
        
        if selected_retriever == "BM25 Retriever":
            message = "Using the BM25 retriever to search for documents related to your query..."
            retriever_endpoint = "bm25"
        elif selected_retriever == "Off-the-shelf Retriever":
            message = "Using the off-the-shelf retriever to fetch detailed documents relevant to your query..."
            retriever_endpoint = "ots"
        elif selected_retriever == "Finetuned Retriever":
            message = "Using the finetuned retriever to fetch detailed documents relevant to your query..."
            retriever_endpoint = "ft"
        else:
            message = "No retriever selected. Skipping document retrieval."
            retriever_endpoint = "None"

        for chunk in message.split():
            text += chunk + " "
            time.sleep(0.05)
            # Add a blinking cursor to simulate typing
            placeholder.markdown(text + "β–Œ")
        placeholder.markdown(text)
        placeholder.markdown("\n")

        if retriever_endpoint == "None":
            return ""

        headers = {
            'Content-Type': 'application/json',
        }

        json_data = {
            'claim': query,
        }

        url = "http://18.227.0.215"
        port = "8000"
        response = requests.post(f'{url}:{port}/{retriever_endpoint}', headers=headers, json=json_data)
        documents = response.json()["Documents"]

        k = 3
        topk_documents = documents[:k]

        corpus = '\n\n'.join(topk_documents)

        retrieved_document_titles = ""
        for document in topk_documents:
            try:
                idx = document.lower().index("abstract:")
                title = document[:idx].replace('\n', ' ')
                retrieved_document_titles = retrieved_document_titles + title + ' ||| '
            except:
                retrieved_document_titles = retrieved_document_titles + document + ' ||| '

        display_retrieved_documents = f" ||| Retrieved Documents - \n{retrieved_document_titles}"

    with st.chat_message("assistant"):
        placeholder = st.empty()
        text = ""

        print(display_retrieved_documents)

        for chunk in display_retrieved_documents.split():
            if chunk == "|||":
                text += "  \n" + "  \n"
            else:
                text += chunk + " "
            time.sleep(0.05)
            # Add a blinking cursor to simulate typing
            placeholder.markdown(text + "β–Œ")
        placeholder.markdown(text)

    # You could return retrieved info here.
    return corpus

def reasoner(query: str, documents: list[str], selected_reasoner: str, llm_client: Any):
    """Simulate a 'reasoner' step, thinking about how to answer."""
    with st.chat_message("assistant"):
        placeholder = st.empty()
        text = ""

        if selected_reasoner == "Claude Sonnet":
            message = "Using Claude Sonnet to reason and verify the claim..."
        elif selected_reasoner == "GPT-4o":
            message = "Using GPT-4o to analyze and verify the claim in detail..."
        elif selected_reasoner == "o3-mini":
            message = "Using o3-mini to quickly analyze the claim..."

        if not documents or len(documents) == 0:
            prompt_template = Template(templates["no_evidence"])
            prompt = prompt_template.substitute(claim=query)
            print(prompt)
        else:
            prompt_template = Template(templates["with_evidence"])
            prompt = prompt_template.substitute(claim=query, corpus_text=documents)

        print(prompt)
        
        llm_response = llm_client.run_inference(prompt)

        answer_dict = safe_parse_json(llm_response)
        try:
            decision = answer_dict.get("decision", "")
            reasoning = answer_dict.get("reasoning", "")
        except:
            print(f"Error with parsing the returned {answer_dict}")
            decision, reasoning = "", ""

        for chunk in message.split():
            text += chunk + " "
            time.sleep(0.05)
            # Add a blinking cursor to simulate typing
            placeholder.markdown(text + "β–Œ")
        placeholder.markdown(text)
    # You could return reasoning info here.
    return reasoning, decision

def main():
    st.header(" Scientific Claim Verification ")
    st.caption("Team UMBC-SBU-UT")

    if not check_password():
        st.stop()

    if "selected_models" not in st.session_state:
        selected_retriever, selected_reasoner = select_models()
        # If valid selections are returned, store them and reset the change flag.
        if selected_retriever is not None and selected_reasoner is not None:
            st.session_state.selected_models = (selected_retriever, selected_reasoner)
            st.rerun()
        else:
            st.stop()  # Halt further execution until valid selections are made.
    else:
        selected_retriever, selected_reasoner = st.session_state.selected_models

    # START OF AGENTIC DEMO

    column1, column2 = st.columns(2)
    column1.caption(f"Retriever Selected: {selected_retriever}")
    column2.caption(f"Reasoner Selected: {selected_reasoner}")

    if st.button("Change Selection", key="change_selection_btn"):
        st.session_state.pop("selected_models", None)
        st.session_state.pop("retriever", None)
        st.session_state.pop("reasoner", None)
        st.session_state.messages = [{"role": "assistant", "content": "Let's start verifying the claims here! πŸ‘‡"}]
        st.rerun()

    # Initialize chat history
    if "messages" not in st.session_state:
        st.session_state.messages = [{"role": "assistant", "content": "Let's start verifying the claims here! πŸ‘‡"}]

    # Display chat messages from history on app rerun
    for message in st.session_state.messages:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])

    # Model configurations on agentic demo page
    options = {}
    options["max_tokens"] = 500
    options["temperature"] = 0.0

    if selected_reasoner == "Claude Sonnet":
        api_key = os.getenv("claude_key")
        options["model_family"] = "Anthropic"
        options["model_name"] = "claude-3-5-sonnet-20240620"

    elif selected_reasoner == "GPT-4o":
        api_key = os.getenv("openai_key")
        options["model_family"] = "OpenAI"
        options["model_name"] = "gpt-4o-2024-11-20"

    elif selected_reasoner == "o3-mini":
        api_key = os.getenv("openai_key")
        options["model_family"] = "OpenAI"
        options["model_name"] = "o3-mini-2025-01-31"

    options["API_KEY"] = api_key

    llm_client = LLMReasoner(options)

    def send_preset_message(text):
        # Add user message to chat history
        st.session_state.messages.append({"role": "user", "content": text})
        # Set the auto_submit flag to process this message
        st.session_state.auto_submit = True
        st.session_state.auto_submit_text = text

    # Quick input buttons - place these above the chat input
    st.sidebar.subheader("Quick Inputs")
    
    # Create buttons in the sidebar
    if st.sidebar.button("Example 1 C"):
        send_preset_message("The density of cytokine receptor bearing cells has no effect on the distance over which cytokines act.")
    
    if st.sidebar.button("Example 2 C"):
        send_preset_message("A total of 1,000 people in the UK are asymptomatic carriers of vCJD infection.")
    
    if st.sidebar.button("Example 3 C"):
        send_preset_message("Sepsis related mortality has risen from 2009 to 2014.")
    
    if st.sidebar.button("Example 4 S"):
        send_preset_message("IL-6 signaling plays a major role in atherosclerotic cardiovascular disease.")
    
    if st.sidebar.button("Example 5 S"):
        send_preset_message("The severity of cardiac involvement in amyloidosis can be described by the degree of transmurality of late gadolinium enhancement in MRI.")

    if st.sidebar.button("Example 6 S"):
        send_preset_message("There was an estimated 30 million cases of pneumonia in young children worldwide in 2010.")

    # Handle auto-submission if a button was clicked
    if "auto_submit" in st.session_state and st.session_state.auto_submit:
        prompt = st.session_state.auto_submit_text

        display_message = prompt + "  \n" + "  \n" + f"Retriever: {selected_retriever}, Reasoner: {selected_reasoner}"
        st.session_state.messages.append({"role": "user", "content": prompt})
        st.session_state.messages.append({"role": "summary", "content": display_message})

        # Display user message in chat message container
        with st.chat_message("user"):
            st.markdown(display_message)

        retrieved_documents = retriever(prompt, selected_retriever)
        reasoning, decision = reasoner(prompt, retrieved_documents, selected_reasoner, llm_client)

        # Display assistant response in chat message container
        with st.chat_message("assistant"):
            message_placeholder = st.empty()
            full_response = ""
            if decision.lower() == 'support':
                assistant_response = f'The claim is CORRECT because {reasoning}'
            elif decision.lower() == 'contradict':
                assistant_response = f'The claim is INCORRECT because {reasoning}'
            else:
                assistant_response = f'Sorry, the query failed due to an issue with connecting to the LLM service.'

            # Simulate stream of response with milliseconds delay
            for chunk in assistant_response.split():
                full_response += chunk + " "
                time.sleep(0.05)
                # Add a blinking cursor to simulate typing
                message_placeholder.markdown(full_response + "β–Œ")
            message_placeholder.markdown(full_response)
        # Add assistant response to chat history
        st.session_state.messages.append({"role": "assistant", "content": full_response})

        # Reset the auto_submit flag
        st.session_state.auto_submit = False
        # Rerun to update the UI immediately - may not be needed
        # st.rerun()

    # Accept user input
    prompt = st.chat_input("Type your claim here")
    if prompt:
        # Add user message to chat history
        display_message = prompt + "  \n"+ "  \n"+ f"Retriever: {selected_retriever}, Reasoner: {selected_reasoner}"
        st.session_state.messages.append({"role": "user", "content": prompt})
        st.session_state.messages.append({"role": "summary", "content": display_message})
        # Display user message in chat message container
        with st.chat_message("user"):
            st.markdown(display_message)

        retrieved_documents = retriever(prompt, selected_retriever)
        reasoning, decision = reasoner(prompt, retrieved_documents, selected_reasoner, llm_client)

        # Display assistant response in chat message container
        with st.chat_message("assistant"):
            message_placeholder = st.empty()
            full_response = ""
            if decision.lower() == 'support':
                assistant_response = f'The claim is CORRECT because {reasoning}'
            elif decision.lower() == 'contradict':
                assistant_response = f'The claim is INCORRECT because {reasoning}'
            else:
                assistant_response = f'Sorry, the query failed due to an issue with connecting to the LLM service.'

            # Simulate stream of response with milliseconds delay
            for chunk in assistant_response.split():
                full_response += chunk + " "
                time.sleep(0.05)
                # Add a blinking cursor to simulate typing
                message_placeholder.markdown(full_response + "β–Œ")
            message_placeholder.markdown(full_response)
        # Add assistant response to chat history
        st.session_state.messages.append({"role": "assistant", "content": full_response})

if __name__ == '__main__':
    main()