06bcdb03ac
- Upgrade LLM to Qwen3-14B-4bit with Thinking mode (MlxChatModel as LangChain BaseChatModel) - Add LangGraph ReAct agent with tool calling loop (search_documents, web_search, get_current_date, remember/recall_user_info) - Add RAG pipeline: BAAI/bge-m3 embeddings + Qdrant vector store + semantic chunking (SemanticSplitter via cosine similarity) - Replace fixed-size RecursiveCharacterTextSplitter with meaning-based SemanticSplitter (numpy only, no extra deps) - Add Gradio Web UI (app.py): chat, document ingestion, document management tabs - Add multi-user support (user_id isolation in DB + per-user agent cache + dropdown selector) - Add conversation history restore from MySQL on agent init (Phase 11) - Add UserProfileRepository for persistent user profile (remember/recall tools) - Add thread-local DB connections to fix pymysql thread-safety with LangGraph ToolNode - Add Phase 14 voice interface: Whisper STT (microphone → text) + macOS TTS (say -v Yuna) - Enforce search_documents-first policy in system prompt and tool descriptions - Update ROADMAP2.md: Phase 14 완료, Phase 13 청킹 부분 완료 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2.6 KiB
Python
67 lines
2.6 KiB
Python
from __future__ import annotations
|
|
from services.db.mysql_service import DatabaseService
|
|
|
|
|
|
class ConversationRepository:
|
|
"""td_conversations / td_messages 테이블 접근을 담당하는 Repository."""
|
|
|
|
def __init__(self, db: DatabaseService):
|
|
self._db = db
|
|
|
|
def create_conversation(self, user_id: str = "default") -> int:
|
|
return self._db.execute_write(
|
|
"INSERT INTO td_conversations (user_id) VALUES (%s)",
|
|
(user_id,),
|
|
)
|
|
|
|
def get_latest_conversation_id(self, user_id: str = "default") -> int | None:
|
|
rows = self._db.execute(
|
|
"SELECT id FROM td_conversations WHERE user_id = %s ORDER BY created_at DESC LIMIT 1",
|
|
(user_id,),
|
|
)
|
|
return rows[0]["id"] if rows else None
|
|
|
|
def save_message(self, conversation_id: int, role: str, content: str) -> None:
|
|
self._db.execute_write(
|
|
"INSERT INTO td_messages (conversation_id, role, content) VALUES (%s, %s, %s)",
|
|
(conversation_id, role, content),
|
|
)
|
|
|
|
def save_summary(self, conversation_id: int, summary: str) -> None:
|
|
self._db.execute_write(
|
|
"INSERT INTO td_messages (conversation_id, role, content) VALUES (%s, %s, %s)",
|
|
(conversation_id, "summary", summary),
|
|
)
|
|
|
|
def get_latest_summary(self, conversation_id: int) -> tuple[int | None, str | None]:
|
|
"""가장 최근 요약 메시지의 (id, content)를 반환. 없으면 (None, None)."""
|
|
rows = self._db.execute(
|
|
"""SELECT id, content FROM td_messages
|
|
WHERE conversation_id = %s AND role = 'summary'
|
|
ORDER BY created_at DESC LIMIT 1""",
|
|
(conversation_id,),
|
|
)
|
|
if rows:
|
|
return rows[0]["id"], rows[0]["content"]
|
|
return None, None
|
|
|
|
def load_turns_after(
|
|
self, conversation_id: int, after_id: int | None, limit: int
|
|
) -> list[dict]:
|
|
"""요약 이후의 user/assistant 턴을 최근 limit개 반환."""
|
|
if after_id is not None:
|
|
rows = self._db.execute(
|
|
"""SELECT role, content FROM td_messages
|
|
WHERE conversation_id = %s AND id > %s AND role IN ('user', 'assistant')
|
|
ORDER BY created_at DESC LIMIT %s""",
|
|
(conversation_id, after_id, limit),
|
|
)
|
|
else:
|
|
rows = self._db.execute(
|
|
"""SELECT role, content FROM td_messages
|
|
WHERE conversation_id = %s AND role IN ('user', 'assistant')
|
|
ORDER BY created_at DESC LIMIT %s""",
|
|
(conversation_id, limit),
|
|
)
|
|
return list(reversed(rows))
|