Self-hosted knowledge management for SMEs: a split-screen Markdown editor whose sections an LLM refines while you write, and RAG question answering over the documents that result. FastAPI + Postgres/pgvector on the back, SvelteKit on the front, everything OpenAI-compatible and self-hostable. Squashed into a single commit; the development history stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
56 lines
1.5 KiB
Python
56 lines
1.5 KiB
Python
"""Request and response shapes for conversations and their turns."""
|
|
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
from app.models import ConversationMode, MessageRole
|
|
|
|
|
|
class ConversationCreate(BaseModel):
|
|
mode: ConversationMode
|
|
|
|
|
|
class ConversationSummary(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: uuid.UUID
|
|
title: str | None = None
|
|
|
|
|
|
class MessageSource(BaseModel):
|
|
document_id: uuid.UUID
|
|
title: str
|
|
heading_path: str
|
|
excerpt: str = ""
|
|
# True when the passage was passed to the model; False for passages that
|
|
# were retrieved but dropped as too weak (a no-answer turn). Old messages
|
|
# predate the flag, so it defaults to True (they were all cited).
|
|
used: bool = True
|
|
# The cited document has an unanswered request to check it. Snapshotted
|
|
# with the citation, so a reload shows what was true when it was answered.
|
|
review_pending: bool = False
|
|
|
|
|
|
class MessageOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: uuid.UUID
|
|
role: MessageRole
|
|
content: str
|
|
created_at: datetime
|
|
sources: list[MessageSource] = []
|
|
# Set when no model answered this turn and `sources` is a plain full-text
|
|
# result list instead: the `llm_*` code that caused it, which the frontend
|
|
# phrases. Null on every normal turn.
|
|
fallback: str | None = None
|
|
|
|
|
|
class ConversationDetail(ConversationSummary):
|
|
messages: list[MessageOut] = []
|
|
|
|
|
|
class SendMessage(BaseModel):
|
|
content: str = Field(min_length=1, max_length=8000)
|