Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c56ca3f
Add chat memory design spec
Sam120204 Jun 1, 2026
0fcbd53
feat: implement chat session management with history attachment
Sam120204 Jun 4, 2026
f60fc33
feat: enhance chat memory request with user ID and thread ID management
Sam120204 Jun 4, 2026
7338779
style: auto-format with Black
github-actions[bot] Jun 4, 2026
efb0ddd
refactor: remove attached session handling from chat query and UI
Sam120204 Jun 4, 2026
f99623f
Merge branch 'feature/chatbot-memory' of https://github.com/ScienceGP…
Sam120204 Jun 4, 2026
19ebd58
style: auto-format with Black
github-actions[bot] Jun 4, 2026
1037b3e
feat: enhance context management in chat memory service and UI
Sam120204 Jun 5, 2026
13a9626
style: auto-format with Prettier
github-actions[bot] Jun 5, 2026
a085140
adding documentations
Sam120204 Jun 10, 2026
fd304d7
Merge branch 'feature/chatbot-memory' of https://github.com/ScienceGP…
Sam120204 Jun 10, 2026
bc2a3b1
adding the summarize feature to the previous conversations
Sam120204 Jun 10, 2026
8a9771d
style: auto-format with Black
github-actions[bot] Jun 10, 2026
7f92836
remove outdated md files
Sam120204 Jun 10, 2026
fc7c2a2
moving the context percentage position an adding the view history tab…
Sam120204 Jun 10, 2026
7a59f26
modifying the context window logic to align well with online resources
Sam120204 Jun 10, 2026
bdc31da
style: auto-format with Prettier
github-actions[bot] Jun 10, 2026
b85922c
update langgraph logic a little bit
Sam120204 Jun 10, 2026
591a46b
Merge branch 'feature/chatbot-memory' of https://github.com/ScienceGP…
Sam120204 Jun 10, 2026
c08cc65
update the chat history display
Sam120204 Jun 10, 2026
27e2d5f
Merge branch 'main' into feature/chatbot-memory
Jordan-Leis Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,12 @@ backend/uploads/
/backend/services/document/processors/markdown_output
/backend/output
/backend/core/hcsx-scigpt2-innocentrhino-acm-f87f8026be3d.json
/backend/.deepeval/.deepeval_telemetry.txt
/backend/tests/
/files/
/backend/.deepeval/.deepeval_telemetry.txt
/backend/tests/
!/backend/tests/
/backend/tests/*
!/backend/tests/test_chat_memory_service.py
/files/

/backend/files/
/backend/services/document/processors/docling/docling_service_sequential.py
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ It supports:
- **Improved authentication** through Better Auth with GitHub OAuth and Microsoft Entra support
- **In-app evaluation workflows** powered by **DeepEval**, including custom evaluation steps and LLM-as-a-judge patterns
- **Batch and interactive workflows** for extraction, review, and evaluation
- **Short-term chatbot memory** for follow-up questions within an independent chat conversation
- **Production-oriented deployment paths** for Azure infrastructure and other containerized environments

---
Expand Down Expand Up @@ -256,6 +257,7 @@ SummarizationTool-dev/
- [Frontend technical design docs](docs/frontend/README.md) — frontend architecture, page-by-page docs, component index, hooks, and TypeScript interfaces
- [Glossary](docs/glossary.md) — definitions for all Azure services, tools, and project-specific terms
- [Backend README](backend/README.md) — backend setup and processing details
- [Chat memory](docs/chat-memory.md) - chatbot memory behavior, API contract, and operational notes
- [Backend technical design docs](docs/backend/README.md) — backend architecture, workflows, diagrams, data models, schemas, and appendices
- [Backend class reference](docs/backend/appendices/class-reference.md) — field-level reference for backend ORM models, schemas, dataclasses, service attributes, and provider classes
- [Migration guide](docs/superpowers/migration-guide.md) — architecture migration and platform transition notes
Expand Down
8 changes: 8 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,18 @@ cd Summarization_tool/backend && uvicorn main:app --reload --port 8000 --host 0.
**Resources:**
- 🔗 [DeepEval G-Eval Metrics](https://deepeval.com/docs/metrics-llm-evals) - Official DeepEval documentation

### Chat Memory
- Chat requests require `chat_session_id` so each browser chat has independent memory.
- Backend memory is persisted with LangGraph checkpoints in PostgreSQL through the existing `DATABASE_URL`.
- Older turns that fall outside the recent-message prompt window are retained through a rolling conversation summary.
- Uploaded document markdown is sent as request-time context and is not stored as permanent chat memory.
- See [Chat Memory](../docs/chat-memory.md) for the API contract and operational notes.

---

## 📚 Documentation
- **[DeepEval Metrics](https://deepeval.com/docs/metrics-llm-evals)** - Official DeepEval LLM evaluation metrics documentation
- **[Chat Memory](../docs/chat-memory.md)** - Chat memory behavior, API contract, and operational notes
- **[Examples](examples/)** - Python examples and usage patterns
- **[API Docs](http://localhost:8000/docs)** - Interactive API documentation (when server running)

Expand Down
167 changes: 119 additions & 48 deletions backend/api/chat/router.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,144 @@
"""Chat API endpoint for support staff chatbot"""

from fastapi import APIRouter, Depends
from typing import Any, Optional

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import Optional
from pydantic import BaseModel, Field

from core.auth import get_current_user
from services.llm.llm_service import LLMService
from services.chat_memory import ChatMemoryRequest, ChatMemoryService
from services.chat_memory.chat_memory_service import GENERIC_MODEL_ERROR_MESSAGE

router = APIRouter(prefix="/api/chat", tags=["chat"])
llm_service = LLMService()
chat_memory_service: Optional[ChatMemoryService] = None


class ChatQueryRequest(BaseModel):
query: str
chat_session_id: str = Field(min_length=1)
query: str = Field(min_length=1)
document_markdown: Optional[str] = None
model_type: str # "azure", "gemini", "anthropic", "llama", "azure-llama", "macbook"
model_type: str
model_id: Optional[str] = None
deployment: Optional[str] = None
api_version: Optional[str] = None


@router.post("/query", dependencies=[Depends(get_current_user)])
async def chat_query(request: ChatQueryRequest):
"""
Send a chat message with optional document context.

When document_markdown is provided, it is injected into the prompt so the
model can answer questions about the uploaded document.
"""
if request.document_markdown:
user_prompt = (
"The following document has been uploaded by the user:\n\n"
f"<document>\n{request.document_markdown}\n</document>\n\n"
f"User question: {request.query}"
)
system_message = (
"You are a helpful document assistant for Health Canada support staff. "
"Answer the user's question based on the provided document. "
"If the answer is not found in the document, say so clearly and offer "
"general guidance if possible."
class ChatHistoryMessage(BaseModel):
id: str
role: str
content: str


class ChatHistorySummary(BaseModel):
chat_session_id: str
title: str
message_count: int
latest_message: str
latest_checkpoint_id: Optional[str] = None


class ChatHistoryListResponse(BaseModel):
chats: list[ChatHistorySummary]
total: int


class ChatHistoryDetailResponse(BaseModel):
chat_session_id: str
messages: list[ChatHistoryMessage]
conversation_summary: str = ""
summarized_message_count: int = 0
context_usage: Optional[dict[str, Any]] = None


def get_chat_memory_service() -> ChatMemoryService:
global chat_memory_service
if chat_memory_service is None:
chat_memory_service = ChatMemoryService()
return chat_memory_service


@router.get("/history", response_model=ChatHistoryListResponse)
async def list_chat_history(
current_user: dict = Depends(get_current_user),
):
try:
return await get_chat_memory_service().list_chat_sessions(current_user["id"])
except Exception as exc:
raise HTTPException(
status_code=500,
detail=f"Error listing chat history: {str(exc)}",
) from exc


@router.get("/history/{chat_session_id}", response_model=ChatHistoryDetailResponse)
async def get_chat_history(
chat_session_id: str,
current_user: dict = Depends(get_current_user),
):
try:
chat = await get_chat_memory_service().get_chat_session(
user_id=current_user["id"],
chat_session_id=chat_session_id,
)
else:
user_prompt = request.query
system_message = (
"You are a helpful assistant for Health Canada support staff. "
"Answer questions clearly and concisely."
except Exception as exc:
raise HTTPException(
status_code=500,
detail=f"Error loading chat history: {str(exc)}",
) from exc

if chat is None:
raise HTTPException(status_code=404, detail="Chat history not found")
return chat


@router.delete("/history/{chat_session_id}")
async def delete_chat_history(
chat_session_id: str,
current_user: dict = Depends(get_current_user),
):
try:
deleted = await get_chat_memory_service().delete_chat_session(
user_id=current_user["id"],
chat_session_id=chat_session_id,
)
except Exception as exc:
raise HTTPException(
status_code=500,
detail=f"Error deleting chat history: {str(exc)}",
) from exc

result = await llm_service.generate_paragraph(
user_prompt=user_prompt,
model_type=request.model_type,
model_id=request.model_id,
deployment=request.deployment,
api_version=request.api_version,
max_tokens=4096,
temperature=0.3,
system_message=system_message,
)

if not result.get("success"):
if not deleted:
raise HTTPException(status_code=404, detail="Chat history not found")
return {"message": f"Chat history {chat_session_id} deleted successfully"}


@router.post("/query", dependencies=[Depends(get_current_user)])
async def chat_query(
request: ChatQueryRequest,
current_user: dict = Depends(get_current_user),
):
document_context = request.document_markdown

try:
result = await get_chat_memory_service().invoke(
ChatMemoryRequest(
user_id=current_user["id"],
chat_session_id=request.chat_session_id,
query=request.query,
model_type=request.model_type,
model_id=request.model_id,
deployment=request.deployment,
api_version=request.api_version,
document_context=document_context,
)
)
return result
except Exception:
return JSONResponse(
status_code=500,
content={
"success": False,
"error": result.get(
"error", "The model call failed. Please try again."
),
"error": GENERIC_MODEL_ERROR_MESSAGE,
},
)

return {"success": True, "response": result.get("content", "")}
2 changes: 2 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ deepeval>=3.6.9
google-cloud-aiplatform>=1.122.0
langchain-openai>=1.0.1
langchain-google-vertexai>=3.0.1
langgraph>=1.2.4
langgraph-checkpoint-postgres>=3.1.0
pytest>=8.4.2
pytest-asyncio>=1.2.0
anthropic[vertex]>=0.39.0
Expand Down
3 changes: 3 additions & 0 deletions backend/services/chat_memory/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .chat_memory_service import ChatMemoryRequest, ChatMemoryService

__all__ = ["ChatMemoryRequest", "ChatMemoryService"]
Loading
Loading