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
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""Pydantic schema for authoring templates (schema version 1.0).
|
|
|
|
A template is a **Markdown skeleton** — a starting document with headings the
|
|
author fills in — plus a persona and optional per-section hints that steer the
|
|
section-refinement model. It is declarative configuration, not code (see
|
|
docs/authoring-templates.md), stored in `templates.config` (JSONB) and
|
|
validated on load.
|
|
"""
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, field_validator
|
|
|
|
|
|
class TemplateModelHints(BaseModel):
|
|
temperature: float = 0.4
|
|
# UI warning when the configured endpoint is weaker than the template
|
|
# expects; read by api/templates.py.
|
|
min_class_hint: str | None = None
|
|
|
|
|
|
class SectionHint(BaseModel):
|
|
"""Steers what the refinement model should draw out of one section.
|
|
|
|
`heading` is matched to a skeleton heading by its exact text, so the hint
|
|
only reaches the model while the author is writing under that heading.
|
|
"""
|
|
|
|
heading: str
|
|
hint: str
|
|
|
|
|
|
class TemplateMetadata(BaseModel):
|
|
visibility: Literal["public", "department", "restricted"] = "department"
|
|
|
|
|
|
class AuthoringTemplate(BaseModel):
|
|
id: str
|
|
name: str
|
|
version: str
|
|
# Names the template's shape, so a differently-shaped config is rejected
|
|
# rather than silently loaded as an authoring template.
|
|
kind: Literal["authoring"] = "authoring"
|
|
# The language this template's CONTENT is written in — persona, skeleton
|
|
# and hints, not the UI. The picker lists matching templates first.
|
|
locale: Literal["de", "en"] | None = None
|
|
description: str = ""
|
|
model: TemplateModelHints = TemplateModelHints()
|
|
persona: str
|
|
# The Markdown the editor opens with: headings the author fills in. This
|
|
# IS the starting content, not a description of it.
|
|
skeleton: str
|
|
sections: list[SectionHint] = []
|
|
title_template: str
|
|
metadata: TemplateMetadata = TemplateMetadata()
|
|
|
|
@field_validator("version", mode="before")
|
|
@classmethod
|
|
def _version_to_string(cls, value: object) -> str:
|
|
# YAML reads an unquoted 1.0 as a float.
|
|
return str(value)
|
|
|
|
def hint_for(self, heading: str) -> str | None:
|
|
for section in self.sections:
|
|
if section.heading == heading:
|
|
return section.hint
|
|
return None
|