Chat Module - Developer Guide¶
This guide explains the Chat module’s architecture, key components, and how to extend or maintain it. It targets developers and maintainers working on provider integrations, request handling, streaming, and system reliability.
Goals & Scope¶
- OpenAI-compatible Chat API (
/api/v1/chat/completions) with streaming (SSE) and non-streaming responses - Multiple LLM providers (commercial and local) through a unified dispatch layer
- Consistent validation (Pydantic schemas) and rich error mapping
- Optional rate limiting, queuing/back-pressure, and metrics/tracing
- Character sessions, dictionaries, world books integration through adjacent subsystems
Authentication¶
- Supports
Authorization: Bearer <token>, legacyToken: <token>, and single-userX-API-KEYheaders. - Actual enforcement depends on deployment settings (
AUTH_MODE, config flags). Seemain.pyauth wiring andcore/Authutilities.
Persistence¶
- Requests are ephemeral by default; set
save_to_db: trueto persist conversations/messages. - Config toggles for default persistence are read from
[Chat-Module]and environment (e.g.,CHAT_SAVE_DEFAULT). - Non-stream responses include
tldw_conversation_idin the JSON payload for client state.
Chat Workflows (Adjacent Module)¶
- Chat Workflows lives beside the core chat completions module at
tldw_Server_API/app/api/v1/endpoints/chat_workflows.py. - It is intentionally not implemented as generic Workflows engine logic. The primary record is a structured run with saved answers, not an open-ended message transcript.
- A workflow run starts from either a saved template or a generated draft, then freezes that structure into an immutable run snapshot before the first question is shown.
- Each step uses either:
stockquestion mode: render the authoredbase_questiondirectly.llm_phrasedquestion mode: route throughcore/Chat_Workflows/question_renderer.py, with service-level fallback to the stock question if phrasing fails.- Workflow step kinds are now:
question_step: classic one-question, one-answer progression.dialogue_round_step: repeated moderated rounds persisted inchat_workflow_roundswhile the parent workflow remains on the same step.- Dialogue steps are executed through
core/Chat_Workflows/dialogue_orchestrator.py, which builds prompts for the debate LLM and moderator LLM and routes both calls throughchat_orchestrator.chat_api_call_async. - Moderator output is schema-constrained and controls only the internal round loop:
continuefinishnext_user_promptmoderator_summary- Dialogue round persistence uses a two-phase claim/execute/finalize flow so SQLite write locks are not held across provider calls.
- Completion is stop-by-default. Free chat requires an explicit
POST /api/v1/chat-workflows/runs/{run_id}/continue-chathandoff. - Access is split across dedicated permissions:
chat_workflows.readchat_workflows.writechat_workflows.run- Current v1 behavior keeps context explicit:
- run-level
selected_context_refsare persisted with the run - step-level
context_refsare persisted with the template snapshot - dialogue-step
dialogue_config.context_refsare passed explicitly into debate/moderator prompt construction - prior answers are fed back into question rendering
- prior dialogue rounds are replayed into the moderated round prompts
- broader context resolution remains intentionally narrow in the current implementation
Published API reference: Docs/Published/API-related/Chat_Workflows_API.md
Directory Map¶
tldw_Server_API/app/core/Chat/chat_orchestrator.py- Primary dispatcher/utilities (build inputs, assemble context) that delegate to the adapter registrychat_helpers.py- Request shaping helpers, conversion from API schemas, dictionary/character hooksprovider_manager.py- Provider health/fallback management; adapter registry + capability registry are authoritative for handlers/paramsrate_limiter.py- Module-level rate limiting primitives & startup initializationrequest_queue.py- Optional in-memory queue for back-pressure and concurrency controlstreaming_utils.py- Streaming helpers (yielding tokens/chunks to clients)chat_metrics.py- Metrics emitters (counters, histograms)chat_exceptions.py- Typed exceptions + mapping utilitiesChat_Deps.py- Shared dependency types and error helpersdocument_generator.py- Structured output helpers for document-like responsesprompt_template_manager.py(+prompt_templates/) - Prompt templating utilities
Related:
- API schemas: tldw_Server_API/app/api/v1/schemas/chat_request_schemas.py
- LLM provider calls: tldw_Server_API/app/core/LLM_Calls/ (both cloud and local backends)
- Character sessions & world books: tldw_Server_API/app/api/v1/endpoints/characters_endpoint.py, notes DB
- Chat dictionaries: Config_Files + core/config.py (Chat-Dictionaries section)
- Provider listing/health: tldw_Server_API/app/api/v1/endpoints/llm_providers.py
Request Flow (High-Level)¶
- FastAPI endpoint parses request into
ChatCompletionRequest(Pydantic) - OpenAI-compatible schema. chat_helpers.pyand endpoint logic build the internal payload, apply defaults, and optionally enrich messages with:- Character info (system prompts, world books) when requested
- Chat dictionaries (keyword substitutions, token budgeting)
- The endpoint uses
chat_servicehelpers and delegates tochat_orchestrator.chat_api_call(...). Provider routing/validation comes from the adapter + capability registries; new code should import fromchat_orchestratordirectly. LLM_Calls/*executes the provider-specific request (cloud or local APIs). Streaming responses are normalized to SSE frames viastreaming_utils.chat_exceptionsensures errors from providers, validation, or networking are translated to module exceptions and proper HTTP responses.chat_metricsrecords counters, latencies, sizes, and success/failure labels.
Provider Dispatch¶
- Authoritative mappings live in the adapter registry + capability registry:
adapter_registrymaps provider keys (e.g.,openai,anthropic,groq,mistral) to adapter classes incore/LLM_Calls/providers.capability_registrydefines supported request fields, aliases, and blocked fields per provider.provider_config.pyremains for legacy compatibility only (deprecated, no authoritative mappings).- At app startup,
main.pyseeds theprovider_managerfrom the adapter registry to avoid drift with endpoint mappings.
Provider selection notes:
- Requests may specify models with a provider prefix (e.g., anthropic/claude-opus-4.1). The endpoint extracts the provider and model automatically.
- Provider fallback is available via provider_manager; controlled by [Chat-Module].enable_provider_fallback (disabled by default for stability).
Adding a Provider (Checklist)¶
- Implement a provider adapter in
core/LLM_Calls/providers/(cloud or local). - Register the adapter in
adapter_registry.py. - Add capabilities/aliases/blocked fields in
capability_registry.py. - Add configuration defaults in
config.txt/config loader if needed (API base URL, model, timeouts). - Add tests under
tldw_Server_API/tests/Chat/(schema validation, dispatch, error mapping, optional integration smoke). - Update docs and verify
GET /api/v1/llm/providersreflects the provider (if you expose it).
Validation & Schemas¶
chat_request_schemas.pydefines OpenAI-compatible request models, messages, tools, and response-format constraints.chat_validators.pyprovides additional validators (IDs, temperatures, max tokens, stop sequences, andMAX_REQUEST_SIZE).- The endpoint validates:
- Message roles and content constraints (assistant/tool message rules)
logprobs/top_logprobsrelationships- Tool definitions size limits
- Request size limits (
MAX_REQUEST_SIZE), seechat_validators.py - Model strings with provider prefixes like
anthropic/claude-opus-4.1(provider extracted automatically) - Image inputs on user messages via
image_urlcontent parts (expects data URI with base64; validated/sanitized)
Error Handling¶
- All module errors should use
chat_exceptions.pysubclasses: ChatAuthenticationError,ChatRateLimitError,ChatValidationError,ChatProviderError,ChatConfigurationError,ChatDatabaseError, …- Use the
ErrorHandlercontext manager to normalize exceptions and log details with safe formatting and request context. - Provider HTTP/transport exceptions are converted to module exceptions with proper status codes.
Streaming¶
- Providers that support streaming return an iterator/generator.
streaming_utils.pynormalizes provider chunks and emits OpenAI-style Server-Sent Events (text/event-stream).- The endpoint wraps streams with heartbeats, idle timeouts, and finalization signals.
- Errors in streams are emitted as SSE error frames and logged with context.
- Event framing:
- Emits an initial
event: stream_startwith{ conversation_id, model, timestamp }. - Emits
: heartbeat ...comments at intervals. - Emits
data: {"choices":[{"delta":{"content":"..."}}]}for token deltas and a[DONE]equivalent on completion.
Rate Limiting & Queuing¶
- Chat rate limiter (
core/Chat/rate_limiter.py) provides global, per-user, per-conversation, and tokens/minute controls and is enforced in the endpoint. - RBAC guard: the endpoint includes
Depends(rbac_rate_limit("chat.create"))to scope/limit access by permission. - Optional queued execution: set
CHAT_QUEUED_EXECUTION=trueto route calls throughrequest_queuefor cooperative back-pressure. - TEST_MODE supports deterministic overrides via
TEST_CHAT_*env vars (e.g.,TEST_CHAT_PER_USER_RPM) with burst defaulting to 1.0.
Metrics & Tracing¶
chat_metrics.pyprovides counters and histograms (e.g.,chat_api_call_attempt/success, durations).- Uses the unified telemetry/metrics facades in
app/core/Metricsfor Prometheus and tracing compatibility. - Label cardinality should remain bounded (
provider,model,status). Avoid dynamic or free-form labels.
Configuration¶
- Configuration sources:
Config_Files/config.txt([Chat-Module],[Chat-Dictionaries], and provider sections)- Environment variables (override file settings)
- Adapter + capability registries define handler/parameter mappings;
provider_manager.pymanages health/circuit-breaker/fallback. - Common toggles: default provider/model, streaming timeouts, max tokens, safety filters, rate-limit config, request queue sizes.
Images & Multimodal Input¶
- User messages support mixed content: text parts and
image_urlparts with data URIs. - Image data (base64) is validated and can be persisted when
save_to_dbis enabled. - Large images are processed via the chunked image processor when available.
Character, Dictionaries, and World Books¶
- Character sessions and world books live primarily under
api/v1/endpoints/characters_endpoint.pyand the notes DB layer. The Chat module consumes their outputs to enrich prompts (chat_helpers.py). - Chat dictionaries can be applied to user content (replacement rules, token budgeting). They’re configured under
Chat-Dictionariesin config, surfaced via UI, and exposed via/api/v1/chat/dictionaries/*endpoints.
Testing¶
- Unit tests:
- Schema validation:
tldw_Server_API/tests/Chat/test_chat_request_schemas.pyandtldw_Server_API/tests/Chat_NEW/unit/test_chat_schemas.py - Chat service/orchestrator:
tldw_Server_API/tests/Chat/unit/test_chat_service_fallback.pyandtldw_Server_API/tests/Chat/unit/test_chat_service_normalization.py - Dispatch shape/mapping: update tests when changing adapter/capability registries
- Integration tests:
- Endpoint flow for
/api/v1/chat/completions:tldw_Server_API/tests/Chat/test_chat_endpoint_integration.py,tldw_Server_API/tests/Chat/test_chat_completions_integration.py - Streaming normalization:
tldw_Server_API/tests/Chat/test_chat_endpoint_streaming_normalization.py - Consider provider mocks for deterministic behavior
Maintenance Notes¶
- Treat adapter/capability registries as authoritative for handler/parameter mappings going forward; avoid duplicating translation in provider call sites. Legacy duplicates have been removed to prevent drift.
- Log safe: escape curly braces and large payloads before logging (see existing patterns in exception handling).
- Preserve OpenAI response compatibility in streaming and non-streaming outputs to avoid client regressions.
- Be careful when altering schema constraints; downstream clients (UI and tools) rely on them.
Additional endpoint behavior to note:
- Non-stream responses include tldw_conversation_id in the JSON body for client-side state tracking.
- Streaming responses send a stream_start event and normalized data: deltas; periodic heartbeats keep connections alive; a stream_end event is emitted on success.
Streaming Example (Unified SSE with Metrics Labels)¶
When using the unified streaming abstraction, instantiate SSEStream with optional labels to tag emitted metrics (low-cardinality keys like component and endpoint are recommended):
from fastapi.responses import StreamingResponse
from tldw_Server_API.app.core.Streaming.streams import SSEStream
async def chat_stream_endpoint():
stream = SSEStream(
heartbeat_interval_s=10,
heartbeat_mode="data",
labels={"component": "chat", "endpoint": "chat_stream"},
)
async def gen():
# feed stream in background (e.g., provider-normalized lines or deltas)
async for line in stream.iter_sse():
yield line
headers = {"Cache-Control": "no-cache", "X-Accel-Buffering": "no"}
return StreamingResponse(gen(), media_type="text/event-stream", headers=headers)
Provider Control Pass-through (Advanced)¶
Some providers emit meaningful SSE control lines (e.g., event: ..., id: ..., retry: ...). By default, normalization drops these. When clients or adapters depend on them, enable pass-through per endpoint and optionally filter/rename controls:
from fastapi.responses import StreamingResponse
from tldw_Server_API.app.core.Streaming.streams import SSEStream
def _control_filter(name: str, value: str):
# Example: rename event to a standard value; drop ids
if name.lower() == "event":
return ("event", "provider_event")
if name.lower() == "id":
return None
return (name, value)
async def chat_stream_passthru():
stream = SSEStream(
heartbeat_interval_s=10,
provider_control_passthru=True,
control_filter=_control_filter,
labels={"component": "chat", "endpoint": "chat_stream"},
)
async def gen():
async for line in stream.iter_sse():
yield line
return StreamingResponse(gen(), media_type="text/event-stream", headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
})
Rate Limiting¶
- Resource Governor (RG) middleware (production) provides ingress request limits via policy + route_map.
- Chat limiter enforces per-user, per-conversation, and tokens-per-minute limits and is the primary control.
- RBAC dependency guards
chat.create.
For design changes, include a short proposal under Docs/Design/ and link to affected providers and endpoints.