diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py deleted file mode 100644 index f5196c5..0000000 --- a/app/api/v1/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""API V1 Package""" - -from app.api.v1.chat import router as chat_router - -__all__ = ["chat_router"] diff --git a/app/api/v1/chat.py b/app/api/v1/chat.py index a119462..781ea91 100644 --- a/app/api/v1/chat.py +++ b/app/api/v1/chat.py @@ -2,7 +2,7 @@ Chat Completions API 路由 """ -from typing import Any, Dict, List, Literal, Optional, Union +from typing import Any, Dict, List, Optional, Union from fastapi import APIRouter from fastapi.responses import StreamingResponse, JSONResponse diff --git a/app/api/v1/image.py b/app/api/v1/image.py index d44cfaa..0aa7f7e 100644 --- a/app/api/v1/image.py +++ b/app/api/v1/image.py @@ -14,7 +14,6 @@ from app.services.grok.chat import GrokChatService from app.services.grok.model import ModelService from app.services.grok.processor import ImageStreamProcessor, ImageCollectProcessor from app.services.token import get_token_manager -from app.core.config import get_config from app.core.exceptions import ValidationException, AppException, ErrorType from app.core.logger import logger diff --git a/app/api/v1/models.py b/app/api/v1/models.py new file mode 100644 index 0000000..0adba6e --- /dev/null +++ b/app/api/v1/models.py @@ -0,0 +1,28 @@ +""" +Models API 路由 +""" + +from fastapi import APIRouter + +from app.services.grok.model import ModelService + + +router = APIRouter(tags=["Models"]) + + +@router.get("/models") +async def list_models(): + """OpenAI 兼容 models 列表接口""" + data = [ + { + "id": m.model_id, + "object": "model", + "created": 0, + "owned_by": "grok2api", + } + for m in ModelService.list() + ] + return {"object": "list", "data": data} + + +__all__ = ["router"] diff --git a/app/services/grok/chat.py b/app/services/grok/chat.py index 6026932..5fcbfea 100644 --- a/app/services/grok/chat.py +++ b/app/services/grok/chat.py @@ -4,7 +4,7 @@ Grok Chat 服务 import uuid import orjson -from typing import Dict, List, Any, Optional +from typing import Dict, List, Any from dataclasses import dataclass from curl_cffi.requests import AsyncSession @@ -18,11 +18,11 @@ from app.core.exceptions import ( ErrorType ) from app.services.grok.statsig import StatsigService -from app.services.grok.model import ModelService, Cost +from app.services.grok.model import ModelService from app.services.grok.assets import UploadService from app.services.grok.processor import StreamProcessor, CollectProcessor from app.services.grok.retry import retry_on_status -from app.services.token import get_token_manager, EffortType +from app.services.token import get_token_manager CHAT_API = "https://grok.com/rest/app-chat/conversations/new" @@ -64,34 +64,38 @@ class MessageExtractor: """ texts = [] attachments = [] # 需要上传的附件 (URL 或 base64) - + + # 先抽取每条消息的文本,保留角色信息用于合并 + extracted: List[Dict[str, str]] = [] + for msg in messages: + role = msg.get("role", "") content = msg.get("content", "") - + parts = [] + # 简单字符串内容 if isinstance(content, str): if content.strip(): - texts.append(content) - continue - + parts.append(content) + # 列表格式内容 - if isinstance(content, list): + elif isinstance(content, list): for item in content: item_type = item.get("type", "") - + # 文本类型 if item_type == "text": text = item.get("text", "") if text.strip(): - texts.append(text) - + parts.append(text) + # 图片类型 elif item_type == "image_url": image_data = item.get("image_url", {}) url = image_data.get("url", "") if isinstance(image_data, dict) else str(image_data) if url: attachments.append(("image", url)) - + # 音频类型 elif item_type == "input_audio": if is_video: @@ -100,7 +104,7 @@ class MessageExtractor: data = audio_data.get("data", "") if isinstance(audio_data, dict) else str(audio_data) if data: attachments.append(("audio", data)) - + # 文件类型 elif item_type == "file": if is_video: @@ -112,7 +116,25 @@ class MessageExtractor: url = file_data if url: attachments.append(("file", url)) - + + if parts: + extracted.append({"role": role, "text": "\n".join(parts)}) + + # 合并文本 + last_user_index = None + for i in range(len(extracted) - 1, -1, -1): + if extracted[i]["role"] == "user": + last_user_index = i + break + + for i, item in enumerate(extracted): + role = item["role"] or "user" + text = item["text"] + if i == last_user_index: + texts.append(text) + else: + texts.append(f"{role}: {text}") + # 换行拼接文本 message = "\n\n".join(texts) return message, attachments diff --git a/main.py b/main.py index ca62a0f..c20fd28 100644 --- a/main.py +++ b/main.py @@ -30,6 +30,7 @@ from app.core.response_middleware import ResponseLoggerMiddleware from app.api.v1.chat import router as chat_router from app.api.v1.image import router as image_router from app.api.v1.files import router as files_router +from app.api.v1.models import router as models_router from app.services.token import get_scheduler @@ -100,6 +101,7 @@ def create_app() -> FastAPI: # 注册路由 app.include_router(chat_router, prefix="/v1", dependencies=[Depends(verify_api_key)]) app.include_router(image_router, prefix="/v1", dependencies=[Depends(verify_api_key)]) + app.include_router(models_router, prefix="/v1", dependencies=[Depends(verify_api_key)]) app.include_router(files_router, prefix="/v1/files") # 静态文件服务