Spaces:
Sleeping
Sleeping
Update chat_handler.py
Browse files- chat_handler.py +49 -33
chat_handler.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
"""
|
| 2 |
-
Chat handling logic for Universal MCP Client -
|
| 3 |
"""
|
| 4 |
import re
|
| 5 |
import logging
|
| 6 |
import traceback
|
|
|
|
| 7 |
from datetime import datetime
|
| 8 |
from typing import Dict, Any, List, Tuple, Optional
|
| 9 |
import gradio as gr
|
|
@@ -16,16 +17,26 @@ from mcp_client import UniversalMCPClient
|
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
|
| 18 |
class ChatHandler:
|
| 19 |
-
"""Handles chat interactions with
|
| 20 |
|
| 21 |
def __init__(self, mcp_client: UniversalMCPClient):
|
| 22 |
self.mcp_client = mcp_client
|
| 23 |
|
| 24 |
def process_multimodal_message(self, message: Dict[str, Any], history: List) -> Tuple[List[ChatMessage], Dict[str, Any]]:
|
| 25 |
-
"""Enhanced MCP chat function with multimodal input support and
|
| 26 |
|
| 27 |
-
if
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
history.append(ChatMessage(role="user", content=error_msg))
|
| 30 |
history.append(ChatMessage(role="assistant", content=error_msg))
|
| 31 |
return history, gr.MultimodalTextbox(value=None, interactive=False)
|
|
@@ -44,7 +55,7 @@ class ChatHandler:
|
|
| 44 |
user_text = message
|
| 45 |
user_files = []
|
| 46 |
|
| 47 |
-
logger.info(f"π¬ Processing multimodal message:")
|
| 48 |
logger.info(f" π Text: {user_text}")
|
| 49 |
logger.info(f" π Files: {len(user_files)} files uploaded")
|
| 50 |
logger.info(f" π History type: {type(history)}, length: {len(history)}")
|
|
@@ -86,11 +97,14 @@ class ChatHandler:
|
|
| 86 |
if not user_text.strip() and not user_files:
|
| 87 |
return history, gr.MultimodalTextbox(value=None, interactive=False)
|
| 88 |
|
| 89 |
-
# Create messages for
|
| 90 |
-
messages = self.
|
| 91 |
|
| 92 |
-
# Process the chat
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
# Add all response messages to history
|
| 96 |
history.extend(response_messages)
|
|
@@ -112,11 +126,11 @@ class ChatHandler:
|
|
| 112 |
history.append(ChatMessage(role="assistant", content=error_msg))
|
| 113 |
return history, gr.MultimodalTextbox(value=None, interactive=False)
|
| 114 |
|
| 115 |
-
def
|
| 116 |
-
"""Convert history (ChatMessage or dict) to
|
| 117 |
messages = []
|
| 118 |
|
| 119 |
-
# Convert history to
|
| 120 |
recent_history = history[-16:] if len(history) > 16 else history
|
| 121 |
for msg in recent_history:
|
| 122 |
# Handle both ChatMessage objects and dictionary format for backward compatibility
|
|
@@ -160,8 +174,8 @@ class ChatHandler:
|
|
| 160 |
|
| 161 |
return messages
|
| 162 |
|
| 163 |
-
def
|
| 164 |
-
"""Call
|
| 165 |
|
| 166 |
# Check if we have MCP servers to use
|
| 167 |
if not self.mcp_client.servers:
|
|
@@ -169,6 +183,26 @@ class ChatHandler:
|
|
| 169 |
else:
|
| 170 |
return self._call_claude_with_mcp(messages, user_files)
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
def _call_claude_without_mcp(self, messages: List[Dict[str, Any]]) -> List[ChatMessage]:
|
| 173 |
"""Call Claude API without MCP servers"""
|
| 174 |
logger.info("π¬ No MCP servers available, using regular Claude chat")
|
|
@@ -501,26 +535,8 @@ IMPORTANT - For uploaded images:
|
|
| 501 |
- **Image Editing/Enhancement**: Use MCP image processing tools
|
| 502 |
- **Image Generation**: Use MCP image generation tools
|
| 503 |
|
| 504 |
-
IMPORTANT - File URL Conversion for MCP Tools:
|
| 505 |
-
When using MCP tools that require file inputs, you need to be aware that uploaded files have local paths that remote MCP servers cannot access.
|
| 506 |
-
|
| 507 |
-
For uploaded files in MCP tool calls:
|
| 508 |
-
- If an MCP tool fails with "Invalid file data format" or similar errors about file paths
|
| 509 |
-
- The issue is that remote MCP servers cannot access local file paths like '/tmp/gradio/...'
|
| 510 |
-
- In such cases, inform the user that the MCP server requires files to be accessible via public URLs
|
| 511 |
-
- Suggest that they need a "File Upload" MCP server or that the specific MCP server may need configuration for file handling
|
| 512 |
-
|
| 513 |
-
Current uploaded files that may need URL conversion:
|
| 514 |
-
{uploaded_files_context}
|
| 515 |
-
|
| 516 |
IMPORTANT - GRADIO MEDIA DISPLAY:
|
| 517 |
When MCP tools return media, end your response with "MEDIA_GENERATED: [URL]" where [URL] is the actual media URL.
|
| 518 |
|
| 519 |
-
Examples:
|
| 520 |
-
- User uploads image + "What's in this image?" β Use NATIVE vision (no MCP needed)
|
| 521 |
-
- User uploads image + "Make this vintage" β Use MCP image editing tool
|
| 522 |
-
- User says "Generate a sunset image" β Use MCP image generation tool
|
| 523 |
-
- User uploads audio + "Transcribe this" β Use MCP transcription tool
|
| 524 |
-
|
| 525 |
Current time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 526 |
Available MCP servers: {list(self.mcp_client.servers.keys())}"""
|
|
|
|
| 1 |
"""
|
| 2 |
+
Chat handling logic for Universal MCP Client - Enhanced with Inference Provider Support
|
| 3 |
"""
|
| 4 |
import re
|
| 5 |
import logging
|
| 6 |
import traceback
|
| 7 |
+
import asyncio
|
| 8 |
from datetime import datetime
|
| 9 |
from typing import Dict, Any, List, Tuple, Optional
|
| 10 |
import gradio as gr
|
|
|
|
| 17 |
logger = logging.getLogger(__name__)
|
| 18 |
|
| 19 |
class ChatHandler:
|
| 20 |
+
"""Handles chat interactions with multiple LLM backends and MCP servers using ChatMessage dataclass"""
|
| 21 |
|
| 22 |
def __init__(self, mcp_client: UniversalMCPClient):
|
| 23 |
self.mcp_client = mcp_client
|
| 24 |
|
| 25 |
def process_multimodal_message(self, message: Dict[str, Any], history: List) -> Tuple[List[ChatMessage], Dict[str, Any]]:
|
| 26 |
+
"""Enhanced MCP chat function with multimodal input support and multiple LLM backends"""
|
| 27 |
|
| 28 |
+
# Check if any LLM backend is configured
|
| 29 |
+
backend_configured = False
|
| 30 |
+
|
| 31 |
+
if self.mcp_client.anthropic_client and AppConfig.ANTHROPIC_API_KEY:
|
| 32 |
+
backend_configured = True
|
| 33 |
+
backend_type = "anthropic"
|
| 34 |
+
elif self.mcp_client.hf_client and self.mcp_client.current_provider:
|
| 35 |
+
backend_configured = True
|
| 36 |
+
backend_type = "hf_inference"
|
| 37 |
+
|
| 38 |
+
if not backend_configured:
|
| 39 |
+
error_msg = "β No LLM backend configured. Please configure either Anthropic API key or HuggingFace Inference Provider."
|
| 40 |
history.append(ChatMessage(role="user", content=error_msg))
|
| 41 |
history.append(ChatMessage(role="assistant", content=error_msg))
|
| 42 |
return history, gr.MultimodalTextbox(value=None, interactive=False)
|
|
|
|
| 55 |
user_text = message
|
| 56 |
user_files = []
|
| 57 |
|
| 58 |
+
logger.info(f"π¬ Processing multimodal message with {backend_type} backend:")
|
| 59 |
logger.info(f" π Text: {user_text}")
|
| 60 |
logger.info(f" π Files: {len(user_files)} files uploaded")
|
| 61 |
logger.info(f" π History type: {type(history)}, length: {len(history)}")
|
|
|
|
| 97 |
if not user_text.strip() and not user_files:
|
| 98 |
return history, gr.MultimodalTextbox(value=None, interactive=False)
|
| 99 |
|
| 100 |
+
# Create messages for LLM API
|
| 101 |
+
messages = self._prepare_llm_messages(history)
|
| 102 |
|
| 103 |
+
# Process the chat based on backend type
|
| 104 |
+
if backend_type == "anthropic":
|
| 105 |
+
response_messages = self._call_anthropic_api(messages, user_files)
|
| 106 |
+
else: # hf_inference
|
| 107 |
+
response_messages = self._call_hf_inference_api(messages, user_files)
|
| 108 |
|
| 109 |
# Add all response messages to history
|
| 110 |
history.extend(response_messages)
|
|
|
|
| 126 |
history.append(ChatMessage(role="assistant", content=error_msg))
|
| 127 |
return history, gr.MultimodalTextbox(value=None, interactive=False)
|
| 128 |
|
| 129 |
+
def _prepare_llm_messages(self, history: List) -> List[Dict[str, Any]]:
|
| 130 |
+
"""Convert history (ChatMessage or dict) to LLM API format"""
|
| 131 |
messages = []
|
| 132 |
|
| 133 |
+
# Convert history to LLM API format (text only for context)
|
| 134 |
recent_history = history[-16:] if len(history) > 16 else history
|
| 135 |
for msg in recent_history:
|
| 136 |
# Handle both ChatMessage objects and dictionary format for backward compatibility
|
|
|
|
| 174 |
|
| 175 |
return messages
|
| 176 |
|
| 177 |
+
def _call_anthropic_api(self, messages: List[Dict[str, Any]], user_files: List[str]) -> List[ChatMessage]:
|
| 178 |
+
"""Call Anthropic API (existing implementation)"""
|
| 179 |
|
| 180 |
# Check if we have MCP servers to use
|
| 181 |
if not self.mcp_client.servers:
|
|
|
|
| 183 |
else:
|
| 184 |
return self._call_claude_with_mcp(messages, user_files)
|
| 185 |
|
| 186 |
+
def _call_hf_inference_api(self, messages: List[Dict[str, Any]], user_files: List[str]) -> List[ChatMessage]:
|
| 187 |
+
"""Call HuggingFace Inference API with custom MCP implementation"""
|
| 188 |
+
|
| 189 |
+
# Run async call in sync context
|
| 190 |
+
def run_async():
|
| 191 |
+
loop = asyncio.new_event_loop()
|
| 192 |
+
asyncio.set_event_loop(loop)
|
| 193 |
+
try:
|
| 194 |
+
return loop.run_until_complete(
|
| 195 |
+
self.mcp_client.call_llm_with_mcp(messages, user_files)
|
| 196 |
+
)
|
| 197 |
+
finally:
|
| 198 |
+
loop.close()
|
| 199 |
+
|
| 200 |
+
try:
|
| 201 |
+
return run_async()
|
| 202 |
+
except Exception as e:
|
| 203 |
+
logger.error(f"HF Inference API error: {e}")
|
| 204 |
+
return [ChatMessage(role="assistant", content=f"β Error with HF Inference: {str(e)}")]
|
| 205 |
+
|
| 206 |
def _call_claude_without_mcp(self, messages: List[Dict[str, Any]]) -> List[ChatMessage]:
|
| 207 |
"""Call Claude API without MCP servers"""
|
| 208 |
logger.info("π¬ No MCP servers available, using regular Claude chat")
|
|
|
|
| 535 |
- **Image Editing/Enhancement**: Use MCP image processing tools
|
| 536 |
- **Image Generation**: Use MCP image generation tools
|
| 537 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
IMPORTANT - GRADIO MEDIA DISPLAY:
|
| 539 |
When MCP tools return media, end your response with "MEDIA_GENERATED: [URL]" where [URL] is the actual media URL.
|
| 540 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
Current time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 542 |
Available MCP servers: {list(self.mcp_client.servers.keys())}"""
|