brestok commited on
Commit
ad928d0
·
1 Parent(s): 37e5246

finished with api

Browse files
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.common.dto import ModelType
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, ModelType
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
- message = MessageModel(**message_data.model_dump(), chatId=chat_id)
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
- return message
 
 
 
 
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 openai import BaseModel
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[ChatModel]):
16
  pass
17
 
18
 
19
  class AllMessageResponse(BaseModel):
20
  paging: Paging
21
- data: list[ChatModel]
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.schemas import AllMessageWrapper, MessageWrapper, AllMessageResponse, CreateMessageRequest
 
 
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
- ) -> MessageWrapper:
29
- message = await create_message_obj(chatId, message_data, account)
30
- return MessageWrapper(data=message)
 
 
 
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