mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-08 20:25:19 +02:00
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.
This commit is contained in:
parent
82c5dc5b02
commit
b7ea829371
58 changed files with 25 additions and 17 deletions
6
surfsense_backend/app/agents/runtime/__init__.py
Normal file
6
surfsense_backend/app/agents/runtime/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""Agent runtime infrastructure wired by the boundary (not agent code).
|
||||||
|
|
||||||
|
Modules here are cross-agent infra used to *run* agents (e.g. the LangGraph
|
||||||
|
Postgres checkpointer), as opposed to ``app/agents/shared/`` which holds code
|
||||||
|
imported by 2+ sibling agent packages.
|
||||||
|
"""
|
||||||
|
|
@ -23,7 +23,7 @@ from starlette.requests import Request as StarletteRequest
|
||||||
from starlette.responses import Response as StarletteResponse
|
from starlette.responses import Response as StarletteResponse
|
||||||
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
||||||
|
|
||||||
from app.agents.shared.checkpointer import (
|
from app.agents.runtime.checkpointer import (
|
||||||
close_checkpointer,
|
close_checkpointer,
|
||||||
setup_checkpointer_tables,
|
setup_checkpointer_tables,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Thin compatibility wrapper around :mod:`app.agents.shared.prompts.composer`.
|
Thin compatibility wrapper around :mod:`app.prompts.system_prompt_composer.composer`.
|
||||||
|
|
||||||
The composer split the previous monolithic prompt string into a fragment
|
The composer split the previous monolithic prompt string into a fragment
|
||||||
tree under ``prompts/`` plus a model-family dispatch step (see the
|
tree under ``prompts/`` plus a model-family dispatch step (see the
|
||||||
|
|
@ -11,7 +11,7 @@ that existing call sites — the multi-agent chat factory, anonymous chat
|
||||||
routes, and the configurable-prompt admin path — keep working without churn.
|
routes, and the configurable-prompt admin path — keep working without churn.
|
||||||
|
|
||||||
For new call sites prefer importing ``compose_system_prompt`` directly
|
For new call sites prefer importing ``compose_system_prompt`` directly
|
||||||
from :mod:`app.agents.shared.prompts.composer`.
|
from :mod:`app.prompts.system_prompt_composer.composer`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -20,7 +20,7 @@ from datetime import UTC, datetime
|
||||||
|
|
||||||
from app.db import ChatVisibility
|
from app.db import ChatVisibility
|
||||||
|
|
||||||
from .prompts.composer import (
|
from .system_prompt_composer.composer import (
|
||||||
_read_fragment,
|
_read_fragment,
|
||||||
compose_system_prompt,
|
compose_system_prompt,
|
||||||
detect_provider_variant,
|
detect_provider_variant,
|
||||||
|
|
@ -55,7 +55,7 @@ def build_surfsense_system_prompt(
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Build the default SurfSense system prompt (citations on, defaults).
|
"""Build the default SurfSense system prompt (citations on, defaults).
|
||||||
|
|
||||||
See :func:`app.agents.shared.prompts.composer.compose_system_prompt`
|
See :func:`app.prompts.system_prompt_composer.composer.compose_system_prompt`
|
||||||
for full parameter docs.
|
for full parameter docs.
|
||||||
"""
|
"""
|
||||||
return compose_system_prompt(
|
return compose_system_prompt(
|
||||||
|
|
@ -84,7 +84,7 @@ def build_configurable_system_prompt(
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Build a configurable SurfSense system prompt (NewLLMConfig path).
|
"""Build a configurable SurfSense system prompt (NewLLMConfig path).
|
||||||
|
|
||||||
See :func:`app.agents.shared.prompts.composer.compose_system_prompt`
|
See :func:`app.prompts.system_prompt_composer.composer.compose_system_prompt`
|
||||||
for full parameter docs.
|
for full parameter docs.
|
||||||
"""
|
"""
|
||||||
return compose_system_prompt(
|
return compose_system_prompt(
|
||||||
|
|
@ -108,7 +108,9 @@ def get_default_system_instructions() -> str:
|
||||||
The output reflects the current fragment tree, not a baked-in constant.
|
The output reflects the current fragment tree, not a baked-in constant.
|
||||||
"""
|
"""
|
||||||
resolved_today = datetime.now(UTC).date().isoformat()
|
resolved_today = datetime.now(UTC).date().isoformat()
|
||||||
from .prompts.composer import _build_system_instructions # local import
|
from .system_prompt_composer.composer import (
|
||||||
|
_build_system_instructions, # local import
|
||||||
|
)
|
||||||
|
|
||||||
return _build_system_instructions(
|
return _build_system_instructions(
|
||||||
visibility=ChatVisibility.PRIVATE,
|
visibility=ChatVisibility.PRIVATE,
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
Prompt composer for the SurfSense ``new_chat`` agent.
|
Prompt composer for the SurfSense ``new_chat`` agent.
|
||||||
|
|
||||||
This module assembles the agent's system prompt from the markdown fragments
|
This module assembles the agent's system prompt from the markdown fragments
|
||||||
under :mod:`app.agents.shared.prompts`. It replaces the monolithic
|
under :mod:`app.prompts.system_prompt_composer`. It replaces the monolithic
|
||||||
``system_prompt.py`` with a clean, fragment-based composition:
|
``system_prompt.py`` with a clean, fragment-based composition:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
@ -119,7 +119,7 @@ def detect_provider_variant(model_name: str | None) -> ProviderVariant:
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
_PROMPTS_PACKAGE = "app.agents.shared.prompts"
|
_PROMPTS_PACKAGE = "app.prompts.system_prompt_composer"
|
||||||
|
|
||||||
|
|
||||||
def _read_fragment(subpath: str) -> str:
|
def _read_fragment(subpath: str) -> str:
|
||||||
|
|
@ -352,7 +352,7 @@ async def stream_anonymous_chat(
|
||||||
from langchain_core.messages import AIMessage, HumanMessage
|
from langchain_core.messages import AIMessage, HumanMessage
|
||||||
|
|
||||||
from app.agents.anonymous_chat import create_anonymous_chat_agent
|
from app.agents.anonymous_chat import create_anonymous_chat_agent
|
||||||
from app.agents.shared.checkpointer import get_checkpointer
|
from app.agents.runtime.checkpointer import get_checkpointer
|
||||||
from app.db import shielded_async_session
|
from app.db import shielded_async_session
|
||||||
from app.services.new_streaming_service import VercelStreamingService
|
from app.services.new_streaming_service import VercelStreamingService
|
||||||
from app.services.token_tracking_service import start_turn
|
from app.services.token_tracking_service import start_turn
|
||||||
|
|
|
||||||
|
|
@ -1934,7 +1934,7 @@ async def regenerate_response(
|
||||||
"""
|
"""
|
||||||
from langchain_core.messages import HumanMessage
|
from langchain_core.messages import HumanMessage
|
||||||
|
|
||||||
from app.agents.shared.checkpointer import get_checkpointer
|
from app.agents.runtime.checkpointer import get_checkpointer
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Verify thread exists and user has permission
|
# Verify thread exists and user has permission
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.agents.shared.system_prompt import get_default_system_instructions
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
from app.db import (
|
from app.db import (
|
||||||
NewLLMConfig,
|
NewLLMConfig,
|
||||||
|
|
@ -21,6 +20,7 @@ from app.db import (
|
||||||
User,
|
User,
|
||||||
get_async_session,
|
get_async_session,
|
||||||
)
|
)
|
||||||
|
from app.prompts.default_system_instructions import get_default_system_instructions
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
DefaultSystemInstructionsResponse,
|
DefaultSystemInstructionsResponse,
|
||||||
GlobalNewLLMConfigRead,
|
GlobalNewLLMConfigRead,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.shared.checkpointer import get_checkpointer
|
from app.agents.runtime.checkpointer import get_checkpointer
|
||||||
from app.db import SearchSourceConnectorType
|
from app.db import SearchSourceConnectorType
|
||||||
from app.services.connector_service import ConnectorService
|
from app.services.connector_service import ConnectorService
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ async def setup_connector_and_firecrawl(
|
||||||
async def get_chat_checkpointer():
|
async def get_chat_checkpointer():
|
||||||
"""Resolve the PostgreSQL checkpointer for persistent conversation memory.
|
"""Resolve the PostgreSQL checkpointer for persistent conversation memory.
|
||||||
|
|
||||||
Thin wrapper around ``app.agents.shared.checkpointer.get_checkpointer`` so
|
Thin wrapper around ``app.agents.runtime.checkpointer.get_checkpointer`` so
|
||||||
flow orchestrators can rely on a streaming-local symbol and we have a hook
|
flow orchestrators can rely on a streaming-local symbol and we have a hook
|
||||||
point if the checkpointer source ever needs to vary per flow.
|
point if the checkpointer source ever needs to vary per flow.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ from datetime import UTC, datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.agents.shared.prompts.composer import (
|
from app.db import ChatVisibility
|
||||||
|
from app.prompts.system_prompt_composer.composer import (
|
||||||
ALL_TOOL_NAMES_ORDERED,
|
ALL_TOOL_NAMES_ORDERED,
|
||||||
compose_system_prompt,
|
compose_system_prompt,
|
||||||
detect_provider_variant,
|
detect_provider_variant,
|
||||||
)
|
)
|
||||||
from app.db import ChatVisibility
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.unit
|
pytestmark = pytest.mark.unit
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ class TestProviderVariantDetection:
|
||||||
``gpt-5`` reasoning regex first. Codex is the more specialised
|
``gpt-5`` reasoning regex first. Codex is the more specialised
|
||||||
prompt and mirrors OpenCode's dispatch order.
|
prompt and mirrors OpenCode's dispatch order.
|
||||||
"""
|
"""
|
||||||
from app.agents.shared.prompts.composer import detect_provider_variant
|
from app.prompts.system_prompt_composer.composer import detect_provider_variant
|
||||||
|
|
||||||
assert detect_provider_variant("openai:gpt-5-codex") == "openai_codex"
|
assert detect_provider_variant("openai:gpt-5-codex") == "openai_codex"
|
||||||
assert detect_provider_variant("openai:gpt-5") == "openai_reasoning"
|
assert detect_provider_variant("openai:gpt-5") == "openai_reasoning"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue