Files
pablan/backend/app/authoring/schema.py
T
ProfessorNovaandClaude Opus 5 784b76baf7 Pablan, as it stands
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
2026-09-04 09:21:37 +02:00

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