|
|
import os |
|
|
import sys |
|
|
|
|
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) |
|
|
|
|
|
import logging |
|
|
import uuid |
|
|
|
|
|
import gradio as gr |
|
|
import spaces |
|
|
|
|
|
from unpredictable_lord.chat import chat_with_llm_stream |
|
|
from unpredictable_lord.game_state import ( |
|
|
ADVICE_DESCRIPTIONS, |
|
|
PERSONALITY_DESCRIPTIONS, |
|
|
create_session, |
|
|
get_available_advice, |
|
|
get_session, |
|
|
) |
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
logger.info(f"ZeroGPU: {spaces.config.Config.zero_gpu}") |
|
|
|
|
|
|
|
|
def init_game(personality: str = "cautious") -> dict: |
|
|
""" |
|
|
Initialize a new game session and return the session information. |
|
|
|
|
|
This MCP tool creates a new game session with the specified lord personality. |
|
|
Use the returned session_id for all subsequent game operations. |
|
|
|
|
|
Args: |
|
|
personality: The lord's personality type. |
|
|
Options: "cautious" (risk-averse), "idealist" (emotional idealist), |
|
|
"populist" (popularity-focused). Defaults to "cautious". |
|
|
|
|
|
Returns: |
|
|
dict: Session information including session_id and initial game state. |
|
|
""" |
|
|
session_id = str(uuid.uuid4()) |
|
|
|
|
|
|
|
|
if personality not in PERSONALITY_DESCRIPTIONS: |
|
|
personality = "cautious" |
|
|
|
|
|
|
|
|
state = create_session(session_id, personality) |
|
|
|
|
|
logger.info( |
|
|
f"New game session created: {session_id} with personality: {personality}" |
|
|
) |
|
|
|
|
|
return { |
|
|
"session_id": session_id, |
|
|
"message": f"New game started! You are now the advisor to a {personality} lord.", |
|
|
"personality_description": PERSONALITY_DESCRIPTIONS[personality], |
|
|
"available_advice": ADVICE_DESCRIPTIONS, |
|
|
"game_state": state.to_dict(), |
|
|
} |
|
|
|
|
|
|
|
|
def get_game_state(session_id: str) -> dict: |
|
|
""" |
|
|
Get the current game state for a session. |
|
|
|
|
|
This MCP tool retrieves the current state of a game session, |
|
|
including all parameters, turn number, and game status. |
|
|
|
|
|
Args: |
|
|
session_id: The session ID returned from init_game. |
|
|
|
|
|
Returns: |
|
|
dict: Current game state or error message if session not found. |
|
|
""" |
|
|
state = get_session(session_id) |
|
|
|
|
|
if state is None: |
|
|
return { |
|
|
"error": "Session not found", |
|
|
"message": f"No game session found with ID: {session_id}. Please call init_game first.", |
|
|
} |
|
|
|
|
|
return { |
|
|
"session_id": session_id, |
|
|
"game_state": state.to_dict(), |
|
|
"status_summary": state.get_status_summary(), |
|
|
} |
|
|
|
|
|
|
|
|
def list_available_advice() -> dict: |
|
|
""" |
|
|
Get all available advice options that can be given to the lord. |
|
|
|
|
|
This MCP tool returns a list of all possible advice types that can be |
|
|
used with execute_turn(). The Lord AI should interpret the user's |
|
|
free-form suggestion and map it to one of these options. |
|
|
|
|
|
Returns: |
|
|
dict: Dictionary containing all available advice options with |
|
|
their names, descriptions, and expected effects. |
|
|
""" |
|
|
return { |
|
|
"advice_options": get_available_advice(), |
|
|
"usage": "Interpret the user's advice and select the most appropriate option from the list above.", |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="Unpredictable Lord") as demo: |
|
|
gr.Markdown("# Unpredictable Lord\nLord Advisor AI Simulation") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
with gr.TabItem("Chat"): |
|
|
chatbot = gr.Chatbot(label="Lord AI", height=600, type="messages") |
|
|
|
|
|
with gr.Row(): |
|
|
msg = gr.Textbox( |
|
|
label="Your Advice", |
|
|
placeholder="My Lord, I have a proposal...", |
|
|
scale=4, |
|
|
) |
|
|
submit_btn = gr.Button("Submit", scale=1) |
|
|
|
|
|
clear = gr.Button("Clear History") |
|
|
|
|
|
def user(user_message, history): |
|
|
|
|
|
return "", history + [{"role": "user", "content": user_message}] |
|
|
|
|
|
def bot(history): |
|
|
|
|
|
user_message = history[-1]["content"] |
|
|
history_for_model = history[:-1] |
|
|
|
|
|
for updated_history in chat_with_llm_stream( |
|
|
user_message, history_for_model |
|
|
): |
|
|
yield updated_history |
|
|
|
|
|
msg.submit( |
|
|
user, [msg, chatbot], [msg, chatbot], queue=False, show_api=False |
|
|
).then(bot, chatbot, chatbot, show_api=False) |
|
|
|
|
|
submit_btn.click( |
|
|
user, [msg, chatbot], [msg, chatbot], queue=False, show_api=False |
|
|
).then(bot, chatbot, chatbot, show_api=False) |
|
|
|
|
|
clear.click(lambda: None, None, chatbot, queue=False, show_api=False) |
|
|
|
|
|
|
|
|
with gr.TabItem("MCP Server"): |
|
|
gr.Markdown( |
|
|
""" |
|
|
## MCP Server Guide |
|
|
|
|
|
This application functions as an **MCP (Model Context Protocol) Server**. |
|
|
External LLMs can connect to this server and use game management tools. |
|
|
|
|
|
### Connection URL |
|
|
|
|
|
``` |
|
|
https://<space-name>.hf.space/gradio_api/mcp/ |
|
|
``` |
|
|
|
|
|
For local development: |
|
|
``` |
|
|
http://localhost:7860/gradio_api/mcp/ |
|
|
``` |
|
|
|
|
|
### How to Connect |
|
|
|
|
|
#### Claude Desktop / Cursor / VS Code |
|
|
|
|
|
Add the following to your MCP settings configuration: |
|
|
|
|
|
```json |
|
|
{ |
|
|
"mcpServers": { |
|
|
"unpredictable-lord": { |
|
|
"url": "https://<space-name>.hf.space/gradio_api/mcp/" |
|
|
} |
|
|
} |
|
|
} |
|
|
``` |
|
|
|
|
|
### Available Tools |
|
|
|
|
|
| Tool | Description | |
|
|
|------|-------------| |
|
|
| `init_game` | Initialize a new game session. Returns a session_id and available advice options. | |
|
|
| `get_game_state` | Get the current game state for a session. | |
|
|
| `list_available_advice` | Get all available advice options for execute_turn. | |
|
|
|
|
|
### Usage Flow |
|
|
|
|
|
1. Call `init_game(personality)` to start a new game session |
|
|
2. Use the returned `session_id` for all subsequent game operations |
|
|
3. Game state is managed server-side |
|
|
""" |
|
|
) |
|
|
|
|
|
gr.Markdown("### Test: Initialize Game") |
|
|
with gr.Row(): |
|
|
personality_input = gr.Dropdown( |
|
|
choices=["cautious", "idealist", "populist"], |
|
|
value="cautious", |
|
|
label="Lord Personality", |
|
|
) |
|
|
init_btn = gr.Button("Start New Game") |
|
|
|
|
|
init_output = gr.JSON(label="Game Session Info") |
|
|
|
|
|
init_btn.click(fn=init_game, inputs=personality_input, outputs=init_output) |
|
|
|
|
|
gr.Markdown("### Test: Get Game State") |
|
|
with gr.Row(): |
|
|
session_id_input = gr.Textbox( |
|
|
label="Session ID", |
|
|
placeholder="Enter session_id from init_game", |
|
|
) |
|
|
get_state_btn = gr.Button("Get State") |
|
|
|
|
|
state_output = gr.JSON(label="Current Game State") |
|
|
|
|
|
get_state_btn.click( |
|
|
fn=get_game_state, inputs=session_id_input, outputs=state_output |
|
|
) |
|
|
|
|
|
gr.Markdown("### Test: List Available Advice") |
|
|
list_advice_btn = gr.Button("List Advice Options") |
|
|
advice_output = gr.JSON(label="Available Advice Options") |
|
|
|
|
|
list_advice_btn.click( |
|
|
fn=list_available_advice, inputs=[], outputs=advice_output |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(mcp_server=True) |
|
|
|