Skip to content

Commit

Permalink
Adds multiple choice message handling to MesopUI (#198)
Browse files Browse the repository at this point in the history
* Multiple choice WIP(1)

* Multiple choice WIP(2)

* Multiple choice WIP(3)

* test fix

* tyest fixes

* added all caps instructions for message termination

* added all caps instructions for message termination

---------

Co-authored-by: Davorin Rusevljan <davorin.rusevljan@gmail.com>
  • Loading branch information
davorrunje and davorinrusevljan authored Sep 13, 2024
1 parent 8aaba85 commit f0aaa96
Show file tree
Hide file tree
Showing 17 changed files with 392 additions and 100 deletions.
4 changes: 3 additions & 1 deletion docs/docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,17 +256,19 @@ search:
- [header](api/fastagency/ui/mesop/components/ui_common/header.md)
- data_model
- [Conversation](api/fastagency/ui/mesop/data_model/Conversation.md)
- [ConversationMessage](api/fastagency/ui/mesop/data_model/ConversationMessage.md)
- [State](api/fastagency/ui/mesop/data_model/State.md)
- main
- [conversation_box](api/fastagency/ui/mesop/main/conversation_box.md)
- [conversation_starter_box](api/fastagency/ui/mesop/main/conversation_starter_box.md)
- [get_ui](api/fastagency/ui/mesop/main/get_ui.md)
- [home_page](api/fastagency/ui/mesop/main/home_page.md)
- [on_user_feedback](api/fastagency/ui/mesop/main/on_user_feedback.md)
- [past_conversations_box](api/fastagency/ui/mesop/main/past_conversations_box.md)
- [send_prompt](api/fastagency/ui/mesop/main/send_prompt.md)
- message
- [MesopGUIMessageVisitor](api/fastagency/ui/mesop/message/MesopGUIMessageVisitor.md)
- [consume_responses](api/fastagency/ui/mesop/message/consume_responses.md)
- [handle_message](api/fastagency/ui/mesop/message/handle_message.md)
- [message_box](api/fastagency/ui/mesop/message/message_box.md)
- send_prompt
- [send_prompt_to_autogen](api/fastagency/ui/mesop/send_prompt/send_prompt_to_autogen.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
# 0.5 - API
# 2 - Release
# 3 - Contributing
# 5 - Template Page
# 10 - Default
search:
boost: 0.5
---

::: fastagency.ui.mesop.data_model.ConversationMessage
11 changes: 11 additions & 0 deletions docs/docs/en/api/fastagency/ui/mesop/message/consume_responses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
# 0.5 - API
# 2 - Release
# 3 - Contributing
# 5 - Template Page
# 10 - Default
search:
boost: 0.5
---

::: fastagency.ui.mesop.message.consume_responses
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ search:
boost: 0.5
---

::: fastagency.ui.mesop.main.on_user_feedback
::: fastagency.ui.mesop.message.handle_message
4 changes: 2 additions & 2 deletions docs/docs_src/user_guide/custom_user_interactions/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
wf = AutoGenWorkflows()


@wf.register(name="exam_practice", description="Student and teacher chat") # type: ignore[type-var]
def exam_learning(ui: UI, initial_message: str, session_id: str) -> Optional[str]:
@wf.register(name="exam_practice", description="Student and teacher chat")
def exam_learning(ui: UI, initial_message: str, session_id: str) -> str:

def is_termination_msg(msg: dict[str, Any]) -> bool:
return msg["content"] is not None and "TERMINATE" in msg["content"]
Expand Down
136 changes: 136 additions & 0 deletions docs/docs_src/user_guide/custom_user_interactions/main_mesop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import os
from typing import Annotated, Any, Dict, Optional

from autogen import register_function
from autogen.agentchat import ConversableAgent

from fastagency import FastAgency
from fastagency import UI
from fastagency.base import MultipleChoice, SystemMessage, TextInput
from fastagency.ui.mesop import MesopUI
from fastagency.runtime.autogen.base import AutoGenWorkflows

llm_config = {
"config_list": [
{
"model": "gpt-4o-mini",
"api_key": os.getenv("OPENAI_API_KEY"),
}
],
"temperature": 0.0,
}

wf = AutoGenWorkflows()


@wf.register(name="exam_practice", description="Student and teacher chat")
def exam_learning(ui: UI, initial_message: str, session_id: str) -> str:

def is_termination_msg(msg: dict[str, Any]) -> bool:
return msg["content"] is not None and "TERMINATE" in msg["content"]

student_agent = ConversableAgent(
name="Student_Agent",
system_message="You are a student writing a practice test. Your task is as follows:\n"
" 1) Retrieve exam questions by calling a function.\n"
" 2) Write a draft of proposed answers and engage in dialogue with your tutor.\n"
" 3) Once you are done with the dialogue, register the final answers by calling a function.\n"
" 4) Retrieve the final grade by calling a function.\n"
"Finally, terminate the chat by saying 'TERMINATE'.",
llm_config=llm_config,
human_input_mode="NEVER",
is_termination_msg=is_termination_msg,
)
teacher_agent = ConversableAgent(
name="Teacher_Agent",
system_message="You are a teacher.",
llm_config=llm_config,
human_input_mode="NEVER",
is_termination_msg=is_termination_msg,
)

def retrieve_exam_questions(
message: Annotated[str, "Message for examiner"]
) -> Optional[str]:
try:
msg = MultipleChoice(
sender="student",
recipient="teacher",
prompt=message,
choices=[
"1) Mona Lisa",
"2) Innovations",
"3) Florence at the time of Leonardo",
"4) The Last Supper",
"5) Vitruvian Man",
],
default="1) Mona Lisa"
)
return ui.process_message(msg)
except Exception as e:
return f"retrieve_exam_questions() FAILED! {e}"

def write_final_answers(message: Annotated[str, "Message for examiner"]) -> str:
try:
msg = SystemMessage(
sender="function call logger",
recipient="system",
message={
"operation": "storing final answers",
"content": message,
},
)
ui.process_message(msg)
return "Final answers stored."
except Exception as e:
return f"write_final_answers() FAILED! {e}"

def get_final_grade(
message: Annotated[str, "Message for examiner"]
) -> Optional[str]:
try:
msg = MultipleChoice(
sender="student",
recipient="teacher",
prompt=message,
choices=["A", "B", "C", "D", "F"],
)
return ui.process_message(msg)
except Exception as e:
return f"get_final_grade() FAILED! {e}"

register_function(
retrieve_exam_questions,
caller=student_agent,
executor=teacher_agent,
name="retrieve_exam_questions",
description="Get exam questions from examiner",
)

register_function(
write_final_answers,
caller=student_agent,
executor=teacher_agent,
name="write_final_answers",
description="Write a final answers to exam questions to examiner, but only after discussing with the tutor first.",
)

register_function(
get_final_grade,
caller=student_agent,
executor=teacher_agent,
name="get_final_grade",
description="Get the final grade after submitting the answers.",
)

chat_result = teacher_agent.initiate_chat(
student_agent,
message=initial_message,
summary_method="reflection_with_llm",
max_turns=10,
)

return chat_result.summary # type: ignore[no-any-return]


app = FastAgency(wf=wf, ui=MesopUI())
60 changes: 60 additions & 0 deletions docs/docs_src/user_guide/external_rest_apis/main_mesop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os

from autogen import UserProxyAgent
from autogen.agentchat import ConversableAgent

from fastagency import FastAgency
from fastagency import UI
from fastagency.ui.mesop import MesopUI
from fastagency.runtime.autogen.base import AutoGenWorkflows

from fastagency.api.openapi import OpenAPI


llm_config = {
"config_list": [
{
"model": "gpt-4o-mini",
"api_key": os.getenv("OPENAI_API_KEY"),
}
],
"temperature": 0.0,
}

WEATHER_OPENAPI_URL = "https://weather.tools.fastagency.ai/openapi.json"

wf = AutoGenWorkflows()


@wf.register(name="simple_weather", description="Weather chat")
def weather_workflow(ui: UI, initial_message: str, session_id: str) -> str:

weather_api = OpenAPI.create(openapi_url=WEATHER_OPENAPI_URL)

user_agent = UserProxyAgent(
name="User_Agent",
system_message="You are a user agent",
llm_config=llm_config,
human_input_mode="NEVER",
)
weather_agent = ConversableAgent(
name="Weather_Agent",
system_message="You are a weather agent",
llm_config=llm_config,
human_input_mode="NEVER",
)

weather_api.register_for_llm(weather_agent)
weather_api.register_for_execution(user_agent)

chat_result = user_agent.initiate_chat(
weather_agent,
message=initial_message,
summary_method="reflection_with_llm",
max_turns=3,
)

return chat_result.summary # type: ignore[no-any-return]


app = FastAgency(wf=wf, ui=MesopUI())
2 changes: 1 addition & 1 deletion fastagency/studio/models/agents/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AssistantAgent(AgentBaseModel):
Field(
description="The system message of the agent. This message is used to inform the agent about his role in the conversation"
),
] = "You are a helpful assistant. After you successfully answer all questions and there are no new questions asked after your response (e.g. there is no specific direction or question asked after you give a response), terminate the chat by outputting 'TERMINATE'"
] = "You are a helpful assistant. After you successfully answer all questions and there are no new questions asked after your response (e.g. there is no specific direction or question asked after you give a response), terminate the chat by outputting 'TERMINATE' (IMPORTANT: use all caps)"

