Files
pablan/backend/app/authoring/prompts.py
T
ProfessorNovaandClaude Opus 5 97dbff309c 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 08:36:17 +02:00

82 lines
3.3 KiB
Python

"""Refinement prompt for the writing editor — natural language only.
The model refines exactly ONE section of a document the user is writing. The
rest of the document travels as prefix/suffix context so the section stays
coherent with its surroundings, but the model regenerates ONLY the section —
a large document is never re-emitted whole (FIM-style).
The base texts (persona, rules, framings) are admin-editable: they come from
`app/prompts/overrides.py::get_prompt`, which returns a DB override when one
exists and the code default (`app/prompts/defaults.py`) otherwise.
"""
from app.llm.client import ChatMessage
from app.prompts.overrides import get_prompt
def render_refine_prompt(
section: str,
*,
prefix: str,
suffix: str,
persona: str | None,
hint: str | None,
context: str | None = None,
knowledge: list[str] | None = None,
) -> list[ChatMessage]:
# A template may carry its own persona; otherwise the admin-editable default.
system_parts = [persona.strip() if persona else get_prompt("refine_persona")]
if hint:
system_parts.append(f"What this section should convey: {hint}")
if context:
# Background from the chat this capture came from, so the refinement
# is on-topic — but only as orientation, never a source of new facts.
system_parts.append(
f"Background (the conversation this document came from, for "
f"orientation only — do not invent facts from it): {context}"
)
system_parts.append(get_prompt("refine_rules"))
# The document being edited leads the user turn (prefix/suffix/section);
# the retrieved knowledge trails it, because it changes on every call and
# keeping it last leaves the stable prompt prefix reusable between calls.
user_parts: list[str] = []
if prefix.strip():
user_parts.append(
f"Text before the section (context only, do not repeat it):\n{prefix}"
)
if suffix.strip():
user_parts.append(
f"Text after the section (context only, do not repeat it):\n{suffix}"
)
user_parts.append(f"Refine only this section:\n{section}")
if knowledge:
# What the company has already documented elsewhere. It is grounding,
# not source material: it keeps terminology and facts consistent and
# lets the section point at related documents, but it must not be
# copied in or become a way to add facts the section's notes do not
# support.
joined = "\n\n".join(knowledge)
user_parts.append(f"{get_prompt('grounding_framing')}\n{joined}")
return [
{"role": "system", "content": "\n\n".join(system_parts)},
{"role": "user", "content": "\n\n".join(user_parts)},
]
def render_topic_summary_prompt(transcript: str) -> list[ChatMessage]:
"""Condense a conversation into a short search topic (a few words)."""
return [
{"role": "system", "content": get_prompt("topic_summary")},
{"role": "user", "content": f"Conversation:\n{transcript}"},
]
def render_title_prompt(content_md: str) -> list[ChatMessage]:
"""Suggest a concise document title from its written content."""
return [
{"role": "system", "content": get_prompt("title")},
{"role": "user", "content": f"Document:\n{content_md}"},
]