147 lines
3.8 KiB
Python
147 lines
3.8 KiB
Python
|
|
"""
|
||
|
|
LLM Configuration Management API.
|
||
|
|
[AC-ASA-14, AC-ASA-15, AC-ASA-16, AC-ASA-17, AC-ASA-18] LLM provider management endpoints.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import logging
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Request
|
||
|
|
|
||
|
|
from app.core.tenant import get_tenant_id
|
||
|
|
from app.services.llm.factory import (
|
||
|
|
LLMConfigManager,
|
||
|
|
LLMProviderFactory,
|
||
|
|
get_llm_config_manager,
|
||
|
|
)
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/admin/llm", tags=["LLM Management"])
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/providers")
|
||
|
|
async def list_providers(request: Request) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
List all available LLM providers.
|
||
|
|
[AC-ASA-15] Returns provider list with configuration schemas.
|
||
|
|
"""
|
||
|
|
tenant_id = get_tenant_id(request)
|
||
|
|
logger.info(f"[AC-ASA-15] Listing LLM providers for tenant={tenant_id}")
|
||
|
|
|
||
|
|
providers = LLMProviderFactory.get_providers()
|
||
|
|
return {
|
||
|
|
"providers": [
|
||
|
|
{
|
||
|
|
"name": p.name,
|
||
|
|
"display_name": p.display_name,
|
||
|
|
"description": p.description,
|
||
|
|
"config_schema": p.config_schema,
|
||
|
|
}
|
||
|
|
for p in providers
|
||
|
|
],
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/config")
|
||
|
|
async def get_config(request: Request) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Get current LLM configuration.
|
||
|
|
[AC-ASA-14] Returns current provider and config.
|
||
|
|
"""
|
||
|
|
tenant_id = get_tenant_id(request)
|
||
|
|
logger.info(f"[AC-ASA-14] Getting LLM config for tenant={tenant_id}")
|
||
|
|
|
||
|
|
manager = get_llm_config_manager()
|
||
|
|
config = manager.get_current_config()
|
||
|
|
|
||
|
|
masked_config = _mask_secrets(config.get("config", {}))
|
||
|
|
|
||
|
|
return {
|
||
|
|
"provider": config["provider"],
|
||
|
|
"config": masked_config,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/config")
|
||
|
|
async def update_config(
|
||
|
|
request: Request,
|
||
|
|
body: dict[str, Any],
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Update LLM configuration.
|
||
|
|
[AC-ASA-16] Updates provider and config with validation.
|
||
|
|
"""
|
||
|
|
tenant_id = get_tenant_id(request)
|
||
|
|
provider = body.get("provider")
|
||
|
|
config = body.get("config", {})
|
||
|
|
|
||
|
|
logger.info(f"[AC-ASA-16] Updating LLM config for tenant={tenant_id}, provider={provider}")
|
||
|
|
|
||
|
|
if not provider:
|
||
|
|
return {
|
||
|
|
"success": False,
|
||
|
|
"message": "Provider is required",
|
||
|
|
}
|
||
|
|
|
||
|
|
try:
|
||
|
|
manager = get_llm_config_manager()
|
||
|
|
await manager.update_config(provider, config)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"message": f"LLM configuration updated to {provider}",
|
||
|
|
}
|
||
|
|
|
||
|
|
except ValueError as e:
|
||
|
|
logger.error(f"[AC-ASA-16] Invalid LLM config: {e}")
|
||
|
|
return {
|
||
|
|
"success": False,
|
||
|
|
"message": str(e),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/test")
|
||
|
|
async def test_connection(
|
||
|
|
request: Request,
|
||
|
|
body: dict[str, Any] | None = None,
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Test LLM connection.
|
||
|
|
[AC-ASA-17, AC-ASA-18] Tests connection and returns response.
|
||
|
|
"""
|
||
|
|
tenant_id = get_tenant_id(request)
|
||
|
|
body = body or {}
|
||
|
|
|
||
|
|
test_prompt = body.get("test_prompt", "你好,请简单介绍一下自己。")
|
||
|
|
provider = body.get("provider")
|
||
|
|
config = body.get("config")
|
||
|
|
|
||
|
|
logger.info(
|
||
|
|
f"[AC-ASA-17] Testing LLM connection for tenant={tenant_id}, "
|
||
|
|
f"provider={provider or 'current'}"
|
||
|
|
)
|
||
|
|
|
||
|
|
manager = get_llm_config_manager()
|
||
|
|
result = await manager.test_connection(
|
||
|
|
test_prompt=test_prompt,
|
||
|
|
provider=provider,
|
||
|
|
config=config,
|
||
|
|
)
|
||
|
|
|
||
|
|
return result
|
||
|
|
|
||
|
|
|
||
|
|
def _mask_secrets(config: dict[str, Any]) -> dict[str, Any]:
|
||
|
|
"""Mask secret fields in config for display."""
|
||
|
|
masked = {}
|
||
|
|
for key, value in config.items():
|
||
|
|
if key in ("api_key", "password", "secret"):
|
||
|
|
if value:
|
||
|
|
masked[key] = f"{str(value)[:4]}***"
|
||
|
|
else:
|
||
|
|
masked[key] = ""
|
||
|
|
else:
|
||
|
|
masked[key] = value
|
||
|
|
return masked
|