| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- import json
- from datetime import datetime
- from typing import Annotated, TypeVar
- from core_domain import (
- ChatCompletionRequestContract,
- ChatCompletionResponseContract,
- ServiceHealth,
- )
- from fastapi import APIRouter, Depends, HTTPException
- from fastapi.responses import StreamingResponse
- from sqlalchemy import text
- from sqlalchemy.orm import Session
- from app.application.services import ModelGatewayApplicationService
- from app.bootstrap.settings import ModelGatewayServiceSettings
- from app.db.session import get_db
- from app.domain.repositories import ModelDefinitionRepository, ModelProviderDefinitionRepository
- from app.infrastructure.provider import ModelProviderClient, ModelProviderClientError
- from app.schemas.model import (
- ApiResponse,
- DeleteData,
- DiscoverModelsData,
- DiscoverModelsRequestDto,
- ModelCreateRequest,
- ModelCreateRequestDto,
- ModelDeleteRequestDto,
- ModelDto,
- ModelProviderCreateRequestDto,
- ModelProviderDeleteRequestDto,
- ModelProviderDto,
- ModelProviderTestData,
- ModelProviderTestRequestDto,
- ModelProviderUpdateRequestDto,
- ModelResponse,
- ModelStatusUpdateRequest,
- ModelTestData,
- ModelTestRequest,
- ModelTestRequestDto,
- ModelTestResponse,
- ModelUpdateRequest,
- ModelUpdateRequestDto,
- PageRequest,
- PageResult,
- )
- router = APIRouter()
- DbSession = Annotated[Session, Depends(get_db)]
- T = TypeVar("T")
- def get_model_gateway_settings() -> ModelGatewayServiceSettings:
- return ModelGatewayServiceSettings()
- def get_model_gateway_application_service(
- db: DbSession,
- settings: Annotated[
- ModelGatewayServiceSettings,
- Depends(get_model_gateway_settings),
- ]) -> ModelGatewayApplicationService:
- return ModelGatewayApplicationService(
- model_repository=ModelDefinitionRepository(db),
- provider_repository=ModelProviderDefinitionRepository(db),
- provider_client=ModelProviderClient(settings=settings),
- settings=settings)
- ModelServiceDep = Annotated[
- ModelGatewayApplicationService,
- Depends(get_model_gateway_application_service),
- ]
- def ok(data: T) -> ApiResponse[T]:
- return ApiResponse[T](
- data=data,
- requestId="",
- serverTime=datetime.utcnow())
- def json_dump(payload: dict[str, object]) -> str:
- return json.dumps(payload, ensure_ascii=False, default=str)
- @router.get("/health", response_model=ServiceHealth)
- def health_check(
- db: DbSession,
- settings: Annotated[
- ModelGatewayServiceSettings,
- Depends(get_model_gateway_settings),
- ]) -> ServiceHealth:
- db.execute(text("SELECT 1"))
- provider_status = "configured" if settings.provider_base_url else "missing"
- return ServiceHealth(service="model-gateway-service", status="ok", database=provider_status)
- @router.post("", response_model=ModelResponse)
- def create_model(
- payload: ModelCreateRequest,
- service: ModelServiceDep,
- ) -> ModelResponse:
- try:
- entity = service.create_model(payload)
- except ValueError as exc:
- raise HTTPException(status_code=422, detail=str(exc)) from exc
- return ModelResponse.from_entity(entity)
- @router.get("", response_model=list[ModelResponse])
- def list_models(
- service: ModelServiceDep,
- ) -> list[ModelResponse]:
- return [ModelResponse.from_entity(item) for item in service.list_models()]
- @router.patch("/{model_id}", response_model=ModelResponse)
- def update_model(
- model_id: str,
- payload: ModelUpdateRequest,
- service: ModelServiceDep,
- ) -> ModelResponse:
- try:
- entity = service.update_model(model_id=model_id, payload=payload)
- except ValueError as exc:
- raise HTTPException(status_code=422, detail=str(exc)) from exc
- if entity is None:
- raise HTTPException(status_code=404, detail=f"model not found: {model_id}")
- return ModelResponse.from_entity(entity)
- @router.patch("/{model_id}/status", response_model=ModelResponse)
- def update_model_status(
- model_id: str,
- payload: ModelStatusUpdateRequest,
- service: ModelServiceDep,
- ) -> ModelResponse:
- entity = service.update_model_status(model_id=model_id, payload=payload)
- if entity is None:
- raise HTTPException(status_code=404, detail=f"model not found: {model_id}")
- return ModelResponse.from_entity(entity)
- @router.delete("/{model_id}", status_code=204)
- def delete_model(
- model_id: str,
- service: ModelServiceDep,
- ) -> None:
- if not service.delete_model(model_id):
- raise HTTPException(status_code=404, detail=f"model not found: {model_id}")
- @router.post("/{model_id}/test", response_model=ModelTestResponse)
- def test_model(
- model_id: str,
- payload: ModelTestRequest,
- service: ModelServiceDep,
- ) -> ModelTestResponse:
- try:
- result = service.test_model(model_id=model_id, payload=payload)
- except ModelProviderClientError as exc:
- raise HTTPException(status_code=502, detail=str(exc)) from exc
- if result is None:
- raise HTTPException(status_code=404, detail=f"model not found: {model_id}")
- return result
- @router.post("/chat-completions", response_model=ChatCompletionResponseContract)
- def create_chat_completion(
- payload: ChatCompletionRequestContract,
- service: ModelServiceDep) -> ChatCompletionResponseContract:
- try:
- return service.create_chat_completion(payload)
- except ModelProviderClientError as exc:
- raise HTTPException(status_code=502, detail=str(exc)) from exc
- @router.post("/chat-completions/stream")
- def stream_chat_completion(
- payload: ChatCompletionRequestContract,
- service: ModelServiceDep) -> StreamingResponse:
- def events():
- try:
- for delta in service.stream_chat_completion(payload):
- yield f"event: delta\ndata: {json_dump({'delta': delta})}\n\n"
- yield "event: done\ndata: {}\n\n"
- except ModelProviderClientError as exc:
- yield f"event: error\ndata: {json_dump({'message': str(exc)})}\n\n"
- return StreamingResponse(
- events(),
- media_type="text/event-stream",
- headers={
- "Cache-Control": "no-cache",
- "X-Accel-Buffering": "no",
- })
- @router.post("/list", response_model=ApiResponse[PageResult[ModelDto]])
- def list_models_contract(
- payload: PageRequest,
- service: ModelServiceDep) -> ApiResponse[PageResult[ModelDto]]:
- keyword = (payload.keyword or "").lower().strip()
- items = [
- item
- for item in service.list_models()
- if not keyword
- or keyword in item.name.lower()
- or keyword in item.model_name.lower()
- or keyword in item.provider_type.lower()
- ]
- page_items = items[payload.offset:payload.offset + payload.pageSize]
- return ok(
- PageResult[ModelDto].from_items(
- items=[ModelDto.from_entity(item) for item in page_items],
- total=len(items),
- page=payload.page,
- page_size=payload.pageSize))
- @router.post("/create", response_model=ApiResponse[ModelDto])
- def create_model_contract(
- payload: ModelCreateRequestDto,
- service: ModelServiceDep) -> ApiResponse[ModelDto]:
- try:
- entity = service.create_model_from_contract(payload)
- except ValueError as exc:
- raise HTTPException(status_code=422, detail=str(exc)) from exc
- return ok(ModelDto.from_entity(entity))
- @router.post("/update", response_model=ApiResponse[ModelDto])
- def update_model_contract(
- payload: ModelUpdateRequestDto,
- service: ModelServiceDep) -> ApiResponse[ModelDto]:
- try:
- entity = service.update_model_from_contract(payload)
- except ValueError as exc:
- raise HTTPException(status_code=422, detail=str(exc)) from exc
- if entity is None:
- raise HTTPException(status_code=404, detail=f"model not found: {payload.modelId}")
- return ok(ModelDto.from_entity(entity))
- @router.post("/delete", response_model=ApiResponse[DeleteData])
- def delete_model_contract(
- payload: ModelDeleteRequestDto,
- service: ModelServiceDep) -> ApiResponse[DeleteData]:
- deleted = service.delete_model_from_contract(payload)
- return ok(DeleteData(deleted=deleted, modelId=payload.modelId))
- @router.post("/test", response_model=ApiResponse[ModelTestData])
- def test_model_contract(
- payload: ModelTestRequestDto,
- service: ModelServiceDep) -> ApiResponse[ModelTestData]:
- try:
- result = service.test_model_from_contract(payload)
- except ModelProviderClientError as exc:
- raise HTTPException(status_code=502, detail=str(exc)) from exc
- if result is None:
- raise HTTPException(status_code=404, detail=f"model not found: {payload.modelId}")
- return ok(result)
- @router.post("/providers/list", response_model=ApiResponse[PageResult[ModelProviderDto]])
- def list_model_providers_contract(
- payload: PageRequest,
- service: ModelServiceDep) -> ApiResponse[PageResult[ModelProviderDto]]:
- keyword = (payload.keyword or "").lower().strip()
- items = [
- item
- for item in service.list_providers()
- if not keyword
- or keyword in item.name.lower()
- or keyword in item.provider_type.lower()
- or keyword in item.base_url.lower()
- ]
- page_items = items[payload.offset:payload.offset + payload.pageSize]
- return ok(
- PageResult[ModelProviderDto].from_items(
- items=[ModelProviderDto.from_entity(item) for item in page_items],
- total=len(items),
- page=payload.page,
- page_size=payload.pageSize))
- @router.post("/providers/create", response_model=ApiResponse[ModelProviderDto])
- def create_model_provider_contract(
- payload: ModelProviderCreateRequestDto,
- service: ModelServiceDep) -> ApiResponse[ModelProviderDto]:
- entity = service.create_provider(payload)
- return ok(ModelProviderDto.from_entity(entity))
- @router.post("/providers/update", response_model=ApiResponse[ModelProviderDto])
- def update_model_provider_contract(
- payload: ModelProviderUpdateRequestDto,
- service: ModelServiceDep) -> ApiResponse[ModelProviderDto]:
- entity = service.update_provider(payload)
- if entity is None:
- raise HTTPException(status_code=404, detail=f"provider not found: {payload.providerId}")
- return ok(ModelProviderDto.from_entity(entity))
- @router.post("/providers/delete", response_model=ApiResponse[DeleteData])
- def delete_model_provider_contract(
- payload: ModelProviderDeleteRequestDto,
- service: ModelServiceDep) -> ApiResponse[DeleteData]:
- deleted = service.delete_provider(payload)
- return ok(DeleteData(deleted=deleted, providerId=payload.providerId))
- @router.post("/providers/test", response_model=ApiResponse[ModelProviderTestData])
- def test_model_provider_contract(
- payload: ModelProviderTestRequestDto,
- service: ModelServiceDep) -> ApiResponse[ModelProviderTestData]:
- try:
- result = service.test_provider(payload)
- except ModelProviderClientError as exc:
- raise HTTPException(status_code=502, detail=str(exc)) from exc
- if result is None:
- raise HTTPException(status_code=404, detail=f"provider not found: {payload.providerId}")
- return ok(result)
- @router.post("/providers/discover", response_model=ApiResponse[DiscoverModelsData])
- def discover_models_contract(
- payload: DiscoverModelsRequestDto,
- service: ModelServiceDep) -> ApiResponse[DiscoverModelsData]:
- try:
- return ok(service.discover_models(payload))
- except ModelProviderClientError as exc:
- raise HTTPException(status_code=502, detail=str(exc)) from exc
|