Conversations¶
src.domains.conversations.endpoints
¶
Conversation management API endpoints for chat/streaming functionality.
This module provides REST endpoints for: - Creating and managing conversations (chat sessions) - Fetching messages - Streaming AI responses with token-by-token generation - Starring/unstarring messages for bookmarking - Auto-generating conversation titles
Architecture
Conversation Flow: ┌────────────────────────────────────────────────────────────┐ │ POST /conversations/ │ │ └─> Create conversation with LLM + generation params │ └────────────────────────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────────────────────────┐ │ POST /{conversation_id}/generate_title │ │ └─> Stream AI-generated title based on first message │ └────────────────────────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────────────────────────┐ │ POST /{conversation_id}/query │ │ └─> Stream AI response token-by-token │ │ └─> Save user message + AI response to database │ └────────────────────────────────────────────────────────────┘ ↓ ┌────────────────────────────────────────────────────────────┐ │ GET /{conversation_id}/fetch_messages │ │ └─> Retrieve all messages in conversation │ └────────────────────────────────────────────────────────────┘
Streaming Pattern
All generation endpoints return StreamingResponse with: - Content-Type: application/x-ndjson - Token-by-token yields via async generator - Automatic database persistence after stream completes
Endpoints
- GET / → List all conversations
- GET /{conversation_id} → Get conversation with messages
- POST / → Create new conversation
- PATCH /{conversation_id} → Update conversation (name, LLM, params)
- DELETE /{conversation_id} → Delete single conversation
- POST /{conversation_id}/query → Stream AI response
- POST /{conversation_id}/generate_title → Stream title generation
- GET /{conversation_id}/fetch_messages → List messages
- PATCH /messages/{message_id}/star → Star/unstar message
Example
Create conversation and generate response::
# 1. Create conversation
POST /erudi/conversations/
{
"llm_id": 1,
"temperature": 0.7,
"top_p": 0.9,
"max_tokens": 512,
"custom_prompt": "You are a helpful assistant."
}
→ {"id": 42, "name": "New Conversation", ...}
# 2. Send message and stream response
POST /erudi/conversations/42/query
{
"user_message": "Explain quantum computing"
}
→ StreamingResponse (application/x-ndjson)
→ "Quantum computing..." (token-by-token)
# 3. Fetch conversation history
GET /erudi/conversations/42/fetch_messages
→ [
{"role": "user", "content": "Explain quantum computing"},
{"role": "assistant", "content": "Quantum computing..."}
]
Note
- StreamingResponse allows frontend to display tokens in real-time
- Messages are saved to database only after streaming completes
- Use run_in_threadpool() for sync repository/service calls
Warning
Streaming endpoints hold database sessions during generation. Long generations (>60s) may cause connection timeouts.
create_conversation(payload, service=Depends(get_conversation_service), db=Depends(get_db))
async
¶
Create a new conversation with specified LLM and generation parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
ConversationCreate
|
ConversationCreate schema with llm_id, temperature, top_p, max_tokens, and optional custom_prompt. |
required |
service
|
ConversationService
|
Injected conversation service. |
Depends(get_conversation_service)
|
db
|
Session
|
Database session for transaction control. |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
ConversationResponse |
Created conversation with auto-generated name (e.g., "New Conversation") and assigned ID. |
Example
::
POST /erudi/conversations/
{
"llm_id": 5,
"temperature": 0.8,
"top_p": 0.95,
"max_tokens": 1024,
"custom_prompt": "You are a creative writer."
}
→ {
"id": 123,
"name": "New Conversation",
"llm_id": 5,
"temperature": 0.8,
"top_p": 0.95,
"max_tokens": 1024,
"custom_prompt": "You are a creative writer."
}
Note
Use POST /{conversation_id}/generate_title to auto-generate a meaningful name after the first message exchange.
Source code in backend/src/domains/conversations/endpoints.py
delete_conversation(conversation_id, service=Depends(get_conversation_service), db=Depends(get_db))
async
¶
Delete a conversation and all associated messages (cascade delete).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
int
|
ID of the conversation to delete. |
required |
service
|
ConversationService
|
Injected conversation service. |
Depends(get_conversation_service)
|
db
|
Session
|
Database session for transaction control. |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
Success confirmation message. |
Example
::
DELETE /erudi/conversations/42
→ {"message": "Conversation deleted successfully"}
Warning
This is a permanent deletion. All messages in the conversation will also be deleted due to cascade constraints.
Source code in backend/src/domains/conversations/endpoints.py
generate_title(conversation_id, payload, service=Depends(get_conversation_service))
async
¶
Stream AI-generated conversation title based on first message.
Uses the loaded LLM to generate a concise title (1-5 words) summarizing the conversation topic. Streams title token-by-token for real-time display.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
int
|
ID of the conversation to generate title for. |
required |
payload
|
ConversationQuery
|
ConversationQuery with question (typically first user message). |
required |
service
|
ConversationService
|
Injected conversation service. |
Depends(get_conversation_service)
|
Returns:
| Name | Type | Description |
|---|---|---|
StreamingResponse |
Text stream with generated title (text/plain). |
Example
::
POST /erudi/conversations/42/generate_title
{
"question": "Explain quantum entanglement in simple terms"
}
→ StreamingResponse: "Quantum Entanglement Basics"
Note
Title generation uses the same LLM as the conversation but with a specialized prompt: "Generate a 1-5 word title for: {question}"
Source code in backend/src/domains/conversations/endpoints.py
get_all_conversations(conversation_repo=Depends(get_conversation_repository))
async
¶
Fetch all conversations from the database.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_repo
|
ConversationRepository
|
Injected conversation repository. |
Depends(get_conversation_repository)
|
Returns:
| Type | Description |
|---|---|
|
List[ConversationResponse]: All conversations with metadata (id, name, llm_id, creation date, last update). |
Example
::
GET /erudi/conversations/
→ [
{
"id": 1,
"name": "Quantum Computing Discussion",
"llm_id": 5,
"created_at": "2025-10-24T10:30:00",
"temperature": 0.7,
"top_p": 0.9
},
...
]
Note
Does not include messages. Use GET /{conversation_id} for full details.
Source code in backend/src/domains/conversations/endpoints.py
get_conversation_by_id(conversation_id, conversation_repo=Depends(get_conversation_repository))
async
¶
Fetch a single conversation by ID with all associated messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
int
|
ID of the conversation to retrieve. |
required |
conversation_repo
|
ConversationRepository
|
Injected conversation repository. |
Depends(get_conversation_repository)
|
Returns:
| Name | Type | Description |
|---|---|---|
ConversationWithMessagesResponse |
Conversation metadata and full message history (user and assistant messages). |
Example
::
GET /erudi/conversations/42
→ {
"id": 42,
"name": "Python Tutorial",
"llm_id": 3,
"messages": [
{"role": "user", "content": "Explain decorators"},
{"role": "assistant", "content": "Decorators are..."}
],
"temperature": 0.7,
"custom_prompt": "You are a Python expert."
}
Source code in backend/src/domains/conversations/endpoints.py
get_conversation_repository(db=Depends(get_db))
¶
Provide a conversation repository instance for dependency injection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
Session
|
SQLAlchemy session injected by FastAPI Depends(get_db). |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
ConversationRepository |
ConversationRepository
|
Repository for conversation CRUD operations. |
Example
::
@router.get("/")
async def list_conversations(
repo: ConversationRepository = Depends(get_conversation_repository)
):
return await run_in_threadpool(repo.get_all_conversations)
Source code in backend/src/domains/conversations/endpoints.py
get_conversation_service(db=Depends(get_db), checkpointer=Depends(get_checkpointer))
¶
Provide a conversation service instance for dependency injection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
Session
|
SQLAlchemy session injected by FastAPI Depends(get_db). |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
ConversationService |
ConversationService
|
Service layer for complex conversation operations (streaming generation, title generation, message persistence). |
Example
::
@router.post("/{conversation_id}/query")
async def query(
conversation_id: int,
payload: ConversationQuery,
service: ConversationService = Depends(get_conversation_service)
):
return StreamingResponse(
service.query_conversation(...),
media_type="text/event-stream"
)
Source code in backend/src/domains/conversations/endpoints.py
get_message_repository(db=Depends(get_db))
¶
Provide a message repository instance for dependency injection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
Session
|
SQLAlchemy session injected by FastAPI Depends(get_db). |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
MessageRepository |
MessageRepository
|
Repository for message CRUD operations. |
Example
::
@router.get("/{conversation_id}/messages")
async def get_messages(
conversation_id: int,
repo: MessageRepository = Depends(get_message_repository)
):
return await run_in_threadpool(
repo.get_messages_by_conversation, conversation_id
)
Source code in backend/src/domains/conversations/endpoints.py
get_messages_by_conversation(conversation_id, message_repo=Depends(get_message_repository))
async
¶
Fetch all messages for a specific conversation ordered by creation time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
int
|
ID of the conversation to fetch messages from. |
required |
message_repo
|
MessageRepository
|
Injected message repository. |
Depends(get_message_repository)
|
Returns:
| Type | Description |
|---|---|
|
List[MessageResponse]: All messages (user and assistant) in the conversation. |
Example
::
GET /erudi/conversations/42/fetch_messages
→ [
{"id": 1, "role": "user", "content": "Hello"},
{"id": 2, "role": "assistant", "content": "Hi there!"}
]
Source code in backend/src/domains/conversations/endpoints.py
query_and_respond(conversation_id, payload, service=Depends(get_conversation_service))
async
¶
Stream AI response to user query and persist message history.
This is the core conversation endpoint. It: 1. Loads conversation history and LLM configuration 2. Appends user message to history 3. Streams AI-generated response token-by-token 4. Saves both user message and AI response to database after streaming
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
int
|
ID of the conversation to query. |
required |
payload
|
ConversationQuery
|
ConversationQuery with user_message and optional kb_id (knowledge base for RAG injection). |
required |
service
|
ConversationService
|
Injected conversation service. |
Depends(get_conversation_service)
|
Returns:
| Name | Type | Description |
|---|---|---|
StreamingResponse |
AI response stream (text/plain), sent token-by-token for real-time display in frontend. |
Example
::
POST /erudi/conversations/42/query
{
"user_message": "What is the capital of France?",
"kb_id": null
}
→ StreamingResponse: "The capital of France is Paris..."
Note
- Supports RAG: If kb_id provided, injects relevant KB chunks into prompt
- Messages are saved AFTER streaming completes (ensures full response captured)
- Uses the conversation's temperature and top_p; the output budget is derived from the model's context window, not from the request
Source code in backend/src/domains/conversations/endpoints.py
star_message(payload, message_repo=Depends(get_message_repository), db=Depends(get_db))
async
¶
Mark a message as starred (bookmarked for later reference).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
MessageStarRequest
|
MessageStarRequest with message_id to star. |
required |
message_repo
|
MessageRepository
|
Injected message repository. |
Depends(get_message_repository)
|
db
|
Session
|
Database session for transaction control. |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
Success confirmation with state="success". |
Example
::
POST /erudi/conversations/star_message
{
"message_id": 345
}
→ {"state": "success", "message": "Message starred successfully"}
Note
Starred messages can be filtered/highlighted in frontend for quick access to important responses or bookmarked content.
Source code in backend/src/domains/conversations/endpoints.py
store_error_message(conversation_id, service=Depends(get_conversation_service), db=Depends(get_db))
async
¶
Store an error message when AI generation fails (fallback mechanism).
Used by frontend to record generation failures (OOM, timeout, model crash). Ensures conversation history remains consistent even after errors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
int
|
ID of the conversation where error occurred. |
required |
service
|
ConversationService
|
Injected conversation service. |
Depends(get_conversation_service)
|
db
|
Session
|
Database session for transaction control. |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
ID of the created error message record. |
Example
::
POST /erudi/conversations/42/store_error_message
→ {"error_message_id": 567}
Note
Error messages are stored with role="assistant" and content indicating failure (e.g., "Generation failed due to system error").
Source code in backend/src/domains/conversations/endpoints.py
unstar_message(payload, message_repo=Depends(get_message_repository), db=Depends(get_db))
async
¶
Remove star/bookmark from a previously starred message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
MessageStarRequest
|
MessageStarRequest with message_id to unstar. |
required |
message_repo
|
MessageRepository
|
Injected message repository. |
Depends(get_message_repository)
|
db
|
Session
|
Database session for transaction control. |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
Success confirmation with state="success". |
Example
::
POST /erudi/conversations/unstar_message
{
"message_id": 345
}
→ {"state": "success", "message": "Message unstarred successfully"}
Source code in backend/src/domains/conversations/endpoints.py
update_conversation(conversation_id, payload, service=Depends(get_conversation_service), db=Depends(get_db))
async
¶
Update conversation metadata (name, LLM, generation parameters).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
int
|
ID of the conversation to update. |
required |
payload
|
ConversationUpdate
|
ConversationUpdate schema with optional name, llm_id, temperature, top_p, max_tokens, custom_prompt. |
required |
service
|
ConversationService
|
Injected conversation service. |
Depends(get_conversation_service)
|
db
|
Session
|
Database session for transaction control. |
Depends(get_db)
|
Returns:
| Name | Type | Description |
|---|---|---|
ConversationResponse |
Updated conversation with new metadata. |
Example
::
PATCH /erudi/conversations/42
{
"name": "Python Advanced Topics",
"llm_id": 7,
"temperature": 0.5,
"max_tokens": 2048
}
→ {
"id": 42,
"name": "Python Advanced Topics",
"llm_id": 7,
"temperature": 0.5,
"max_tokens": 2048,
...
}
Note
Only provided fields are updated. Omitted fields remain unchanged.
Source code in backend/src/domains/conversations/endpoints.py
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 | |
src.domains.conversations.schemas
¶
Pydantic schemas for conversation and message data validation.
This module defines request/response schemas for: - Message creation and responses (user/assistant messages) - Conversation CRUD operations (create, update, read) - Query parameters for streaming generation - Bulk operations (delete multiple conversations) - Message starring (bookmarking)
Schema Hierarchy
::
MessageBase → MessageResponse
ConversationBase → ConversationCreate, ConversationUpdate, ConversationResponse
ConversationResponse → ConversationWithMessagesResponse
Validation Rules
- Message content: non-empty, no length cap
- Message sender: Must be "user" or "assistant"
- Temperature: 0.0-2.0 (default 0.2)
- Top-p: 0.0-1.0 (default 0.5)
- Max tokens: 1-32768 (default 1024)
- Custom prompt: Max 4096 characters
Example
Create conversation request::
payload = ConversationCreate(
llm_id=5,
temperature=0.7,
top_p=0.9,
max_tokens=2048,
custom_prompt="You are a helpful coding assistant."
)
Query conversation with message::
query = ConversationQuery(
question="Explain asyncio in Python",
temperature=0.5,
)
Note
All schemas use Pydantic v2 field validation with clear error messages. from_attributes=True allows ORM model conversion to schema.
ConversationBase
¶
Bases: BaseModel
Base schema for conversations with required LLM ID.
Attributes:
| Name | Type | Description |
|---|---|---|
llm_id |
int
|
ID of the LLM model to use for this conversation. |
ConversationCreate
¶
Bases: ConversationBase
Schema for creating new conversations with generation parameters.
Attributes:
| Name | Type | Description |
|---|---|---|
llm_id |
int
|
Inherited from ConversationBase. |
temperature |
Optional[float]
|
Sampling temperature (0.0-2.0, default 0.2). |
top_p |
Optional[float]
|
Nucleus sampling threshold (0.0-1.0, default 0.5). |
max_tokens |
Optional[int]
|
Legacy fallback ceiling, used ONLY when the engine cannot report a context window; with a window, the output budget is automatic and this value is ignored. |
custom_prompt |
Optional[str]
|
Custom system prompt override (max 4096 chars). |
Example
::
conv = ConversationCreate(
llm_id=3,
temperature=0.8,
top_p=0.95,
max_tokens=2048,
custom_prompt="You are a creative storyteller."
)
ConversationQuery
¶
Bases: BaseModel
Schema for querying conversations with streaming generation parameters.
Attributes:
| Name | Type | Description |
|---|---|---|
question |
str
|
User message/query to send to LLM. |
images |
Optional[List[str]]
|
Optional base64 data-URL images attached to the question (vision models). |
image_paths |
Optional[List[str]]
|
Local filesystem path of each image, parallel to |
attachments |
Optional[List[str]]
|
Local filesystem paths of documents/folders attached to the question. |
temperature |
Optional[float]
|
Optional temperature override for this query only. |
top_p |
Optional[float]
|
Optional top_p override for this query only. |
max_new_tokens |
Optional[int]
|
Accepted and ignored -- the output budget is computed per
model call from the context window the model runs in
( |
custom_prompt |
Optional[str]
|
Optional system prompt override for this query only. |
Example
::
query = ConversationQuery(
question="Explain list comprehensions",
temperature=0.3,
)
ConversationResponse
¶
Bases: ConversationBase
Schema for conversation responses with full metadata.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
int
|
Conversation database ID. |
created_at |
datetime
|
Conversation creation timestamp. |
last_message_time |
datetime
|
Timestamp of most recent message. |
name |
str
|
Conversation display name. |
temperature |
float
|
Current temperature setting. |
top_p |
float
|
Current top_p setting. |
max_tokens |
int
|
Current max_tokens setting. |
custom_prompt |
str
|
Current system prompt. |
ConversationUpdate
¶
Bases: ConversationBase
Schema for partial conversation updates (PATCH operations).
All fields are optional to support partial updates. Only provided fields will be updated in the database.
Attributes:
| Name | Type | Description |
|---|---|---|
llm_id |
Optional[int]
|
Optional LLM ID override. |
name |
Optional[str]
|
Optional conversation name. |
temperature |
Optional[float]
|
Optional temperature override. |
top_p |
Optional[float]
|
Optional top_p override. |
max_tokens |
Optional[int]
|
Legacy fallback ceiling (window-less engines only); persisted but ignored whenever the engine reports a window. |
custom_prompt |
Optional[str]
|
Optional system prompt override. |
Example
::
update = ConversationUpdate(
name="Python Best Practices",
temperature=0.3 # Only update name and temperature
)
ConversationWithMessagesResponse
¶
Bases: ConversationResponse
Extended conversation response including full message history.
Attributes:
| Name | Type | Description |
|---|---|---|
messages |
List[MessageResponse]
|
List of all MessageResponse objects in conversation. |
Example
::
GET /conversations/42
→ ConversationWithMessagesResponse {
id: 42,
name: "Python Tutorial",
messages: [
MessageResponse(...),
MessageResponse(...)
]
}
MessageBase
¶
Bases: BaseModel
Base schema for messages with sender and content validation.
Attributes:
| Name | Type | Description |
|---|---|---|
sender |
str
|
Message sender, must be "user" or "assistant". |
content |
str
|
Message content, non-empty, no length cap. |
validate_sender(v)
¶
Ensure sender is either 'user', 'assistant', or 'llm'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
v
|
Sender value to validate. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
Validated sender value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If sender is not "user", "assistant", or "llm". |
Source code in backend/src/domains/conversations/schemas.py
MessageResponse
¶
Bases: MessageBase
Schema for message responses with database fields.
Extends MessageBase with id, conversation_id, timestamp, starred.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
int
|
Database message ID. |
conversation_id |
int
|
Parent conversation ID. |
timestamp |
datetime
|
Message creation timestamp. |
starred |
bool
|
Whether message is bookmarked (default False). |
trace |
Optional[List[dict]]
|
Optional list of the assistant turn's non-answer stream events
(thinking / tool_call / tool_result, and the compaction |
MessageStarRequest
¶
Bases: BaseModel
Schema for starring/unstarring messages.
Attributes:
| Name | Type | Description |
|---|---|---|
message_id |
int
|
ID of the message to toggle star state. |
Example
::
POST /conversations/star_message
{
"message_id": 345
}
src.domains.conversations.services
¶
Services for conversation management and streaming AI generation.
Business logic for the conversation lifecycle and streaming generation. Since the
LangChain refactor, generation goes through AgentRunner (one create_agent
per turn, history persisted in the LangGraph checkpointer) instead of the
hand-rolled prompt/engine loop. The SQLAlchemy Message/Conversation tables
remain the source of truth for display (fetch_messages, starred, timestamps); the
checkpointer holds the agent's working state and rolling summary.
Layering: streaming methods are async generators (the FastAPI StreamingResponse
consumes them directly on the event loop); all synchronous SQLAlchemy work is
wrapped in run_in_threadpool so DB commits never block the loop.
ConversationService(db, checkpointer=None)
¶
Service for managing conversations and message processing.
Initialize the service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
SQLAlchemy session for repository operations. |
required | |
checkpointer
|
LangGraph checkpointer (app-wide |
None
|
Source code in backend/src/domains/conversations/services.py
create_conversation(llm_id, temperature=None, top_p=None, max_tokens=None, custom_prompt='', web_search_enabled=None, reasoning_effort=None, compaction_threshold=None)
¶
Create a new conversation with the given LLM and generation params.
temperature / top_p / max_tokens left None resolve to the
MODEL's defaults (#388: the captured generation_config, else the neutral
constants, see src.database.generation_hints); an explicit value
wins. Mirrors
web_search_enabled=None, which copies the GLOBAL user-settings
default at creation (#310), reasoning_effort=None, which copies
the global default level the same way (1.1.2), and
compaction_threshold=None, which copies the global default
threshold (#607). The conversation owns its values afterwards -- later
model/global changes never retro-affect it.
Source code in backend/src/domains/conversations/services.py
delete_conversation(conversation_id)
async
¶
Delete a conversation and purge its checkpointer thread.
Both stores must be cleared: ids can be reused after a fresh start or a sequence reset, so a stale checkpointer thread would otherwise leak a deleted conversation's agent context into a future one with the same id (BLOCKER B3).
Source code in backend/src/domains/conversations/services.py
generate_title_stream(conversation_id, question)
async
¶
Stream an auto-generated 2–4 word title (stateless one-shot).
Source code in backend/src/domains/conversations/services.py
query_and_respond_stream(conversation_id, payload)
async
¶
Stream the agent's turn as NDJSON events (application/x-ndjson).
One JSON object per line: answer (streamed answer text), thinking
(streamed reasoning), tool_call / tool_result (agent activity),
compaction (#614: phase: "start" while earlier messages are being
summarized, phase: "done" with summarized, reason, summary
and summary_lost once they were), memory_reload
(the model reloads: the memory brake unloaded it, or, with
reason: "idle", the idle timeout did) / memory_warning (this
machine's memory), error (the mapped ERROR
sentinel), then a terminal done (#90).
The answer text is accumulated exactly as before and persisted as the
assistant message content; thinking, tool and compaction-done
events are collected into an ordered trace (capped, drop-oldest,
compactions pinned) and persisted alongside so the panel and the
compaction marker replay on reload. compaction start and the
memory events reach the wire only. Persists the user message up front (so
it shows immediately); the agent restores prior history from the
checkpointer.
Error mapping (#252/#225-D4): the runner yields its curated error as an
answer event carrying the ERROR sentinel. Here that answer is (a) still
appended to assistant_response so the persisted content is byte-for-byte
the pre-#90 error turn, and (b) re-framed on the wire as an error event
(the frontend renders the red bubble from error as it did from the
sentinel substring). Error turns persist only their compaction events
(the compaction is checkpointed even when the model call then fails).
Source code in backend/src/domains/conversations/services.py
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 | |
store_error_message(conversation_id)
¶
Persist a fallback error message (called by the frontend on failure).
Source code in backend/src/domains/conversations/services.py
update_conversation(conversation_id, name=None, llm_id=None, temperature=None, top_p=None, max_tokens=None, custom_prompt=None, web_search_enabled=None, reasoning_effort=None, compaction_threshold=None)
¶
Partial update of conversation metadata (only non-None fields).
Source code in backend/src/domains/conversations/services.py
build_stream_error_event(answer_event)
¶
The wire error event for a sentinel-carrying answer event.
The renderer has always read {"t": "error", "text": ...} and renders it
as the red turn. A failure the engine could identify adds two keys the
renderer acts on rather than displays: code (one of
src.engines.cuda_compatibility.CUDA_FAILURE_CODES) and raw (the
child's captured output, shown in the copyable block of the decision
dialog). Both are omitted for every other failure, so the untyped path
keeps exactly the shape it had.