Coverage for mcpgateway / routers / llm_proxy_router.py: 100%
46 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
1# -*- coding: utf-8 -*-
2"""Location: ./mcpgateway/routers/llm_proxy_router.py
3Copyright 2025
4SPDX-License-Identifier: Apache-2.0
6LLM Proxy Router.
7This module provides OpenAI-compatible API endpoints for the internal
8LLM proxy service. It routes requests to configured LLM providers.
9"""
11# Third-Party
12from fastapi import APIRouter, Depends, HTTPException, status
13from fastapi.responses import StreamingResponse
14from sqlalchemy.orm import Session
16# First-Party
17from mcpgateway.auth import get_current_user
18from mcpgateway.config import settings
19from mcpgateway.db import get_db
20from mcpgateway.llm_schemas import ChatCompletionRequest, ChatCompletionResponse
21from mcpgateway.services.llm_provider_service import (
22 LLMModelNotFoundError,
23 LLMProviderNotFoundError,
24)
25from mcpgateway.services.llm_proxy_service import (
26 LLMProxyAuthError,
27 LLMProxyRequestError,
28 LLMProxyService,
29)
30from mcpgateway.services.logging_service import LoggingService
32# Initialize logging
33logging_service = LoggingService()
34logger = logging_service.get_logger(__name__)
36# Create router
37llm_proxy_router = APIRouter()
39# Initialize service
40llm_proxy_service = LLMProxyService()
43@llm_proxy_router.post(
44 "/chat/completions",
45 response_model=ChatCompletionResponse,
46 summary="Chat Completions",
47 description="Create a chat completion using configured LLM providers. OpenAI-compatible API.",
48 responses={
49 200: {"description": "Chat completion response"},
50 400: {"description": "Invalid request"},
51 401: {"description": "Authentication required"},
52 404: {"description": "Model not found"},
53 500: {"description": "Provider error"},
54 },
55)
56async def chat_completions(
57 request: ChatCompletionRequest,
58 db: Session = Depends(get_db),
59 current_user: dict = Depends(get_current_user),
60):
61 """Create a chat completion.
63 This endpoint is compatible with the OpenAI Chat Completions API.
64 It routes requests to configured LLM providers based on the model ID.
66 Args:
67 request: Chat completion request (OpenAI-compatible).
68 db: Database session.
69 current_user: Authenticated user.
71 Returns:
72 ChatCompletionResponse or StreamingResponse for streaming requests.
74 Raises:
75 HTTPException: If model not found, streaming disabled, or provider error.
76 """
77 # Check if streaming is enabled
78 if request.stream and not settings.llm_streaming_enabled:
79 raise HTTPException(
80 status_code=status.HTTP_400_BAD_REQUEST,
81 detail="Streaming is disabled in gateway configuration",
82 )
84 try:
85 if request.stream:
86 # Return streaming response
87 return StreamingResponse(
88 llm_proxy_service.chat_completion_stream(db, request),
89 media_type="text/event-stream",
90 headers={
91 "Cache-Control": "no-cache",
92 "Connection": "keep-alive",
93 "X-Accel-Buffering": "no",
94 },
95 )
96 else:
97 # Return regular response
98 return await llm_proxy_service.chat_completion(db, request)
100 except LLMModelNotFoundError as e:
101 logger.warning(f"Model not found: {request.model}")
102 raise HTTPException(
103 status_code=status.HTTP_404_NOT_FOUND,
104 detail=str(e),
105 )
106 except LLMProviderNotFoundError as e:
107 logger.warning(f"Provider not found for model: {request.model}")
108 raise HTTPException(
109 status_code=status.HTTP_404_NOT_FOUND,
110 detail=str(e),
111 )
112 except LLMProxyAuthError as e:
113 logger.error(f"Authentication error: {e}")
114 raise HTTPException(
115 status_code=status.HTTP_401_UNAUTHORIZED,
116 detail=str(e),
117 )
118 except LLMProxyRequestError as e:
119 logger.error(f"Proxy request error: {e}")
120 raise HTTPException(
121 status_code=status.HTTP_502_BAD_GATEWAY,
122 detail=str(e),
123 )
124 except Exception as e:
125 logger.error(f"Unexpected error in chat completion: {e}")
126 raise HTTPException(
127 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
128 detail=f"Internal error: {str(e)}",
129 )
132@llm_proxy_router.get(
133 "/models",
134 summary="List Models",
135 description="List available models from configured providers. OpenAI-compatible API.",
136)
137async def list_models(
138 db: Session = Depends(get_db),
139 current_user: dict = Depends(get_current_user),
140):
141 """List available models.
143 Returns a list of available models in OpenAI-compatible format.
145 Args:
146 db: Database session.
147 current_user: Authenticated user.
149 Returns:
150 List of available models.
151 """
152 # First-Party
153 from mcpgateway.services.llm_provider_service import LLMProviderService
155 provider_service = LLMProviderService()
156 models = provider_service.get_gateway_models(db)
158 # Format as OpenAI-compatible response
159 model_list = []
160 for model in models:
161 model_list.append(
162 {
163 "id": model.model_id,
164 "object": "model",
165 "created": 0,
166 "owned_by": model.provider_name,
167 }
168 )
170 return {
171 "object": "list",
172 "data": model_list,
173 }