- **Bootstrap IoC-based architecture with modular services.**
- **Implement `MlxModelService` for local LLM backend.** - **Introduce `DatabaseService` for MySQL integration.** - **Add `HistoryService` to manage conversation context.** - **Set up CLI interface via `CliUiService`.** - **Establish EventBus for token streaming.** - **Include conversation repository for data persistence.** - **Add environment-based configuration management.** - **Draft IoC architectural plan.**
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
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) -> int:
|
||||
return self._db.execute_write(
|
||||
"INSERT INTO td_conversations () VALUES ()"
|
||||
)
|
||||
|
||||
def get_latest_conversation_id(self) -> int | None:
|
||||
rows = self._db.execute(
|
||||
"SELECT id FROM td_conversations ORDER BY created_at DESC LIMIT 1"
|
||||
)
|
||||
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))
|
||||
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
|
||||
class DatabaseService:
|
||||
"""MySQL 연결을 캡슐화하는 서비스. 미설정 시 graceful skip."""
|
||||
|
||||
def __init__(self, host: str, port: int, db: str, user: str, password: str):
|
||||
self._config = dict(host=host, port=port, db=db, user=user, passwd=password)
|
||||
self._conn = None
|
||||
|
||||
def connect(self) -> None:
|
||||
if not self._config["user"]:
|
||||
return
|
||||
try:
|
||||
import pymysql
|
||||
self._conn = pymysql.connect(**self._config)
|
||||
except Exception as e:
|
||||
print(f"[DB] 연결 실패 (선택적 기능): {e}")
|
||||
|
||||
def execute(self, sql: str, params: tuple = ()) -> list[dict[str, Any]]:
|
||||
if self._conn is None:
|
||||
return []
|
||||
cursor = self._conn.cursor()
|
||||
cursor.execute(sql, params)
|
||||
columns = [d[0] for d in cursor.description or []]
|
||||
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
||||
|
||||
def execute_write(self, sql: str, params: tuple = ()) -> int:
|
||||
"""INSERT/UPDATE/DELETE 실행 후 lastrowid 반환."""
|
||||
if self._conn is None:
|
||||
return 0
|
||||
cursor = self._conn.cursor()
|
||||
cursor.execute(sql, params)
|
||||
self._conn.commit()
|
||||
return cursor.lastrowid
|
||||
|
||||
def init_schema(self) -> None:
|
||||
if self._conn is None:
|
||||
return
|
||||
cursor = self._conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS td_conversations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS td_messages (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
conversation_id INT NOT NULL,
|
||||
role VARCHAR(20) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (conversation_id) REFERENCES td_conversations(id)
|
||||
)
|
||||
""")
|
||||
self._conn.commit()
|
||||
|
||||
def close(self) -> None:
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
self._conn = None
|
||||
Reference in New Issue
Block a user