knakamura924's picture
correct sentences
21d33f8
"""
Chatbot App for Cognitive Debriefing Interview
Author: Dr Musashi Hinck
Updated for PolMeth Demo by Kentaro Nakamura
"""
from __future__ import annotations
import os
import json
import logging
import gradio as gr
from uuid import uuid4
from typing import Generator, Any
from pathlib import Path
from utils import (
PromptTemplate,
convert_gradio_to_openai,
initialize_client,
load_dotenv,
upload_azure,
record_chat,
ChatLoggerHandler
)
# %% Initialize common assets
base_logger = logging.getLogger(__name__)
chat_logger = ChatLoggerHandler()
if os.environ.get("AZURE_ENDPOINT") is None: # Set Azure credentials from local files
load_dotenv()
client = initialize_client() # Shared across sessions
question_mapping: dict[str, str] = json.loads(Path("assets/question_mapping.json").read_text())
# %% (functions)
# Initialization
# - Record user and session id
# - Record question and response
# - Build system message
# - Build initial message
# - Wrapper - start_survey
def initialize_interview(request: gr.Request) -> tuple:
"""
Read: Request
Set: values of userId, sessionId, questionWording, initialMessage, systemMessage
"""
# Parse request
request_params = request.query_params
user_id: str = request_params.get("user", "testUser")
session_id: str = request_params.get("session", "testSession")
base_logger.info(f"User: {user_id} (Session: {session_id})")
# Return all
return (
user_id,
session_id,
)
def initialize_interface(sentence_input: str, question_input: str) -> tuple:
"""
Change interface to interactive mode.
Read: initial_message
Set:
instruction_text: modify (to empty)
chat_display: set initial_message
chat_input: update placeholder, make interactive
chat_submit: make interactive
start_button: hide
"""
#divide by comma
question_input = question_input.split(", ")
initial_message: str = PromptTemplate.from_file("assets/initial_message.txt").format(surveyQuestion=sentence_input)
system_message: str = PromptTemplate.from_file("assets/system_message.txt").format(surveyQuestion=sentence_input, responseVal=question_input[0])
instruction_text = gr.Markdown("")
chat_display = gr.Chatbot(
value=[[None, initial_message]],
elem_id="chatDisplay",
show_label=False,
visible=True,
)
chat_input = gr.Textbox(
placeholder="Type response here. Hit `Enter` or click the arrow to submit.",
visible=True,
interactive=True,
show_label=False,
scale=10,
)
chat_submit = gr.Button(
"",
variant="primary",
interactive=True,
icon="./arrow_icon.svg",
visible=True,
)
start_button = gr.Button("Start Interview", visible=False, variant="primary")
return (instruction_text, chat_display, chat_input, chat_submit, start_button, system_message)
# Interaction
# - User message
# - Bot message
# - Check if interview finished
# - Record interaction (local log)
def user_message(
message: str, chat_history: list[list[str | None]]
) -> tuple[str, list[list[str | None]]]:
"Display user message immediately"
return "", chat_history + [[message, None]]
def bot_message(
chat_history: list[list[str | None]],
system_message: str,
model_args: dict = {"model": "gpt-4o-default", "temperature": 0.0},
) -> Generator[Any, Any, Any]:
"Streams response from OpenAI API to chat interface."
# Prep messages
user_msg = chat_history[-1][0]
messages = convert_gradio_to_openai(chat_history[:-1])
messages = (
[{"role": "system", "content": system_message}]
+ messages
+ [{"role": "user", "content": user_msg}]
)
# API call
response = client.chat.completions.create(
messages=messages, stream=True, **model_args
)
# Streaming
chat_history[-1][1] = ""
for chunk in response:
delta = chunk.choices[0].delta.content
if delta:
chat_history[-1][1] += delta
yield chat_history
def log_interaction(
chat_history: list[list[str | None]],
session_id: str,
) -> None:
"Record last pair of interactions"
record_chat(chat_logger, session_id, "user", chat_history[-1][0])
record_chat(chat_logger, session_id, "bot", chat_history[-1][1])
def interview_end_check(
chat_history: list[list[str | None]],
limit: int = 20,
end_of_interview: str = "<end_of_survey>",
) -> tuple[list[list[str | None]], gr.Button, gr.Textbox, gr.Button]:
"""
Checks if interview has completed using two conditions:
1. If the last bot message contains `end_of_interview` (default: "<end_of_survey>". Replaced "<end_interview>" with this new default token by Kentaro)
2. Conversation length has reached `limit` (default: 10)
If either condition is met, the end of interview button is displayed.
"""
flag = False
if len(chat_history) >= limit:
flag = True
if end_of_interview in chat_history[-1][1]:
chat_history[-1][1] = chat_history[-1][1].replace(end_of_interview, "")
flag = True
input_button = gr.Textbox(
placeholder="Type response here. Hit `Enter` or click the arrow to submit.",
visible= not flag,
interactive=True,
show_label=False,
scale=10,
)
submit_button = gr.Button(
"",
variant="primary",
interactive=True,
icon="./arrow_icon.svg",
visible= not flag,
)
button = gr.Button("Save and Exit", visible=flag, variant="stop")
return chat_history, button, input_button, submit_button
# Completion
# - Create completion code
# - Append to message history
# - Display completion code
def generate_completion_code(prefix: str = "cd-") -> str:
return prefix + str(uuid4())
def upload_interview(
session_id: str,
chat_history: list[list[str | None]],
) -> None:
"Upload chat history to Azure blob storage"
upload_azure(session_id, chat_history)
def end_interview(
session_id: str,
chat_history: list[list[str | None]],
) -> tuple[list[list[str | None]], gr.Text]:
"""Create completion code and display in chat interface."""
completion_message = (
"This is the end of demo. Thank you for participating. Have a good day!"
)
upload_interview(session_id, chat_history)
EndMessage = gr.Text(completion_message, visible=True, show_label=False, scale=10)
return chat_history, EndMessage
def disable_inputs():
#return (gr.update(visible = False), gr.update(visible=False))
return (gr.update(visible = False), gr.update(visible = False))
# LAYOUT
with gr.Blocks(theme="sudeepshouche/minimalist") as demo:
# Header and instructions
gr.Markdown("# Cognitive Debriefing Demo")
instructionText = gr.Markdown(
"Please input a survey question and answer options and experience cognitive debriefing!"
)
#added for PolMeth
sentence_input = gr.Textbox(
label="Survey Question",
placeholder="Enter your survey question here (e.g., Generally speaking, do you think of yourself as a Republican, a Democrat, an independent, or something else?)",
value="Generally speaking, do you think of yourself as a Republican, a Democrat, an independent, or something else?",
interactive=True,
)
question_input = gr.Textbox(
label="Answer Options",
placeholder="Enter your answer options here (e.g., Republican, Democrat, Independent, Something else)",
value="Republican, Democrat, Independent, Something else",
interactive=True,
)
# Initialize empty hidden values.
userId = gr.State()
sessionId = gr.State()
questionWording = gr.State()
initialMessage = gr.State()
systemMessage = gr.State()
modelArgs = gr.State(value={"model": "gpt-4o-default", "temperature": 0.0})
# Chat app (display, input, submit button)
startButton = gr.Button("Start Interview", visible=True, variant="primary")
chatDisplay = gr.Chatbot(
value=None,
elem_id="chatDisplay",
show_label=False,
visible=True,
)
EndMessage = gr.Text("", visible=False, show_label=False, scale=10)
with gr.Row(): # Interaction
chatInput = gr.Textbox(
placeholder="Click 'Start Interview' to begin.",
visible=False,
interactive=False,
show_label=False,
scale=10,
)
chatSubmit = gr.Button(
"",
variant="primary",
visible=False,
interactive=False,
icon="./arrow_icon.svg",
)
exitButton = gr.Button("Generate Completion Code", visible=False, variant="stop")
# testExitButton = gr.Button("Save and Exit", visible=True, variant="stop")
# Footer
#disclaimer = gr.HTML(
# """
# <div
# style='font-size: 1em;
# font-style: italic;
# position: fixed;
# left: 50%;
# bottom: 20px;
# transform: translate(-50%, -50%);
# margin: 0 auto;
# '
# >{}</div>
# """.format(
# "Statements by the chatbot may contain factual inaccuracies."
# )
#)
# INTERACTIONS
# Initialization
startButton.click(
initialize_interview, # Reads in request params
inputs=None,
outputs=[
userId,
sessionId,
],
).then(
disable_inputs, #make the inputs invisible
inputs = None,
outputs = [sentence_input, question_input],
).then(
initialize_interface, # Changes interface to interactive mode
inputs=[sentence_input, question_input],
outputs=[
instructionText,
chatDisplay,
chatInput,
chatSubmit,
startButton,
systemMessage,
],
)
# Chat interaction
# "Enter"
chatInput.submit(
user_message,
inputs=[chatInput, chatDisplay],
outputs=[chatInput, chatDisplay],
queue=False,
).then(
bot_message,
inputs=[chatDisplay, systemMessage, modelArgs],
outputs=[chatDisplay],
).then(
log_interaction,
inputs=[chatDisplay, sessionId],
).then(
interview_end_check, inputs=[chatDisplay], outputs=[chatDisplay, exitButton, chatInput, chatSubmit]
)
# Button
chatSubmit.click(
user_message,
inputs=[chatInput, chatDisplay],
outputs=[chatInput, chatDisplay],
queue=False,
).then(
bot_message,
inputs=[chatDisplay, systemMessage, modelArgs],
outputs=[chatDisplay],
).then(
log_interaction,
inputs=[chatDisplay, sessionId],
).then(
interview_end_check, inputs=[chatDisplay], outputs=[chatDisplay, exitButton, chatInput, chatSubmit]
)
# Reset button
exitButton.click(
end_interview, inputs=[sessionId, chatDisplay], outputs=[chatDisplay, EndMessage]
)
# testExitButton.click(
# end_interview, inputs=[sessionId, chatDisplay], outputs=[chatDisplay]
# )
if __name__ == "__main__":
demo.launch()#auth=auth_no_user)