feat: integrate tool prefix handling for MCP tools in chat functionality
Browse files
app.py
CHANGED
|
@@ -23,6 +23,7 @@ from unpredictable_lord.mcp_server.mcp_server import (
|
|
| 23 |
list_available_advice,
|
| 24 |
)
|
| 25 |
from unpredictable_lord.settings import ENABLE_GRADIO_DEPRECATION_WARNING, LOGGING_LEVEL
|
|
|
|
| 26 |
|
| 27 |
# Configure logging level from environment variable
|
| 28 |
log_level = getattr(logging, LOGGING_LEVEL, logging.INFO)
|
|
@@ -52,6 +53,10 @@ NO_GAME_MESSAGE = """_No active game._
|
|
| 52 |
2. Click "⚔️ Start New Game"
|
| 53 |
"""
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
# Gradio UI
|
| 57 |
with gr.Blocks(title="Unpredictable Lord") as demo:
|
|
@@ -145,12 +150,6 @@ with gr.Blocks(title="Unpredictable Lord") as demo:
|
|
| 145 |
# Helper functions for Chat tab
|
| 146 |
async def start_chat_game(personality: str):
|
| 147 |
"""Start a new game via MCP client and return updated UI state."""
|
| 148 |
-
# Get tool prefix from Gradio's MCP server (handles HF Spaces naming)
|
| 149 |
-
tool_prefix = ""
|
| 150 |
-
if demo.mcp_server_obj:
|
| 151 |
-
tool_prefix = demo.mcp_server_obj.tool_prefix.replace("-", "_")
|
| 152 |
-
logger.info(f"Using tool prefix: '{tool_prefix}'")
|
| 153 |
-
|
| 154 |
# Call init_game via MCP client
|
| 155 |
mcp_client = MCPClient(tool_prefix=tool_prefix)
|
| 156 |
result_json = await mcp_client.call_tool(
|
|
@@ -298,7 +297,7 @@ _{personality_desc}_"""
|
|
| 298 |
history_for_model = history[:-1]
|
| 299 |
|
| 300 |
async for updated_history in chat_with_mcp_tools(
|
| 301 |
-
user_message, history_for_model, system_instructions
|
| 302 |
):
|
| 303 |
yield updated_history
|
| 304 |
|
|
|
|
| 23 |
list_available_advice,
|
| 24 |
)
|
| 25 |
from unpredictable_lord.settings import ENABLE_GRADIO_DEPRECATION_WARNING, LOGGING_LEVEL
|
| 26 |
+
from unpredictable_lord.utils import get_tool_prefix
|
| 27 |
|
| 28 |
# Configure logging level from environment variable
|
| 29 |
log_level = getattr(logging, LOGGING_LEVEL, logging.INFO)
|
|
|
|
| 53 |
2. Click "⚔️ Start New Game"
|
| 54 |
"""
|
| 55 |
|
| 56 |
+
# MCP tool prefix for HF Spaces
|
| 57 |
+
tool_prefix = get_tool_prefix()
|
| 58 |
+
logger.info(f"Using tool prefix: '{tool_prefix}'")
|
| 59 |
+
|
| 60 |
|
| 61 |
# Gradio UI
|
| 62 |
with gr.Blocks(title="Unpredictable Lord") as demo:
|
|
|
|
| 150 |
# Helper functions for Chat tab
|
| 151 |
async def start_chat_game(personality: str):
|
| 152 |
"""Start a new game via MCP client and return updated UI state."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
# Call init_game via MCP client
|
| 154 |
mcp_client = MCPClient(tool_prefix=tool_prefix)
|
| 155 |
result_json = await mcp_client.call_tool(
|
|
|
|
| 297 |
history_for_model = history[:-1]
|
| 298 |
|
| 299 |
async for updated_history in chat_with_mcp_tools(
|
| 300 |
+
user_message, history_for_model, system_instructions, tool_prefix
|
| 301 |
):
|
| 302 |
yield updated_history
|
| 303 |
|
src/unpredictable_lord/chat/chat.py
CHANGED
|
@@ -135,6 +135,7 @@ async def chat_with_mcp_tools(
|
|
| 135 |
user_message: str,
|
| 136 |
chat_history: list[dict[str, str]],
|
| 137 |
system_instructions: str,
|
|
|
|
| 138 |
) -> AsyncGenerator[list[dict], None]:
|
| 139 |
"""
|
| 140 |
Chat with LLM with MCP tool support (async streaming version).
|
|
@@ -211,7 +212,7 @@ async def chat_with_mcp_tools(
|
|
| 211 |
yield partial_history
|
| 212 |
|
| 213 |
# Execute tools via MCP
|
| 214 |
-
tool_result_messages = await execute_tool_calls(tool_calls)
|
| 215 |
|
| 216 |
messages.extend(tool_result_messages)
|
| 217 |
|
|
|
|
| 135 |
user_message: str,
|
| 136 |
chat_history: list[dict[str, str]],
|
| 137 |
system_instructions: str,
|
| 138 |
+
tool_prefix: str,
|
| 139 |
) -> AsyncGenerator[list[dict], None]:
|
| 140 |
"""
|
| 141 |
Chat with LLM with MCP tool support (async streaming version).
|
|
|
|
| 212 |
yield partial_history
|
| 213 |
|
| 214 |
# Execute tools via MCP
|
| 215 |
+
tool_result_messages = await execute_tool_calls(tool_calls, tool_prefix)
|
| 216 |
|
| 217 |
messages.extend(tool_result_messages)
|
| 218 |
|
src/unpredictable_lord/chat/chat_tools.py
CHANGED
|
@@ -14,6 +14,7 @@ from unpredictable_lord.chat.mcp_client import MCPClient
|
|
| 14 |
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
|
|
|
| 17 |
# Tool definitions matching MCP tools in mcp_tools.py
|
| 18 |
TOOL_DEFINITIONS = [
|
| 19 |
oh.ToolDescription(
|
|
@@ -123,11 +124,12 @@ def extract_tool_calls(parsed_messages: list[oh.Message]) -> list[oh.Message]:
|
|
| 123 |
]
|
| 124 |
|
| 125 |
|
| 126 |
-
async def _execute_tool(tool_name: str, params: dict) -> str:
|
| 127 |
"""Execute a single MCP tool and return the result as JSON string.
|
| 128 |
|
| 129 |
Args:
|
| 130 |
tool_name: Name of the tool to execute.
|
|
|
|
| 131 |
params: Tool parameters as a dictionary.
|
| 132 |
|
| 133 |
Returns:
|
|
@@ -135,7 +137,7 @@ async def _execute_tool(tool_name: str, params: dict) -> str:
|
|
| 135 |
"""
|
| 136 |
import ast
|
| 137 |
|
| 138 |
-
mcp_client = MCPClient()
|
| 139 |
try:
|
| 140 |
result = await mcp_client.call_tool(tool_name, params)
|
| 141 |
|
|
@@ -162,11 +164,14 @@ async def _execute_tool(tool_name: str, params: dict) -> str:
|
|
| 162 |
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 163 |
|
| 164 |
|
| 165 |
-
async def execute_tool_calls(
|
|
|
|
|
|
|
| 166 |
"""Execute tool calls and return result messages.
|
| 167 |
|
| 168 |
Args:
|
| 169 |
tool_calls: List of tool call messages extracted from parser.
|
|
|
|
| 170 |
|
| 171 |
Returns:
|
| 172 |
List of tool result messages to be added to conversation.
|
|
@@ -189,7 +194,7 @@ async def execute_tool_calls(tool_calls: list[oh.Message]) -> list[oh.Message]:
|
|
| 189 |
logger.info(f"Executing MCP tool: {tool_name} with params: {params}")
|
| 190 |
|
| 191 |
# Execute tool via MCP (returns JSON string)
|
| 192 |
-
result_json = await _execute_tool(tool_name, params)
|
| 193 |
|
| 194 |
logger.info(f"Tool {tool_name} result: {result_json}")
|
| 195 |
|
|
|
|
| 14 |
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
+
|
| 18 |
# Tool definitions matching MCP tools in mcp_tools.py
|
| 19 |
TOOL_DEFINITIONS = [
|
| 20 |
oh.ToolDescription(
|
|
|
|
| 124 |
]
|
| 125 |
|
| 126 |
|
| 127 |
+
async def _execute_tool(tool_name: str, tool_prefix: str, params: dict) -> str:
|
| 128 |
"""Execute a single MCP tool and return the result as JSON string.
|
| 129 |
|
| 130 |
Args:
|
| 131 |
tool_name: Name of the tool to execute.
|
| 132 |
+
tool_prefix: Prefix to use for the tool name.
|
| 133 |
params: Tool parameters as a dictionary.
|
| 134 |
|
| 135 |
Returns:
|
|
|
|
| 137 |
"""
|
| 138 |
import ast
|
| 139 |
|
| 140 |
+
mcp_client = MCPClient(tool_prefix=tool_prefix)
|
| 141 |
try:
|
| 142 |
result = await mcp_client.call_tool(tool_name, params)
|
| 143 |
|
|
|
|
| 164 |
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
| 165 |
|
| 166 |
|
| 167 |
+
async def execute_tool_calls(
|
| 168 |
+
tool_calls: list[oh.Message], tool_prefix: str
|
| 169 |
+
) -> list[oh.Message]:
|
| 170 |
"""Execute tool calls and return result messages.
|
| 171 |
|
| 172 |
Args:
|
| 173 |
tool_calls: List of tool call messages extracted from parser.
|
| 174 |
+
tool_prefix: Prefix to use for the tool names.
|
| 175 |
|
| 176 |
Returns:
|
| 177 |
List of tool result messages to be added to conversation.
|
|
|
|
| 194 |
logger.info(f"Executing MCP tool: {tool_name} with params: {params}")
|
| 195 |
|
| 196 |
# Execute tool via MCP (returns JSON string)
|
| 197 |
+
result_json = await _execute_tool(tool_name, tool_prefix, params)
|
| 198 |
|
| 199 |
logger.info(f"Tool {tool_name} result: {result_json}")
|
| 200 |
|
src/unpredictable_lord/utils.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def get_tool_prefix() -> str:
|
| 5 |
+
"""Get the MCP tool prefix for HuggingFace Spaces.
|
| 6 |
+
|
| 7 |
+
Gradio adds a prefix based on the Space ID. This function replicates
|
| 8 |
+
that logic to ensure tool names match.
|
| 9 |
+
|
| 10 |
+
Returns:
|
| 11 |
+
Tool prefix string (empty on local, "space_name_" on HF Spaces).
|
| 12 |
+
"""
|
| 13 |
+
space_id = os.environ.get("SPACE_ID", "")
|
| 14 |
+
if space_id:
|
| 15 |
+
prefix = space_id.split("/")[-1] + "_"
|
| 16 |
+
prefix = prefix.replace("-", "_")
|
| 17 |
+
return prefix
|
| 18 |
+
return ""
|