Conversation Session Management#
RAGFlow organizes chat history through a Dialog → Conversation (one-to-many) hierarchy. A Dialog (also called a "chat" in the REST API) holds LLM configuration, prompt templates, and knowledge-base bindings. A Conversation (also called a "session") belongs to exactly one Dialog and stores the full message history for a single user thread.
Key entities:
Dialogtable — LLM config, prompt settings,kb_ids,tenant_idConversationtable —id,dialog_idFK,name,message(JSON),reference(JSON),user_idAPI4Conversationtable — like Conversation but addstokens,duration,round,source,dsl,thumb_up,errorsfor API/agent-driven sessions
Session ID Generation#
Session IDs are 32-character hex strings produced by get_uuid() in common/misc_utils.py:
def get_uuid():
return uuid.uuid1().hex
uuid.uuid1() encodes a timestamp + MAC address, making IDs time-ordered and globally unique. IDs are assigned at creation time by the REST API layer and by async_completion.
Exception — channel sessions: get_or_create_for_channel() derives a deterministic ID from SHA-256(dialog_id:channel_id:chat_id)[:32] so history survives process restarts without storing a back-reference column. The method includes a migration path from the prior MD5 scheme and uses IntegrityError catch-and-retry for concurrent callers .
Message & Reference Persistence#
Messages are not stored in a separate table. Each Conversation row has a message JSONField — a list of {role, content, id, created_at} objects. The reference JSONField is a parallel list: reference[n] holds the retrieval chunks used for the n-th assistant response.
structure_answer() is the single writer for both arrays. It appends or updates the last assistant entry during streaming and commits reference[-1] when the final answer arrives. After each completed exchange, ConversationService.update_by_id() flushes the full row back to the database .
New sessions are seeded with the Dialog's prompt_config.prologue as the first assistant message .
REST API Surface#
All session endpoints nest under the parent chat_id, enforcing the Dialog ownership check before any session operation .
| Method | Endpoint | Notes |
|---|---|---|
POST | /chats/<chat_id>/sessions | Create session; seeds prologue message |
GET | /chats/<chat_id>/sessions | List sessions, filterable by user_id, name, id |
GET | /chats/<chat_id>/sessions/<session_id> | Fetch full history + formatted references |
PATCH | /chats/<chat_id>/sessions/<session_id> | Rename only; messages and reference are immutable via API |
DELETE | /chats/<chat_id>/sessions | Delete by ids or delete_all; cleans up uploaded file blobs |
POST | /chat/completions | Stateful completion — auto-creates session if session_id is omitted |
_build_session_response() normalizes the DB model for API consumers, remapping dialog_id → chat_id and message → messages.
ConversationService#
ConversationService in api/db/services/conversation_service.py wraps the Conversation model. Key methods:
get_list(dialog_id, ...)— paginated query filtered bydialog_id; supports optionaluser_idandnamefilters.get_or_create_for_channel(...)— idempotent upsert for channel-backed sessions with a deterministic SHA-256 ID and race-safeIntegrityErrorhandling.get_all_conversation_by_dialog_ids(dialog_ids)— bulk fetch across multiple dialogs, batched in pages of 100.
Dual Session Tables#
Two parallel tables serve different integration paths:
| Table | Service | Used for |
|---|---|---|
conversation | ConversationService | UI chat sessions owned by a tenant user |
api_4_conversation | API4ConversationService | Embedded/iframe sessions; adds tokens, duration, round tracking |
The iframe/embed flow (async_iframe_completion) writes to API4Conversation and calls API4ConversationService.append_message() instead of update_by_id.