Luxx/luxx/services/message_service.py

159 lines
4.4 KiB
Python

"""Message Service - handles message building and persistence"""
import json
import uuid
import logging
from typing import List, Dict, Any, Optional
from luxx.core.database import SessionLocal
from luxx.models import Conversation, Message
logger = logging.getLogger(__name__)
class MessageService:
"""Service for building and persisting messages"""
def build_messages(
self,
conversation: Conversation,
include_system: bool = True
) -> List[Dict[str, str]]:
"""
Build message list from conversation history.
Args:
conversation: Conversation object
include_system: Whether to include system prompt
Returns:
List of message dicts with 'role' and 'content' keys
"""
messages = []
# Add system prompt
if include_system and conversation.system_prompt:
messages.append({
"role": "system",
"content": conversation.system_prompt
})
# Load messages from database
db = SessionLocal()
try:
db_messages = db.query(Message).filter(
Message.conversation_id == conversation.id
).order_by(Message.created_at).all()
for msg in db_messages:
content = self._parse_content(msg.content)
messages.append({
"role": msg.role,
"content": content
})
finally:
db.close()
return messages
def _parse_content(self, content: str) -> str:
"""Parse JSON content if possible, return plain content otherwise"""
if not content:
return ""
try:
content_obj = json.loads(content)
if isinstance(content_obj, dict):
return content_obj.get("text", content)
return str(content_obj)
except (json.JSONDecodeError, TypeError):
return content
def add_user_message(
self,
messages: List[Dict[str, str]],
user_message: str,
attachments: List[Dict] = None
) -> List[Dict[str, str]]:
"""
Add user message to the message list.
Args:
messages: Existing message list
user_message: User's message text
attachments: Optional list of attachments
Returns:
Updated message list
"""
content = {
"text": user_message,
"attachments": attachments or []
}
messages.append({
"role": "user",
"content": json.dumps(content, ensure_ascii=False)
})
return messages
def create_message_id(self) -> str:
"""Generate unique message ID"""
return str(uuid.uuid4())
def save_assistant_message(
self,
conversation_id: int,
msg_id: str,
full_content: str,
all_tool_calls: List[Dict],
all_tool_results: List[Dict],
all_steps: List[Dict],
token_count: int = 0,
usage: Optional[Dict] = None
) -> Optional[Message]:
"""
Save assistant message to database.
Args:
conversation_id: Conversation ID
msg_id: Message UUID
full_content: Full text content
all_tool_calls: List of tool calls made
all_tool_results: List of tool results
all_steps: List of processing steps
token_count: Token count
usage: Token usage dict
Returns:
Created Message object or None on error
"""
content_json = {
"text": full_content,
"steps": all_steps
}
if all_tool_calls:
content_json["tool_calls"] = all_tool_calls
db = SessionLocal()
try:
msg = Message(
id=msg_id,
conversation_id=conversation_id,
role="assistant",
content=json.dumps(content_json, ensure_ascii=False),
token_count=token_count,
usage=json.dumps(usage) if usage else None
)
db.add(msg)
db.commit()
return msg
except Exception as e:
db.rollback()
logger.error(f"Failed to save message: {e}")
return None
finally:
db.close()
# Global service instance
message_service = MessageService()