-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
248 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,150 @@ | ||
# 免登录的助手相关接口 | ||
import time | ||
import uuid | ||
from typing import Optional | ||
from uuid import UUID | ||
|
||
from fastapi import APIRouter, Request, HTTPException, WebSocket | ||
from fastapi.responses import StreamingResponse, ORJSONResponse | ||
from langchain_core.messages import HumanMessage, AIMessage | ||
from loguru import logger | ||
|
||
router = APIRouter(prefix='/chat', tags=['AssistantOpenApi']) | ||
from bisheng.api.services.assistant import AssistantService | ||
from bisheng.api.services.assistant_agent import AssistantAgent | ||
from bisheng.api.services.user_service import UserPayload | ||
from bisheng.api.utils import get_request_ip | ||
from bisheng.api.v1.schemas import OpenAIChatCompletionResp, OpenAIChatCompletionReq, UnifiedResponseModel, \ | ||
AssistantInfo, OpenAIChoice | ||
from bisheng.database.models.user import UserDao | ||
from bisheng.settings import settings | ||
|
||
router = APIRouter(prefix='/assistant', tags=['AssistantOpenApi']) | ||
|
||
|
||
def get_default_operator(): | ||
user_id = settings.get_from_db('default_operator').get('user') | ||
if not user_id: | ||
raise HTTPException(status_code=500, detail='未配置default_operator中user配置') | ||
# 查找默认用户信息 | ||
login_user = UserDao.get_user(user_id) | ||
if not login_user: | ||
raise HTTPException(status_code=500, detail='未找到默认用户信息') | ||
return login_user | ||
|
||
|
||
@router.post('/chat/completions', response_model=OpenAIChatCompletionResp) | ||
async def assistant_chat_completions(request: Request, | ||
req_data: OpenAIChatCompletionReq): | ||
""" | ||
兼容openai接口格式,所有的错误必须返回非http200的状态码 | ||
和助手进行聊天 | ||
""" | ||
logger.info(f'act=assistant_chat_completions assistant_id={req_data.model}, ip={get_request_ip(request)}') | ||
try: | ||
# 获取系统配置里配置的默认用户信息 | ||
default_user = get_default_operator() | ||
except Exception as e: | ||
return ORJSONResponse(status_code=500, content=str(e), media_type='application/json') | ||
login_user = UserPayload(**{ | ||
'user_id': default_user.user_id, | ||
'user_name': default_user.user_name, | ||
'role': '' | ||
}) | ||
# 查找助手信息 | ||
res = AssistantService.get_assistant_info(UUID(req_data.model), login_user) | ||
if res.status_code != 200: | ||
return ORJSONResponse(status_code=500, content=res.status_message, media_type='application/json') | ||
|
||
assistant_info = res.data | ||
# 覆盖温度设置 | ||
if req_data.temperature != 0: | ||
assistant_info.temperature = req_data.temperature | ||
|
||
chat_history = [] | ||
question = '' | ||
# 解析出对话历史和用户最新的问题 | ||
for one in req_data.messages: | ||
if one['role'] == 'user': | ||
chat_history.append(HumanMessage(content=one['content'])) | ||
question = one['content'] | ||
elif one['role'] == 'assistant': | ||
chat_history.append(AIMessage(content=one['content'])) | ||
# 在历史记录里去除用户的问题 | ||
if chat_history and chat_history[-1].content == question: | ||
chat_history = chat_history[:-1] | ||
|
||
# 初始化助手agent | ||
agent = AssistantAgent(assistant_info, '') # 初始化agent | ||
await agent.init_assistant() | ||
answer = await agent.run(question, chat_history) | ||
answer = answer[0].content | ||
|
||
openai_resp_id = uuid.uuid4().hex | ||
logger.info(f'act=assistant_chat_completions_over openai_resp_id={openai_resp_id}') | ||
# 将结果包装成openai的数据格式 | ||
openai_resp = OpenAIChatCompletionResp( | ||
id=openai_resp_id, | ||
object='chat.completion', | ||
created=int(time.time()), | ||
model=req_data.model, | ||
choices=[OpenAIChoice( | ||
index=0, | ||
message={ | ||
'role': 'assistant', | ||
'content': answer | ||
} | ||
)], | ||
) | ||
|
||
# 非流式直接返回结果 | ||
if not req_data.stream: | ||
return openai_resp | ||
|
||
# 流式返回最终结果, 兼容openai格式处理 | ||
openai_resp.object = 'chat.completion.chunk' | ||
openai_resp.choices = [ | ||
OpenAIChoice( | ||
index=0, | ||
delta={ | ||
'content': answer | ||
} | ||
) | ||
] | ||
|
||
async def _event_stream(): | ||
# todo:zgq 后续优化成真正的流式输出,目前是出现最终答案之后直接流式返回的 | ||
yield f'data: {openai_resp.json()}\n\n' | ||
# 最后的[DONE] | ||
yield 'data: [DONE]\n\n' | ||
|
||
try: | ||
return StreamingResponse(_event_stream(), media_type='text/event-stream') | ||
except Exception as exc: | ||
logger.error(exc) | ||
return ORJSONResponse(status_code=500, content=str(exc)) | ||
|
||
|
||
@router.get('/info/{assistant_id}', response_model=UnifiedResponseModel[AssistantInfo]) | ||
async def get_assistant_info(request: Request, assistant_id: UUID): | ||
""" | ||
获取助手信息, 用系统配置里的default_operator.user的用户信息来做权限校验 | ||
""" | ||
logger.info('act=assistant_info') | ||
default_user = get_default_operator() | ||
login_user = UserPayload(**{ | ||
'user_id': default_user.user_id, | ||
'user_name': default_user.user_name, | ||
'role': '' | ||
}) | ||
return AssistantService.get_assistant_info(assistant_id, login_user) | ||
|
||
|
||
@router.websocket('/chat/{assistant_id}') | ||
async def chat(*, | ||
assistant_id: str, | ||
websocket: WebSocket, | ||
chat_id: Optional[str] = None): | ||
""" | ||
助手的ws免登录接口 | ||
""" | ||
pass |