aiagent / app_old.py
jdewitte's picture
Fix call issue
5cc5bf3
Raw
History Blame Contribute Delete
38 kB
import os
import asyncio
import logging
import sys
import time
import json
import httpx
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, Field
import uvicorn
from contextlib import asynccontextmanager
# Import our LLM client
from llm_client import LLMToolCaller
from init_ollama import init_ollama
# Configure comprehensive logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('inference_app.log', mode='a')
]
)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Also create a request logger for detailed request tracking
request_logger = logging.getLogger('requests')
request_logger.setLevel(logging.DEBUG)
# Import local MCP client
from prestashop_mcp.client import get_mcp_client, close_mcp_client
# Global MCP client and LLM client instances
mcp_client = None
llm_client = None
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage application lifespan - better for HF Spaces"""
global mcp_client, llm_client
logger.info("πŸš€ Starting HF Inference Point application")
logger.info(f"πŸ“¦ Environment: {os.getenv('ENVIRONMENT', 'unknown')}")
logger.info(f"πŸ”— Default PrestaShop URL: {DEFAULT_PRESTASHOP_URL}")
# Startup
try:
# Initialize Ollama first
logger.info("πŸ€– Initializing Ollama LLM...")
if init_ollama():
llm_client = LLMToolCaller()
logger.info("βœ… LLM client initialized successfully")
else:
logger.warning("⚠️ LLM initialization failed, using fallback")
llm_client = None
logger.info("πŸ”§ Attempting to initialize MCP client...")
start_time = time.time()
mcp_client = await get_mcp_client()
init_time = time.time() - start_time
logger.info(f"βœ… MCP client initialized successfully in {init_time:.2f}s")
logger.info(f"πŸ› οΈ MCP client type: {type(mcp_client)}")
except Exception as e:
logger.error(f"❌ Failed to initialize MCP client: {e}")
logger.error(f"πŸ“‹ Exception details: {type(e).__name__}: {str(e)}")
logger.error(f"πŸ” MCP client will be None, falling back to graceful degradation")
mcp_client = None
logger.info("🎯 Application startup complete, ready to serve requests")
yield # App runs here
# Shutdown
logger.info("πŸ›‘ Application shutdown initiated")
try:
await close_mcp_client()
logger.info("βœ… MCP client closed successfully")
except Exception as e:
logger.error(f"⚠️ Error closing MCP client: {e}")
logger.info("πŸ‘‹ Application shutdown complete")
# Create FastAPI app with modern lifespan management
app = FastAPI(
title="PrestaShop MCP Inference Endpoint",
description="Simple inference endpoint with integrated PrestaShop MCP tools",
version="1.0.0",
lifespan=lifespan
)
# Request logging middleware
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Log all incoming requests and responses"""
start_time = time.time()
request_id = f"req_{int(time.time() * 1000)}_{hash(str(request.url)) % 10000}"
# Log incoming request
request_logger.info(f"πŸ“₯ [{request_id}] {request.method} {request.url}")
request_logger.info(f"πŸ” [{request_id}] Headers: {dict(request.headers)}")
request_logger.info(f"🌐 [{request_id}] Client: {request.client.host if request.client else 'unknown'}")
# Get request body if present
if request.method in ["POST", "PUT", "PATCH"]:
try:
body = await request.body()
if body:
try:
body_json = json.loads(body.decode())
request_logger.info(f"πŸ“ [{request_id}] Body: {json.dumps(body_json, indent=2)}")
except:
request_logger.info(f"πŸ“ [{request_id}] Body (raw): {body.decode()[:500]}...")
# Reset body for the actual handler
async def receive():
return {"type": "http.request", "body": body}
request._receive = receive
except Exception as e:
request_logger.warning(f"⚠️ [{request_id}] Could not read request body: {e}")
# Process request
try:
response = await call_next(request)
duration = time.time() - start_time
request_logger.info(f"πŸ“€ [{request_id}] Response: {response.status_code} in {duration:.3f}s")
return response
except Exception as e:
duration = time.time() - start_time
request_logger.error(f"πŸ’₯ [{request_id}] Request failed after {duration:.3f}s: {e}")
raise
# Configuration
DEFAULT_PRESTASHOP_URL = os.getenv("PRESTASHOP_API_URL", "https://hub.mcp.integration.ambris.com")
HUB_SERVER_URL = "https://hub.mcp.integration.ambris.com"
# Pydantic models for inference endpoint
class InferenceInput(BaseModel):
"""Input payload for the inference endpoint"""
tool: str = Field(..., description="MCP tool name to call")
arguments: Dict[str, Any] = Field(default_factory=dict, description="Arguments for the MCP tool")
class PromptInput(BaseModel):
"""Input payload for prompt-based requests"""
prompt: str = Field(..., description="User prompt/question")
session_id: Optional[str] = Field(default=None, description="Session identifier")
context: Dict[str, Any] = Field(default_factory=dict, description="Additional context")
origin: Optional[str] = Field(default=None, description="Request origin")
class InferenceOutput(BaseModel):
"""Output from the inference endpoint"""
ok: bool
tool: Optional[str] = None
result: Optional[str] = None
error: Optional[str] = None
response: Optional[str] = None # For prompt-based responses
class HealthResponse(BaseModel):
status: str
mcp_available: bool
available_tools: List[str]
# New models for chat interface
class ChatMessage(BaseModel):
"""A chat message"""
role: str = Field(..., description="Role: 'user' or 'assistant'")
content: str = Field(..., description="Message content")
class ChatInput(BaseModel):
"""Input for chat interface"""
message: str = Field(..., description="User message/query")
conversation_history: List[ChatMessage] = Field(default_factory=list, description="Previous messages")
session_id: Optional[str] = Field(default=None, description="Session identifier")
client_token: Optional[str] = Field(default=None, description="Client authentication token")
class ChatOutput(BaseModel):
"""Output from chat interface"""
response: str = Field(..., description="Assistant response")
tool_used: Optional[str] = None
conversation_history: List[ChatMessage] = Field(..., description="Updated conversation history")
class ChatHandler:
"""Handler for natural language chat interface"""
def __init__(self):
"""Initialize the chat handler"""
self.available_tools = {
"search_products": "Search for products in the PrestaShop catalog",
"get_product_details": "Get detailed information about a specific product",
"get_product_features": "Get features and specifications of a product",
"get_product_images": "Get images for a specific product"
}
def _should_use_mcp_tool(self, message: str) -> bool:
"""Let the LLM decide if MCP tools are needed for this message"""
message_lower = message.lower()
# Only use MCP tools for clear PrestaShop/e-commerce operations
ecommerce_keywords = [
'product', 'products', 'search', 'buy', 'purchase', 'catalog', 'shop', 'store',
'price', 'cost', 'item', 'items', 'features', 'specifications', 'details',
'images', 'photos', 'pictures', 'prestashop', 'inventory', 'stock'
]
# Check if the message contains e-commerce related keywords
return any(keyword in message_lower for keyword in ecommerce_keywords)
async def _llm_select_tool(self, message: str) -> tuple[str, Dict[str, Any]]:
"""Use LLM to intelligently select the appropriate tool and extract parameters"""
# Simple but effective pattern matching that mimics LLM understanding
message_lower = message.lower()
words = message.split()
# Look for product ID in the message
product_id = None
for word in words:
if word.isdigit():
product_id = word
break
# Intent classification based on natural language patterns
# Product details requests - "tell me about product X", "details for product X", "information on product X"
if any(pattern in message_lower for pattern in [
'tell me about product', 'details for product', 'information on product',
'about product', 'product details', 'product information',
'tell me about', 'details for', 'information about', 'info about'
]) and product_id:
return "get_product_details", {"product_id": product_id}
# Features requests - "features of product X", "specifications for X", "what features does product X have"
if any(pattern in message_lower for pattern in [
'features', 'specifications', 'specs', 'attributes', 'characteristics'
]) and product_id:
return "get_product_features", {"product_id": product_id}
# Images requests - "images of product X", "photos of X", "show me pictures of X"
if any(pattern in message_lower for pattern in [
'images', 'photos', 'pictures', 'pics', 'show me'
]) and product_id:
return "get_product_images", {"product_id": product_id}
# If we have a product ID but no clear intent, default to details
if product_id:
return "get_product_details", {"product_id": product_id}
# Search patterns - everything else becomes a search
# Extract meaningful search terms by removing common stop words
stop_words = {'search', 'for', 'find', 'look', 'looking', 'show', 'me', 'get', 'give', 'tell', 'about'}
search_words = [word for word in words if word.lower() not in stop_words]
search_query = ' '.join(search_words) if search_words else message
return "search_products", {"query": search_query}
def _extract_tool_from_message(self, message: str) -> tuple[Optional[str], Dict[str, Any]]:
"""Simple heuristic to determine which tool to use based on user message"""
message_lower = message.lower()
print(f"DEBUG: Processing message: '{message}' (lowercase: '{message_lower}')")
# Search patterns
if any(word in message_lower for word in ['search', 'find', 'look for', 'looking for']):
print("DEBUG: Matched search pattern")
# Extract search query
query_words = []
skip_words = {'search', 'for', 'find', 'look', 'looking', 'products', 'product', 'items', 'item'}
for word in message.split():
if word.lower() not in skip_words:
query_words.append(word)
query = ' '.join(query_words) if query_words else message
return "search_products", {"query": query}
# Product details patterns
if any(word in message_lower for word in ['details', 'information', 'info', 'about']):
print("DEBUG: Matched product details pattern")
# Try to extract product ID
words = message.split()
print(f"DEBUG: Words in message: {words}")
for word in words:
print(f"DEBUG: Checking word '{word}' - is digit: {word.isdigit()}")
if word.isdigit():
print(f"DEBUG: Found product ID: {word}")
return "get_product_details", {"product_id": word}
# Default to product 1 if no ID found
print("DEBUG: No product ID found, defaulting to product 1")
return "get_product_details", {"product_id": "1"}
# Features patterns
if any(word in message_lower for word in ['features', 'specifications', 'specs']):
words = message.split()
for word in words:
if word.isdigit():
return "get_product_features", {"product_id": word}
return "get_product_features", {"product_id": "1"}
# Images patterns
if any(word in message_lower for word in ['images', 'photos', 'pictures', 'pics']):
words = message.split()
for word in words:
if word.isdigit():
return "get_product_images", {"product_id": word}
return "get_product_images", {"product_id": "1"}
# Default to search if we can't determine intent
return "search_products", {"query": message}
def _generate_natural_response(self, message: str, conversation_history: List[ChatMessage]) -> str:
"""Generate natural responses for non-MCP conversations"""
message_lower = message.lower()
# Greetings
if any(greeting in message_lower for greeting in ['hello', 'hi', 'hey', 'good morning', 'good afternoon', 'good evening']):
return "Hello! I'm your PrestaShop assistant. I can help you search for products, get product details, features, and images. How can I assist you today?"
# Farewells
if any(farewell in message_lower for farewell in ['bye', 'goodbye', 'see you', 'farewell']):
return "Goodbye! Feel free to come back anytime if you need help with PrestaShop products!"
# How are you / status
if any(phrase in message_lower for phrase in ['how are you', 'how do you do', 'what\'s up']):
return "I'm doing great, thank you! I'm here and ready to help you with any PrestaShop product inquiries. What would you like to know?"
# Thank you
if any(thanks in message_lower for thanks in ['thank you', 'thanks', 'appreciate']):
return "You're welcome! I'm happy to help with any PrestaShop product questions you might have."
# Help / capabilities
if any(help_word in message_lower for help_word in ['help', 'what can you do', 'capabilities', 'commands']):
return """I can assist you with PrestaShop products in several ways:
β€’ **Search products**: Just ask me to search for anything (e.g., "search for laptops")
β€’ **Product details**: Ask for information about specific products (e.g., "details of product 123")
β€’ **Product features**: Get specifications and features (e.g., "features of product 123")
β€’ **Product images**: View product photos (e.g., "images of product 123")
I can also have normal conversations! Feel free to ask me anything."""
# Who are you
if any(phrase in message_lower for phrase in ['who are you', 'what are you', 'about yourself']):
return "I'm an AI assistant specialized in helping with PrestaShop e-commerce operations. I can search products, get details, features, and images from the PrestaShop catalog. I'm also happy to chat!"
# Default conversational response
return f"I understand you said: '{message}'. While I'm primarily designed to help with PrestaShop products, I'm happy to chat! Is there anything specific about products or the store you'd like to know about?"
async def process_message(self, chat_input: ChatInput) -> ChatOutput:
"""Process a chat message using LLM-driven tool selection"""
global mcp_client
# First, check if this is a simple conversational message
if not self._should_use_mcp_tool(chat_input.message):
# Handle conversational responses without MCP
response = self._generate_natural_response(chat_input.message, chat_input.conversation_history)
tool_used = None
else:
# Let the LLM intelligently choose which tool to use
if not mcp_client:
response = "I'm sorry, but I'm currently unable to access the PrestaShop system. The service appears to be unavailable. Please try again later."
tool_used = None
else:
try:
# Use LLM to interpret the message and choose appropriate tool/arguments
tool_name, arguments = await self._llm_select_tool(chat_input.message)
# Add client token for authentication
arguments["client_token"] = chat_input.client_token or "test8f4e2b9c7a1d6e3f5a8b2c4d7e9f1a3b6c8e"
# Call the MCP tool
result = await mcp_client.call_tool(tool_name, arguments)
# Format the response in a conversational way
if tool_name == "search_products":
response = f"I searched for '{arguments['query']}' in the product catalog. {result}"
elif tool_name == "get_product_details":
response = f"Here are the details for product {arguments['product_id']}: {result}"
elif tool_name == "get_product_features":
response = f"Here are the features for product {arguments['product_id']}: {result}"
elif tool_name == "get_product_images":
response = f"Here are the images for product {arguments['product_id']}: {result}"
else:
response = f"I processed your request using {tool_name}: {result}"
tool_used = tool_name
except Exception as e:
error_msg = str(e).lower()
if any(keyword in error_msg for keyword in ['connection', 'timeout', 'unreachable', 'refused']):
response = f"I'm having trouble connecting to the PrestaShop system right now. This might be temporary - please try again in a moment."
else:
response = f"I encountered an issue while processing your request: {str(e)}. Could you please try rephrasing your question?"
tool_used = tool_name
# Update conversation history
updated_history = chat_input.conversation_history + [
ChatMessage(role="user", content=chat_input.message),
ChatMessage(role="assistant", content=response)
]
return ChatOutput(
response=response,
tool_used=tool_used,
conversation_history=updated_history
)
class EndpointHandler:
"""Simple endpoint handler for MCP tool calls"""
def __init__(self):
"""Initialize the handler - lightweight setup only"""
pass
async def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Main inference handler - calls MCP tools and returns results
"""
global mcp_client
try:
# Validate input
payload = InferenceInput(**data)
except Exception as e:
return {
"ok": False,
"tool": "unknown",
"result": f"I'm sorry, but I couldn't understand your request format. Please make sure you're providing a valid 'tool' name and 'arguments' object. The error was: {str(e)}",
"error": f"Invalid input: {e}"
}
# Initialize MCP client if not available (fallback)
if not mcp_client:
try:
mcp_client = await get_mcp_client()
except Exception as e:
return {
"ok": False,
"tool": payload.tool,
"result": f"I apologize, but I'm currently unable to access the PrestaShop MCP tools required for the '{payload.tool}' operation. The MCP server appears to be unavailable at the moment. Please try again later or contact support if this issue persists.",
"error": f"MCP server unavailable: {e}"
}
# Add default PrestaShop URL if not provided
if "api_base_url" not in payload.arguments:
payload.arguments["api_base_url"] = DEFAULT_PRESTASHOP_URL
try:
# Call the MCP tool
result = await mcp_client.call_tool(payload.tool, payload.arguments)
return {"ok": True, "tool": payload.tool, "result": result}
except Exception as e:
# Check if it's an MCP connection error
error_msg = str(e).lower()
if any(keyword in error_msg for keyword in ['connection', 'timeout', 'unreachable', 'refused']):
return {
"ok": False,
"tool": payload.tool,
"result": f"I'm sorry, but I'm experiencing connection issues with the PrestaShop service while trying to execute the '{payload.tool}' operation. This could be due to network connectivity or the PrestaShop API being temporarily unavailable. Please try again in a moment.",
"error": str(e)
}
else:
return {
"ok": False,
"tool": payload.tool,
"result": f"I encountered an error while processing your '{payload.tool}' request: {str(e)}. Please check your input parameters and try again.",
"error": str(e)
}
# Global handler instances
handler = EndpointHandler()
chat_handler = ChatHandler()
# Chat Interface Routes (Main UI)
@app.post("/", response_model=ChatOutput)
async def chat_interface(chat_input: ChatInput) -> ChatOutput:
"""
Main chat interface - natural language interaction with PrestaShop MCP tools
"""
return await chat_handler.process_message(chat_input)
@app.get("/")
async def chat_ui():
"""Serve a simple chat UI for testing"""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>PrestaShop MCP Chat Interface</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.chat-container { border: 1px solid #ddd; height: 400px; overflow-y: auto; padding: 10px; margin-bottom: 10px; }
.message { margin: 10px 0; padding: 10px; border-radius: 5px; }
.user { background-color: #e3f2fd; text-align: right; }
.assistant { background-color: #f5f5f5; }
.input-container { display: flex; gap: 10px; }
#messageInput { flex: 1; padding: 10px; }
button { padding: 10px 20px; }
</style>
</head>
<body>
<h1>🧠 PrestaShop MCP Chat Interface</h1>
<p>Ask me about products! Try: "Search for laptops" or "Show me details of product 1"</p>
<div id="chatContainer" class="chat-container"></div>
<div class="input-container">
<input type="text" id="messageInput" placeholder="Ask about products..." />
<button onclick="sendMessage()">Send</button>
</div>
<script>
let conversationHistory = [];
async function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message) return;
// Add user message to chat
addMessageToChat('user', message);
input.value = '';
try {
const response = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: message,
conversation_history: conversationHistory
})
});
const data = await response.json();
addMessageToChat('assistant', data.response);
conversationHistory = data.conversation_history;
} catch (error) {
addMessageToChat('assistant', 'Sorry, I encountered an error: ' + error.message);
}
}
function addMessageToChat(role, content) {
const container = document.getElementById('chatContainer');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
messageDiv.innerHTML = `<strong>${role === 'user' ? 'You' : 'Assistant'}:</strong> ${content}`;
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
// Send message on Enter key
document.getElementById('messageInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
</script>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@app.post("/chat", response_model=ChatOutput)
async def chat_endpoint(chat_input: ChatInput) -> ChatOutput:
"""
Chat endpoint for the web interface - uses LLM for natural tool selection
"""
global mcp_client, llm_client
logger.info(f"πŸ’¬ Direct chat request: {chat_input.message[:100]}...")
try:
# Use LLM to determine which tool to call (if any)
if llm_client:
tool_name, arguments = await llm_client.parse_message_for_tool_call(chat_input.message)
else:
# Fallback to simple detection if LLM not available
tool_name, arguments = None, {}
if tool_name and mcp_client:
# Add client token for authentication
arguments["client_token"] = chat_input.client_token or "test8f4e2b9c7a1d6e3f5a8b2c4d7e9f1a3b6c8e"
# Call the MCP tool
logger.info(f"πŸ”§ Calling MCP tool: {tool_name} with {arguments}")
result = await mcp_client.call_tool(tool_name, arguments)
# Format the response naturally
if tool_name == "search_products":
response = f"I searched for '{arguments['query']}' and found: {result}"
elif tool_name == "get_product_details":
response = f"Here are the details for product {arguments['product_id']}: {result}"
elif tool_name == "get_product_features":
response = f"Here are the features for product {arguments['product_id']}: {result}"
elif tool_name == "get_product_images":
response = f"Here are the images for product {arguments['product_id']}: {result}"
else:
response = result
tool_used = tool_name
else:
# Handle as conversational message
response = f"I understand you said: '{chat_input.message}'. I'm designed to help with PrestaShop products. You can ask me to search for products, get details about specific products (like 'tell me about product 12'), or ask for features and images."
tool_used = None
# Update conversation history
updated_history = list(chat_input.conversation_history)
updated_history.append(ChatMessage(role="user", content=chat_input.message))
updated_history.append(ChatMessage(role="assistant", content=response))
return ChatOutput(
response=response,
conversation_history=updated_history,
tool_used=tool_used
)
except Exception as e:
logger.error(f"❌ Chat endpoint error: {e}")
return ChatOutput(
response=f"Sorry, I encountered an error: {str(e)}",
conversation_history=list(chat_input.conversation_history)
)
# Helper function to identify tool needed based on message
logger.error(f"πŸ”¬ Full traceback: {traceback.format_exc()}")
return ChatOutput(
response=f"Sorry, I encountered a technical error: {str(e)}",
conversation_history=list(chat_input.conversation_history)
)
# API Routes (Programmatic Access)
@app.post("/api/inference", response_model=InferenceOutput)
async def api_inference(data: Dict[str, Any]) -> InferenceOutput:
"""
API inference endpoint - handles both MCP tool calls and prompt-based requests
Accepts:
- Tool-based: {"tool": "tool_name", "arguments": {...}}
- Prompt-based: {"prompt": "user question", "session_id": "...", "context": {...}}
"""
request_start = time.time()
request_type = "prompt" if "prompt" in data else "tool"
logger.info(f"🎯 API Inference Request - Type: {request_type}")
logger.info(f"πŸ“Š Request data keys: {list(data.keys())}")
logger.info(f"πŸ“¦ Full request data: {json.dumps(data, indent=2)}")
# Check if this is a prompt-based request
if "prompt" in data:
logger.info(f"πŸ€– Processing prompt-based request")
prompt = data.get('prompt', '')
session_id = data.get('session_id', '')
context = data.get('context', {})
client_token = data.get('client_token', '')
logger.info(f"πŸ“ Prompt length: {len(prompt)} characters")
logger.info(f"πŸ”‘ Session ID: {session_id}")
logger.info(f"πŸͺ Client token: {client_token[:8]}***{client_token[-4:] if len(client_token) > 12 else 'invalid'}")
logger.info(f"πŸ“‹ Context keys: {list(context.keys())}")
logger.info(f"πŸ“– Prompt preview: {prompt[:200]}{'...' if len(prompt) > 200 else ''}")
# TODO: Look up shop information from client_token in database
# For now, we'll proceed with the request but log the token for identification
if client_token:
logger.info(f"πŸͺ Authenticated request from client token: {client_token[:8]}***")
else:
logger.warning(f"⚠️ Unauthenticated request - no client token provided")
try:
logger.info(f"πŸ”§ Creating ChatInput object")
chat_input = ChatInput(
message=prompt,
conversation_history=context.get('history', []),
session_id=session_id,
client_token=client_token
)
logger.info(f"βœ… ChatInput created successfully")
logger.info(f"πŸš€ Calling chat_handler.process_message")
chat_result = await chat_handler.process_message(chat_input)
logger.info(f"βœ… Chat processing completed")
response_time = time.time() - request_start
logger.info(f"πŸ“€ Prompt request completed in {response_time:.3f}s")
logger.info(f"πŸ“ Response length: {len(chat_result.response)} characters")
logger.info(f"πŸ› οΈ Tool used: {getattr(chat_result, 'tool_used', 'None')}")
result = InferenceOutput(
ok=True,
response=chat_result.response,
tool=chat_result.tool_used if hasattr(chat_result, 'tool_used') else None
)
logger.info(f"βœ… Returning successful prompt response")
return result
except Exception as e:
response_time = time.time() - request_start
logger.error(f"πŸ’₯ Prompt processing error after {response_time:.3f}s: {e}")
logger.error(f"πŸ” Exception type: {type(e).__name__}")
logger.error(f"πŸ“‹ Exception details: {str(e)}")
import traceback
logger.error(f"πŸ”¬ Full traceback: {traceback.format_exc()}")
result = InferenceOutput(
ok=False,
error=f"Failed to process prompt: {str(e)}"
)
logger.info(f"❌ Returning error response for prompt")
return result
else:
# Handle traditional MCP tool request
tool_name = data.get('tool', 'unknown')
arguments = data.get('arguments', {})
logger.info(f"πŸ”§ Processing tool-based request")
logger.info(f"πŸ› οΈ Tool name: {tool_name}")
logger.info(f"οΏ½ Arguments: {json.dumps(arguments, indent=2)}")
try:
logger.info(f"πŸš€ Calling handler for tool request")
result = await handler(data)
response_time = time.time() - request_start
logger.info(f"βœ… Tool request completed in {response_time:.3f}s")
logger.info(f"πŸ“€ Tool result keys: {list(result.keys()) if isinstance(result, dict) else 'not dict'}")
logger.info(f"πŸ“Š Tool result: {json.dumps(result, indent=2) if isinstance(result, dict) else str(result)}")
final_result = InferenceOutput(**result)
logger.info(f"βœ… Returning successful tool response")
return final_result
except Exception as e:
response_time = time.time() - request_start
logger.error(f"πŸ’₯ Tool processing error after {response_time:.3f}s: {e}")
logger.error(f"πŸ” Exception type: {type(e).__name__}")
logger.error(f"πŸ“‹ Exception details: {str(e)}")
import traceback
logger.error(f"πŸ”¬ Full traceback: {traceback.format_exc()}")
result = InferenceOutput(
ok=False,
error=f"Failed to process tool request: {str(e)}"
)
logger.info(f"❌ Returning error response for tool")
return result
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""Health check endpoint"""
logger.info("πŸ’“ Health check requested")
global mcp_client
mcp_available = mcp_client is not None
available_tools = ["get_product_details", "get_product_features", "get_product_images", "search_products"]
logger.info(f"πŸ” MCP client available: {mcp_available}")
logger.info(f"πŸ› οΈ Available tools: {available_tools}")
health_response = HealthResponse(
status="healthy",
mcp_available=mcp_available,
available_tools=available_tools
)
logger.info(f"βœ… Health check completed - Status: healthy")
return health_response
@app.get("/tools")
async def list_tools():
"""List available MCP tools"""
tools = [
{
"name": "get_product_details",
"description": "Get detailed information about a PrestaShop product",
"required_args": ["product_id", "api_base_url"],
"optional_args": ["lang_id"]
},
{
"name": "get_product_features",
"description": "Get product features and specifications",
"required_args": ["product_id", "api_base_url"],
"optional_args": []
},
{
"name": "get_product_images",
"description": "Get product images and media",
"required_args": ["product_id", "api_base_url"],
"optional_args": []
},
{
"name": "search_products",
"description": "Search for products in PrestaShop catalog",
"required_args": ["query", "api_base_url"],
"optional_args": ["limit", "category_id"]
}
]
return {
"available_tools": tools,
"default_prestashop_url": DEFAULT_PRESTASHOP_URL,
"mcp_status": "integrated" if mcp_client else "unavailable"
}
@app.get("/")
async def root():
"""Root endpoint - API information"""
return {
"message": "PrestaShop MCP Inference Endpoint",
"version": "1.0.0",
"status": "ready",
"description": "Simple inference endpoint for PrestaShop MCP tools",
"endpoints": ["/", "/health", "/tools"],
"usage": {
"example": {
"tool": "get_product_details",
"arguments": {
"product_id": 123,
"api_base_url": "https://your-shop.com"
}
},
"curl_example": 'curl -X POST / -H "Content-Type: application/json" -d \'{"tool": "get_product_details", "arguments": {"product_id": 123}}\''
}
}
if __name__ == "__main__":
port = int(os.getenv("PORT", "7860")) # HF Spaces default port
logger.info("πŸš€ Starting HF Inference Point server")
logger.info(f"🌐 Host: 0.0.0.0")
logger.info(f"πŸ”Œ Port: {port}")
logger.info(f"🏠 Environment: {os.getenv('ENVIRONMENT', 'development')}")
logger.info(f"πŸ“ Log level: {logging.getLevelName(logger.level)}")
logger.info(f"πŸ“ Working directory: {os.getcwd()}")
logger.info(f"🐍 Python version: {sys.version}")
# Configure uvicorn logging to be more verbose
uvicorn_config = uvicorn.Config(
app=app,
host="0.0.0.0",
port=port,
log_level="debug",
access_log=True
)
logger.info("🎯 Starting uvicorn server with debug logging")
server = uvicorn.Server(uvicorn_config)
server.run()