| 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 |
|
|
| |
| from llm_client import LLMToolCaller |
| from init_ollama import init_ollama |
|
|
| |
| 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) |
|
|
| |
| request_logger = logging.getLogger('requests') |
| request_logger.setLevel(logging.DEBUG) |
|
|
| |
| from prestashop_mcp.client import get_mcp_client, close_mcp_client |
|
|
| |
| 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}") |
| |
| |
| try: |
| |
| 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 |
| |
| |
| 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") |
|
|
| |
| app = FastAPI( |
| title="PrestaShop MCP Inference Endpoint", |
| description="Simple inference endpoint with integrated PrestaShop MCP tools", |
| version="1.0.0", |
| lifespan=lifespan |
| ) |
|
|
| |
| @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}" |
| |
| |
| 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'}") |
| |
| |
| 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]}...") |
| |
| 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}") |
| |
| |
| 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 |
|
|
| |
| DEFAULT_PRESTASHOP_URL = os.getenv("PRESTASHOP_API_URL", "https://hub.mcp.integration.ambris.com") |
| HUB_SERVER_URL = "https://hub.mcp.integration.ambris.com" |
|
|
| |
| 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 |
|
|
| class HealthResponse(BaseModel): |
| status: str |
| mcp_available: bool |
| available_tools: List[str] |
|
|
| |
| 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() |
| |
| |
| ecommerce_keywords = [ |
| 'product', 'products', 'search', 'buy', 'purchase', 'catalog', 'shop', 'store', |
| 'price', 'cost', 'item', 'items', 'features', 'specifications', 'details', |
| 'images', 'photos', 'pictures', 'prestashop', 'inventory', 'stock' |
| ] |
| |
| |
| 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""" |
| |
| |
| message_lower = message.lower() |
| words = message.split() |
| |
| |
| product_id = None |
| for word in words: |
| if word.isdigit(): |
| product_id = word |
| break |
| |
| |
| |
| |
| 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} |
| |
| |
| 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} |
| |
| |
| 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 product_id: |
| return "get_product_details", {"product_id": product_id} |
| |
| |
| |
| 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}')") |
| |
| |
| if any(word in message_lower for word in ['search', 'find', 'look for', 'looking for']): |
| print("DEBUG: Matched search pattern") |
| |
| 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} |
| |
| |
| if any(word in message_lower for word in ['details', 'information', 'info', 'about']): |
| print("DEBUG: Matched product details pattern") |
| |
| 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} |
| |
| print("DEBUG: No product ID found, defaulting to product 1") |
| return "get_product_details", {"product_id": "1"} |
| |
| |
| 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"} |
| |
| |
| 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"} |
| |
| |
| 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() |
| |
| |
| 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?" |
| |
| |
| 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!" |
| |
| |
| 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?" |
| |
| |
| 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." |
| |
| |
| 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.""" |
| |
| |
| 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!" |
| |
| |
| 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 |
| |
| |
| if not self._should_use_mcp_tool(chat_input.message): |
| |
| response = self._generate_natural_response(chat_input.message, chat_input.conversation_history) |
| tool_used = None |
| else: |
| |
| 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: |
| |
| tool_name, arguments = await self._llm_select_tool(chat_input.message) |
| |
| |
| arguments["client_token"] = chat_input.client_token or "test8f4e2b9c7a1d6e3f5a8b2c4d7e9f1a3b6c8e" |
| |
| |
| result = await mcp_client.call_tool(tool_name, arguments) |
| |
| |
| 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 |
| |
| |
| 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: |
| |
| 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}" |
| } |
| |
| |
| 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}" |
| } |
| |
| |
| if "api_base_url" not in payload.arguments: |
| payload.arguments["api_base_url"] = DEFAULT_PRESTASHOP_URL |
| |
| try: |
| |
| result = await mcp_client.call_tool(payload.tool, payload.arguments) |
| return {"ok": True, "tool": payload.tool, "result": result} |
| except Exception as e: |
| |
| 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) |
| } |
|
|
| |
| handler = EndpointHandler() |
| chat_handler = ChatHandler() |
|
|
| |
| @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: |
| |
| if llm_client: |
| tool_name, arguments = await llm_client.parse_message_for_tool_call(chat_input.message) |
| else: |
| |
| tool_name, arguments = None, {} |
| |
| if tool_name and mcp_client: |
| |
| arguments["client_token"] = chat_input.client_token or "test8f4e2b9c7a1d6e3f5a8b2c4d7e9f1a3b6c8e" |
| |
| |
| logger.info(f"π§ Calling MCP tool: {tool_name} with {arguments}") |
| result = await mcp_client.call_tool(tool_name, arguments) |
| |
| |
| 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: |
| |
| 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 |
| |
| |
| 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) |
| ) |
|
|
|
|
| |
| 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) |
| ) |
|
|
| |
| @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)}") |
| |
| |
| 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 ''}") |
| |
| |
| |
| 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: |
| |
| 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")) |
| |
| 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}") |
| |
| |
| 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() |
|
|