File size: 36,168 Bytes
5f18966 892824a 1546fb5 fe69671 7329024 fe69671 7329024 fe69671 1546fb5 fe69671 08bb700 7329024 1546fb5 fe69671 1546fb5 7329024 08bb700 1546fb5 7329024 c5e63f1 7796a0e c5e63f1 fe69671 7329024 bf19b95 1bcc2ac bf19b95 1546fb5 f596c9f b1c33cf 1546fb5 b1c33cf 1546fb5 b1c33cf 1546fb5 b1c33cf 1546fb5 b1c33cf 1546fb5 b1c33cf 1546fb5 b1c33cf 1546fb5 b1c33cf 1546fb5 fe69671 7329024 1546fb5 7329024 1546fb5 fe69671 7329024 1546fb5 fe69671 1546fb5 7329024 fe69671 1546fb5 fe69671 1546fb5 7329024 fe69671 1546fb5 fe69671 1546fb5 fe69671 1546fb5 7329024 fe69671 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 01251d0 1546fb5 01251d0 1546fb5 01251d0 1546fb5 01251d0 1546fb5 01251d0 1546fb5 7329024 01251d0 1546fb5 01251d0 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 1546fb5 7329024 5144838 8df0297 1546fb5 8df0297 1546fb5 7e5fe23 1546fb5 7329024 1546fb5 2478fa8 1546fb5 3790d03 1546fb5 fd29248 1546fb5 fd29248 2e37092 fd29248 1546fb5 fe69671 1c04cea fe69671 fd29248 1546fb5 fe69671 1546fb5 fe69671 1546fb5 2478fa8 1546fb5 fe69671 1c04cea |
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 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 |
# to do: i) xtts-v2/eleven labs, ii) summarization, iii) active role of the moderator, iv) moderator's conclusion
import os, requests, datetime
import streamlit as st
from functools import partial
from tempfile import NamedTemporaryFile
from typing import List, Callable, Literal, Optional
from streamlit.runtime.uploaded_file_manager import UploadedFile
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.utilities import BingSearchAPIWrapper
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.tools import Tool
from langchain.tools.retriever import create_retriever_tool
from langchain.agents import create_openai_tools_agent
# from langchain.agents import create_tool_calling_agent
from langchain.agents import AgentExecutor
from langchain_community.agent_toolkits.load_tools import load_tools
from langchain.pydantic_v1 import BaseModel, Field
from TTS.api import TTS
import tempfile
# Initialize the TTS model
tts_model = TTS("tts_models/multilingual/multi-dataset/xtts_v2", gpu=False)
def initialize_session_state_variables() -> None:
"""
Initialize all the session state variables.
"""
session_defaults = {
"ready": False,
"bing_subscription_validity": False,
"model": "gpt-4o",
"language": "English",
"topic": "",
"positive": "",
"negative": "",
"agent_descriptions": {},
"new_debate": True,
"conversations": [],
"conversations4print": [],
"simulator": None,
"tools": [],
"retriever_tool": None,
"vector_store_message": "",
"conclusions": "",
"comments_key": 0,
"specified_topic": "",
}
for key, value in session_defaults.items():
if key not in st.session_state:
st.session_state[key] = value
def initialize_api_keys():
"""
Initialize API keys from Hugging Face secrets and validate them.
"""
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "")
os.environ["BING_SUBSCRIPTION_KEY"] = os.getenv("BING_SUBSCRIPTION_KEY", "")
# Validate keys and show warnings/errors if missing
if not os.environ["OPENAI_API_KEY"]:
st.error("Missing OPENAI_API_KEY. Add it to Hugging Face Space secrets.")
if not os.environ["BING_SUBSCRIPTION_KEY"]:
st.warning("Missing BING_SUBSCRIPTION_KEY. Bing search may not work.")
initialize_api_keys()
def is_openai_api_key_valid():
"""
Validate the OpenAI API key from Hugging Face secrets.
"""
openai_api_key = os.environ.get("OPENAI_API_KEY")
if not openai_api_key:
return False
headers = {"Authorization": f"Bearer {openai_api_key}"}
response = requests.get("https://api.openai.com/v1/models", headers=headers)
return response.status_code == 200
def is_bing_subscription_key_valid():
"""
Validate the Bing Subscription key from Hugging Face secrets.
"""
bing_subscription_key = os.environ.get("BING_SUBSCRIPTION_KEY")
if not bing_subscription_key:
return False
try:
bing_search = BingSearchAPIWrapper(
bing_subscription_key=bing_subscription_key,
bing_search_url="https://api.bing.microsoft.com/v7.0/search",
k=1
)
bing_search.run("Test Query")
except Exception:
return False
return True
def check_api_keys() -> None:
"""
Unset this flag to check the validity of the OpenAI API key.
"""
st.session_state.ready = False
def append_period(text: str) -> str:
"""
Append a '.' to the input text
if it is nonempty and does not end with '.' or '?'.
"""
if text and text[-1] not in (".", "?"):
text += "."
return text
def get_vector_store(uploaded_files: List[UploadedFile]) -> Optional[FAISS]:
"""
Take a list of UploadedFile objects as input,
and return a FAISS vector store.
"""
if not uploaded_files:
return None
documents = []
filepaths = []
loader_map = {
".pdf": PyPDFLoader,
".txt": TextLoader,
".docx": Docx2txtLoader
}
try:
for uploaded_file in uploaded_files:
# Create a temporary file within the "files/" directory
with NamedTemporaryFile(dir="files/", delete=False) as file:
file.write(uploaded_file.getbuffer())
filepath = file.name
filepaths.append(filepath)
file_ext = os.path.splitext(uploaded_file.name.lower())[1]
loader_class = loader_map.get(file_ext)
if not loader_class:
st.error(f"Unsupported file type: {file_ext}", icon="🚨")
for filepath in filepaths:
if os.path.exists(filepath):
os.remove(filepath)
return None
# Load the document using the selected loader.
loader = loader_class(filepath)
documents.extend(loader.load())
with st.spinner("Vector DB in preparation..."):
# Split the loaded text into smaller chunks for processing.
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
# separators=["\n", "\n\n", "(?<=\. )", "", " "],
)
doc = text_splitter.split_documents(documents)
# Create a FAISS vector database.
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large", dimensions=1536
)
vector_store = FAISS.from_documents(doc, embeddings)
except Exception as e:
vector_store = None
st.error(f"An error occurred: {e}", icon="🚨")
finally:
# Ensure the temporary file is deleted after processing
for filepath in filepaths:
if os.path.exists(filepath):
os.remove(filepath)
return vector_store
def get_retriever() -> None:
"""
Upload document(s), create a vector store, prepare a retriever tool,
save the tool to the variable st.session_state.retriever_tool
"""
st.write("")
st.write("##### Document(s) to ask about")
uploaded_files = st.file_uploader(
label="Upload an article",
type=["txt", "pdf", "docx"],
accept_multiple_files=True,
label_visibility="collapsed",
)
left, right = st.columns(2)
if left.button(label="$\:\!$Create a vector DB$\,$"):
# Create the vector store.
vector_store = get_vector_store(uploaded_files)
if vector_store is not None:
retriever = vector_store.as_retriever()
st.session_state.retriever_tool = create_retriever_tool(
retriever,
name="retriever",
description=(
"Search for information about the uploaded documents. "
"For any questions about the documents, you must use "
"this tool!"
),
)
st.session_state.vector_store_message = "Vector DB is ready!"
if st.session_state.vector_store_message:
right.write(f":blue[{st.session_state.vector_store_message}]")
class DialogueAgent:
"""
Class for an individual agent participating in the debate.
"""
def __init__(
self,
name: str,
system_message: SystemMessage,
llm: ChatOpenAI,
tools: List[str],
) -> None:
self.name = name
self.system_message = system_message
self.llm = llm
self.prefix = f"{self.name}: "
self.tools = tools
self.reset()
def reset(self):
self.message_history = ["\nHere is the conversation so far.\n"]
def send(self) -> str:
"""
Apply the llm to the message history and return the message string.
"""
chat_prompt_list = [
("system", "You are a helpful assistant."),
("human", "{input}"),
]
agent_prompt_list = chat_prompt_list + [
MessagesPlaceholder(variable_name="agent_scratchpad")
]
chat_prompt = ChatPromptTemplate.from_messages(chat_prompt_list)
agent_prompt = ChatPromptTemplate.from_messages(agent_prompt_list)
if self.tools:
agent = create_openai_tools_agent(
self.llm, self.tools, agent_prompt
)
agent_executor = AgentExecutor(
agent=agent, tools=self.tools, verbose=False
)
else:
agent_executor = chat_prompt | self.llm
output = agent_executor.invoke(
{
"input": "\n".join(
[self.system_message.content]
+ self.message_history
+ [self.prefix]
)
}
)
message = output["output"] if self.tools else output.content
return message
def receive(self, name: str, message: str) -> None:
"""
Concatenate {message} spoken by {name} into message history
"""
self.message_history.append(f"{name}: {message}\n")
class DialogueSimulator:
"""
Class for simulating the debate.
"""
def __init__(
self,
agents: List[DialogueAgent],
selection_function: Callable[[int, List[DialogueAgent]], int],
) -> None:
self.agents = agents
self._step = 0
self.select_next_speaker = selection_function
def reset(self):
for agent in self.agents:
agent.reset()
def inject(self, name: str, message: str):
"""
Initiate the conversation with a {message} from {name}
"""
for agent in self.agents:
agent.receive(name, message)
# increment time
# self._step += 1
def step(self) -> tuple[str, str]:
# 1. choose the next speaker
speaker_idx = self.select_next_speaker(self._step, self.agents)
speaker = self.agents[speaker_idx]
# 2. next speaker sends message
try:
with st.spinner(f"{speaker.name} is thinking..."):
message = speaker.send()
except Exception as e:
st.error(f"An error occurred: {e}", icon="🚨")
st.stop()
# 3. everyone receives message
for receiver in self.agents:
receiver.receive(speaker.name, message)
# 4. increment time
self._step += 1
return speaker.name, message
def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:
"""
Return 0, 1, ..., or (the number of agents - 1) corresponding
to the next speaker.
"""
idx = (step) % len(agents)
return idx
def generate_speech(text, speaker_wav=None, language="en"):
"""
Generate speech using xtts-v2. Use a default voice if no speaker WAV is provided.
Args:
text (str): Text to synthesize.
speaker_wav (str or UploadedFile): Path to a speaker WAV file for voice cloning (optional).
language (str): Language of the text.
Returns:
str: Path to the generated audio file.
"""
try:
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_audio:
tts_model.tts_to_file(
text=text,
file_path=temp_audio.name,
speaker_wav=speaker_wav.name if speaker_wav else None, # None uses the default model voice
language=language,
)
return temp_audio.name
except Exception as e:
st.error(f"Error generating speech: {e}")
return None
def run_simulator(no_of_rounds: int, simulator: DialogueSimulator) -> None:
"""
Simulate a given number of rounds for the debate.
Add TTS audio playback for each dialogue generated.
"""
max_iters = 2 * no_of_rounds
iter = 0
# Optional: Set speaker WAV files dynamically or fallback to defaults
positive_speaker_wav = st.session_state.get("positive_speaker_wav", None)
negative_speaker_wav = st.session_state.get("negative_speaker_wav", None)
while iter < max_iters:
# Step through the simulator to get the speaker and their message
name, message = simulator.step()
color = "blue" if iter % 2 == 0 else "red"
message4print = f"**:{color}[{name}]**: {message}"
# Append text to conversation for display and logging
st.session_state.conversations.append(f"{name}: {message}")
st.session_state.conversations4print.append(message4print)
# Display text in the app
st.write(message4print)
# Generate speech for the message
speaker_wav = (
positive_speaker_wav if name == st.session_state.positive else negative_speaker_wav
)
audio_path = generate_speech(message, speaker_wav, st.session_state.language)
# Add audio playback in the UI
if audio_path:
st.audio(audio_path, format="audio/wav")
# Optionally allow users to download the audio
with open(audio_path, "rb") as audio_file:
st.download_button(
label=f"Download {name}'s Response Audio",
data=audio_file,
file_name=f"{name}_response.wav",
mime="audio/wav",
)
iter += 1
def generate_agent_description(
name: str,
conversation_description: str,
language: Literal['English', 'Korean'],
word_limit: int
) -> str:
"""
Generate the description for a participant.
"""
agent_specifier_prompt = [
SystemMessage(
content=(
"You can add detail to the description of "
"the conversation participant."
)
),
HumanMessage(
content=(
f"{conversation_description}\n"
f"Please reply with a creative description of '{name}', "
f"in {word_limit} words or less in {language}.\n"
f"Speak directly to '{name}'.\n"
"Give them a point of view.\n"
"Do not add anything else."
)
),
]
agent_specifier_llm = ChatOpenAI(
model=st.session_state.model, temperature=1.0
)
agent_description = agent_specifier_llm.invoke(agent_specifier_prompt)
return agent_description.content
def generate_system_message(
name: str,
conversation_description: str,
description: str,
language: Literal['English', 'Korean'],
word_limit: int
) -> str:
"""
Generate the system message for a participant.
"""
if description:
description_statement = (
f"Your description is as follows: {description}\n\n"
)
else:
description_statement = ""
generated_system_message = (
f"{conversation_description}\n\n"
f"Your name is '{name}'.\n\n"
f"{description_statement}"
"Your goal is to persuade your conversation partner "
"of your point of view.\n\n"
"DO look up information with your tool "
"to refute your partner's claims.\n"
"DO cite your sources.\n\n"
"DO NOT fabricate fake citations.\n"
"DO NOT cite any source that you did not look up.\n\n"
"DO NOT restate something that has been said in the past.\n"
"Do not add anything else.\n\nStop speaking the moment "
"you finish speaking from your perspective.\n\n"
f"Answer in {word_limit} words or less in {language}."
)
return generated_system_message
def get_participant_names(topic: str) -> List[str]:
"""
Get the names of the positive and negative for the debate.
"""
participants = ["positive", "negative"]
participant_names = []
for participant in participants:
ex = "AI alarmist" if participant == "negative" else "AI accelerationist"
name_specifier_prompt = [
SystemMessage(content="You are a helpful moderator for a debate."),
HumanMessage(
content=(
"Here is the topic of conversation: "
f"{append_period(topic)}\n"
f"For the {participant} perspective on the topic, "
"write a name in three words or less. Start the name "
"with a capital letter and do not use ':' .\n"
"For example, for the topic 'The current impact of "
"automation and artificial intelligence on employment', "
f"'{ex}' could serve as an appropriate name for "
f"the {participant} side.\n"
"Use a common noun instead of a proper noun, "
"as shown in the example."
)
),
]
name_specifier_llm = ChatOpenAI(
model=st.session_state.model, temperature=1.0
)
participant_name = name_specifier_llm.invoke(
name_specifier_prompt
).content
participant_names.append(participant_name)
return participant_names
def continue_debate() -> None:
"""
Unset the new debate flag to signal that the debate has been set up.
"""
st.session_state.new_debate = False
def reset_debate() -> None:
"""
Reset all the session state variables.
"""
st.session_state.topic = ""
st.session_state.language = "English"
st.session_state.positive = ""
st.session_state.negative = ""
st.session_state.agent_descriptions = {}
st.session_state.specified_topic = ""
st.session_state.new_debate = True
st.session_state.conversations = []
st.session_state.conversations4print = []
st.session_state.simulator = None
st.session_state.names = {}
st.session_state.tools = []
st.session_state.retriever_tool = None
st.session_state.vector_store_message = ""
st.session_state.conclusions = ""
st.session_state.comments_key = 0
def set_tools() -> None:
"""
Set the tools for the agents. Tools that can be selected are
bing_search, arxiv, and retrieval.
"""
class MySearchToolInput(BaseModel):
query: str = Field(description="search query to look up")
arxiv = load_tools(["arxiv"])[0]
wikipedia = load_tools(["wikipedia"])[0]
tool_options = ["ArXiv", "Wikipedia", "Retrieval"]
tool_dictionary = {"ArXiv": arxiv, "Wikipedia": wikipedia}
if st.session_state.bing_subscription_validity:
search = BingSearchAPIWrapper()
bing_search = Tool(
name="bing_search",
description=(
"A search engine for comprehensive, accurate, and trusted results. "
"Useful for when you need to answer questions about current events. "
"Input should be a search query."
),
func=partial(search.results, num_results=5),
args_schema=MySearchToolInput,
)
tool_options.insert(0, "Search")
tool_dictionary["Search"] = bing_search
st.write("**Tools**")
st.session_state.selected_tools = st.multiselect(
label="agent tools",
options=tool_options,
label_visibility="collapsed",
)
if "Search" not in tool_options:
st.write(
"<small>To search the internet, obtain your Bing Subscription "
"Key [here](https://portal.azure.com/) and enter it in the "
"sidebar. Once entered, 'Search' will be displayed in the "
"list of tools.</small>",
unsafe_allow_html=True,
)
if "Retrieval" in st.session_state.selected_tools:
# Get the retriever tool and save it to st.session_state.retriever_tool.
get_retriever()
if st.session_state.retriever_tool is not None:
tool_dictionary["Retrieval"] = st.session_state.retriever_tool
else:
st.session_state.selected_tools.remove("Retrieval")
st.session_state.tools = [
tool_dictionary[key] for key in st.session_state.selected_tools
]
def set_debate() -> None:
"""
Prepare the agents for the debate by setting the topic, names,
descriptions of the participants, and the questions for the debate,
uploading speaker WAVs, and allowing the use of default voices.
"""
st.write("**Upload Speaker WAV Files** (Optional)")
st.session_state.positive_speaker_wav = st.file_uploader(
label="Upload WAV for Positive Debater (Optional)",
type=["wav"],
key="positive_speaker",
)
st.session_state.negative_speaker_wav = st.file_uploader(
label="Upload WAV for Negative Debater (Optional)",
type=["wav"],
key="negative_speaker",
)
st.write("**Topic of the debate**")
topic = st.text_input(
label="topic of the debate",
placeholder="Enter your topic",
value=st.session_state.topic,
label_visibility="collapsed",
)
st.session_state.topic = topic.strip()
st.write(
"**Language** "
"<small>used by the debaters</small>",
unsafe_allow_html=True
)
st.session_state.language = st.radio(
label="language",
options=("English", "Hindi", "Spanish", "French", "Chinese", "Korean", "Japanese"),
label_visibility="collapsed",
index=1,
horizontal=True
)
st.write("**Model**")
st.session_state.model = st.radio(
label="Model",
options=("gpt-4o-mini", "gpt-4o"),
label_visibility="collapsed",
horizontal=True,
index=1,
)
# Set the tools for the agents
set_tools()
left, right = st.columns(2)
left.write("**Word limit for question suggestions** (≥ 10)")
# Word limit for task brainstorming
description_word_limit = left.number_input(
label="description_word_limit",
min_value=10,
max_value=500,
value=20,
step=10,
label_visibility="collapsed"
)
right.write("**Word limit for each debate response** (≥ 50)")
# Word limit for each debate
st.session_state.word_limit = right.number_input(
label="answer_word_limit",
min_value=50,
max_value=2000,
value=100,
step=50,
label_visibility="collapsed"
)
if st.button("Suggest names for the debaters"):
st.session_state.positive, st.session_state.negative = (
get_participant_names(topic)
)
left, right = st.columns(2)
left.write("**Name for the positive**")
positive = left.text_input(
label="name of the positive",
value=st.session_state.positive,
label_visibility="collapsed"
)
st.session_state.positive = positive
right.write("**Name for the negative**")
negative = right.text_input(
label="name of the negative",
value=st.session_state.negative,
label_visibility="collapsed"
)
st.session_state.negative = negative
st.session_state.names = {
positive: st.session_state.tools,
negative: st.session_state.tools,
}
conversation_description = (
"Here is the topic of conversation: "
f"{append_period(topic)}\nThe participants are: "
f"{' and '.join(st.session_state.names.keys())}."
)
agent_descriptions, agent_system_messages = {}, {}
if positive and negative:
if st.button("Suggest descriptions for the debaters"):
for name in st.session_state.names.keys():
st.session_state.agent_descriptions[name] = (
generate_agent_description(
name,
conversation_description,
st.session_state.language,
description_word_limit
)
)
for name in st.session_state.names.keys():
st.write(f"**Description for {name}**")
agent_descriptions[name] = st.text_area(
label=f"description for {name}",
value=st.session_state.agent_descriptions.get(name, ""),
label_visibility="collapsed"
)
st.session_state.agent_descriptions[name] = agent_descriptions[name]
keys_to_delete = [
key for key in st.session_state.agent_descriptions
if key not in st.session_state.names
]
for key in keys_to_delete:
del st.session_state.agent_descriptions[key]
for name in st.session_state.names.keys():
agent_system_messages[name] = generate_system_message(
name,
conversation_description,
agent_descriptions[name],
st.session_state.language,
st.session_state.word_limit
)
if st.button("Suggest questions for the debaters"):
topic_specifier_prompt = [
SystemMessage(content="You can make a topic more specific."),
HumanMessage(
content=(
"Here is the topic of conversation: "
f"{append_period(topic)}\n"
"You are the moderator.\n"
"Please make the topic more specific.\n"
"Please reply with the specified quest in "
f"{description_word_limit} words or less in "
f"{st.session_state.language}.\n"
"Speak directly to the participants: "
f"{*st.session_state.names,}.\n"
"Do not add anything else."
)
),
]
topic_specifier_llm = ChatOpenAI(
model=st.session_state.model, temperature=1.0
)
st.session_state.specified_topic = topic_specifier_llm.invoke(
topic_specifier_prompt
).content
st.write("**Questions for the debaters**")
specified_topic = st.text_area(
label="questions for the debaters",
value=st.session_state.specified_topic,
label_visibility="collapsed",
)
st.session_state.specified_topic = specified_topic
if st.session_state.specified_topic:
if st.button("Prepare the debate"):
agent_llm = ChatOpenAI(
model=st.session_state.model, temperature=0.2
)
agents = [
DialogueAgent(
name=name,
system_message=SystemMessage(content=system_message),
llm=agent_llm,
tools=tools,
)
for (name, tools), system_message in zip(
st.session_state.names.items(),
agent_system_messages.values()
)
]
st.session_state.simulator = DialogueSimulator(
agents=agents, selection_function=select_next_speaker
)
st.session_state.simulator.reset()
st.session_state.simulator.inject("Moderator", specified_topic)
st.session_state.new_debate = False
st.rerun()
def print_topic_debaters_questions() -> str:
"""
Print the topic, the names and descriptions of the participants,
and questions for the debate.
"""
st.write("**Topic of the debate**")
st.info(f"**{st.session_state.topic}**")
st.write(
"**Name for the positive**$\:$: "
f"$~$:blue[{st.session_state.positive}]"
)
st.write(
"**Name for the negative**: "
f"$~$:blue[{st.session_state.negative}]"
)
agent_descriptions = st.session_state.agent_descriptions
dict_name = "agent_descriptions"
for name in st.session_state.names.keys():
st.write(f"**Description for {name}**")
st.info(agent_descriptions[name])
st.write("**Moderator**: Here are the questions for the debaters")
st.info(st.session_state.specified_topic)
headers = (
"Topic of the debate: "
f"{st.session_state.topic}\n\n"
"Name for the positive: "
f"{st.session_state.positive}\n"
"Name for the negative: "
f"{st.session_state.negative}\n\n"
)
if agent_descriptions[st.session_state.positive]:
headers += (
f"Description for {st.session_state.positive}:\n"
f"{locals()[dict_name][st.session_state.positive]}\n\n"
)
if agent_descriptions[st.session_state.negative]:
headers += (
f"Description for {st.session_state.negative}:\n"
f"{locals()[dict_name][st.session_state.negative]}\n\n"
)
headers += f"Moderator: {st.session_state.specified_topic}\n\n"
return headers
def conclude_debate() -> None:
"""
End the debate by providing a summary of the points raised by
each participant and making a concluding remark. Add this conclusion
to the list of conversations.
"""
word_limit = 2 * st.session_state.word_limit
moderator_prompt = [
SystemMessage(
content=(
"You are the Moderator. "
"Your goal is to provide a comprehensive summary "
"highlighting the key points raised by each participant, "
"and then to conclude the debate in a productive manner. "
"If there is a clear standout in terms of being more "
"persuasive or convincing, mention this in your conclusion."
)
),
HumanMessage(
content=(
f"Answer in {word_limit} words or less "
f"in {st.session_state.language}.\n\n"
"Here is the complete conversation.\n\n"
f"{st.session_state.complete_conversations}\n\n"
"Moderator: "
)
),
]
moderator_llm = ChatOpenAI(
model=st.session_state.model, temperature=0.2
)
with st.spinner("Moderator is thinking..."):
st.session_state.conclusions = moderator_llm.invoke(
moderator_prompt
).content
st.session_state.conversations.append(
f"Moderator: {st.session_state.conclusions}"
)
st.session_state.conversations4print.append(
f"**Moderator**: {st.session_state.conclusions}"
)
def multi_agent_debate():
"""
Let two agents, equipped with tools such as bing search, arxiv,
and retriever, debate on a given topic. The debate can be concluded
with a remark and be downloaded.
"""
page_title = "Multi-lingual Multi-Agent Debate"
page_icon = "📚"
st.set_page_config(
page_title=page_title,
page_icon=page_icon,
layout="centered"
)
# Title and introductory section
st.write(f"## {page_icon} $\,${page_title}")
# Always display the cover image and acknowledgment at the top
st.image("./files/image-3.png", caption=" ", use_container_width=True)
st.info(
"""
**Acknowledgment**: This project is inspired by [Twy's Work](https://github.com/twy80/Multi_Agent_Debate).
"""
)
# Initialize all the session state variables
initialize_session_state_variables()
# Sidebar for API Key Status
with st.sidebar:
st.write("### API Key Status")
openai_key_status = "✔️ Available" if os.getenv("OPENAI_API_KEY") else "❌ Missing"
bing_key_status = "✔️ Available" if os.getenv("BING_SUBSCRIPTION_KEY") else "❌ Missing"
st.write(f"**OpenAI Key**: {openai_key_status}")
st.write(f"**Bing Key**: {bing_key_status}")
# Error if OpenAI API key is missing
if not os.getenv("OPENAI_API_KEY"):
st.error("OpenAI Key is required for this app to function properly.")
# Main app logic
if st.session_state.new_debate:
set_debate()
else:
with st.sidebar:
st.write("")
st.write(f"**Model**: :blue[{st.session_state.model}]")
st.write(f"**Language**: :blue[{st.session_state.language}]")
st.write(f"**Word limit**: :blue[{st.session_state.word_limit}]")
if st.session_state.selected_tools:
used_tools = (
f":blue[{', '.join(st.session_state.selected_tools)}]"
)
if len(st.session_state.selected_tools) == 1:
st.write(f"**Tool**: {used_tools}")
else:
st.write(f"**Tools**: {used_tools}")
else:
st.write(f"**Tool**: :blue[None]")
headers = print_topic_debaters_questions()
st.session_state.complete_conversations = (
headers + "\n\n".join(st.session_state.conversations)
)
if st.session_state.conversations:
label_debate = "$\,$Continue the debate$\,$"
label_no_of_rounds = "Number of additional rounds"
value_no_of_rounds = 1
else:
label_debate = "$~~~\,$Start the debate$~~~\,$"
label_no_of_rounds = "Number of rounds in this debate"
value_no_of_rounds = 5
st.write("")
for message in st.session_state.conversations4print:
st.write(message)
if not st.session_state.conclusions:
st.write(f"**{label_no_of_rounds}**")
c1, _, _ = st.columns(3)
no_of_rounds = c1.number_input(
label=f"{label_no_of_rounds}",
min_value=1,
max_value=10,
value=value_no_of_rounds,
step=1,
label_visibility="collapsed",
)
if st.session_state.conversations and not st.session_state.conclusions:
st.write("**Facilitative comments by the (human) moderator** (Optional)")
facilitative_comments = st.text_input(
label="facilitative_comments",
value="",
key="comments" + str(st.session_state.comments_key),
label_visibility="collapsed",
)
if facilitative_comments:
st.session_state.simulator.inject("Moderator", facilitative_comments)
st.session_state.conversations.append(
f"Moderator: {facilitative_comments}"
)
st.session_state.conversations4print.append(
f"**Moderator**: {facilitative_comments}"
)
st.session_state.comments_key += 1
left, right = st.columns(2)
if not st.session_state.conclusions:
if left.button(f"{label_debate}"):
run_simulator(no_of_rounds, st.session_state.simulator)
st.rerun()
if st.session_state.conversations:
if right.button("Conclude the debate$\,$"):
conclude_debate()
st.rerun()
else:
if right.button("$~\:$Back to the setting$~\:$"):
st.session_state.new_debate = True
st.rerun()
left.download_button(
label="Download the debate",
data=st.session_state.complete_conversations,
file_name="multi_agent_debate.txt",
mime="text/plain"
)
right.button(
label="$~~\,\:\!$Reset the debate$~~\,\,$",
on_click=reset_debate
)
if __name__ == "__main__":
multi_agent_debate()
|