@classmethod
async def create_autogen(
Expand Down
14 changes: 14 additions & 0 deletions fastagency/ui/mesop/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,27 @@ def conversation_worker(ui: MesopUI, subconversation: MesopUI) -> None:
},
)
)

# io.process_message(
# IOMessage.create(
# sender="tester",
# recipient="workflow",
# type="multiple_choice",
# prompt="Concentrate and choose correct answer. When are you going to write unit tests?",
# choices=["Today", "Tomorrow", "Never", "I already have unit tests"],
# default="Tomorrow",
# single=True,
# )
# )

try:
result = wf.run(
name=name,
session_id="session_id",
ui=subconversation, # type: ignore[arg-type]
initial_message=initial_message,
)

except Exception as ex:
ui.process_message(
IOMessage.create(
Expand Down
16 changes: 12 additions & 4 deletions fastagency/ui/mesop/components/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ def _on_blur(e: me.InputBlurEvent) -> None:


def input_user_feedback(
send_feedback: Callable[[me.ClickEvent], Iterator[None]],
send_feedback: Callable[[me.ClickEvent], Iterator[None]], disabled: bool = False
) -> None:
def _on_feedback_blur(e: me.InputBlurEvent) -> None:
state = me.state(State)
state.conversation.feedback = e.value

with me.box(
style=me.Style(
border_radius=16,
Expand All @@ -26,16 +30,20 @@ def input_user_feedback(
with me.box(style=me.Style(flex_grow=1)):
me.native_textarea(
placeholder="Provide a feedback to the team",
on_blur=_on_blur,
key="feedback",
on_blur=_on_feedback_blur,
disabled=disabled,
style=me.Style(
padding=me.Padding(top=16, left=16),
outline="none",
width="100%",
border=me.Border.all(me.BorderSide(style="none")),
),
)
with me.content_button(type="icon", on_click=send_feedback):
with me.content_button(
type="icon",
on_click=send_feedback,
disabled=disabled,
):
me.icon("send")


Expand Down
12 changes: 9 additions & 3 deletions fastagency/ui/mesop/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
import mesop as me


@dataclass
class ConversationMessage:
io_message_json: str = ""
level: int = 0
conversation_id: str = ""
feedback: list[str] = field(default_factory=list)


@dataclass
class Conversation:
id: str = ""
Expand All @@ -12,8 +20,7 @@ class Conversation:
waiting_for_feedback: bool = False
feedback: str = ""
is_from_the_past: bool = False
# messages: list[ConversationMessage] = field(default_factory=list)
messages: list[str] = field(default_factory=list)
messages: list[ConversationMessage] = field(default_factory=list)
fastagency: Optional[str] = None


Expand All @@ -24,4 +31,3 @@ class State:
conversation: Conversation
past_conversations: list[Conversation] = field(default_factory=list)
hide_past: bool = True
fastagency: Optional[str] = None
Loading

0 comments on commit f0aaa96

Please sign in to comment.