283 lines
9.6 KiB
Python
283 lines
9.6 KiB
Python
import pickle
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import gradio as gr
|
|
from theflow.storage import storage
|
|
|
|
from kotaemon.chatbot import ChatConversation
|
|
from kotaemon.contribs.promptui.base import get_component
|
|
from kotaemon.contribs.promptui.export import export
|
|
|
|
USAGE_INSTRUCTION = """## How to use:
|
|
|
|
1. Set the desired parameters.
|
|
2. Click "New chat" to start a chat session with the supplied parameters. This
|
|
set of parameters will persist until the end of the chat session. During an
|
|
ongoing chat session, changing the parameters will not take any effect.
|
|
3. Chat and interact with the chat bot on the right panel. You can add any
|
|
additional input (if any), and they will be supplied to the chatbot.
|
|
4. During chat, the log of the chat will show up in the "Output" tabs. This is
|
|
empty by default, so if you want to show the log here, tell the AI developers
|
|
to configure the UI settings.
|
|
5. When finishing chat, select your preference in the radio box. Click "End chat".
|
|
This will save the chat log and the preference to disk.
|
|
6. To compare the result of different run, click "Export" to get an Excel
|
|
spreadsheet summary of different run.
|
|
|
|
## Support:
|
|
|
|
In case of errors, you can:
|
|
|
|
- PromptUI instruction:
|
|
https://github.com/Cinnamon/kotaemon/wiki/Utilities#prompt-engineering-ui
|
|
- Create bug fix and make PR at: https://github.com/Cinnamon/kotaemon
|
|
- Ping any of @john @tadashi @ian @jacky in Slack channel #llm-productization
|
|
|
|
## Contribute:
|
|
|
|
- Follow installation at: https://github.com/Cinnamon/kotaemon/
|
|
"""
|
|
|
|
|
|
def construct_chat_ui(
|
|
config, func_new_chat, func_chat, func_end_chat, func_export_to_excel
|
|
) -> gr.Blocks:
|
|
"""Construct the prompt engineering UI for chat
|
|
|
|
Args:
|
|
config: the UI config
|
|
func_new_chat: the function for starting a new chat session
|
|
func_chat: the function for chatting interaction
|
|
func_end_chat: the function for ending and saving the chat
|
|
func_export_to_excel: the function to export the logs to excel
|
|
|
|
Returns:
|
|
the UI object
|
|
"""
|
|
inputs, outputs, params = [], [], []
|
|
for name, component_def in config.get("inputs", {}).items():
|
|
if "params" not in component_def:
|
|
component_def["params"] = {}
|
|
component_def["params"]["interactive"] = True
|
|
component = get_component(component_def)
|
|
if hasattr(component, "label") and not component.label: # type: ignore
|
|
component.label = name # type: ignore
|
|
|
|
inputs.append(component)
|
|
|
|
for name, component_def in config.get("params", {}).items():
|
|
if "params" not in component_def:
|
|
component_def["params"] = {}
|
|
component_def["params"]["interactive"] = True
|
|
component = get_component(component_def)
|
|
if hasattr(component, "label") and not component.label: # type: ignore
|
|
component.label = name # type: ignore
|
|
|
|
params.append(component)
|
|
|
|
for idx, component_def in enumerate(config.get("outputs", [])):
|
|
if "params" not in component_def:
|
|
component_def["params"] = {}
|
|
component_def["params"]["interactive"] = False
|
|
component = get_component(component_def)
|
|
if hasattr(component, "label") and not component.label: # type: ignore
|
|
component.label = f"Output {idx}" # type: ignore
|
|
|
|
outputs.append(component)
|
|
|
|
sess = gr.State(value=None)
|
|
chatbot = gr.Chatbot(label="Chatbot")
|
|
chat = gr.ChatInterface(func_chat, chatbot=chatbot, additional_inputs=[sess])
|
|
param_state = gr.Textbox(interactive=False)
|
|
|
|
with gr.Blocks(analytics_enabled=False, title="Welcome to PromptUI") as demo:
|
|
sess.render()
|
|
with gr.Accordion(label="HOW TO", open=False):
|
|
gr.Markdown(USAGE_INSTRUCTION)
|
|
with gr.Row():
|
|
run_btn = gr.Button("New chat")
|
|
run_btn.click(
|
|
func_new_chat,
|
|
inputs=params,
|
|
outputs=[
|
|
chat.chatbot,
|
|
chat.chatbot_state,
|
|
chat.saved_input,
|
|
param_state,
|
|
sess,
|
|
],
|
|
)
|
|
with gr.Accordion(label="End chat", open=False):
|
|
likes = gr.Radio(["like", "dislike", "neutral"], value="neutral")
|
|
save_log = gr.Checkbox(
|
|
value=True,
|
|
label="Save log",
|
|
info="If saved, log can be exported later",
|
|
show_label=True,
|
|
)
|
|
end_btn = gr.Button("End chat")
|
|
end_btn.click(
|
|
func_end_chat,
|
|
inputs=[likes, save_log, sess],
|
|
outputs=[param_state, sess],
|
|
)
|
|
with gr.Accordion(label="Export", open=False):
|
|
exported_file = gr.File(
|
|
label="Output file", show_label=True, height=100
|
|
)
|
|
export_btn = gr.Button("Export")
|
|
export_btn.click(
|
|
func_export_to_excel, inputs=None, outputs=exported_file
|
|
)
|
|
|
|
with gr.Row():
|
|
with gr.Column():
|
|
with gr.Tab("Params"):
|
|
for component in params:
|
|
component.render()
|
|
with gr.Accordion(label="Session state", open=False):
|
|
param_state.render()
|
|
|
|
with gr.Tab("Outputs"):
|
|
for component in outputs:
|
|
component.render()
|
|
with gr.Column():
|
|
chat.render()
|
|
|
|
return demo.queue()
|
|
|
|
|
|
def build_chat_ui(config, pipeline_def):
|
|
"""Build the chat UI
|
|
|
|
Args:
|
|
config: the UI config
|
|
pipeline_def: the pipeline definition
|
|
|
|
Returns:
|
|
the UI object
|
|
"""
|
|
output_dir: Path = Path(storage.url(pipeline_def().config.store_result))
|
|
exported_dir = output_dir.parent / "exported"
|
|
exported_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def new_chat(*args):
|
|
"""Start a new chat function
|
|
|
|
Args:
|
|
*args: the pipeline init params
|
|
|
|
Returns:
|
|
new empty states
|
|
"""
|
|
gr.Info("Starting new session...")
|
|
param_dicts = {
|
|
name: value for name, value in zip(config["params"].keys(), args)
|
|
}
|
|
for key in param_dicts.keys():
|
|
if config["params"][key].get("component").lower() == "file":
|
|
param_dicts[key] = param_dicts[key].name
|
|
|
|
# TODO: currently hard-code as ChatConversation
|
|
pipeline = pipeline_def()
|
|
session = ChatConversation(bot=pipeline)
|
|
session.set(param_dicts)
|
|
session.start_session()
|
|
|
|
param_state_str = "\n".join(
|
|
f"- {name}: {value}" for name, value in param_dicts.items()
|
|
)
|
|
|
|
gr.Info("New chat session started.")
|
|
return [], [], None, param_state_str, session
|
|
|
|
def chat(message, history, session, *args):
|
|
"""The chat interface
|
|
|
|
# TODO: wrap the input and output of this chat function so that it
|
|
work with more types of chat conversation than simple text
|
|
|
|
Args:
|
|
message: the message from the user
|
|
history: the gradio history of the chat
|
|
session: the chat object session
|
|
*args: the additional inputs
|
|
|
|
Returns:
|
|
the response from the chatbot
|
|
"""
|
|
if session is None:
|
|
raise gr.Error(
|
|
"No active chat session. Please set the params and click New chat"
|
|
)
|
|
|
|
return session(message).content
|
|
|
|
def end_chat(preference: str, save_log: bool, session):
|
|
"""End the chat session
|
|
|
|
Args:
|
|
preference: the preference of the user
|
|
save_log: whether to save the result
|
|
session: the chat object session
|
|
|
|
Returns:
|
|
the new empty state
|
|
"""
|
|
gr.Info("Ending session...")
|
|
session.end_session()
|
|
output_dir: Path = (
|
|
Path(storage.url(session.config.store_result)) / session.last_run.id()
|
|
)
|
|
|
|
if not save_log:
|
|
if output_dir.exists():
|
|
import shutil
|
|
|
|
shutil.rmtree(output_dir)
|
|
|
|
session = None
|
|
param_state = ""
|
|
gr.Info("End session without saving log.")
|
|
return param_state, session
|
|
|
|
# add preference result to progress
|
|
with (output_dir / "progress.pkl").open("rb") as fi:
|
|
progress = pickle.load(fi)
|
|
progress["preference"] = preference
|
|
with (output_dir / "progress.pkl").open("wb") as fo:
|
|
pickle.dump(progress, fo)
|
|
|
|
# get the original params
|
|
param_dicts = {name: session.getx(name) for name in config["params"].keys()}
|
|
with (output_dir / "params.pkl").open("wb") as fo:
|
|
pickle.dump(param_dicts, fo)
|
|
|
|
session = None
|
|
param_state = ""
|
|
gr.Info("End session and save log.")
|
|
return param_state, session
|
|
|
|
def export_func():
|
|
name = (
|
|
f"{pipeline_def.__module__}.{pipeline_def.__name__}_{datetime.now()}.xlsx"
|
|
)
|
|
path = str(exported_dir / name)
|
|
gr.Info(f"Begin exporting {name}...")
|
|
try:
|
|
export(config=config, pipeline_def=pipeline_def, output_path=path)
|
|
except Exception as e:
|
|
raise gr.Error(f"Failed to export. Please contact project's AIR: {e}")
|
|
gr.Info(f"Exported {name}. Please go to the `Exported file` tab to download")
|
|
return path
|
|
|
|
demo = construct_chat_ui(
|
|
config=config,
|
|
func_new_chat=new_chat,
|
|
func_chat=chat,
|
|
func_end_chat=end_chat,
|
|
func_export_to_excel=export_func,
|
|
)
|
|
return demo
|