| """Conversation memory. |
| |
| Short-term, per-session chat memory using LangChain's RunnableWithMessageHistory |
| backed by SQLChatMessageHistory, persisted to SQLite at ./data/sessions.db. |
| |
| Each Gradio session maps to a session_id. RunnableWithMessageHistory transparently |
| loads prior turns before each call and saves the new turn afterward; the actual |
| trimming to the configured window happens inside the assistant's _respond. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
|
|
| from langchain_community.chat_message_histories import SQLChatMessageHistory |
| from langchain_core.chat_history import BaseChatMessageHistory |
| from langchain_core.runnables import RunnableLambda |
| from langchain_core.runnables.history import RunnableWithMessageHistory |
|
|
| from src.assistants.base import BaseAssistant |
| from src.config import settings |
|
|
|
|
| def _connection_string() -> str: |
| """SQLAlchemy-style sqlite URL, ensuring the parent directory exists.""" |
| path = settings.sqlite_path |
| os.makedirs(os.path.dirname(path) or ".", exist_ok=True) |
| return f"sqlite:///{path}" |
|
|
|
|
| def get_session_history(session_id: str) -> BaseChatMessageHistory: |
| """Return the persistent chat history for one session.""" |
| return SQLChatMessageHistory( |
| session_id=session_id, |
| connection=_connection_string(), |
| ) |
|
|
|
|
| def build_conversational(assistant: BaseAssistant) -> RunnableWithMessageHistory: |
| """Wrap an assistant's core generation step with persistent memory. |
| |
| Invoke as: |
| conversational.invoke( |
| {"input": user_msg}, |
| config={"configurable": {"session_id": sid}}, |
| ) |
| RunnableWithMessageHistory injects the loaded history under "history" and |
| saves both the user input and the returned AIMessage afterward. |
| """ |
| core = RunnableLambda(assistant._respond) |
| return RunnableWithMessageHistory( |
| core, |
| get_session_history, |
| input_messages_key="input", |
| history_messages_key="history", |
| ) |
|
|