From 0d6baa6195ddba1086ff6439c91c9526ccbca8e0 Mon Sep 17 00:00:00 2001 From: jingrow Date: Tue, 26 May 2026 22:41:18 +0800 Subject: [PATCH] =?UTF-8?q?jchat=E5=A2=9E=E5=8A=A0=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jchat/api.py | 41 ++++++++++++++++++++++++++++++++ apps/jchat/service.py | 53 +++++++++++++++++++++++++++++++++++++++--- apps/jchat/settings.py | 1 + 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/apps/jchat/api.py b/apps/jchat/api.py index 3c4b671..6367637 100644 --- a/apps/jchat/api.py +++ b/apps/jchat/api.py @@ -1,4 +1,5 @@ from fastapi import APIRouter, HTTPException, Request +from fastapi.responses import StreamingResponse from service import ChatService from utils import jingrow_api_verify_and_billing from settings import settings @@ -57,3 +58,43 @@ async def chat_api(data: dict, request: Request): return result except Exception as e: raise HTTPException(status_code=500, detail=str(e)) + +@router.post(settings.stream_route) +@dynamic_billing_wrapper +async def chat_stream_api(data: dict, request: Request): + """ + 流式文本聊天API,使用SSE (Server-Sent Events)格式返回 + + Args: + data: 包含以下字段的字典: + - messages: 消息列表,每个消息包含 role 和 content(必需) + - model: 选择使用的模型(可选,默认为配置的默认模型) + - temperature: 温度参数(可选,默认为0.7) + - top_p: top_p参数(可选,默认为0.9) + - max_tokens: 最大生成token数(可选,默认为2048) + request: FastAPI 请求对象 + + Returns: + 流式SSE响应 + """ + if "messages" not in data: + raise HTTPException(status_code=400, detail="缺少messages参数") + + if "model" in data: + service.model = data["model"] + if "temperature" in data: + service.temperature = data["temperature"] + if "top_p" in data: + service.top_p = data["top_p"] + if "max_tokens" in data: + service.max_tokens = data["max_tokens"] + + return StreamingResponse( + service.chat_stream(data["messages"]), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no" + } + ) diff --git a/apps/jchat/service.py b/apps/jchat/service.py index 3bccd30..2d773be 100644 --- a/apps/jchat/service.py +++ b/apps/jchat/service.py @@ -1,7 +1,8 @@ import json import requests +import aiohttp import asyncio -from typing import Dict, Optional, List, Union +from typing import Dict, Optional, List, Union, AsyncGenerator from settings import settings # 默认模型配置 @@ -205,10 +206,10 @@ class ChatService: async def chat(self, messages: List[Dict]) -> Dict: """异步处理聊天请求 - + Args: messages: 消息列表,每个消息包含 role 和 content - + Returns: 处理结果 """ @@ -221,3 +222,49 @@ class ChatService: "status": "error", "message": f"聊天请求失败: {str(e)}" } + + async def chat_stream(self, messages: List[Dict]) -> AsyncGenerator[bytes, None]: + """流式聊天,直接透传上游 SSE 数据""" + model_config = self._get_model_config(self.model or default_model) + model_type = model_config["type"] + model_name = model_config["model"] + api_config = self._get_api_config(model_type) + + payload = { + "model": model_name, + "messages": messages, + "temperature": self.temperature, + "top_p": self.top_p, + "max_tokens": self.max_tokens, + "stream": True + } + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_config['key']}" + } + + async with aiohttp.ClientSession() as session: + try: + async with session.post( + api_config["url"], + headers=headers, + json=payload, + timeout=aiohttp.ClientTimeout(total=300) + ) as response: + if response.status != 200: + body = await response.text() + yield f'data: [DONE]\n'.encode() + return + + buffer = b"" + async for chunk in response.content.iter_chunked(1024): + buffer += chunk + while b'\n' in buffer: + line, buffer = buffer.split(b'\n', 1) + if line: + yield line + b'\n' + if buffer: + yield buffer + b'\n' + except aiohttp.ClientError: + yield f'data: [DONE]\n'.encode() diff --git a/apps/jchat/settings.py b/apps/jchat/settings.py index db1fa57..1a7a352 100644 --- a/apps/jchat/settings.py +++ b/apps/jchat/settings.py @@ -11,6 +11,7 @@ class Settings(BaseSettings): # API路由配置 router_prefix: str = "/jchat" chat_route: str = "/chat" + stream_route: str = "/chat/stream" default_api_name: str = "jingrow-chat" # 默认API名称 upload_url: str = "http://images.jingrow.com:8080/api/v1/image"