SurfSense/surfsense_backend/app/prompts/default_system_instructions.py
CREDO23 b7ea829371 refactor(agents): relocate boundary-only infra out of shared/
Neither module is imported by any sibling agent package, so neither belongs in
the cross-agent shared kernel:

- checkpointer.py -> app/agents/runtime/checkpointer.py
  LangGraph Postgres checkpoint saver. It's cross-agent *runtime infra* wired by
  the boundary (app lifespan + anonymous_chat & multi_agent_chat flows), not
  agent code. New app/agents/runtime/ layer holds boundary-wired agent infra.

- shared/system_prompt.py + shared/prompts/ -> app/prompts/
  The legacy single-agent prompt composer. The live agents don't use it
  (main_agent has its own system_prompt/ builder; anonymous_chat builds inline);
  its only consumer is new_llm_config_routes for displaying default instructions.
  Moved to the existing non-agent prompt domain:
    system_prompt.py        -> app/prompts/default_system_instructions.py
    prompts/                 -> app/prompts/system_prompt_composer/

app/agents/shared/ now contains only genuinely cross-agent code: context,
middleware/{compaction,retry_after,dedup_tool_calls}, tools/.

NOTE: get_default_system_instructions() (LLM-config UI) composes from the legacy
library, which differs from what the live agents actually run -- pre-existing
latent staleness, not changed here.
2026-06-05 12:36:44 +02:00

135 lines
4.9 KiB
Python

"""
Thin compatibility wrapper around :mod:`app.prompts.system_prompt_composer.composer`.
The composer split the previous monolithic prompt string into a fragment
tree under ``prompts/`` plus a model-family dispatch step (see the
composer module docstring for credits). This module preserves the public
function surface (``build_surfsense_system_prompt`` /
``build_configurable_system_prompt`` /
``get_default_system_instructions`` / ``SURFSENSE_SYSTEM_PROMPT``) so
that existing call sites — the multi-agent chat factory, anonymous chat
routes, and the configurable-prompt admin path — keep working without churn.
For new call sites prefer importing ``compose_system_prompt`` directly
from :mod:`app.prompts.system_prompt_composer.composer`.
"""
from __future__ import annotations
from datetime import UTC, datetime
from app.db import ChatVisibility
from .system_prompt_composer.composer import (
_read_fragment,
compose_system_prompt,
detect_provider_variant,
)
# Optional routing fragments under ``prompts/routing/`` (see composer).
_DEFAULT_CONNECTOR_ROUTING: tuple[str, ...] = ("linear", "slack")
# Public re-exports for backwards compatibility (some legacy code reads the
# raw default-instructions text directly).
SURFSENSE_SYSTEM_INSTRUCTIONS_TEMPLATE = (
"<system_instruction>\nDefault SurfSense agent system instructions are now\n"
"composed from prompts/base/*.md. See compose_system_prompt() for details.\n"
"</system_instruction>"
)
# Citation block re-exposed for legacy importers that referenced this constant
# directly. The composer is the canonical source; this is a frozen snapshot
# loaded at module-init time.
SURFSENSE_CITATION_INSTRUCTIONS = _read_fragment("base/citations_on.md")
SURFSENSE_NO_CITATION_INSTRUCTIONS = _read_fragment("base/citations_off.md")
def build_surfsense_system_prompt(
today: datetime | None = None,
thread_visibility: ChatVisibility | None = None,
enabled_tool_names: set[str] | None = None,
disabled_tool_names: set[str] | None = None,
mcp_connector_tools: dict[str, list[str]] | None = None,
*,
model_name: str | None = None,
) -> str:
"""Build the default SurfSense system prompt (citations on, defaults).
See :func:`app.prompts.system_prompt_composer.composer.compose_system_prompt`
for full parameter docs.
"""
return compose_system_prompt(
today=today,
thread_visibility=thread_visibility,
enabled_tool_names=enabled_tool_names,
disabled_tool_names=disabled_tool_names,
mcp_connector_tools=mcp_connector_tools,
citations_enabled=True,
model_name=model_name,
connector_routing=_DEFAULT_CONNECTOR_ROUTING,
)
def build_configurable_system_prompt(
custom_system_instructions: str | None = None,
use_default_system_instructions: bool = True,
citations_enabled: bool = True,
today: datetime | None = None,
thread_visibility: ChatVisibility | None = None,
enabled_tool_names: set[str] | None = None,
disabled_tool_names: set[str] | None = None,
mcp_connector_tools: dict[str, list[str]] | None = None,
*,
model_name: str | None = None,
) -> str:
"""Build a configurable SurfSense system prompt (NewLLMConfig path).
See :func:`app.prompts.system_prompt_composer.composer.compose_system_prompt`
for full parameter docs.
"""
return compose_system_prompt(
today=today,
thread_visibility=thread_visibility,
enabled_tool_names=enabled_tool_names,
disabled_tool_names=disabled_tool_names,
mcp_connector_tools=mcp_connector_tools,
custom_system_instructions=custom_system_instructions,
use_default_system_instructions=use_default_system_instructions,
citations_enabled=citations_enabled,
model_name=model_name,
connector_routing=_DEFAULT_CONNECTOR_ROUTING,
)
def get_default_system_instructions() -> str:
"""Return the default ``<system_instruction>`` block (no tools / citations).
Useful for populating the UI when seeding ``NewLLMConfig.system_instructions``.
The output reflects the current fragment tree, not a baked-in constant.
"""
resolved_today = datetime.now(UTC).date().isoformat()
from .system_prompt_composer.composer import (
_build_system_instructions, # local import
)
return _build_system_instructions(
visibility=ChatVisibility.PRIVATE,
resolved_today=resolved_today,
).strip()
# Backwards compatibility — some modules import the constant directly.
SURFSENSE_SYSTEM_PROMPT = build_surfsense_system_prompt()
__all__ = [
"SURFSENSE_CITATION_INSTRUCTIONS",
"SURFSENSE_NO_CITATION_INSTRUCTIONS",
"SURFSENSE_SYSTEM_INSTRUCTIONS_TEMPLATE",
"SURFSENSE_SYSTEM_PROMPT",
"build_configurable_system_prompt",
"build_surfsense_system_prompt",
"compose_system_prompt",
"detect_provider_variant",
"get_default_system_instructions",
]