Coverage for mcpgateway / routers / llm_proxy_router.py: 100%
48 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 03:05 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 03:05 +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.config import settings
18from mcpgateway.db import get_db
19from mcpgateway.llm_schemas import ChatCompletionRequest, ChatCompletionResponse
20from mcpgateway.middleware.rbac import get_current_user_with_permissions, require_permission
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)
56@require_permission("llm.invoke")
57async def chat_completions(
58 request: ChatCompletionRequest,
59 db: Session = Depends(get_db),
60 current_user: dict = Depends(get_current_user_with_permissions),
61):
62 """Create a chat completion.
64 This endpoint is compatible with the OpenAI Chat Completions API.
65 It routes requests to configured LLM providers based on the model ID.
67 Args:
68 request: Chat completion request (OpenAI-compatible).
69 db: Database session.
70 current_user: Authenticated user.
72 Returns:
73 ChatCompletionResponse or StreamingResponse for streaming requests.
75 Raises:
76 HTTPException: If model not found, streaming disabled, or provider error.
77 """
78 # Check if streaming is enabled
79 if request.stream and not settings.llm_streaming_enabled:
80 raise HTTPException(
81 status_code=status.HTTP_400_BAD_REQUEST,
82 detail="Streaming is disabled in gateway configuration",
83 )
85 try:
86 if request.stream:
87 # Return streaming response
88 return StreamingResponse(
89 llm_proxy_service.chat_completion_stream(db, request),
90 media_type="text/event-stream",
91 headers={
92 "Cache-Control": "no-cache",
93 "Connection": "keep-alive",
94 "X-Accel-Buffering": "no",
95 },
96 )
97 else:
98 # Return regular response
99 return await llm_proxy_service.chat_completion(db, request)
101 except LLMModelNotFoundError as e:
102 logger.warning(f"Model not found: {request.model}")
103 raise HTTPException(
104 status_code=status.HTTP_404_NOT_FOUND,
105 detail=str(e),
106 )
107 except LLMProviderNotFoundError as e:
108 logger.warning(f"Provider not found for model: {request.model}")
109 raise HTTPException(
110 status_code=status.HTTP_404_NOT_FOUND,
111 detail=str(e),
112 )
113 except LLMProxyAuthError as e:
114 logger.error(f"Authentication error: {e}")
115 raise HTTPException(
116 status_code=status.HTTP_401_UNAUTHORIZED,
117 detail=str(e),
118 )
119 except LLMProxyRequestError as e:
120 logger.error(f"Proxy request error: {e}")
121 raise HTTPException(
122 status_code=status.HTTP_502_BAD_GATEWAY,
123 detail=str(e),
124 )
125 except Exception as e:
126 logger.error(f"Unexpected error in chat completion: {e}")
127 raise HTTPException(
128 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
129 detail=f"Internal error: {str(e)}",
130 )
133@llm_proxy_router.get(
134 "/models",
135 summary="List Models",
136 description="List available models from configured providers. OpenAI-compatible API.",
137)
138@require_permission("llm.read")
139async def list_models(
140 db: Session = Depends(get_db),
141 current_user: dict = Depends(get_current_user_with_permissions),
142):
143 """List available models.
145 Returns a list of available models in OpenAI-compatible format.
147 Args:
148 db: Database session.
149 current_user: Authenticated user.
151 Returns:
152 List of available models.
153 """
154 # First-Party
155 from mcpgateway.services.llm_provider_service import LLMProviderService
157 provider_service = LLMProviderService()
158 models = provider_service.get_gateway_models(db)
160 # Format as OpenAI-compatible response
161 model_list = []
162 for model in models:
163 model_list.append(
164 {
165 "id": model.model_id,
166 "object": "model",
167 "created": 0,
168 "owned_by": model.provider_name,
169 }
170 )
172 return {
173 "object": "list",
174 "data": model_list,
175 }