CoolFace
Modelpublic

Frankenstein-Labs/Cortex-ai

sourceHugging Facemitupdated 6d agoView on Hugging Face
1likes1.3kdownloads
server.py202 linesDownload Raw Back to api
1"""OpenAI-compatible HTTP API for CORTEX AI.2 3Any client that speaks the OpenAI chat-completions protocol -- the official4Python SDK, LangChain, LlamaIndex, a curl script -- can talk to CORTEX AI by5changing only the base URL.6 7Endpoints:8    GET  /health9    GET  /v1/models10    POST /v1/chat/completions11"""12 13from __future__ import annotations14 15import time16import uuid17from typing import Any18 19from fastapi import FastAPI, Header, HTTPException20from pydantic import BaseModel, Field21 22from ..adapters.base import ModelAdapter23from ..config import CortexConfig24from ..engine.agent import CortexAgent25from ..identity import identity_language, identity_response, is_identity_question26from ..tools.registry import registry_from_names27 28 29class ChatMessage(BaseModel):30    role: str31    content: str = ""32    name: str | None = None33 34 35class ChatCompletionRequest(BaseModel):36    model: str | None = None37    messages: list[ChatMessage]38    temperature: float | None = None39    max_tokens: int | None = None40    stream: bool = False41    tools: list[dict[str, Any]] | None = None42 43 44class Usage(BaseModel):45    prompt_tokens: int = 046    completion_tokens: int = 047    total_tokens: int = 048 49 50class ChatCompletionChoice(BaseModel):51    index: int = 052    message: ChatMessage53    finish_reason: str = "stop"54 55 56class ChatCompletionResponse(BaseModel):57    id: str58    object: str = "chat.completion"59    created: int60    model: str61    choices: list[ChatCompletionChoice]62    usage: Usage63    # CORTEX extension: the reasoning trace, when thinking mode is on.64    reasoning_content: str = ""65    tool_calls: list[dict[str, Any]] = Field(default_factory=list)66 67 68def create_app(adapter: ModelAdapter, config: CortexConfig | None = None) -> FastAPI:69    """Build the FastAPI application around a model adapter."""70    cfg = config or CortexConfig()71    tools = registry_from_names(cfg.enabled_tools)72    agent = CortexAgent(73        adapter,74        tools,75        cfg.engine,76        system_prompt=cfg.system_prompt,77    )78 79    app = FastAPI(80        title="CORTEX AI API",81        version="1.0.0",82        description="API compatible OpenAI pour CORTEX AI, un projet de Frankenstein-Labs.",83    )84    app.state.cortex_config = cfg85    app.state.cortex_agent = agent86    app.state.identity_interception = True87 88    def _check_auth(authorization: str | None) -> None:89        if not cfg.server.requires_auth:90            return91        expected = f"Bearer {cfg.server.api_key}"92        if authorization != expected:93            raise HTTPException(status_code=401, detail="invalid API key")94 95    @app.get("/health")96    def health() -> dict[str, Any]:97        return {98            "status": "ok",99            "model": cfg.model_id,100            "tools": tools.names(),101            "thinking_mode": cfg.engine.thinking_mode,102            "identity_interception": "deterministic",103        }104 105    @app.get("/v1/models")106    def list_models(authorization: str | None = Header(default=None)) -> dict[str, Any]:107        _check_auth(authorization)108        return {109            "object": "list",110            "data": [111                {112                    "id": cfg.model_id,113                    "object": "model",114                    "created": int(time.time()),115                    "owned_by": "Frankenstein-Labs",116                }117            ],118        }119 120    @app.post("/v1/chat/completions", response_model=ChatCompletionResponse)121    def chat_completions(122        request: ChatCompletionRequest,123        authorization: str | None = Header(default=None),124    ) -> ChatCompletionResponse:125        _check_auth(authorization)126 127        if request.stream:128            raise HTTPException(129                status_code=400,130                detail="stream=true is not supported yet; use stream=false",131            )132        if not request.messages:133            raise HTTPException(status_code=400, detail="messages must not be empty")134 135        # Deterministic identity boundary: answer before system prompts, tools,136        # or model inference can alter the canonical creator attribution.137        last_user_message = next(138            (m.content for m in reversed(request.messages) if m.role == "user"),139            None,140        )141        if last_user_message is not None and is_identity_question(last_user_message):142            content = identity_response(identity_language(last_user_message))143            prompt_tokens = adapter.count_tokens(last_user_message)144            completion_tokens = adapter.count_tokens(content)145            return ChatCompletionResponse(146                id=f"chatcmpl-{uuid.uuid4().hex[:24]}",147                created=int(time.time()),148                model=request.model or cfg.model_id,149                choices=[150                    ChatCompletionChoice(151                        index=0,152                        message=ChatMessage(role="assistant", content=content),153                        finish_reason="stop",154                    )155                ],156                usage=Usage(157                    prompt_tokens=prompt_tokens,158                    completion_tokens=completion_tokens,159                    total_tokens=prompt_tokens + completion_tokens,160                ),161            )162 163        system_override: list[dict[str, Any]] = []164        turns: list[dict[str, Any]] = []165        for m in request.messages:166            if m.role == "system":167                system_override.append({"role": "system", "content": m.content})168            else:169                turns.append({"role": m.role, "content": m.content})170 171        if system_override:172            agent.system_prompt = system_override[-1]["content"]173 174        result = agent.run(turns)175        prompt_tokens = sum(adapter.count_tokens(m["content"]) for m in turns)176        completion_tokens = adapter.count_tokens(result.content)177 178        return ChatCompletionResponse(179            id=f"chatcmpl-{uuid.uuid4().hex[:24]}",180            created=int(time.time()),181            model=request.model or cfg.model_id,182            choices=[183                ChatCompletionChoice(184                    index=0,185                    message=ChatMessage(role="assistant", content=result.content),186                    finish_reason="stop",187                )188            ],189            usage=Usage(190                prompt_tokens=prompt_tokens,191                completion_tokens=completion_tokens,192                total_tokens=prompt_tokens + completion_tokens,193            ),194            reasoning_content=result.reasoning,195            tool_calls=[196                {"name": c.name, "arguments": c.arguments, "result": c.result, "ok": c.ok}197                for c in result.tool_calls198            ],199        )200 201    return app202