feat: conversation/completion_id api implementation completed
Browse files- app/api/chat_api.py +2 -2
- app/config/security_config.py +2 -1
- app/core/api_response.py +1 -0
- app/core/initial_setup/setup.py +6 -6
- app/mapper/base_mapper.py +8 -7
- app/mapper/conversation_mapper.py +2 -5
- app/model/chat_model.py +4 -7
- app/repository/chat_repository.py +22 -23
- app/security/auth_service.py +6 -6
- app/service/chat_service.py +14 -13
app/api/chat_api.py
CHANGED
|
@@ -145,7 +145,7 @@ async def retrieve_plot(
|
|
| 145 |
|
| 146 |
|
| 147 |
# get all conversations
|
| 148 |
-
@router.get("/conversations", response_model=ConversationResponse, response_model_exclude_none=True)
|
| 149 |
async def list_conversations(
|
| 150 |
request: Request,
|
| 151 |
username: str = Depends(auth_service.verify_credentials),
|
|
@@ -155,7 +155,7 @@ async def list_conversations(
|
|
| 155 |
"""
|
| 156 |
logger.debug(f"Listing conversations for username: {username}")
|
| 157 |
try:
|
| 158 |
-
return await service.find_all_conversations(username)
|
| 159 |
except Exception as e:
|
| 160 |
logger.error(f"Error in list_conversations: {str(e)}")
|
| 161 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 145 |
|
| 146 |
|
| 147 |
# get all conversations
|
| 148 |
+
@router.get("/conversations", response_model=ConversationResponse, response_model_exclude_none=True)
|
| 149 |
async def list_conversations(
|
| 150 |
request: Request,
|
| 151 |
username: str = Depends(auth_service.verify_credentials),
|
|
|
|
| 155 |
"""
|
| 156 |
logger.debug(f"Listing conversations for username: {username}")
|
| 157 |
try:
|
| 158 |
+
return await service.find_all_conversations(username)
|
| 159 |
except Exception as e:
|
| 160 |
logger.error(f"Error in list_conversations: {str(e)}")
|
| 161 |
raise HTTPException(status_code=500, detail=str(e))
|
app/config/security_config.py
CHANGED
|
@@ -16,6 +16,7 @@ class SecurityConfig(BaseSettings):
|
|
| 16 |
ENABLED: bool = True
|
| 17 |
DEFAULT_USERNAME: str = "admin"
|
| 18 |
|
|
|
|
| 19 |
@lru_cache()
|
| 20 |
def get_security_config() -> SecurityConfig:
|
| 21 |
-
return SecurityConfig()
|
|
|
|
| 16 |
ENABLED: bool = True
|
| 17 |
DEFAULT_USERNAME: str = "admin"
|
| 18 |
|
| 19 |
+
|
| 20 |
@lru_cache()
|
| 21 |
def get_security_config() -> SecurityConfig:
|
| 22 |
+
return SecurityConfig()
|
app/core/api_response.py
CHANGED
|
@@ -16,6 +16,7 @@ MOCK_DIR = env.str("MOCK_DIR", "resources/mock")
|
|
| 16 |
# Deprecated code. because we are using mongomock-motor for database_type=embedded
|
| 17 |
# TODO: remove this code after we switch to real database
|
| 18 |
|
|
|
|
| 19 |
def url_to_filename(url: str, method: str) -> str:
|
| 20 |
"""
|
| 21 |
Convert API URL to mock filename.
|
|
|
|
| 16 |
# Deprecated code. because we are using mongomock-motor for database_type=embedded
|
| 17 |
# TODO: remove this code after we switch to real database
|
| 18 |
|
| 19 |
+
|
| 20 |
def url_to_filename(url: str, method: str) -> str:
|
| 21 |
"""
|
| 22 |
Convert API URL to mock filename.
|
app/core/initial_setup/setup.py
CHANGED
|
@@ -37,7 +37,7 @@ class InitialSetup:
|
|
| 37 |
if db_config.DATABASE_TYPE != "embedded":
|
| 38 |
logger.info("Skipping initial setup as database type is not embedded")
|
| 39 |
return
|
| 40 |
-
|
| 41 |
# if MONGO_URI is not set, it means we are using embedded database
|
| 42 |
# last check is for the case of using mongomock-motor for database_type=embedded
|
| 43 |
# so last exit before the bridge :) turkish joke
|
|
@@ -46,24 +46,24 @@ class InitialSetup:
|
|
| 46 |
logger.warning("Deleting all chat completions in the embedded database")
|
| 47 |
await self.chat_repository.db.chat_completion.delete_many({})
|
| 48 |
logger.warning("Deleting all chat completions in the embedded database done")
|
| 49 |
-
|
| 50 |
chat_completions = self._load_initial_data()
|
| 51 |
logger.info(f"Loaded {len(chat_completions)} initial chat completions")
|
| 52 |
-
|
| 53 |
for completion in chat_completions:
|
| 54 |
try:
|
| 55 |
found_id = await self.chat_repository.find_by_id(completion.completion_id)
|
| 56 |
if found_id:
|
| 57 |
logger.debug(f"Chat completion already exists: {found_id}")
|
| 58 |
continue
|
| 59 |
-
|
| 60 |
saved = await self.chat_repository.save(completion)
|
| 61 |
logger.info(f"Successfully saved chat completion: {saved.completion_id}")
|
| 62 |
-
|
| 63 |
except Exception as e:
|
| 64 |
logger.error(f"Error saving chat completion {completion.completion_id}: {e}")
|
| 65 |
raise
|
| 66 |
-
|
| 67 |
except Exception as e:
|
| 68 |
logger.error(f"Setup failed: {e}")
|
| 69 |
raise
|
|
|
|
| 37 |
if db_config.DATABASE_TYPE != "embedded":
|
| 38 |
logger.info("Skipping initial setup as database type is not embedded")
|
| 39 |
return
|
| 40 |
+
|
| 41 |
# if MONGO_URI is not set, it means we are using embedded database
|
| 42 |
# last check is for the case of using mongomock-motor for database_type=embedded
|
| 43 |
# so last exit before the bridge :) turkish joke
|
|
|
|
| 46 |
logger.warning("Deleting all chat completions in the embedded database")
|
| 47 |
await self.chat_repository.db.chat_completion.delete_many({})
|
| 48 |
logger.warning("Deleting all chat completions in the embedded database done")
|
| 49 |
+
|
| 50 |
chat_completions = self._load_initial_data()
|
| 51 |
logger.info(f"Loaded {len(chat_completions)} initial chat completions")
|
| 52 |
+
|
| 53 |
for completion in chat_completions:
|
| 54 |
try:
|
| 55 |
found_id = await self.chat_repository.find_by_id(completion.completion_id)
|
| 56 |
if found_id:
|
| 57 |
logger.debug(f"Chat completion already exists: {found_id}")
|
| 58 |
continue
|
| 59 |
+
|
| 60 |
saved = await self.chat_repository.save(completion)
|
| 61 |
logger.info(f"Successfully saved chat completion: {saved.completion_id}")
|
| 62 |
+
|
| 63 |
except Exception as e:
|
| 64 |
logger.error(f"Error saving chat completion {completion.completion_id}: {e}")
|
| 65 |
raise
|
| 66 |
+
|
| 67 |
except Exception as e:
|
| 68 |
logger.error(f"Setup failed: {e}")
|
| 69 |
raise
|
app/mapper/base_mapper.py
CHANGED
|
@@ -1,26 +1,27 @@
|
|
| 1 |
from abc import ABC, abstractmethod
|
| 2 |
from typing import TypeVar, Generic, List
|
| 3 |
|
| 4 |
-
T = TypeVar(
|
| 5 |
-
U = TypeVar(
|
|
|
|
| 6 |
|
| 7 |
class BaseMapper(Generic[T, U], ABC):
|
| 8 |
"""Base mapper class for mapping between model and schema objects."""
|
| 9 |
-
|
| 10 |
@abstractmethod
|
| 11 |
def to_schema(self, model: T) -> U:
|
| 12 |
"""Map from model to schema."""
|
| 13 |
pass
|
| 14 |
-
|
| 15 |
@abstractmethod
|
| 16 |
def to_model(self, schema: U) -> T:
|
| 17 |
"""Map from schema to model."""
|
| 18 |
pass
|
| 19 |
-
|
| 20 |
def to_schema_list(self, models: List[T]) -> List[U]:
|
| 21 |
"""Map a list of models to schemas."""
|
| 22 |
return [self.to_schema(model) for model in models]
|
| 23 |
-
|
| 24 |
def to_model_list(self, schemas: List[U]) -> List[T]:
|
| 25 |
"""Map a list of schemas to models."""
|
| 26 |
-
return [self.to_model(schema) for schema in schemas]
|
|
|
|
| 1 |
from abc import ABC, abstractmethod
|
| 2 |
from typing import TypeVar, Generic, List
|
| 3 |
|
| 4 |
+
T = TypeVar("T")
|
| 5 |
+
U = TypeVar("U")
|
| 6 |
+
|
| 7 |
|
| 8 |
class BaseMapper(Generic[T, U], ABC):
|
| 9 |
"""Base mapper class for mapping between model and schema objects."""
|
| 10 |
+
|
| 11 |
@abstractmethod
|
| 12 |
def to_schema(self, model: T) -> U:
|
| 13 |
"""Map from model to schema."""
|
| 14 |
pass
|
| 15 |
+
|
| 16 |
@abstractmethod
|
| 17 |
def to_model(self, schema: U) -> T:
|
| 18 |
"""Map from schema to model."""
|
| 19 |
pass
|
| 20 |
+
|
| 21 |
def to_schema_list(self, models: List[T]) -> List[U]:
|
| 22 |
"""Map a list of models to schemas."""
|
| 23 |
return [self.to_schema(model) for model in models]
|
| 24 |
+
|
| 25 |
def to_model_list(self, schemas: List[U]) -> List[T]:
|
| 26 |
"""Map a list of schemas to models."""
|
| 27 |
+
return [self.to_model(schema) for schema in schemas]
|
app/mapper/conversation_mapper.py
CHANGED
|
@@ -10,9 +10,6 @@ class ConversationMapper(BaseMapper[ChatCompletion, ConversationItemResponse]):
|
|
| 10 |
|
| 11 |
def to_schema(self, model: ChatCompletion) -> ConversationItemResponse:
|
| 12 |
"""Convert ChatCompletion model to ConversationItem schema."""
|
| 13 |
-
# Convert datetime to Unix timestamp
|
| 14 |
-
created_timestamp = int(model.created_date.timestamp()) if model.created_date else None
|
| 15 |
-
last_updated_timestamp = int(model.last_updated_date.timestamp()) if model.last_updated_date else None
|
| 16 |
|
| 17 |
# Get the first message content as title if title is not set
|
| 18 |
title = model.title
|
|
@@ -23,8 +20,8 @@ class ConversationMapper(BaseMapper[ChatCompletion, ConversationItemResponse]):
|
|
| 23 |
return ConversationItemResponse(
|
| 24 |
completion_id=model.completion_id,
|
| 25 |
title=title,
|
| 26 |
-
create_time=
|
| 27 |
-
update_time=
|
| 28 |
is_archived=model.is_archived,
|
| 29 |
is_starred=model.is_starred,
|
| 30 |
)
|
|
|
|
| 10 |
|
| 11 |
def to_schema(self, model: ChatCompletion) -> ConversationItemResponse:
|
| 12 |
"""Convert ChatCompletion model to ConversationItem schema."""
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
# Get the first message content as title if title is not set
|
| 15 |
title = model.title
|
|
|
|
| 20 |
return ConversationItemResponse(
|
| 21 |
completion_id=model.completion_id,
|
| 22 |
title=title,
|
| 23 |
+
create_time=model.created_date,
|
| 24 |
+
update_time=model.last_updated_date,
|
| 25 |
is_archived=model.is_archived,
|
| 26 |
is_starred=model.is_starred,
|
| 27 |
)
|
app/model/chat_model.py
CHANGED
|
@@ -84,12 +84,13 @@ class ChatCompletion(BaseModel):
|
|
| 84 |
"""
|
| 85 |
A chat completion.
|
| 86 |
"""
|
|
|
|
| 87 |
id: Optional[ObjectId] = Field(alias="_id", default_factory=ObjectId, description="MongoDB unique identifier")
|
| 88 |
completion_id: Optional[str] = Field(None, description="The unique identifier for the chat completion")
|
| 89 |
|
| 90 |
# openai compatible fields
|
| 91 |
model: Optional[str] = Field(None, description="The model used for the chat completion", examples=["gpt-4o-mini", "gpt-4o", "gpt-3.5-turbo"])
|
| 92 |
-
messages: List[ChatMessage] = Field(
|
| 93 |
|
| 94 |
# not implemented yet
|
| 95 |
# temperature: float = Field(default=0.7,ge=0.0, le=1.0, description="What sampling temperature to use, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.")
|
|
@@ -121,14 +122,10 @@ class ChatCompletion(BaseModel):
|
|
| 121 |
description="The date and time the chat completion was last updated",
|
| 122 |
)
|
| 123 |
|
| 124 |
-
|
| 125 |
-
class Config():
|
| 126 |
populate_by_name = True
|
| 127 |
arbitrary_types_allowed = True
|
| 128 |
-
json_encoders = {
|
| 129 |
-
ObjectId: lambda o: str(o),
|
| 130 |
-
datetime: lambda o: o.isoformat()
|
| 131 |
-
}
|
| 132 |
|
| 133 |
def __str__(self):
|
| 134 |
return f"""
|
|
|
|
| 84 |
"""
|
| 85 |
A chat completion.
|
| 86 |
"""
|
| 87 |
+
|
| 88 |
id: Optional[ObjectId] = Field(alias="_id", default_factory=ObjectId, description="MongoDB unique identifier")
|
| 89 |
completion_id: Optional[str] = Field(None, description="The unique identifier for the chat completion")
|
| 90 |
|
| 91 |
# openai compatible fields
|
| 92 |
model: Optional[str] = Field(None, description="The model used for the chat completion", examples=["gpt-4o-mini", "gpt-4o", "gpt-3.5-turbo"])
|
| 93 |
+
messages: Optional[List[ChatMessage]] = Field(None, description="The messages in the chat completion")
|
| 94 |
|
| 95 |
# not implemented yet
|
| 96 |
# temperature: float = Field(default=0.7,ge=0.0, le=1.0, description="What sampling temperature to use, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.")
|
|
|
|
| 122 |
description="The date and time the chat completion was last updated",
|
| 123 |
)
|
| 124 |
|
| 125 |
+
class Config:
|
|
|
|
| 126 |
populate_by_name = True
|
| 127 |
arbitrary_types_allowed = True
|
| 128 |
+
json_encoders = {ObjectId: lambda o: str(o), datetime: lambda o: o.isoformat()}
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
def __str__(self):
|
| 131 |
return f"""
|
app/repository/chat_repository.py
CHANGED
|
@@ -9,10 +9,13 @@ import pymongo
|
|
| 9 |
|
| 10 |
class DocumentNotFoundError(Exception):
|
| 11 |
"""Raised when a document is not found in the database."""
|
|
|
|
| 12 |
pass
|
| 13 |
|
|
|
|
| 14 |
# TODO: llm_model, llm_provider will come from .env file
|
| 15 |
|
|
|
|
| 16 |
class ChatRepository:
|
| 17 |
def __init__(self):
|
| 18 |
logger.info("Initializing ChatRepository")
|
|
@@ -33,13 +36,13 @@ class ChatRepository:
|
|
| 33 |
Exception: If the chat completion is not created
|
| 34 |
"""
|
| 35 |
logger.info(f"Creating new chat completion for user: {entity.created_by}")
|
| 36 |
-
|
| 37 |
entity.completion_id = str(uuid.uuid4()) if entity.completion_id is None else entity.completion_id
|
| 38 |
-
entity_dict = entity.model_dump(by_alias=True)
|
| 39 |
|
| 40 |
# MongoDB'ye kaydet
|
| 41 |
insert_result = await self.db.chat_completion.insert_one(entity_dict)
|
| 42 |
-
|
| 43 |
if not insert_result.inserted_id:
|
| 44 |
logger.error(f"Failed to create new chat completion with ID: {entity.completion_id}")
|
| 45 |
raise Exception(f"Failed to create chat completion with ID: {entity.completion_id}")
|
|
@@ -50,13 +53,13 @@ class ChatRepository:
|
|
| 50 |
async def _update(self, entity: ChatCompletion) -> ChatCompletion:
|
| 51 |
"""
|
| 52 |
Update an existing chat completion in the database.
|
| 53 |
-
|
| 54 |
Args:
|
| 55 |
entity (ChatCompletion): The chat completion entity to update
|
| 56 |
-
|
| 57 |
Returns:
|
| 58 |
ChatCompletion: The updated chat completion
|
| 59 |
-
|
| 60 |
Raises:
|
| 61 |
ValueError: If completion_id is not provided
|
| 62 |
DocumentNotFoundError: If the document to update is not found
|
|
@@ -65,37 +68,34 @@ class ChatRepository:
|
|
| 65 |
raise ValueError("Cannot update chat completion without completion_id")
|
| 66 |
|
| 67 |
logger.info(f"Updating chat completion with ID: {entity.completion_id}")
|
| 68 |
-
|
| 69 |
# these fields are not updatable
|
| 70 |
non_updatable_fields = {"created_date", "created_by", "completion_id"}
|
| 71 |
-
|
| 72 |
# get the model data and remove the non-updatable fields
|
| 73 |
-
update_payload = {
|
| 74 |
-
|
| 75 |
-
if k not in non_updatable_fields
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
if not update_payload:
|
| 79 |
logger.warning(f"No updatable fields found for chat completion ID: {entity.completion_id}")
|
| 80 |
return await self.find_by_id(entity.completion_id)
|
| 81 |
-
|
| 82 |
query = {"completion_id": entity.completion_id}
|
| 83 |
update = {"$set": update_payload}
|
| 84 |
-
|
| 85 |
try:
|
| 86 |
result = await self.db.chat_completion.update_one(query, update)
|
| 87 |
-
|
| 88 |
if result.matched_count == 0:
|
| 89 |
logger.error(f"Chat completion with ID {entity.completion_id} not found for update")
|
| 90 |
raise DocumentNotFoundError(f"Chat completion with ID {entity.completion_id} not found")
|
| 91 |
-
|
| 92 |
if result.modified_count == 0:
|
| 93 |
logger.info(f"Chat completion with ID {entity.completion_id} matched but not modified")
|
| 94 |
else:
|
| 95 |
logger.info(f"Successfully updated chat completion with ID: {entity.completion_id}")
|
| 96 |
-
|
| 97 |
return await self.find_by_id(entity.completion_id)
|
| 98 |
-
|
| 99 |
except Exception as e:
|
| 100 |
logger.error(f"Error updating chat completion with ID {entity.completion_id}: {str(e)}")
|
| 101 |
raise
|
|
@@ -106,9 +106,8 @@ class ChatRepository:
|
|
| 106 |
it will be updated. Otherwise, a new chat completion will be created.
|
| 107 |
"""
|
| 108 |
logger.debug(f"BEGIN REPO: save chat completion. username: {entity.created_by}, completion_id: {entity.completion_id}")
|
| 109 |
-
|
| 110 |
-
try:
|
| 111 |
|
|
|
|
| 112 |
result = await self.find_by_id(entity.completion_id)
|
| 113 |
if result:
|
| 114 |
return await self._update(entity)
|
|
@@ -156,8 +155,8 @@ class ChatRepository:
|
|
| 156 |
Example : completion_id = "123"
|
| 157 |
"""
|
| 158 |
logger.debug(f"BEGIN REPO: find chat completion by id. input parameters: completion_id: {completion_id}, projection: {projection}")
|
| 159 |
-
|
| 160 |
-
entity_doc = await self.db.chat_completion.find_one(
|
| 161 |
|
| 162 |
if entity_doc:
|
| 163 |
logger.trace(f"REPO find_by_id. Found entity_doc: {entity_doc}")
|
|
|
|
| 9 |
|
| 10 |
class DocumentNotFoundError(Exception):
|
| 11 |
"""Raised when a document is not found in the database."""
|
| 12 |
+
|
| 13 |
pass
|
| 14 |
|
| 15 |
+
|
| 16 |
# TODO: llm_model, llm_provider will come from .env file
|
| 17 |
|
| 18 |
+
|
| 19 |
class ChatRepository:
|
| 20 |
def __init__(self):
|
| 21 |
logger.info("Initializing ChatRepository")
|
|
|
|
| 36 |
Exception: If the chat completion is not created
|
| 37 |
"""
|
| 38 |
logger.info(f"Creating new chat completion for user: {entity.created_by}")
|
| 39 |
+
|
| 40 |
entity.completion_id = str(uuid.uuid4()) if entity.completion_id is None else entity.completion_id
|
| 41 |
+
entity_dict = entity.model_dump(by_alias=True)
|
| 42 |
|
| 43 |
# MongoDB'ye kaydet
|
| 44 |
insert_result = await self.db.chat_completion.insert_one(entity_dict)
|
| 45 |
+
|
| 46 |
if not insert_result.inserted_id:
|
| 47 |
logger.error(f"Failed to create new chat completion with ID: {entity.completion_id}")
|
| 48 |
raise Exception(f"Failed to create chat completion with ID: {entity.completion_id}")
|
|
|
|
| 53 |
async def _update(self, entity: ChatCompletion) -> ChatCompletion:
|
| 54 |
"""
|
| 55 |
Update an existing chat completion in the database.
|
| 56 |
+
|
| 57 |
Args:
|
| 58 |
entity (ChatCompletion): The chat completion entity to update
|
| 59 |
+
|
| 60 |
Returns:
|
| 61 |
ChatCompletion: The updated chat completion
|
| 62 |
+
|
| 63 |
Raises:
|
| 64 |
ValueError: If completion_id is not provided
|
| 65 |
DocumentNotFoundError: If the document to update is not found
|
|
|
|
| 68 |
raise ValueError("Cannot update chat completion without completion_id")
|
| 69 |
|
| 70 |
logger.info(f"Updating chat completion with ID: {entity.completion_id}")
|
| 71 |
+
|
| 72 |
# these fields are not updatable
|
| 73 |
non_updatable_fields = {"created_date", "created_by", "completion_id"}
|
| 74 |
+
|
| 75 |
# get the model data and remove the non-updatable fields
|
| 76 |
+
update_payload = {k: v for k, v in entity.model_dump(by_alias=True).items() if k not in non_updatable_fields}
|
| 77 |
+
|
|
|
|
|
|
|
|
|
|
| 78 |
if not update_payload:
|
| 79 |
logger.warning(f"No updatable fields found for chat completion ID: {entity.completion_id}")
|
| 80 |
return await self.find_by_id(entity.completion_id)
|
| 81 |
+
|
| 82 |
query = {"completion_id": entity.completion_id}
|
| 83 |
update = {"$set": update_payload}
|
| 84 |
+
|
| 85 |
try:
|
| 86 |
result = await self.db.chat_completion.update_one(query, update)
|
| 87 |
+
|
| 88 |
if result.matched_count == 0:
|
| 89 |
logger.error(f"Chat completion with ID {entity.completion_id} not found for update")
|
| 90 |
raise DocumentNotFoundError(f"Chat completion with ID {entity.completion_id} not found")
|
| 91 |
+
|
| 92 |
if result.modified_count == 0:
|
| 93 |
logger.info(f"Chat completion with ID {entity.completion_id} matched but not modified")
|
| 94 |
else:
|
| 95 |
logger.info(f"Successfully updated chat completion with ID: {entity.completion_id}")
|
| 96 |
+
|
| 97 |
return await self.find_by_id(entity.completion_id)
|
| 98 |
+
|
| 99 |
except Exception as e:
|
| 100 |
logger.error(f"Error updating chat completion with ID {entity.completion_id}: {str(e)}")
|
| 101 |
raise
|
|
|
|
| 106 |
it will be updated. Otherwise, a new chat completion will be created.
|
| 107 |
"""
|
| 108 |
logger.debug(f"BEGIN REPO: save chat completion. username: {entity.created_by}, completion_id: {entity.completion_id}")
|
|
|
|
|
|
|
| 109 |
|
| 110 |
+
try:
|
| 111 |
result = await self.find_by_id(entity.completion_id)
|
| 112 |
if result:
|
| 113 |
return await self._update(entity)
|
|
|
|
| 155 |
Example : completion_id = "123"
|
| 156 |
"""
|
| 157 |
logger.debug(f"BEGIN REPO: find chat completion by id. input parameters: completion_id: {completion_id}, projection: {projection}")
|
| 158 |
+
query = {"completion_id": completion_id}
|
| 159 |
+
entity_doc = await self.db.chat_completion.find_one(query, projection)
|
| 160 |
|
| 161 |
if entity_doc:
|
| 162 |
logger.trace(f"REPO find_by_id. Found entity_doc: {entity_doc}")
|
app/security/auth_service.py
CHANGED
|
@@ -12,12 +12,12 @@ api_key_header = APIKeyHeader(
|
|
| 12 |
name="Authorization",
|
| 13 |
scheme_name="ApiKeyAuth",
|
| 14 |
description="API key in the format: sk-{username}-{base64_encoded_data}",
|
| 15 |
-
auto_error=False # API key olmadığında otomatik hata vermesini engelle
|
| 16 |
)
|
| 17 |
|
| 18 |
|
| 19 |
class AuthService:
|
| 20 |
-
def __init__(self):
|
| 21 |
self.api_key_header = api_key_header
|
| 22 |
self.security_config = get_security_config()
|
| 23 |
|
|
@@ -87,20 +87,20 @@ class AuthService:
|
|
| 87 |
detail=f"Invalid API key: {str(e)}",
|
| 88 |
)
|
| 89 |
|
| 90 |
-
async def verify_credentials(self, api_key: str =
|
| 91 |
"""Verify API key and extract username."""
|
| 92 |
logger.trace(f"BEGIN: api_key: {api_key}")
|
| 93 |
-
|
| 94 |
if not self.security_config.ENABLED:
|
| 95 |
logger.info("Security is disabled, using default username: " + self.security_config.DEFAULT_USERNAME)
|
| 96 |
return self.security_config.DEFAULT_USERNAME
|
| 97 |
-
|
| 98 |
if not api_key:
|
| 99 |
raise HTTPException(
|
| 100 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 101 |
detail="API key is required when security is enabled",
|
| 102 |
)
|
| 103 |
-
|
| 104 |
username = self.decode_api_key(api_key)
|
| 105 |
result = username
|
| 106 |
logger.trace(f"END: result: {result}")
|
|
|
|
| 12 |
name="Authorization",
|
| 13 |
scheme_name="ApiKeyAuth",
|
| 14 |
description="API key in the format: sk-{username}-{base64_encoded_data}",
|
| 15 |
+
auto_error=False, # API key olmadığında otomatik hata vermesini engelle
|
| 16 |
)
|
| 17 |
|
| 18 |
|
| 19 |
class AuthService:
|
| 20 |
+
def __init__(self):
|
| 21 |
self.api_key_header = api_key_header
|
| 22 |
self.security_config = get_security_config()
|
| 23 |
|
|
|
|
| 87 |
detail=f"Invalid API key: {str(e)}",
|
| 88 |
)
|
| 89 |
|
| 90 |
+
async def verify_credentials(self, api_key: str = Security(api_key_header)) -> str:
|
| 91 |
"""Verify API key and extract username."""
|
| 92 |
logger.trace(f"BEGIN: api_key: {api_key}")
|
| 93 |
+
|
| 94 |
if not self.security_config.ENABLED:
|
| 95 |
logger.info("Security is disabled, using default username: " + self.security_config.DEFAULT_USERNAME)
|
| 96 |
return self.security_config.DEFAULT_USERNAME
|
| 97 |
+
|
| 98 |
if not api_key:
|
| 99 |
raise HTTPException(
|
| 100 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 101 |
detail="API key is required when security is enabled",
|
| 102 |
)
|
| 103 |
+
|
| 104 |
username = self.decode_api_key(api_key)
|
| 105 |
result = username
|
| 106 |
logger.trace(f"END: result: {result}")
|
app/service/chat_service.py
CHANGED
|
@@ -1,13 +1,8 @@
|
|
| 1 |
import datetime
|
| 2 |
from typing import List
|
| 3 |
from app.repository.chat_repository import ChatRepository
|
| 4 |
-
from app.schema.chat_schema import
|
| 5 |
-
|
| 6 |
-
ChatCompletionResponse,
|
| 7 |
-
ChoiceResponse,
|
| 8 |
-
MessageResponse,
|
| 9 |
-
)
|
| 10 |
-
from app.model.chat_model import ChatCompletion, ChatMessage
|
| 11 |
from app.mapper.chat_mapper import ChatMapper
|
| 12 |
from app.mapper.conversation_mapper import ConversationMapper
|
| 13 |
import uuid
|
|
@@ -28,7 +23,7 @@ class ChatService:
|
|
| 28 |
|
| 29 |
# Convert request to model
|
| 30 |
entity = self.chat_mapper.to_model(request)
|
| 31 |
-
|
| 32 |
if entity.completion_id:
|
| 33 |
entity.completion_id = str(uuid.uuid4())
|
| 34 |
entity.created_by = username
|
|
@@ -65,10 +60,16 @@ class ChatService:
|
|
| 65 |
result = self.conversation_mapper.to_schema_list(entities)
|
| 66 |
return ConversationResponse(items=result, total=len(result), limit=100, offset=0)
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
async def find_conversation_by_id(self, completion_id: str) -> ConversationResponse:
|
| 71 |
"""Find a conversation by its completion ID."""
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import datetime
|
| 2 |
from typing import List
|
| 3 |
from app.repository.chat_repository import ChatRepository
|
| 4 |
+
from app.schema.chat_schema import ChatCompletionRequest, ChatCompletionResponse
|
| 5 |
+
from app.model.chat_model import ChatMessage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
from app.mapper.chat_mapper import ChatMapper
|
| 7 |
from app.mapper.conversation_mapper import ConversationMapper
|
| 8 |
import uuid
|
|
|
|
| 23 |
|
| 24 |
# Convert request to model
|
| 25 |
entity = self.chat_mapper.to_model(request)
|
| 26 |
+
|
| 27 |
if entity.completion_id:
|
| 28 |
entity.completion_id = str(uuid.uuid4())
|
| 29 |
entity.created_by = username
|
|
|
|
| 60 |
result = self.conversation_mapper.to_schema_list(entities)
|
| 61 |
return ConversationResponse(items=result, total=len(result), limit=100, offset=0)
|
| 62 |
|
|
|
|
|
|
|
| 63 |
async def find_conversation_by_id(self, completion_id: str) -> ConversationResponse:
|
| 64 |
"""Find a conversation by its completion ID."""
|
| 65 |
+
logger.debug(f"BEGIN SERVICE: find_conversation_by_id for completion_id: {completion_id}")
|
| 66 |
+
projection = {"messages": 0, "_id": 0}
|
| 67 |
+
entity = await self.chat_repository.find_by_id(completion_id, projection=projection)
|
| 68 |
+
logger.debug(f"END SERVICE: find_conversation_by_id for completion_id: {completion_id}, entity: {entity}")
|
| 69 |
+
|
| 70 |
+
if entity:
|
| 71 |
+
# Tekil kayıt için doğrudan dönüşüm yapıyoruz
|
| 72 |
+
result = self.conversation_mapper.to_schema(entity)
|
| 73 |
+
return result
|
| 74 |
+
else:
|
| 75 |
+
return None
|