Spaces:
Sleeping
Sleeping
finished with api
Browse files- Dockerfile +13 -0
- app/api/chat/__init__.py +1 -1
- app/api/chat/dto.py +8 -0
- app/api/chat/model.py +1 -1
- app/api/chat/openai_request.py +0 -0
- app/api/chat/schemas.py +2 -1
- app/api/common/dto.py +1 -8
- app/api/message/__init__.py +1 -1
- app/api/message/ai/openai_request.py +33 -0
- app/api/message/ai/prompts.py +2 -0
- app/api/message/ai/utils.py +30 -0
- app/api/message/db_requests.py +7 -8
- app/api/message/dto.py +1 -1
- app/api/message/schemas.py +3 -4
- app/api/message/views.py +9 -4
- app/core/config.py +1 -7
- app/core/wrappers.py +0 -26
Dockerfile
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.12.7
|
2 |
+
|
3 |
+
RUN useradd -m -u 1000 user
|
4 |
+
USER user
|
5 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
6 |
+
|
7 |
+
WORKDIR /app
|
8 |
+
|
9 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
10 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
11 |
+
|
12 |
+
COPY --chown=user . /app
|
13 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
app/api/chat/__init__.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
from fastapi.routing import APIRouter
|
2 |
|
3 |
chat_router = APIRouter(
|
4 |
-
prefix="/chat", tags=["chat"]
|
5 |
)
|
6 |
|
7 |
from . import views
|
|
|
1 |
from fastapi.routing import APIRouter
|
2 |
|
3 |
chat_router = APIRouter(
|
4 |
+
prefix="/api/chat", tags=["chat"]
|
5 |
)
|
6 |
|
7 |
from . import views
|
app/api/chat/dto.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from enum import Enum
|
2 |
+
|
3 |
+
|
4 |
+
class ModelType(Enum):
|
5 |
+
gpt_4o = "gpt-4o"
|
6 |
+
gpt_4o_mini = "gpt-4o-mini"
|
7 |
+
o1_mini = "o1-mini"
|
8 |
+
o1_preview = "o1-preview"
|
app/api/chat/model.py
CHANGED
@@ -3,7 +3,7 @@ from datetime import datetime
|
|
3 |
from pydantic import Field
|
4 |
|
5 |
from app.api.account.model import AccountModel
|
6 |
-
from app.api.
|
7 |
from app.core.database import MongoBaseModel
|
8 |
|
9 |
|
|
|
3 |
from pydantic import Field
|
4 |
|
5 |
from app.api.account.model import AccountModel
|
6 |
+
from app.api.chat.dto import ModelType
|
7 |
from app.core.database import MongoBaseModel
|
8 |
|
9 |
|
app/api/chat/openai_request.py
DELETED
File without changes
|
app/api/chat/schemas.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1 |
from pydantic import BaseModel
|
2 |
|
|
|
3 |
from app.api.chat.model import ChatModel
|
4 |
-
from app.api.common.dto import Paging
|
5 |
from app.core.wrappers import HectoolResponseWrapper
|
6 |
|
7 |
|
|
|
1 |
from pydantic import BaseModel
|
2 |
|
3 |
+
from app.api.chat.dto import ModelType
|
4 |
from app.api.chat.model import ChatModel
|
5 |
+
from app.api.common.dto import Paging
|
6 |
from app.core.wrappers import HectoolResponseWrapper
|
7 |
|
8 |
|
app/api/common/dto.py
CHANGED
@@ -6,11 +6,4 @@ from pydantic import BaseModel
|
|
6 |
class Paging(BaseModel):
|
7 |
pageSize: int
|
8 |
pageIndex: int
|
9 |
-
totalCount: int
|
10 |
-
|
11 |
-
|
12 |
-
class ModelType(Enum):
|
13 |
-
gpt_4o = "gpt-4o"
|
14 |
-
gpt_4o_mini = "gpt-4o-mini"
|
15 |
-
o1_mini = "o1-mini"
|
16 |
-
o1_preview = "o1-preview"
|
|
|
6 |
class Paging(BaseModel):
|
7 |
pageSize: int
|
8 |
pageIndex: int
|
9 |
+
totalCount: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/message/__init__.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
from fastapi.routing import APIRouter
|
2 |
|
3 |
message_router = APIRouter(
|
4 |
-
prefix="/message", tags=["message"]
|
5 |
)
|
6 |
|
7 |
from . import views
|
|
|
1 |
from fastapi.routing import APIRouter
|
2 |
|
3 |
message_router = APIRouter(
|
4 |
+
prefix="/api/message", tags=["message"]
|
5 |
)
|
6 |
|
7 |
from . import views
|
app/api/message/ai/openai_request.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from app.api.chat.model import ChatModel
|
2 |
+
from app.api.message.dto import Author
|
3 |
+
from app.api.message.model import MessageModel
|
4 |
+
from app.api.message.schemas import MessageWrapper
|
5 |
+
from app.core.config import settings
|
6 |
+
|
7 |
+
|
8 |
+
async def response_generator(chat: ChatModel, messages: list):
|
9 |
+
completion = await settings.OPENAI_CLIENT.chat.completions.create(
|
10 |
+
model=chat.model.value,
|
11 |
+
messages=messages,
|
12 |
+
n=1,
|
13 |
+
temperature=0.4,
|
14 |
+
stream=True
|
15 |
+
)
|
16 |
+
|
17 |
+
message_obj, full_response = None, ''
|
18 |
+
|
19 |
+
async for chunk in completion:
|
20 |
+
chunk_text = chunk.choices[0].delta.content
|
21 |
+
if chunk_text:
|
22 |
+
full_response += chunk_text
|
23 |
+
message_obj = MessageModel(
|
24 |
+
chatId=chat.id,
|
25 |
+
author=Author.Assistant,
|
26 |
+
text=chunk_text,
|
27 |
+
)
|
28 |
+
yield MessageWrapper(data=message_obj).model_dump_json()
|
29 |
+
|
30 |
+
message_obj.text = full_response
|
31 |
+
await settings.DB_CLIENT.messages.insert_one(message_obj.to_mongo())
|
32 |
+
|
33 |
+
|
app/api/message/ai/prompts.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
class Prompts:
|
2 |
+
generate_response = """You must always Return 'Hello world!'"""
|
app/api/message/ai/utils.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from app.api.message.ai.prompts import Prompts
|
2 |
+
from app.api.message.model import MessageModel
|
3 |
+
|
4 |
+
|
5 |
+
def transform_messages_to_openai(messages: list[MessageModel]) -> list[dict]:
|
6 |
+
openai_messages = [{"role": "system", "content": Prompts.generate_response}]
|
7 |
+
for message in messages:
|
8 |
+
|
9 |
+
if message.file:
|
10 |
+
content = [
|
11 |
+
{
|
12 |
+
"type": "text", "text": message.text
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"type": "image_url",
|
16 |
+
"image_url": {
|
17 |
+
"url": f"data:image/jpeg;base64,{message.file.base64String}",
|
18 |
+
"detail": "low"
|
19 |
+
}
|
20 |
+
},
|
21 |
+
]
|
22 |
+
else:
|
23 |
+
content = message.text
|
24 |
+
|
25 |
+
openai_messages.append({
|
26 |
+
"role": message.author.value,
|
27 |
+
"content": content
|
28 |
+
})
|
29 |
+
|
30 |
+
return openai_messages
|
app/api/message/db_requests.py
CHANGED
@@ -4,6 +4,7 @@ from fastapi import HTTPException
|
|
4 |
|
5 |
from app.api.account.model import AccountModel
|
6 |
from app.api.chat.model import ChatModel
|
|
|
7 |
from app.api.message.model import MessageModel
|
8 |
from app.api.message.schemas import CreateMessageRequest
|
9 |
from app.core.config import settings
|
@@ -29,13 +30,8 @@ async def get_all_chat_messages_obj(
|
|
29 |
|
30 |
async def create_message_obj(
|
31 |
chat_id: str, message_data: CreateMessageRequest, account: AccountModel
|
32 |
-
) -> MessageModel:
|
33 |
-
|
34 |
-
_, chat = await asyncio.gather(
|
35 |
-
settings.DB_CLIENT.insert_one(message.to_mongo()),
|
36 |
-
settings.DB_CLIENT.chats.find_one({"id": chat_id})
|
37 |
-
)
|
38 |
-
|
39 |
if not chat:
|
40 |
raise HTTPException(status_code=404, detail="Chat not found")
|
41 |
|
@@ -43,4 +39,7 @@ async def create_message_obj(
|
|
43 |
if account and chat.account != account:
|
44 |
raise HTTPException(status_code=403, detail="Chat account not match")
|
45 |
|
46 |
-
|
|
|
|
|
|
|
|
4 |
|
5 |
from app.api.account.model import AccountModel
|
6 |
from app.api.chat.model import ChatModel
|
7 |
+
from app.api.message.dto import Author
|
8 |
from app.api.message.model import MessageModel
|
9 |
from app.api.message.schemas import CreateMessageRequest
|
10 |
from app.core.config import settings
|
|
|
30 |
|
31 |
async def create_message_obj(
|
32 |
chat_id: str, message_data: CreateMessageRequest, account: AccountModel
|
33 |
+
) -> tuple[MessageModel, ChatModel]:
|
34 |
+
chat = await settings.DB_CLIENT.chats.find_one({"id": chat_id})
|
|
|
|
|
|
|
|
|
|
|
35 |
if not chat:
|
36 |
raise HTTPException(status_code=404, detail="Chat not found")
|
37 |
|
|
|
39 |
if account and chat.account != account:
|
40 |
raise HTTPException(status_code=403, detail="Chat account not match")
|
41 |
|
42 |
+
message = MessageModel(**message_data.model_dump(), chatId=chat_id, author=Author.User)
|
43 |
+
await settings.DB_CLIENT.messages.insert_one(message.to_mongo())
|
44 |
+
|
45 |
+
return message, chat
|
app/api/message/dto.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
from enum import Enum
|
2 |
|
3 |
-
from
|
4 |
|
5 |
|
6 |
class Author(Enum):
|
|
|
1 |
from enum import Enum
|
2 |
|
3 |
+
from pydantic import BaseModel
|
4 |
|
5 |
|
6 |
class Author(Enum):
|
app/api/message/schemas.py
CHANGED
@@ -1,24 +1,23 @@
|
|
1 |
from pydantic import BaseModel
|
2 |
|
3 |
-
from app.api.chat.model import ChatModel
|
4 |
from app.api.common.dto import Paging
|
5 |
from app.api.message.dto import Author, File
|
|
|
6 |
from app.core.wrappers import HectoolResponseWrapper
|
7 |
|
8 |
|
9 |
class CreateMessageRequest(BaseModel):
|
10 |
-
author: Author
|
11 |
text: str
|
12 |
file: File | None = None
|
13 |
|
14 |
|
15 |
-
class MessageWrapper(HectoolResponseWrapper[
|
16 |
pass
|
17 |
|
18 |
|
19 |
class AllMessageResponse(BaseModel):
|
20 |
paging: Paging
|
21 |
-
data: list[
|
22 |
|
23 |
|
24 |
class AllMessageWrapper(HectoolResponseWrapper[AllMessageResponse]):
|
|
|
1 |
from pydantic import BaseModel
|
2 |
|
|
|
3 |
from app.api.common.dto import Paging
|
4 |
from app.api.message.dto import Author, File
|
5 |
+
from app.api.message.model import MessageModel
|
6 |
from app.core.wrappers import HectoolResponseWrapper
|
7 |
|
8 |
|
9 |
class CreateMessageRequest(BaseModel):
|
|
|
10 |
text: str
|
11 |
file: File | None = None
|
12 |
|
13 |
|
14 |
+
class MessageWrapper(HectoolResponseWrapper[MessageModel]):
|
15 |
pass
|
16 |
|
17 |
|
18 |
class AllMessageResponse(BaseModel):
|
19 |
paging: Paging
|
20 |
+
data: list[MessageModel]
|
21 |
|
22 |
|
23 |
class AllMessageWrapper(HectoolResponseWrapper[AllMessageResponse]):
|
app/api/message/views.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1 |
from fastapi.params import Depends
|
|
|
2 |
|
3 |
from app.api.account.model import AccountModel
|
4 |
from app.api.common.dto import Paging
|
5 |
from app.api.message import message_router
|
6 |
from app.api.message.db_requests import get_all_chat_messages_obj, create_message_obj
|
7 |
-
from app.api.message.
|
|
|
|
|
8 |
from app.core.security import PermissionDependency
|
9 |
|
10 |
|
@@ -25,6 +28,8 @@ async def create_message(
|
|
25 |
chatId: str,
|
26 |
message_data: CreateMessageRequest,
|
27 |
account: AccountModel = Depends(PermissionDependency(is_public=True))
|
28 |
-
) ->
|
29 |
-
|
30 |
-
|
|
|
|
|
|
1 |
from fastapi.params import Depends
|
2 |
+
from starlette.responses import StreamingResponse
|
3 |
|
4 |
from app.api.account.model import AccountModel
|
5 |
from app.api.common.dto import Paging
|
6 |
from app.api.message import message_router
|
7 |
from app.api.message.db_requests import get_all_chat_messages_obj, create_message_obj
|
8 |
+
from app.api.message.ai.openai_request import response_generator
|
9 |
+
from app.api.message.schemas import AllMessageWrapper, AllMessageResponse, CreateMessageRequest
|
10 |
+
from app.api.message.ai.utils import transform_messages_to_openai
|
11 |
from app.core.security import PermissionDependency
|
12 |
|
13 |
|
|
|
28 |
chatId: str,
|
29 |
message_data: CreateMessageRequest,
|
30 |
account: AccountModel = Depends(PermissionDependency(is_public=True))
|
31 |
+
) -> StreamingResponse:
|
32 |
+
_, chat = await create_message_obj(chatId, message_data, account)
|
33 |
+
messages = await get_all_chat_messages_obj(chatId, account)
|
34 |
+
openai_messages = transform_messages_to_openai(messages)
|
35 |
+
return StreamingResponse(response_generator(chat, openai_messages), media_type='application/json')
|
app/core/config.py
CHANGED
@@ -11,13 +11,7 @@ load_dotenv()
|
|
11 |
class BaseConfig:
|
12 |
BASE_DIR: pathlib.Path = pathlib.Path(__file__).parent.parent
|
13 |
SECRET_KEY = os.getenv('SECRET')
|
14 |
-
DB_CLIENT = motor.motor_asyncio.AsyncIOMotorClient(
|
15 |
-
'mongodb://'
|
16 |
-
f'{os.getenv("DB_USER")}:'
|
17 |
-
f'{os.getenv("DB_PASSWORD")}@'
|
18 |
-
f'{os.getenv("DB_HOST")}:'
|
19 |
-
f'{os.getenv("DB_PORT")}?authSource=hectool'
|
20 |
-
).hectool
|
21 |
OPENAI_CLIENT = AsyncClient(api_key=os.getenv('OPENAI_API_KEY'))
|
22 |
|
23 |
|
|
|
11 |
class BaseConfig:
|
12 |
BASE_DIR: pathlib.Path = pathlib.Path(__file__).parent.parent
|
13 |
SECRET_KEY = os.getenv('SECRET')
|
14 |
+
DB_CLIENT = motor.motor_asyncio.AsyncIOMotorClient(os.getenv("MONGO_DB_URL")).hectool
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
OPENAI_CLIENT = AsyncClient(api_key=os.getenv('OPENAI_API_KEY'))
|
16 |
|
17 |
|
app/core/wrappers.py
CHANGED
@@ -5,7 +5,6 @@ from fastapi import HTTPException
|
|
5 |
from pydantic import BaseModel
|
6 |
from starlette.responses import JSONResponse
|
7 |
|
8 |
-
from app.api.common.dto import ModelType
|
9 |
from app.core.config import settings
|
10 |
|
11 |
T = TypeVar('T')
|
@@ -43,28 +42,3 @@ def exception_wrapper(http_error: int, error_message: str):
|
|
43 |
return wrapper
|
44 |
|
45 |
return decorator
|
46 |
-
|
47 |
-
def openai_wrapper(
|
48 |
-
temperature: int | float = 0,
|
49 |
-
model: ModelType = ModelType.gpt_4o_mini,
|
50 |
-
is_json: bool = False
|
51 |
-
):
|
52 |
-
def decorator(func):
|
53 |
-
@wraps(func)
|
54 |
-
async def wrapper(*args, **kwargs) -> str:
|
55 |
-
messages = await func(*args, **kwargs)
|
56 |
-
completion = await settings.OPENAI_CLIENT.chat.completions.create(
|
57 |
-
messages=messages,
|
58 |
-
temperature=temperature,
|
59 |
-
n=1,
|
60 |
-
model=model.value,
|
61 |
-
response_format={"type": "json_object"} if is_json else {"type": "text"}
|
62 |
-
)
|
63 |
-
response = completion.choices[0].message.content
|
64 |
-
if is_json:
|
65 |
-
response = json.loads(response)
|
66 |
-
return response
|
67 |
-
|
68 |
-
return wrapper
|
69 |
-
|
70 |
-
return decorator
|
|
|
5 |
from pydantic import BaseModel
|
6 |
from starlette.responses import JSONResponse
|
7 |
|
|
|
8 |
from app.core.config import settings
|
9 |
|
10 |
T = TypeVar('T')
|
|
|
42 |
return wrapper
|
43 |
|
44 |
return decorator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|