feat: initial conversation api implementation
Browse files
app/api/chat_api.py
CHANGED
|
@@ -9,7 +9,7 @@ from app.schema.chat_schema import (
|
|
| 9 |
MessageResponse,
|
| 10 |
PlotResponse,
|
| 11 |
)
|
| 12 |
-
from app.schema.
|
| 13 |
ConversationResponse,
|
| 14 |
ConversationItemResponse,
|
| 15 |
)
|
|
@@ -57,7 +57,7 @@ async def create_chat_completion(
|
|
| 57 |
|
| 58 |
|
| 59 |
# get all chat completions
|
| 60 |
-
@router.get("/chat/completions", response_model=List[ChatCompletionResponse])
|
| 61 |
async def list_chat_completions(
|
| 62 |
request: Request,
|
| 63 |
username: str = Depends(auth_service.verify_credentials),
|
|
@@ -80,10 +80,7 @@ async def list_chat_completions(
|
|
| 80 |
|
| 81 |
|
| 82 |
# get a chat completion by id
|
| 83 |
-
@router.get(
|
| 84 |
-
"/chat/completions/{completion_id}",
|
| 85 |
-
response_model=ChatCompletionResponse,
|
| 86 |
-
)
|
| 87 |
@api_response()
|
| 88 |
async def retrieve_chat_completion(
|
| 89 |
completion_id: str,
|
|
@@ -101,10 +98,7 @@ async def retrieve_chat_completion(
|
|
| 101 |
|
| 102 |
|
| 103 |
# get all messages for a chat completion
|
| 104 |
-
@router.get(
|
| 105 |
-
"/chat/completions/{completion_id}/messages",
|
| 106 |
-
response_model=List[MessageResponse],
|
| 107 |
-
)
|
| 108 |
@api_response()
|
| 109 |
async def list_messages(
|
| 110 |
completion_id: str,
|
|
@@ -125,10 +119,7 @@ async def list_messages(
|
|
| 125 |
# plot api list
|
| 126 |
################
|
| 127 |
# get a plot for a message
|
| 128 |
-
@router.get(
|
| 129 |
-
"/chat/completions/{completion_id}/messages/{message_id}/plot",
|
| 130 |
-
response_model=PlotResponse,
|
| 131 |
-
)
|
| 132 |
@api_response()
|
| 133 |
async def retrieve_plot(
|
| 134 |
completion_id: str,
|
|
@@ -154,8 +145,7 @@ async def retrieve_plot(
|
|
| 154 |
|
| 155 |
|
| 156 |
# get all conversations
|
| 157 |
-
@router.get("/conversations", response_model=ConversationResponse)
|
| 158 |
-
@api_response()
|
| 159 |
async def list_conversations(
|
| 160 |
request: Request,
|
| 161 |
username: str = Depends(auth_service.verify_credentials),
|
|
@@ -165,8 +155,7 @@ async def list_conversations(
|
|
| 165 |
"""
|
| 166 |
logger.debug(f"Listing conversations for username: {username}")
|
| 167 |
try:
|
| 168 |
-
|
| 169 |
-
return {"items": [], "total": 0, "limit": 10, "offset": 0}
|
| 170 |
except Exception as e:
|
| 171 |
logger.error(f"Error in list_conversations: {str(e)}")
|
| 172 |
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -175,11 +164,7 @@ async def list_conversations(
|
|
| 175 |
# get a conversation by id
|
| 176 |
|
| 177 |
|
| 178 |
-
@router.get(
|
| 179 |
-
"/conversations/{completion_id}",
|
| 180 |
-
response_model=ConversationItemResponse,
|
| 181 |
-
)
|
| 182 |
-
@api_response()
|
| 183 |
async def retrieve_conversation(
|
| 184 |
completion_id: str,
|
| 185 |
request: Request,
|
|
|
|
| 9 |
MessageResponse,
|
| 10 |
PlotResponse,
|
| 11 |
)
|
| 12 |
+
from app.schema.conversation_schema import (
|
| 13 |
ConversationResponse,
|
| 14 |
ConversationItemResponse,
|
| 15 |
)
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
# get all chat completions
|
| 60 |
+
@router.get("/chat/completions", response_model=List[ChatCompletionResponse], deprecated=True)
|
| 61 |
async def list_chat_completions(
|
| 62 |
request: Request,
|
| 63 |
username: str = Depends(auth_service.verify_credentials),
|
|
|
|
| 80 |
|
| 81 |
|
| 82 |
# get a chat completion by id
|
| 83 |
+
@router.get("/chat/completions/{completion_id}", response_model=ChatCompletionResponse)
|
|
|
|
|
|
|
|
|
|
| 84 |
@api_response()
|
| 85 |
async def retrieve_chat_completion(
|
| 86 |
completion_id: str,
|
|
|
|
| 98 |
|
| 99 |
|
| 100 |
# get all messages for a chat completion
|
| 101 |
+
@router.get("/chat/completions/{completion_id}/messages", response_model=List[MessageResponse], deprecated=True)
|
|
|
|
|
|
|
|
|
|
| 102 |
@api_response()
|
| 103 |
async def list_messages(
|
| 104 |
completion_id: str,
|
|
|
|
| 119 |
# plot api list
|
| 120 |
################
|
| 121 |
# get a plot for a message
|
| 122 |
+
@router.get("/chat/completions/{completion_id}/messages/{message_id}/plot", response_model=PlotResponse)
|
|
|
|
|
|
|
|
|
|
| 123 |
@api_response()
|
| 124 |
async def retrieve_plot(
|
| 125 |
completion_id: str,
|
|
|
|
| 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))
|
|
|
|
| 164 |
# get a conversation by id
|
| 165 |
|
| 166 |
|
| 167 |
+
@router.get("/conversations/{completion_id}", response_model=ConversationItemResponse, response_model_exclude_none=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
async def retrieve_conversation(
|
| 169 |
completion_id: str,
|
| 170 |
request: Request,
|
app/mapper/conversation_mapper.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ChatCompletion to ConversationItem
|
| 2 |
+
|
| 3 |
+
from app.mapper.base_mapper import BaseMapper
|
| 4 |
+
from app.model.chat_model import ChatCompletion
|
| 5 |
+
from app.schema.conversation_schema import ConversationItemResponse
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ConversationMapper(BaseMapper[ChatCompletion, ConversationItemResponse]):
|
| 9 |
+
"""Mapper for converting between ChatCompletion model and ConversationItem schema."""
|
| 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
|
| 19 |
+
if not title and model.messages:
|
| 20 |
+
first_message = model.messages[0]
|
| 21 |
+
title = first_message.content[:20] + "..." if len(first_message.content) > 20 else first_message.content
|
| 22 |
+
|
| 23 |
+
return ConversationItemResponse(
|
| 24 |
+
completion_id=model.completion_id,
|
| 25 |
+
title=title,
|
| 26 |
+
create_time=created_timestamp,
|
| 27 |
+
update_time=last_updated_timestamp,
|
| 28 |
+
is_archived=model.is_archived,
|
| 29 |
+
is_starred=model.is_starred,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
def to_model(self, schema: ConversationItemResponse) -> ChatCompletion:
|
| 33 |
+
raise NotImplementedError("ConversationMapper.to_model is not implemented")
|
app/schema/{conversation.py → conversation_schema.py}
RENAMED
|
@@ -37,7 +37,10 @@ class ConversationItemResponse(BaseModel):
|
|
| 37 |
default=None,
|
| 38 |
description="Indicates whether the conversation is excluded from memory or history, if set.",
|
| 39 |
)
|
| 40 |
-
memory_scope: str = Field(
|
|
|
|
|
|
|
|
|
|
| 41 |
workspace_id: Optional[str] = Field(
|
| 42 |
default=None,
|
| 43 |
description="Identifier for the workspace the conversation belongs to, if applicable.",
|
|
@@ -46,8 +49,14 @@ class ConversationItemResponse(BaseModel):
|
|
| 46 |
default=None,
|
| 47 |
description="Status of any asynchronous operations related to the conversation, if applicable.",
|
| 48 |
)
|
| 49 |
-
safe_urls: List[str] = Field(
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
conversation_origin: Optional[str] = Field(
|
| 52 |
default=None,
|
| 53 |
description="Origin or source of the conversation, if specified.",
|
|
|
|
| 37 |
default=None,
|
| 38 |
description="Indicates whether the conversation is excluded from memory or history, if set.",
|
| 39 |
)
|
| 40 |
+
memory_scope: Optional[str] = Field(
|
| 41 |
+
default=None,
|
| 42 |
+
description="Scope of the conversation's memory, e.g., 'global_enabled' for global memory access.",
|
| 43 |
+
)
|
| 44 |
workspace_id: Optional[str] = Field(
|
| 45 |
default=None,
|
| 46 |
description="Identifier for the workspace the conversation belongs to, if applicable.",
|
|
|
|
| 49 |
default=None,
|
| 50 |
description="Status of any asynchronous operations related to the conversation, if applicable.",
|
| 51 |
)
|
| 52 |
+
safe_urls: Optional[List[str]] = Field(
|
| 53 |
+
default=None,
|
| 54 |
+
description="List of URLs deemed safe for the conversation context.",
|
| 55 |
+
)
|
| 56 |
+
blocked_urls: Optional[List[str]] = Field(
|
| 57 |
+
default=None,
|
| 58 |
+
description="List of URLs blocked for the conversation context.",
|
| 59 |
+
)
|
| 60 |
conversation_origin: Optional[str] = Field(
|
| 61 |
default=None,
|
| 62 |
description="Origin or source of the conversation, if specified.",
|
app/service/chat_service.py
CHANGED
|
@@ -9,19 +9,21 @@ from app.schema.chat_schema import (
|
|
| 9 |
)
|
| 10 |
from app.model.chat_model import ChatCompletion, ChatMessage
|
| 11 |
from app.mapper.chat_mapper import ChatMapper
|
|
|
|
| 12 |
import uuid
|
| 13 |
from loguru import logger
|
| 14 |
-
from app.schema.
|
| 15 |
|
| 16 |
|
| 17 |
class ChatService:
|
| 18 |
def __init__(self):
|
| 19 |
self.chat_repository = ChatRepository()
|
| 20 |
self.chat_mapper = ChatMapper()
|
|
|
|
| 21 |
|
| 22 |
async def handle_chat_completion(self, request: ChatCompletionRequest) -> ChatCompletionResponse:
|
| 23 |
last_user_message = request.messages[-1].content
|
| 24 |
-
|
| 25 |
username = "admin"
|
| 26 |
|
| 27 |
# Convert request to model
|
|
@@ -55,7 +57,18 @@ class ChatService:
|
|
| 55 |
|
| 56 |
# conversation service
|
| 57 |
async def find_all_conversations(self, username: str) -> List[ConversationResponse]:
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
async def find_conversation_by_id(self, completion_id: str) -> ConversationResponse:
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 14 |
from loguru import logger
|
| 15 |
+
from app.schema.conversation_schema import ConversationResponse
|
| 16 |
|
| 17 |
|
| 18 |
class ChatService:
|
| 19 |
def __init__(self):
|
| 20 |
self.chat_repository = ChatRepository()
|
| 21 |
self.chat_mapper = ChatMapper()
|
| 22 |
+
self.conversation_mapper = ConversationMapper()
|
| 23 |
|
| 24 |
async def handle_chat_completion(self, request: ChatCompletionRequest) -> ChatCompletionResponse:
|
| 25 |
last_user_message = request.messages[-1].content
|
| 26 |
+
logger.debug(f"TODO implement ai-agent response for this message: {last_user_message}")
|
| 27 |
username = "admin"
|
| 28 |
|
| 29 |
# Convert request to model
|
|
|
|
| 57 |
|
| 58 |
# conversation service
|
| 59 |
async def find_all_conversations(self, username: str) -> List[ConversationResponse]:
|
| 60 |
+
"""Find all conversations for a given username."""
|
| 61 |
+
query = {"created_by": username}
|
| 62 |
+
sort = {"last_updated_date": -1} # Sort by last updated date in descending order
|
| 63 |
+
|
| 64 |
+
entities = await self.chat_repository.find(query, page=1, limit=100, sort=sort)
|
| 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 |
+
entity = await self.chat_repository.find_by_id(completion_id)
|
| 73 |
+
result = self.conversation_mapper.to_schema(entity) if entity else None
|
| 74 |
+
return ConversationResponse(items=[result], total=1, limit=1, offset=0)
|