fastrag.query.chat.base#
The ChatStore contract: sessions, chats, and messages.
Serving a chatbot means remembering conversations: anonymous sessions, the
chats inside them, and the messages inside those. ChatStore is the
pluggable contract for that persistence — a session’s identity and expiry, a
chat’s ownership and lifecycle, and a message’s role/content/generation
outcome — covering exactly what a serving layer needs and nothing about how
that layer is built (no FastAPI, no HTTP types, no SSE).
A persisted ChatMessage is a different thing from
Message, the model-facing turn a
ChatModel consumes: a ChatMessage carries
an id, timestamps, sources, and a generation outcome that a Message has no
use for. to_llm_history() is the conversion between them.
Module Attributes
The literal values above, as plain tuples — the single source of truth both this module's |
|
Default and maximum page size for |
Functions
|
Turn a page of persisted messages into model-facing chat history. |
Classes
|
One conversation: an ordered, linear list of messages. |
|
One persisted turn, from |
Pluggable abstract persistence for sessions, chats, and messages. |
|
|
One page of a ULID-cursor listing. |
|
One anonymous browser identity, owning its chats. |
- fastrag.query.chat.base.MESSAGE_ROLES: tuple[str, ...] = ('user', 'assistant', 'system')#
The literal values above, as plain tuples — the single source of truth both this module’s
Literalaliases andfastrag.query.chat.schema’s PostgresENUMtypes are built from, so the two can never drift apart.
- fastrag.query.chat.base.DEFAULT_LIMIT = 50#
Default and maximum page size for
ChatStore.list_chats()andChatStore.list_messages().
- class fastrag.query.chat.base.Session(id, created_at, expires_at, ip_address=None, country=None)[source]#
Bases:
objectOne anonymous browser identity, owning its chats.
- Parameters:
- id#
ULID, minted on creation. The bearer credential itself — a caller who holds this id can act as this session — so it is never logged.
- Type:
- created_at#
Derived from
id, soORDER BY idandORDER BY created_atalways agree.- Type:
- expires_at#
When this session (and, by cascade, its chats and their messages) stops being usable.
- Type:
- class fastrag.query.chat.base.Chat(id, session_id, created_at, expires_at)[source]#
Bases:
objectOne conversation: an ordered, linear list of messages.
- created_at#
Derived from
id.- Type:
- expires_at#
When this chat (and, by cascade, its messages) stops being usable. Independent of its session’s
expires_at— a chat can go idle while its session stays active.- Type:
- class fastrag.query.chat.base.ChatMessage(id, chat_id, created_at, role, content=None, sources=None, finish_reason=None, error_code=None, error_message=None)[source]#
Bases:
objectOne persisted turn, from
user,assistant, orsystem.A different thing from
Message, the model-facing turn aChatModelconsumes — seeto_llm_history()for the conversion.- Parameters:
id (str)
chat_id (str)
created_at (datetime)
role (Literal['user', 'assistant', 'system'])
content (str | None)
finish_reason (Literal['stop', 'length', 'error', 'interrupted'] | None)
error_code (Literal['MODEL_ERROR', 'TIMEOUT', 'RATE_LIMIT', 'CONTEXT_TOO_LONG', 'NETWORK_ERROR', 'SERVICE_UNAVAILABLE', 'INVALID_REQUEST', 'INVALID_RESPONSE', 'INTERNAL_ERROR'] | None)
error_message (str | None)
- created_at#
Derived from
id.- Type:
- role#
Who sent the turn.
- Type:
Literal[‘user’, ‘assistant’, ‘system’]
- content#
The turn’s text.
Noneonly on anassistantturn whosefinish_reasonis"error"and nothing was generated before it failed.- Type:
str | None
- sources#
The documents the answer was grounded in, serialized as plain JSON objects (a store never interprets their shape).
Nonefor auser/systemturn.
- finish_reason#
Why an
assistantturn stopped. Required forassistantturns, absent otherwise.- Type:
Literal[‘stop’, ‘length’, ‘error’, ‘interrupted’] | None
- error_code#
Set exactly when
finish_reason == "error".- Type:
Literal[‘MODEL_ERROR’, ‘TIMEOUT’, ‘RATE_LIMIT’, ‘CONTEXT_TOO_LONG’, ‘NETWORK_ERROR’, ‘SERVICE_UNAVAILABLE’, ‘INVALID_REQUEST’, ‘INVALID_RESPONSE’, ‘INTERNAL_ERROR’] | None
- class fastrag.query.chat.base.Page(items, has_more)[source]#
Bases:
Generic[T]One page of a ULID-cursor listing.
- fastrag.query.chat.base.to_llm_history(messages)[source]#
Turn a page of persisted messages into model-facing chat history.
Reorders
messagesinto chronological (oldest-first) order — the order everyChatModelexpects — and drops any turn withcontent is None(anassistantturn that errored before generating anything), which a chat model has nothing to replay. Alist_messagespage is newest-first, so this is normally the conversion a caller applies right before building a prompt.- Parameters:
messages (Sequence[ChatMessage])
- Return type:
- class fastrag.query.chat.base.ChatStore[source]#
Bases:
AbstractAsyncContextManager[ChatStore],ABCPluggable abstract persistence for sessions, chats, and messages.
Storage only: no FastAPI, no HTTP types, no SSE, no
QueryEngine— a serving layer built on top of aChatStoreowns all of that.- abstractmethod async create_session(*, ttl_days, ip_address=None, country=None)[source]#
Mint and persist a new session.
- abstractmethod async touch_session(session_id, *, ttl_days, min_bump_days)[source]#
Bump an active session’s expiry, throttled.
The expiry is only written when the new value is at least
min_bump_dayslater than the stored one, so an active conversation stays alive without a write on every request.- Parameters:
- Returns:
The session as it now stands (bumped or not), or
Noneifsession_idis unknown or already expired.- Return type:
Session | None
- abstractmethod async create_chat(*, session_id, ttl_days)[source]#
Start a new, empty chat owned by
session_id.
- abstractmethod async get_chat(chat_id, *, session_id)[source]#
Fetch a chat, scoped to its owning session.
- abstractmethod async list_chats(session_id, *, before=None, limit=50)[source]#
List
session_id’s chats, newest-first.
- abstractmethod async delete_chat(chat_id, *, session_id)[source]#
Delete a chat and every message in it.
- abstractmethod async append_message(chat_id, *, role, content=None, sources=None, finish_reason=None, error_code=None, error_message=None)[source]#
Append a new turn to a chat.
- Parameters:
chat_id (str) – The chat this turn belongs to.
role (Literal['user', 'assistant', 'system']) – Who sent the turn.
content (str | None) – The turn’s text. May be
Noneonly for anassistantturn whosefinish_reasonis"error".sources (list[dict[str, Any]] | None) – The documents the answer was grounded in, as plain JSON objects. Only meaningful for an
assistantturn.finish_reason (Literal['stop', 'length', 'error', 'interrupted'] | None) – Required for an
assistantturn, must be absent otherwise.error_code (Literal['MODEL_ERROR', 'TIMEOUT', 'RATE_LIMIT', 'CONTEXT_TOO_LONG', 'NETWORK_ERROR', 'SERVICE_UNAVAILABLE', 'INVALID_REQUEST', 'INVALID_RESPONSE', 'INTERNAL_ERROR'] | None) – Set exactly when
finish_reason == "error".error_message (str | None) – Set exactly when
finish_reason == "error".
- Return type:
- abstractmethod async list_messages(chat_id, *, before=None, limit=50)[source]#
List a chat’s messages, newest-first.
- Parameters:
- Return type:
- abstractmethod async update_last_assistant_message(chat_id, *, content, sources=None, finish_reason, error_code=None, error_message=None)[source]#
Overwrite a chat’s most recent
assistantturn in place.The storage primitive behind both the
/retryflow (regenerate the last answer) and the/editflow (regenerate after editing an earlier turn): both end by replacing the chat’s latestassistantmessage with a new outcome, in place, rather than deleting and re-appending.- Parameters:
chat_id (str) – The chat whose latest
assistantmessage is updated.content (str | None) – The turn’s new text. May be
Noneonly whenfinish_reason == "error".sources (list[dict[str, Any]] | None) – The documents the new answer was grounded in.
finish_reason (Literal['stop', 'length', 'error', 'interrupted']) – Why generation stopped this time.
error_code (Literal['MODEL_ERROR', 'TIMEOUT', 'RATE_LIMIT', 'CONTEXT_TOO_LONG', 'NETWORK_ERROR', 'SERVICE_UNAVAILABLE', 'INVALID_REQUEST', 'INVALID_RESPONSE', 'INTERNAL_ERROR'] | None) – Set exactly when
finish_reason == "error".error_message (str | None) – Set exactly when
finish_reason == "error".
- Returns:
The updated message, or
Noneifchat_idhas noassistantmessage yet.- Return type:
ChatMessage | None