jchat增加流式返回接口

This commit is contained in:
jingrow 2026-05-26 22:41:18 +08:00
parent 9cd7ef99b6
commit 0d6baa6195
3 changed files with 92 additions and 3 deletions

View File

@ -1,4 +1,5 @@
from fastapi import APIRouter, HTTPException, Request from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import StreamingResponse
from service import ChatService from service import ChatService
from utils import jingrow_api_verify_and_billing from utils import jingrow_api_verify_and_billing
from settings import settings from settings import settings
@ -57,3 +58,43 @@ async def chat_api(data: dict, request: Request):
return result return result
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(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"
}
)

View File

@ -1,7 +1,8 @@
import json import json
import requests import requests
import aiohttp
import asyncio import asyncio
from typing import Dict, Optional, List, Union from typing import Dict, Optional, List, Union, AsyncGenerator
from settings import settings from settings import settings
# 默认模型配置 # 默认模型配置
@ -221,3 +222,49 @@ class ChatService:
"status": "error", "status": "error",
"message": f"聊天请求失败: {str(e)}" "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()

View File

@ -11,6 +11,7 @@ class Settings(BaseSettings):
# API路由配置 # API路由配置
router_prefix: str = "/jchat" router_prefix: str = "/jchat"
chat_route: str = "/chat" chat_route: str = "/chat"
stream_route: str = "/chat/stream"
default_api_name: str = "jingrow-chat" # 默认API名称 default_api_name: str = "jingrow-chat" # 默认API名称
upload_url: str = "http://images.jingrow.com:8080/api/v1/image" upload_url: str = "http://images.jingrow.com:8080/api/v1/image"