lucadipalma commited on
Commit ·
59048dd
1
Parent(s): 08959c5
modify graph to set limits; visualization bugs
Browse files- graph.png +0 -0
- pages/play.py +9 -6
- support/build_graph.py +73 -11
- support/my_tools.py +0 -18
- support/style/css.py +12 -4
- support/tools/captain_agent_tools.py +23 -0
- support/utils.py +37 -124
graph.png
CHANGED
|
|
pages/play.py
CHANGED
|
@@ -278,6 +278,7 @@ with gr.Blocks(fill_width=True) as demo:
|
|
| 278 |
|
| 279 |
def update_api_key_inputs(red_team, blue_team):
|
| 280 |
"""Update visibility and state of API key inputs based on team selection."""
|
|
|
|
| 281 |
required_teams = get_required_teams(red_team, blue_team)
|
| 282 |
|
| 283 |
# Return visibility states for each team's API input
|
|
@@ -315,8 +316,6 @@ with gr.Blocks(fill_width=True) as demo:
|
|
| 315 |
accumulated_messages = list(messages) if messages else []
|
| 316 |
async for new_msg, guessed, updated_board, updated_chat_history, winners in graph.stream_graph(user_msg, messages, players, board, dropdown, guessed, chat_history, is_human_playing, turn):
|
| 317 |
|
| 318 |
-
logger.error(f"Winners: {winners}")
|
| 319 |
-
|
| 320 |
if len(new_msg) > len(accumulated_messages):
|
| 321 |
# Add only the new messages that weren't there before
|
| 322 |
accumulated_messages = list(new_msg)
|
|
@@ -328,16 +327,20 @@ with gr.Blocks(fill_width=True) as demo:
|
|
| 328 |
feed_html = format_messages_as_feed(accumulated_messages, players, winners)
|
| 329 |
yield feed_html, guessed, messages, updated_board, updated_chat_history, winners
|
| 330 |
|
| 331 |
-
def update_plot(guessed_words, board,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
if not guessed_words or not board:
|
| 333 |
-
return
|
|
|
|
| 334 |
logger.info(f"Calling update_plot: {guessed_words}")
|
| 335 |
board_img = plot_game_board_with_guesses(board, guessed_words)
|
| 336 |
return board_img
|
| 337 |
|
| 338 |
def deactivate_send(game, winner_and_score):
|
| 339 |
if winner_and_score is None or game is None:
|
| 340 |
-
logger.error(f"Winner state: {winner_and_score}")
|
| 341 |
return gr.Button(visible=True)
|
| 342 |
else:
|
| 343 |
return gr.Button(visible=False)
|
|
@@ -482,7 +485,7 @@ with gr.Blocks(fill_width=True) as demo:
|
|
| 482 |
|
| 483 |
guessed_words_state.change(
|
| 484 |
fn=update_plot,
|
| 485 |
-
inputs=[guessed_words_state, board_state,
|
| 486 |
outputs=[board_plot]
|
| 487 |
)
|
| 488 |
|
|
|
|
| 278 |
|
| 279 |
def update_api_key_inputs(red_team, blue_team):
|
| 280 |
"""Update visibility and state of API key inputs based on team selection."""
|
| 281 |
+
|
| 282 |
required_teams = get_required_teams(red_team, blue_team)
|
| 283 |
|
| 284 |
# Return visibility states for each team's API input
|
|
|
|
| 316 |
accumulated_messages = list(messages) if messages else []
|
| 317 |
async for new_msg, guessed, updated_board, updated_chat_history, winners in graph.stream_graph(user_msg, messages, players, board, dropdown, guessed, chat_history, is_human_playing, turn):
|
| 318 |
|
|
|
|
|
|
|
| 319 |
if len(new_msg) > len(accumulated_messages):
|
| 320 |
# Add only the new messages that weren't there before
|
| 321 |
accumulated_messages = list(new_msg)
|
|
|
|
| 327 |
feed_html = format_messages_as_feed(accumulated_messages, players, winners)
|
| 328 |
yield feed_html, guessed, messages, updated_board, updated_chat_history, winners
|
| 329 |
|
| 330 |
+
def update_plot(guessed_words, board, game_state):
|
| 331 |
+
# Don't update if no game is active
|
| 332 |
+
if game_state is None:
|
| 333 |
+
return gr.update() # No update
|
| 334 |
+
|
| 335 |
if not guessed_words or not board:
|
| 336 |
+
return gr.update() # No update
|
| 337 |
+
|
| 338 |
logger.info(f"Calling update_plot: {guessed_words}")
|
| 339 |
board_img = plot_game_board_with_guesses(board, guessed_words)
|
| 340 |
return board_img
|
| 341 |
|
| 342 |
def deactivate_send(game, winner_and_score):
|
| 343 |
if winner_and_score is None or game is None:
|
|
|
|
| 344 |
return gr.Button(visible=True)
|
| 345 |
else:
|
| 346 |
return gr.Button(visible=False)
|
|
|
|
| 485 |
|
| 486 |
guessed_words_state.change(
|
| 487 |
fn=update_plot,
|
| 488 |
+
inputs=[guessed_words_state, board_state, game_state], # Remove board_plot from inputs
|
| 489 |
outputs=[board_plot]
|
| 490 |
)
|
| 491 |
|
support/build_graph.py
CHANGED
|
@@ -11,6 +11,7 @@ from langgraph.prebuilt import ToolNode
|
|
| 11 |
from support.log_manager import logger
|
| 12 |
from support.my_tools import captain_agent_tools, boss_agent_tools
|
| 13 |
from support.tools.boss_agent_tools import create_fake_tool_call
|
|
|
|
| 14 |
from support.prompts import captain_agent_system_prompt, player_agent_system_prompt, boss_agent_system_prompt
|
| 15 |
from typing import Annotated, List, Literal, Optional
|
| 16 |
from typing_extensions import TypedDict
|
|
@@ -38,6 +39,11 @@ class State(TypedDict):
|
|
| 38 |
end_round: bool
|
| 39 |
winner_and_score: tuple
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
class MyGraph:
|
| 43 |
def __init__(self):
|
|
@@ -64,6 +70,8 @@ class MyGraph:
|
|
| 64 |
builder.add_node("red_captain", self.red_captain)
|
| 65 |
builder.add_node("red_agent_1", self.red_agent_1)
|
| 66 |
builder.add_node("red_agent_2", self.red_agent_2)
|
|
|
|
|
|
|
| 67 |
|
| 68 |
# Tool nodes
|
| 69 |
choose_word_tool = ToolNode(tools=[boss_agent_tools[0]])
|
|
@@ -84,10 +92,11 @@ class MyGraph:
|
|
| 84 |
self.boss_choice,
|
| 85 |
{
|
| 86 |
"choose_word_tool": "choose_word_tool",
|
| 87 |
-
"
|
| 88 |
},
|
| 89 |
)
|
| 90 |
-
|
|
|
|
| 91 |
builder.add_edge("choose_word_tool", "red_captain")
|
| 92 |
|
| 93 |
builder.add_conditional_edges(
|
|
@@ -97,10 +106,11 @@ class MyGraph:
|
|
| 97 |
"final_choice": "final_choice",
|
| 98 |
"transfer_to_agent_1": "transfer_to_agent_1",
|
| 99 |
"transfer_to_agent_2": "transfer_to_agent_2",
|
| 100 |
-
"
|
| 101 |
},
|
| 102 |
)
|
| 103 |
|
|
|
|
| 104 |
builder.add_edge("transfer_to_agent_1", "red_agent_1")
|
| 105 |
builder.add_edge("transfer_to_agent_2", "red_agent_2")
|
| 106 |
builder.add_edge("red_agent_1", "red_captain")
|
|
@@ -119,6 +129,8 @@ class MyGraph:
|
|
| 119 |
builder.add_node("blue_captain", self.blue_captain)
|
| 120 |
builder.add_node("blue_agent_1", self.blue_agent_1)
|
| 121 |
builder.add_node("blue_agent_2", self.blue_agent_2)
|
|
|
|
|
|
|
| 122 |
|
| 123 |
# Tool nodes
|
| 124 |
choose_word_tool = ToolNode(tools=[boss_agent_tools[0]])
|
|
@@ -139,10 +151,11 @@ class MyGraph:
|
|
| 139 |
self.boss_choice,
|
| 140 |
{
|
| 141 |
"choose_word_tool": "choose_word_tool",
|
| 142 |
-
"
|
| 143 |
},
|
| 144 |
)
|
| 145 |
-
|
|
|
|
| 146 |
builder.add_edge("choose_word_tool", "blue_captain")
|
| 147 |
|
| 148 |
builder.add_conditional_edges(
|
|
@@ -152,10 +165,11 @@ class MyGraph:
|
|
| 152 |
"final_choice": "final_choice",
|
| 153 |
"transfer_to_agent_1": "transfer_to_agent_1",
|
| 154 |
"transfer_to_agent_2": "transfer_to_agent_2",
|
| 155 |
-
"
|
| 156 |
},
|
| 157 |
)
|
| 158 |
|
|
|
|
| 159 |
builder.add_edge("transfer_to_agent_1", "blue_agent_1")
|
| 160 |
builder.add_edge("transfer_to_agent_2", "blue_agent_2")
|
| 161 |
builder.add_edge("blue_agent_1", "blue_captain")
|
|
@@ -320,7 +334,13 @@ class MyGraph:
|
|
| 320 |
llm_with_tools = captain.model.bind_tools(
|
| 321 |
captain_agent_tools,
|
| 322 |
)
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
logger.info(f"[RED CAPTAIN ANSWER]: {answer}")
|
| 326 |
logger.info("°°°"*50)
|
|
@@ -576,7 +596,13 @@ class MyGraph:
|
|
| 576 |
llm_with_tools = captain.model.bind_tools(
|
| 577 |
captain_agent_tools,
|
| 578 |
)
|
| 579 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
|
| 581 |
logger.info(f"[BLUE CAPTAIN ANSWER] : {answer}")
|
| 582 |
logger.info("°°°"*50)
|
|
@@ -718,6 +744,38 @@ class MyGraph:
|
|
| 718 |
"turn": turn+1
|
| 719 |
}
|
| 720 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
def judge(self, state: State):
|
| 722 |
"""Evaluate guesses, update board, check win/lose conditions."""
|
| 723 |
|
|
@@ -1074,7 +1132,7 @@ class MyGraph:
|
|
| 1074 |
logger.warning("[WARNING] NESSUN TOOL E' STATO RICHIAMATO, TORNIAMO DAL BOSS")
|
| 1075 |
current_team = state['current_team']
|
| 1076 |
logger.info(f"[CURRENT TEAM: {current_team}]")
|
| 1077 |
-
return f"{current_team}
|
| 1078 |
|
| 1079 |
def should_continue(self, state: State) -> str:
|
| 1080 |
"""Determine whether to continue to tools or return a final answer."""
|
|
@@ -1099,7 +1157,7 @@ class MyGraph:
|
|
| 1099 |
logger.warning("[WARNING] NESSUN TOOL E' STATO RICHIAMATO, TORNIAMO DAL CAPITANO")
|
| 1100 |
current_team = state['current_team']
|
| 1101 |
logger.info(f"[CURRENT TEAM: {current_team}]")
|
| 1102 |
-
return f"{current_team}
|
| 1103 |
|
| 1104 |
def _convert_to_string(self, value):
|
| 1105 |
"""Helper to convert any value to string"""
|
|
@@ -1247,7 +1305,11 @@ class MyGraph:
|
|
| 1247 |
"teams_reviewed": [],
|
| 1248 |
"history_guessed_words": guessed_words,
|
| 1249 |
"end_round": False,
|
| 1250 |
-
"winner_and_score": None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1251 |
}
|
| 1252 |
|
| 1253 |
final_msg = ""
|
|
|
|
| 11 |
from support.log_manager import logger
|
| 12 |
from support.my_tools import captain_agent_tools, boss_agent_tools
|
| 13 |
from support.tools.boss_agent_tools import create_fake_tool_call
|
| 14 |
+
from support.tools.captain_agent_tools import create_agent_fake_tool_call
|
| 15 |
from support.prompts import captain_agent_system_prompt, player_agent_system_prompt, boss_agent_system_prompt
|
| 16 |
from typing import Annotated, List, Literal, Optional
|
| 17 |
from typing_extensions import TypedDict
|
|
|
|
| 39 |
end_round: bool
|
| 40 |
winner_and_score: tuple
|
| 41 |
|
| 42 |
+
red_boss_is_called_counter: int
|
| 43 |
+
blue_boss_is_called_counter: int
|
| 44 |
+
red_captain_is_called_counter: int
|
| 45 |
+
blue_captain_is_called_counter: int
|
| 46 |
+
|
| 47 |
|
| 48 |
class MyGraph:
|
| 49 |
def __init__(self):
|
|
|
|
| 70 |
builder.add_node("red_captain", self.red_captain)
|
| 71 |
builder.add_node("red_agent_1", self.red_agent_1)
|
| 72 |
builder.add_node("red_agent_2", self.red_agent_2)
|
| 73 |
+
builder.add_node("red_boss_is_called", self.red_boss_is_called)
|
| 74 |
+
builder.add_node("red_captain_is_called", self.red_captain_is_called)
|
| 75 |
|
| 76 |
# Tool nodes
|
| 77 |
choose_word_tool = ToolNode(tools=[boss_agent_tools[0]])
|
|
|
|
| 92 |
self.boss_choice,
|
| 93 |
{
|
| 94 |
"choose_word_tool": "choose_word_tool",
|
| 95 |
+
"red_boss_is_called": "red_boss_is_called",
|
| 96 |
},
|
| 97 |
)
|
| 98 |
+
|
| 99 |
+
builder.add_edge("red_boss_is_called", "red_boss")
|
| 100 |
builder.add_edge("choose_word_tool", "red_captain")
|
| 101 |
|
| 102 |
builder.add_conditional_edges(
|
|
|
|
| 106 |
"final_choice": "final_choice",
|
| 107 |
"transfer_to_agent_1": "transfer_to_agent_1",
|
| 108 |
"transfer_to_agent_2": "transfer_to_agent_2",
|
| 109 |
+
"red_captain_is_called": "red_captain_is_called",
|
| 110 |
},
|
| 111 |
)
|
| 112 |
|
| 113 |
+
builder.add_edge("red_captain_is_called", "red_captain")
|
| 114 |
builder.add_edge("transfer_to_agent_1", "red_agent_1")
|
| 115 |
builder.add_edge("transfer_to_agent_2", "red_agent_2")
|
| 116 |
builder.add_edge("red_agent_1", "red_captain")
|
|
|
|
| 129 |
builder.add_node("blue_captain", self.blue_captain)
|
| 130 |
builder.add_node("blue_agent_1", self.blue_agent_1)
|
| 131 |
builder.add_node("blue_agent_2", self.blue_agent_2)
|
| 132 |
+
builder.add_node("blue_boss_is_called", self.blue_boss_is_called)
|
| 133 |
+
builder.add_node("blue_captain_is_called", self.blue_captain_is_called)
|
| 134 |
|
| 135 |
# Tool nodes
|
| 136 |
choose_word_tool = ToolNode(tools=[boss_agent_tools[0]])
|
|
|
|
| 151 |
self.boss_choice,
|
| 152 |
{
|
| 153 |
"choose_word_tool": "choose_word_tool",
|
| 154 |
+
"blue_boss_is_called": "blue_boss_is_called",
|
| 155 |
},
|
| 156 |
)
|
| 157 |
+
|
| 158 |
+
builder.add_edge("blue_boss_is_called", "blue_boss")
|
| 159 |
builder.add_edge("choose_word_tool", "blue_captain")
|
| 160 |
|
| 161 |
builder.add_conditional_edges(
|
|
|
|
| 165 |
"final_choice": "final_choice",
|
| 166 |
"transfer_to_agent_1": "transfer_to_agent_1",
|
| 167 |
"transfer_to_agent_2": "transfer_to_agent_2",
|
| 168 |
+
"blue_captain_is_called": "blue_captain_is_called",
|
| 169 |
},
|
| 170 |
)
|
| 171 |
|
| 172 |
+
builder.add_edge("blue_captain_is_called", "blue_captain")
|
| 173 |
builder.add_edge("transfer_to_agent_1", "blue_agent_1")
|
| 174 |
builder.add_edge("transfer_to_agent_2", "blue_agent_2")
|
| 175 |
builder.add_edge("blue_agent_1", "blue_captain")
|
|
|
|
| 334 |
llm_with_tools = captain.model.bind_tools(
|
| 335 |
captain_agent_tools,
|
| 336 |
)
|
| 337 |
+
|
| 338 |
+
if state['red_captain_is_called_counter'] > 4:
|
| 339 |
+
logger.info("Creating fake answer to stop loop")
|
| 340 |
+
answer = create_agent_fake_tool_call()
|
| 341 |
+
else:
|
| 342 |
+
logger.info(f"red_captain_is_called_counter: {state['red_captain_is_called_counter']}")
|
| 343 |
+
answer = await llm_with_tools.ainvoke(new_message)
|
| 344 |
|
| 345 |
logger.info(f"[RED CAPTAIN ANSWER]: {answer}")
|
| 346 |
logger.info("°°°"*50)
|
|
|
|
| 596 |
llm_with_tools = captain.model.bind_tools(
|
| 597 |
captain_agent_tools,
|
| 598 |
)
|
| 599 |
+
|
| 600 |
+
if state['blue_captain_is_called_counter'] > 4:
|
| 601 |
+
logger.info("Creating fake answer to stop loop")
|
| 602 |
+
answer = create_agent_fake_tool_call()
|
| 603 |
+
else:
|
| 604 |
+
logger.info(f"blue_captain_is_called_counter: {state['blue_captain_is_called_counter']}")
|
| 605 |
+
answer = await llm_with_tools.ainvoke(new_message)
|
| 606 |
|
| 607 |
logger.info(f"[BLUE CAPTAIN ANSWER] : {answer}")
|
| 608 |
logger.info("°°°"*50)
|
|
|
|
| 744 |
"turn": turn+1
|
| 745 |
}
|
| 746 |
|
| 747 |
+
def red_boss_is_called(self, state: State):
|
| 748 |
+
"""Update turn counter"""
|
| 749 |
+
_counter = state.get("red_boss_is_called_counter", 1)
|
| 750 |
+
|
| 751 |
+
return {
|
| 752 |
+
"red_boss_is_called_counter": _counter+1
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
def blue_boss_is_called(self, state: State):
|
| 756 |
+
"""Update turn counter"""
|
| 757 |
+
_counter = state.get("blue_boss_is_called_counter", 1)
|
| 758 |
+
|
| 759 |
+
return {
|
| 760 |
+
"blue_boss_is_called_counter": _counter+1
|
| 761 |
+
}
|
| 762 |
+
|
| 763 |
+
def red_captain_is_called(self, state: State):
|
| 764 |
+
"""Update turn counter"""
|
| 765 |
+
_counter = state.get("red_captain_is_called_counter", 1)
|
| 766 |
+
|
| 767 |
+
return {
|
| 768 |
+
"red_captain_is_called_counter": _counter+1
|
| 769 |
+
}
|
| 770 |
+
|
| 771 |
+
def blue_captain_is_called(self, state: State):
|
| 772 |
+
"""Update turn counter"""
|
| 773 |
+
_counter = state.get("blue_captain_is_called_counter", 1)
|
| 774 |
+
|
| 775 |
+
return {
|
| 776 |
+
"blue_captain_is_called_counter": _counter+1
|
| 777 |
+
}
|
| 778 |
+
|
| 779 |
def judge(self, state: State):
|
| 780 |
"""Evaluate guesses, update board, check win/lose conditions."""
|
| 781 |
|
|
|
|
| 1132 |
logger.warning("[WARNING] NESSUN TOOL E' STATO RICHIAMATO, TORNIAMO DAL BOSS")
|
| 1133 |
current_team = state['current_team']
|
| 1134 |
logger.info(f"[CURRENT TEAM: {current_team}]")
|
| 1135 |
+
return f"{current_team}_boss_is_called"
|
| 1136 |
|
| 1137 |
def should_continue(self, state: State) -> str:
|
| 1138 |
"""Determine whether to continue to tools or return a final answer."""
|
|
|
|
| 1157 |
logger.warning("[WARNING] NESSUN TOOL E' STATO RICHIAMATO, TORNIAMO DAL CAPITANO")
|
| 1158 |
current_team = state['current_team']
|
| 1159 |
logger.info(f"[CURRENT TEAM: {current_team}]")
|
| 1160 |
+
return f"{current_team}_captain_is_called"
|
| 1161 |
|
| 1162 |
def _convert_to_string(self, value):
|
| 1163 |
"""Helper to convert any value to string"""
|
|
|
|
| 1305 |
"teams_reviewed": [],
|
| 1306 |
"history_guessed_words": guessed_words,
|
| 1307 |
"end_round": False,
|
| 1308 |
+
"winner_and_score": None,
|
| 1309 |
+
"red_boss_is_called_counter": 0,
|
| 1310 |
+
"blue_boss_is_called_counter": 0,
|
| 1311 |
+
"red_captain_is_called_counter": 0,
|
| 1312 |
+
"blue_captain_is_called_counter": 0,
|
| 1313 |
}
|
| 1314 |
|
| 1315 |
final_msg = ""
|
support/my_tools.py
CHANGED
|
@@ -39,24 +39,6 @@ async def load_tools():
|
|
| 39 |
return boss_agent_tools, captain_agent_tools
|
| 40 |
|
| 41 |
|
| 42 |
-
# boss_client = MultiServerMCPClient(
|
| 43 |
-
# {
|
| 44 |
-
# "BossServer": {
|
| 45 |
-
# "url": "http://localhost:8000/mcp",
|
| 46 |
-
# "transport": "streamable_http",
|
| 47 |
-
# }
|
| 48 |
-
# }
|
| 49 |
-
# )
|
| 50 |
-
|
| 51 |
-
# captain_client = MultiServerMCPClient(
|
| 52 |
-
# {
|
| 53 |
-
# "CaptainServer": {
|
| 54 |
-
# "url": "http://localhost:8001/mcp",
|
| 55 |
-
# "transport": "streamable_http",
|
| 56 |
-
# }
|
| 57 |
-
# }
|
| 58 |
-
# )
|
| 59 |
-
|
| 60 |
boss_client = MultiServerMCPClient(
|
| 61 |
{
|
| 62 |
"BossServer": {
|
|
|
|
| 39 |
return boss_agent_tools, captain_agent_tools
|
| 40 |
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
boss_client = MultiServerMCPClient(
|
| 43 |
{
|
| 44 |
"BossServer": {
|
support/style/css.py
CHANGED
|
@@ -1854,7 +1854,7 @@ strong {
|
|
| 1854 |
margin-bottom: 16px;
|
| 1855 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 1856 |
transition: transform 0.2s, box-shadow 0.2s;
|
| 1857 |
-
border-left:
|
| 1858 |
}
|
| 1859 |
|
| 1860 |
.message_card:hover {
|
|
@@ -1908,11 +1908,19 @@ strong {
|
|
| 1908 |
padding-left: 52px;
|
| 1909 |
}
|
| 1910 |
|
| 1911 |
-
.message_content
|
| 1912 |
-
.message_content
|
| 1913 |
color: black;
|
| 1914 |
}
|
| 1915 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1916 |
.message_thinking {
|
| 1917 |
border-left-color: #9b59b6;
|
| 1918 |
background: linear-gradient(to right, #f8f4fc, white);
|
|
@@ -2247,7 +2255,7 @@ final_css = navbar_css + stats_css + css + """
|
|
| 2247 |
margin-bottom: 16px;
|
| 2248 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 2249 |
transition: transform 0.2s, box-shadow 0.2s;
|
| 2250 |
-
border-left:
|
| 2251 |
}
|
| 2252 |
|
| 2253 |
.message_card:hover {
|
|
|
|
| 1854 |
margin-bottom: 16px;
|
| 1855 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 1856 |
transition: transform 0.2s, box-shadow 0.2s;
|
| 1857 |
+
border-left: 8px solid #e0e0e0;
|
| 1858 |
}
|
| 1859 |
|
| 1860 |
.message_card:hover {
|
|
|
|
| 1908 |
padding-left: 52px;
|
| 1909 |
}
|
| 1910 |
|
| 1911 |
+
.message_content,
|
| 1912 |
+
.message_content * {
|
| 1913 |
color: black;
|
| 1914 |
}
|
| 1915 |
|
| 1916 |
+
.message_content think{
|
| 1917 |
+
font-style: italic;
|
| 1918 |
+
}
|
| 1919 |
+
|
| 1920 |
+
.message_content .prose * {
|
| 1921 |
+
color: #000000 !important;
|
| 1922 |
+
}
|
| 1923 |
+
|
| 1924 |
.message_thinking {
|
| 1925 |
border-left-color: #9b59b6;
|
| 1926 |
background: linear-gradient(to right, #f8f4fc, white);
|
|
|
|
| 2255 |
margin-bottom: 16px;
|
| 2256 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 2257 |
transition: transform 0.2s, box-shadow 0.2s;
|
| 2258 |
+
border-left: 8px solid #e0e0e0;
|
| 2259 |
}
|
| 2260 |
|
| 2261 |
.message_card:hover {
|
support/tools/captain_agent_tools.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fake tool call generator when human user is playing
|
| 2 |
+
|
| 3 |
+
def create_agent_fake_tool_call():
|
| 4 |
+
from langchain_core.messages import AIMessage
|
| 5 |
+
from uuid import uuid4
|
| 6 |
+
"""Creates a fake AIMessage that mimics an LLM tool call"""
|
| 7 |
+
tool_call_id = str(uuid4())
|
| 8 |
+
|
| 9 |
+
fake_message = AIMessage(
|
| 10 |
+
content="",
|
| 11 |
+
tool_calls=[
|
| 12 |
+
{
|
| 13 |
+
"name": "TeamFinalChoice",
|
| 14 |
+
"args": {
|
| 15 |
+
"guesses": ['STOP_TURN'],
|
| 16 |
+
},
|
| 17 |
+
"id": tool_call_id,
|
| 18 |
+
"type": "tool_call"
|
| 19 |
+
}
|
| 20 |
+
]
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
return fake_message
|
support/utils.py
CHANGED
|
@@ -1,10 +1,17 @@
|
|
| 1 |
import io
|
| 2 |
import markdown
|
|
|
|
|
|
|
|
|
|
| 3 |
import matplotlib.pyplot as plt
|
| 4 |
import matplotlib.patches as mpatches
|
|
|
|
| 5 |
import regex
|
|
|
|
|
|
|
| 6 |
|
| 7 |
from PIL import Image
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
def display_model_name(model_name):
|
|
@@ -369,124 +376,14 @@ def format_game_history_html(game_history):
|
|
| 369 |
return "".join(games_html)
|
| 370 |
|
| 371 |
|
| 372 |
-
def plot_game_board(board):
|
| 373 |
-
"""
|
| 374 |
-
Create a single combined visualization showing words with their color-coded backgrounds.
|
| 375 |
-
Each card displays the word in white text on a colored background (red, blue, beige, black).
|
| 376 |
-
"""
|
| 377 |
-
# Color mapping
|
| 378 |
-
color_map = {
|
| 379 |
-
'red': '#dc3545',
|
| 380 |
-
'blue': '#0d6efd',
|
| 381 |
-
'neutral': '#d4c5b9', # beige
|
| 382 |
-
'killer': '#212529' # black
|
| 383 |
-
}
|
| 384 |
-
|
| 385 |
-
# Get word-color pairs and arrange in 5x5 grid
|
| 386 |
-
word_color_pairs = board.get('word_color_pairs', [])
|
| 387 |
-
if len(word_color_pairs) < 25:
|
| 388 |
-
word_color_pairs = word_color_pairs + [("???", 'neutral')] * (25 - len(word_color_pairs))
|
| 389 |
-
|
| 390 |
-
# Create figure with specific size
|
| 391 |
-
fig, ax = plt.subplots(figsize=(12, 12))
|
| 392 |
-
ax.set_xlim(-0.5, 4.5)
|
| 393 |
-
ax.set_ylim(-0.5, 4.5)
|
| 394 |
-
ax.set_xticks([])
|
| 395 |
-
ax.set_yticks([])
|
| 396 |
-
ax.axis('off')
|
| 397 |
-
ax.set_aspect('equal')
|
| 398 |
-
|
| 399 |
-
# Draw grid of cards
|
| 400 |
-
idx = 0
|
| 401 |
-
for i in range(5):
|
| 402 |
-
for j in range(5):
|
| 403 |
-
if idx >= len(word_color_pairs):
|
| 404 |
-
break
|
| 405 |
-
|
| 406 |
-
word, color = word_color_pairs[idx]
|
| 407 |
-
bg_color = color_map.get(color, '#e9ecef')
|
| 408 |
-
|
| 409 |
-
# Draw card background with rounded corners effect
|
| 410 |
-
rect = mpatches.FancyBboxPatch(
|
| 411 |
-
(j - 0.48, i - 0.48), 0.96, 0.96,
|
| 412 |
-
boxstyle="round,pad=0.02",
|
| 413 |
-
facecolor=bg_color,
|
| 414 |
-
edgecolor='#495057',
|
| 415 |
-
linewidth=2.5,
|
| 416 |
-
zorder=1
|
| 417 |
-
)
|
| 418 |
-
ax.add_patch(rect)
|
| 419 |
-
|
| 420 |
-
# Add word text in white
|
| 421 |
-
# Adjust font size based on word length
|
| 422 |
-
word_length = len(word)
|
| 423 |
-
if word_length > 12:
|
| 424 |
-
fontsize = 13
|
| 425 |
-
elif word_length > 8:
|
| 426 |
-
fontsize = 15
|
| 427 |
-
else:
|
| 428 |
-
fontsize = 17
|
| 429 |
-
|
| 430 |
-
ax.text(
|
| 431 |
-
j, i, word,
|
| 432 |
-
ha="center", va="center",
|
| 433 |
-
fontsize=fontsize,
|
| 434 |
-
fontweight='bold',
|
| 435 |
-
color='white',
|
| 436 |
-
zorder=2,
|
| 437 |
-
wrap=True
|
| 438 |
-
)
|
| 439 |
-
|
| 440 |
-
idx += 1
|
| 441 |
-
|
| 442 |
-
plt.gca().invert_yaxis()
|
| 443 |
-
|
| 444 |
-
# Add title with starting team info
|
| 445 |
-
starting_team = board.get('starting_team', 'red')
|
| 446 |
-
title_color = color_map[starting_team]
|
| 447 |
-
|
| 448 |
-
fig.suptitle(
|
| 449 |
-
f'{starting_team.upper()} TEAM STARTS',
|
| 450 |
-
fontsize=18,
|
| 451 |
-
fontweight='bold',
|
| 452 |
-
color=title_color,
|
| 453 |
-
y=0.98
|
| 454 |
-
)
|
| 455 |
-
|
| 456 |
-
# Add legend
|
| 457 |
-
legend_elements = [
|
| 458 |
-
mpatches.Patch(facecolor=color_map['red'], edgecolor='#495057', label='Red Team'),
|
| 459 |
-
mpatches.Patch(facecolor=color_map['blue'], edgecolor='#495057', label='Blue Team'),
|
| 460 |
-
mpatches.Patch(facecolor=color_map['neutral'], edgecolor='#495057', label='Neutral'),
|
| 461 |
-
mpatches.Patch(facecolor=color_map['killer'], edgecolor='#495057', label='Killer ☠️')
|
| 462 |
-
]
|
| 463 |
-
ax.legend(
|
| 464 |
-
handles=legend_elements,
|
| 465 |
-
loc='upper center',
|
| 466 |
-
bbox_to_anchor=(0.5, -0.02),
|
| 467 |
-
ncol=4,
|
| 468 |
-
frameon=True,
|
| 469 |
-
fancybox=True,
|
| 470 |
-
shadow=True,
|
| 471 |
-
fontsize=11
|
| 472 |
-
)
|
| 473 |
-
|
| 474 |
-
plt.tight_layout()
|
| 475 |
-
|
| 476 |
-
# Save to buffer
|
| 477 |
-
buf = io.BytesIO()
|
| 478 |
-
plt.savefig(buf, format="png", bbox_inches="tight", dpi=120, facecolor='white')
|
| 479 |
-
buf.seek(0)
|
| 480 |
-
plt.close(fig)
|
| 481 |
-
|
| 482 |
-
return Image.open(buf)
|
| 483 |
-
|
| 484 |
-
|
| 485 |
def plot_game_board_with_guesses(board, guessed_words=None):
|
| 486 |
"""
|
| 487 |
Plot the game board with crosses or marks over guessed words.
|
| 488 |
"""
|
| 489 |
|
|
|
|
|
|
|
|
|
|
| 490 |
color_map = {
|
| 491 |
'red': '#dc3545',
|
| 492 |
'blue': '#0d6efd',
|
|
@@ -499,7 +396,9 @@ def plot_game_board_with_guesses(board, guessed_words=None):
|
|
| 499 |
if len(word_color_pairs) < 25:
|
| 500 |
word_color_pairs += [("???", 'neutral')] * (25 - len(word_color_pairs))
|
| 501 |
|
| 502 |
-
|
|
|
|
|
|
|
| 503 |
ax.set_xlim(-0.5, 4.5)
|
| 504 |
ax.set_ylim(-0.5, 4.5)
|
| 505 |
ax.axis('off')
|
|
@@ -514,7 +413,6 @@ def plot_game_board_with_guesses(board, guessed_words=None):
|
|
| 514 |
word, color = word_color_pairs[idx]
|
| 515 |
bg_color = color_map.get(color, '#e9ecef')
|
| 516 |
|
| 517 |
-
# Draw card background
|
| 518 |
rect = mpatches.FancyBboxPatch(
|
| 519 |
(j - 0.48, i - 0.48), 0.96, 0.96,
|
| 520 |
boxstyle="round,pad=0.02",
|
|
@@ -525,7 +423,6 @@ def plot_game_board_with_guesses(board, guessed_words=None):
|
|
| 525 |
)
|
| 526 |
ax.add_patch(rect)
|
| 527 |
|
| 528 |
-
# Add word
|
| 529 |
ax.text(
|
| 530 |
j, i, word,
|
| 531 |
ha="center", va="center",
|
|
@@ -536,7 +433,6 @@ def plot_game_board_with_guesses(board, guessed_words=None):
|
|
| 536 |
wrap=True
|
| 537 |
)
|
| 538 |
|
| 539 |
-
# If word was guessed, overlay a cross
|
| 540 |
if word in guessed_words:
|
| 541 |
ax.plot(
|
| 542 |
[j - 0.4, j + 0.4], [i - 0.4, i + 0.4],
|
|
@@ -549,7 +445,7 @@ def plot_game_board_with_guesses(board, guessed_words=None):
|
|
| 549 |
|
| 550 |
idx += 1
|
| 551 |
|
| 552 |
-
|
| 553 |
starting_team = board.get('starting_team', 'red')
|
| 554 |
title_color = color_map[starting_team]
|
| 555 |
|
|
@@ -561,9 +457,26 @@ def plot_game_board_with_guesses(board, guessed_words=None):
|
|
| 561 |
y=0.98
|
| 562 |
)
|
| 563 |
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import io
|
| 2 |
import markdown
|
| 3 |
+
|
| 4 |
+
import matplotlib
|
| 5 |
+
matplotlib.use('Agg')
|
| 6 |
import matplotlib.pyplot as plt
|
| 7 |
import matplotlib.patches as mpatches
|
| 8 |
+
import os
|
| 9 |
import regex
|
| 10 |
+
import tempfile
|
| 11 |
+
import uuid
|
| 12 |
|
| 13 |
from PIL import Image
|
| 14 |
+
from support.log_manager import logger
|
| 15 |
|
| 16 |
|
| 17 |
def display_model_name(model_name):
|
|
|
|
| 376 |
return "".join(games_html)
|
| 377 |
|
| 378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
def plot_game_board_with_guesses(board, guessed_words=None):
|
| 380 |
"""
|
| 381 |
Plot the game board with crosses or marks over guessed words.
|
| 382 |
"""
|
| 383 |
|
| 384 |
+
logger.info(f"Board: {board}")
|
| 385 |
+
logger.info(f"Guessed words: {guessed_words}")
|
| 386 |
+
|
| 387 |
color_map = {
|
| 388 |
'red': '#dc3545',
|
| 389 |
'blue': '#0d6efd',
|
|
|
|
| 396 |
if len(word_color_pairs) < 25:
|
| 397 |
word_color_pairs += [("???", 'neutral')] * (25 - len(word_color_pairs))
|
| 398 |
|
| 399 |
+
# Create a new figure explicitly (don't rely on global state)
|
| 400 |
+
fig = plt.figure(figsize=(12, 12))
|
| 401 |
+
ax = fig.add_subplot(111)
|
| 402 |
ax.set_xlim(-0.5, 4.5)
|
| 403 |
ax.set_ylim(-0.5, 4.5)
|
| 404 |
ax.axis('off')
|
|
|
|
| 413 |
word, color = word_color_pairs[idx]
|
| 414 |
bg_color = color_map.get(color, '#e9ecef')
|
| 415 |
|
|
|
|
| 416 |
rect = mpatches.FancyBboxPatch(
|
| 417 |
(j - 0.48, i - 0.48), 0.96, 0.96,
|
| 418 |
boxstyle="round,pad=0.02",
|
|
|
|
| 423 |
)
|
| 424 |
ax.add_patch(rect)
|
| 425 |
|
|
|
|
| 426 |
ax.text(
|
| 427 |
j, i, word,
|
| 428 |
ha="center", va="center",
|
|
|
|
| 433 |
wrap=True
|
| 434 |
)
|
| 435 |
|
|
|
|
| 436 |
if word in guessed_words:
|
| 437 |
ax.plot(
|
| 438 |
[j - 0.4, j + 0.4], [i - 0.4, i + 0.4],
|
|
|
|
| 445 |
|
| 446 |
idx += 1
|
| 447 |
|
| 448 |
+
ax.invert_yaxis()
|
| 449 |
starting_team = board.get('starting_team', 'red')
|
| 450 |
title_color = color_map[starting_team]
|
| 451 |
|
|
|
|
| 457 |
y=0.98
|
| 458 |
)
|
| 459 |
|
| 460 |
+
fig.tight_layout()
|
| 461 |
+
|
| 462 |
+
# Create a unique temporary file for this specific request
|
| 463 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png', prefix=f'board_{uuid.uuid4().hex}_')
|
| 464 |
+
temp_filename = temp_file.name
|
| 465 |
+
temp_file.close()
|
| 466 |
+
|
| 467 |
+
try:
|
| 468 |
+
# Save to the unique temporary file
|
| 469 |
+
fig.savefig(temp_filename, format="png", bbox_inches="tight", dpi=120, facecolor='white')
|
| 470 |
+
plt.close(fig) # Close the figure immediately
|
| 471 |
+
|
| 472 |
+
# Load the image from the file
|
| 473 |
+
img = Image.open(temp_filename)
|
| 474 |
+
img.load() # Load into memory
|
| 475 |
+
|
| 476 |
+
return img
|
| 477 |
+
finally:
|
| 478 |
+
# Clean up the temporary file
|
| 479 |
+
try:
|
| 480 |
+
os.unlink(temp_filename)
|
| 481 |
+
except:
|
| 482 |
+
pass
|