EAP – How You Have Implemented Conversational Memory ?


EAP – How You Handle Conversation Memory?

Table Of Contents:

app/api/routes.py

from uuid import uuid4
from fastapi import APIRouter, Depends
from app.api.dependencies import get_agent_graph, get_memory
from app.core.config import get_settings
from app.middleware.auth import require_user
from app.schemas.chat import ChatRequest, ChatResponse
from app.schemas.incident import IncidentCreate
from app.schemas.response import HealthResponse
from app.tools.incident_tools import create_incident

router = APIRouter()

@router.get("/health", response_model=HealthResponse)
async def health():
    settings = get_settings()
    return HealthResponse(status="ok", environment=settings.app_env)

@router.post("/v1/chat", response_model=ChatResponse)
async def chat(request: ChatRequest, user: dict = Depends(require_user)):
    conversation_id = request.conversation_id or str(uuid4())
    memory = get_memory()
    memory.add(conversation_id, "user", request.message)
    result = await get_agent_graph().ainvoke({"message": request.message, "conversation_id": conversation_id})
    memory.add(conversation_id, "assistant", result["answer"])
    return ChatResponse(answer=result["answer"], conversation_id=conversation_id, intent=result["intent"], sources=result.get("sources", []))

@router.post("/v1/incidents")
async def open_incident(payload: IncidentCreate, user: dict = Depends(require_user)):
    return await create_incident(**payload.model_dump())

app/api/dependencies.py

from functools import lru_cache
from app.graph.graph import build_graph
from app.memory.conversation_memory import ConversationMemory

@lru_cache
def get_agent_graph(): return build_graph()

@lru_cache
def get_memory(): return ConversationMemory()

app/graph/graph.py

from langgraph.graph import END, START, StateGraph
from app.graph.state import AgentState
from app.graph.nodes import classify_node, incident_node, knowledge_node, request_node, general_node
from app.graph.router import route_intent


def build_graph():
    graph = StateGraph(AgentState)
    graph.add_node("classify", classify_node)
    graph.add_node("incident", incident_node)
    graph.add_node("knowledge", knowledge_node)
    graph.add_node("request", request_node)
    graph.add_node("general", general_node)
    graph.add_edge(START, "classify")
    graph.add_conditional_edges("classify", route_intent, {"incident": "incident", "knowledge": "knowledge", "request": "request", "general": "general"})
    for node in ("incident", "knowledge", "request", "general"):
        graph.add_edge(node, END)
    return graph.compile()

app/memory/conversation_memory.py

from app.db.repositories import list_messages, save_message


class ConversationMemory:
    def add(self, conversation_id: str, role: str, content: str) -> None:
        save_message(conversation_id, role, content)
    def history(self, conversation_id: str) -> list[dict]:
        return list(reversed(list_messages(conversation_id)))

app/db/repositories.py

from datetime import datetime, timezone
from app.db.session import get_connection


def save_message(conversation_id: str, role: str, content: str) -> None:
    now = datetime.now(timezone.utc).isoformat()
    with get_connection() as conn:
        conn.execute("INSERT OR IGNORE INTO conversations(id, created_at) VALUES (?, ?)", (conversation_id, now))
        conn.execute("INSERT INTO messages(conversation_id, role, content, created_at) VALUES (?, ?, ?, ?)", (conversation_id, role, content, now))


def list_messages(conversation_id: str, limit: int = 50) -> list[dict]:
    with get_connection() as conn:
        return [dict(row) for row in conn.execute("SELECT role, content, created_at FROM messages WHERE conversation_id=? ORDER BY id DESC LIMIT ?", (conversation_id, limit))]

app/db/session.py

import sqlite3
from contextlib import contextmanager
from app.core.config import get_settings


@contextmanager
def get_connection():
    conn = sqlite3.connect(get_settings().database_path)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
        conn.commit()
    finally:
        conn.close()


def init_db() -> None:
    with get_connection() as conn:
        conn.execute("CREATE TABLE IF NOT EXISTS conversations (id TEXT PRIMARY KEY, created_at TEXT NOT NULL)")
        conn.execute("CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, conversation_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, created_at TEXT NOT NULL)")

app/core/config.py

from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", extra="ignore")
    app_env: str = "development"
    log_level: str = "INFO"
    gcp_project_id: str = ""
    gcp_location: str = "us-central1"
    vertex_model: str = "gemini-2.0-flash-001"
    servicenow_instance: str = ""
    servicenow_username: str = ""
    servicenow_password: str = ""
    jwt_secret: str = "local-development-secret-change-me"
    jwt_issuer: str = "servicenow-agentic-ai"
    database_path: str = "./data/agent.db"
    enable_write_operations: bool = False

    def ensure_data_dir(self) -> None:
        Path(self.database_path).parent.mkdir(parents=True, exist_ok=True)


@lru_cache
def get_settings() -> Settings:
    settings = Settings()
    settings.ensure_data_dir()
    return settings

Leave a Reply

Your email address will not be published. Required fields are marked *