Files
2026-07-13 12:24:33 +08:00

148 lines
4.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Standard
from typing import Any, Dict, Optional, Union
import json
# Third Party
from fastapi import APIRouter
from starlette.requests import Request
from starlette.responses import JSONResponse, PlainTextResponse
import torch
# First Party
from lmcache.logging import init_logger
from lmcache.v1.config import _CONFIG_DEFINITIONS, LMCacheEngineConfig
from lmcache.v1.config_base import validate_and_set_config_value
logger = init_logger(__name__)
router = APIRouter()
def _is_mutable_config(key: str) -> bool:
"""Check if a config key is mutable at runtime.
NOTE: This is currently experimental. All configs default to
mutable=True unless explicitly set to False. Once the feature
is stabilized, the default will be changed to mutable=False.
"""
return _CONFIG_DEFINITIONS.get(key, {}).get("mutable", True)
def _get_config_dict(
config: LMCacheEngineConfig,
keys: Union[list[str], Optional[dict[str, dict[str, Any]]]] = None,
) -> Dict[str, Any]:
"""Get the config dict filtered by keys"""
if keys is None:
keys = _CONFIG_DEFINITIONS
return {key: getattr(config, key) for key in keys if hasattr(config, key)}
@router.get("/conf")
async def get_config(request: Request, names: Optional[str] = None):
config = request.app.state.lmcache_adapter.config
# Parse query parameter names (comma-separated list of config names)
keys = names.split(",") if names else None
config_dict = _get_config_dict(config, keys)
return PlainTextResponse(
content=json.dumps(config_dict, indent=2), media_type="text/plain"
)
@router.get("/meta")
async def get_metadata(request: Request, names: Optional[str] = None):
"""
Get metadata of the cache engine
"""
metadata = request.app.state.lmcache_adapter.lmcache_engine_metadata
if names:
attr_list = names.split(",")
metadata_dict = {}
for attr in attr_list:
if (
hasattr(metadata, attr)
and not attr.startswith("__")
and not callable(getattr(metadata, attr))
):
value = getattr(metadata, attr)
if isinstance(value, (torch.dtype, torch.Size)):
value = str(value)
metadata_dict[attr] = value
else:
metadata_dict = {
attr: getattr(metadata, attr)
for attr in dir(metadata)
if not attr.startswith("__") and not callable(getattr(metadata, attr))
}
for key, value in metadata_dict.items():
if isinstance(value, (torch.dtype, torch.Size)):
metadata_dict[key] = str(value)
return PlainTextResponse(
content=json.dumps(metadata_dict, indent=2, default=str),
media_type="text/plain",
)
@router.post("/conf")
async def set_config(request: Request):
"""Set config values dynamically.
NOTE: Currently experimental — all configs are mutable at runtime
by default unless explicitly set "mutable": False in
_CONFIG_DEFINITIONS. The default will change to immutable once
the feature is stabilized.
Request body should be JSON with config name-value pairs:
{"min_retrieve_tokens": 512, "save_decode_cache": true}
Returns:
PlainTextResponse: JSON response with updated config values
"""
config = request.app.state.lmcache_adapter.config
try:
body = await request.json()
except Exception as e:
return JSONResponse(
content={"error": "Invalid JSON body", "message": str(e)},
status_code=400,
)
if not isinstance(body, dict):
return JSONResponse(
content={"error": "Request body must be a JSON object"},
status_code=400,
)
updated: Dict[str, Any] = {}
errors: Dict[str, str] = {}
for key, value in body.items():
if not _is_mutable_config(key):
errors[key] = "Config is not mutable at runtime"
continue
if key not in _CONFIG_DEFINITIONS:
errors[key] = "Unknown config"
continue
# Use shared validation and setting function
if validate_and_set_config_value(config, key, value):
updated[key] = getattr(config, key)
logger.info("Config %s updated to %s", key, updated[key])
else:
errors[key] = "Failed to set config value"
result: Dict[str, Any] = {"updated": updated}
if errors:
result["errors"] = errors
status_code = 400 if errors and not updated else 200
return JSONResponse(
content=result,
status_code=status_code,
)