Gemini
feat: add detailed logging
01d5a5d
from flask import Blueprint, jsonify, request
from http import HTTPStatus
from typing import Dict, Any
from lpm_kernel.api.dto.user_llm_config_dto import UpdateUserLLMConfigDTO
from lpm_kernel.api.services.user_llm_config_service import UserLLMConfigService
from lpm_kernel.api.common.responses import APIResponse
from lpm_kernel.common.logging import logger
user_llm_config_bp = Blueprint("user_llm_config", __name__, url_prefix="/api/user-llm-configs")
user_llm_config_service = UserLLMConfigService()
# OpenAI configuration constants
OPENAI_ENDPOINT = "https://api.openai.com/v1"
def validate_llm_config(data: Dict[Any, Any]) -> Dict[str, str]:
"""Validate LLM configuration based on provider type
Args:
data: Configuration data
Returns:
Dictionary with error messages if validation fails, empty dict if validation passes
"""
errors = {}
provider_type = data.get('provider_type')
if provider_type == 'openai':
# For OpenAI, key is required
if not data.get('key'):
errors['key'] = 'API key is required for OpenAI provider'
elif provider_type:
# For custom providers, all endpoint and key fields are required
required_fields = [
'chat_endpoint', 'chat_api_key', 'chat_model_name',
'embedding_endpoint', 'embedding_api_key', 'embedding_model_name'
]
for field in required_fields:
if not data.get(field):
errors[field] = f'{field} is required for custom provider'
else:
errors['provider_type'] = 'provider_type is required'
return errors
def validate_thinking_model(data: Dict[Any, Any]) -> Dict[str, str]:
"""Validate thinking model configuration
Args:
data: Configuration data
Returns:
Dictionary with error messages if validation fails, empty dict if validation passes
"""
errors = {}
# Validate required fields
if not data.get('thinking_model_name'):
errors['thinking_model_name'] = 'Thinking model name is required'
if not data.get('thinking_endpoint'):
errors['thinking_endpoint'] = 'Thinking endpoint is required'
return errors
def process_openai_config(data: Dict[Any, Any]) -> Dict[Any, Any]:
"""Process OpenAI configuration, using simplified config if provider type is OpenAI"""
if data.get('provider_type') == 'openai' and data.get('key'):
# Use the unified key to fill chat and embedding api_keys
data['chat_api_key'] = data['key']
data['chat_model_name'] ='gpt-4o-mini'
data['embedding_api_key'] = data['key']
data['embedding_model_name'] = 'text-embedding-ada-002'
data['chat_endpoint'] = OPENAI_ENDPOINT
data['embedding_endpoint'] = OPENAI_ENDPOINT
return data
@user_llm_config_bp.route("", methods=["GET"])
def get_config():
"""Get LLM configuration"""
try:
config = user_llm_config_service.get_available_llm() # Default configuration ID is 1
if not config:
# Return null instead of 404 when no configuration is found
return jsonify(
APIResponse.success(
data=None,
message="No LLM configuration found"
)
), HTTPStatus.OK
return jsonify(
APIResponse.success(
data=config.dict(),
message="Successfully retrieved LLM configuration"
)
), HTTPStatus.OK
except Exception as e:
logger.error(f"Failed to retrieve configuration: {str(e)}", exc_info=True)
return jsonify(
APIResponse.error(f"Failed to retrieve configuration: {str(e)}")
), HTTPStatus.INTERNAL_SERVER_ERROR
@user_llm_config_bp.route("", methods=["PUT"])
def update_config():
"""Update LLM configuration"""
try:
# Validate request data
request_data = request.json
validation_errors = validate_llm_config(request_data)
if validation_errors:
error_message = "; ".join([f"{k}: {v}" for k, v in validation_errors.items()])
return jsonify(
APIResponse.error(f"Validation failed: {error_message}")
), HTTPStatus.BAD_REQUEST
# Process request data
processed_data = process_openai_config(request_data)
data = UpdateUserLLMConfigDTO(**processed_data)
config = user_llm_config_service.update_config(1, data) # Default configuration ID is 1
return jsonify(
APIResponse.success(
data=config.dict(),
message="Configuration updated successfully"
)
), HTTPStatus.OK
except Exception as e:
logger.error(f"Failed to update configuration: {str(e)}", exc_info=True)
return jsonify(
APIResponse.error(f"Failed to update configuration: {str(e)}")
), HTTPStatus.INTERNAL_SERVER_ERROR
@user_llm_config_bp.route("/thinking", methods=["PUT"])
def update_thinking_model():
"""Update thinking model configuration"""
try:
# Validate request data
request_data = request.json
validation_errors = validate_thinking_model(request_data)
if validation_errors:
error_message = "; ".join([f"{k}: {v}" for k, v in validation_errors.items()])
return jsonify(
APIResponse.error(f"Validation failed: {error_message}")
), HTTPStatus.BAD_REQUEST
# Create a DTO with only thinking model fields
thinking_data = {}
if 'thinking_model_name' in request_data:
thinking_data['thinking_model_name'] = request_data['thinking_model_name']
if 'thinking_endpoint' in request_data:
thinking_data['thinking_endpoint'] = request_data['thinking_endpoint']
if 'thinking_api_key' in request_data:
thinking_data['thinking_api_key'] = request_data['thinking_api_key']
# Update the configuration
data = UpdateUserLLMConfigDTO(**thinking_data)
config = user_llm_config_service.update_config(1, data) # Default configuration ID is 1
return jsonify(
APIResponse.success(
data=config.dict(),
message="Thinking model configuration updated successfully"
)
), HTTPStatus.OK
except Exception as e:
logger.error(f"Failed to update thinking model configuration: {str(e)}", exc_info=True)
return jsonify(
APIResponse.error(f"Failed to update thinking model configuration: {str(e)}")
), HTTPStatus.INTERNAL_SERVER_ERROR
@user_llm_config_bp.route("/key", methods=["DELETE"])
def delete_key():
"""Delete API key from LLM configuration"""
try:
# use default configuration ID (1)
config = user_llm_config_service.delete_key()
if not config:
return jsonify(
APIResponse.error("No LLM configuration found")
), HTTPStatus.NOT_FOUND
return jsonify(
APIResponse.success(
data=config.dict(),
message="API key deleted successfully"
)
), HTTPStatus.OK
except Exception as e:
logger.error(f"Failed to delete API key: {str(e)}", exc_info=True)
return jsonify(
APIResponse.error(f"Failed to delete API key: {str(e)}")
), HTTPStatus.INTERNAL_SERVER_ERROR