mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-08 15:22:39 +02:00
Compose async multi-agent chat entrypoint and drop legacy supervisor scaffolding.
This commit is contained in:
parent
5497f472b2
commit
388e86ebc9
27 changed files with 149 additions and 779 deletions
|
|
@ -1,20 +1,14 @@
|
||||||
"""
|
"""
|
||||||
Multi-agent chat (LangChain Subagents pattern).
|
Multi-agent chat (LangChain Subagents pattern).
|
||||||
|
|
||||||
**Vertical slices**
|
**Layout (SRP)**
|
||||||
|
|
||||||
- :mod:`gmail` — connector tools, domain agent, ``domain_prompt.md``
|
- :mod:`expert_agent.builtins` — general categories from the tool registry (research, memory, deliverables — not tied to one vendor).
|
||||||
- :mod:`calendar` — connector tools, domain agent, ``domain_prompt.md``
|
- :mod:`expert_agent.connectors` — external integrations (one subgraph per product where split).
|
||||||
|
- :mod:`core` — prompts, compiled subgraph helper, delegation, registry subsets, tool-factory kwargs (:mod:`core.bindings`).
|
||||||
**Shared**
|
- :mod:`routing` — supervisor-facing ``@tool`` routers → domain invoke.
|
||||||
|
- :mod:`supervisor` — orchestrator graph + ``supervisor_prompt.md``.
|
||||||
- :mod:`shared` — prompt loader, ``build_domain_agent``, connector deps, invoke result parsing
|
- :mod:`integration` — async ``create_multi_agent_chat`` composer (partitions MCP tools into experts).
|
||||||
|
|
||||||
**Cross-cutting**
|
|
||||||
|
|
||||||
- :mod:`routing` — supervisor routing tools + invoke helpers
|
|
||||||
- :mod:`supervisor` — top graph + ``supervisor_prompt.md``
|
|
||||||
- :mod:`integration` — ``create_multi_agent_chat``
|
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
https://docs.langchain.com/oss/python/langchain/multi-agent
|
https://docs.langchain.com/oss/python/langchain/multi-agent
|
||||||
|
|
@ -23,38 +17,116 @@ https://docs.langchain.com/oss/python/langchain/multi-agent/subagents
|
||||||
Display name: ``multi-agent-chat`` — Python package: ``multi_agent_chat``.
|
Display name: ``multi-agent-chat`` — Python package: ``multi_agent_chat``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.calendar import (
|
from app.agents.multi_agent_chat.expert_agent.builtins.deliverables import (
|
||||||
build_calendar_domain_agent,
|
build_deliverables_tools,
|
||||||
build_google_calendar_connector_tools,
|
build_deliverables_domain_agent,
|
||||||
)
|
)
|
||||||
from app.agents.multi_agent_chat.gmail import (
|
from app.agents.multi_agent_chat.expert_agent.builtins.memory import (
|
||||||
build_gmail_connector_tools,
|
build_memory_tools,
|
||||||
|
build_memory_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.builtins.research import (
|
||||||
|
build_research_tools,
|
||||||
|
build_research_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.calendar import (
|
||||||
|
build_calendar_domain_agent,
|
||||||
|
build_calendar_tools,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.confluence import (
|
||||||
|
build_confluence_tools,
|
||||||
|
build_confluence_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.discord import (
|
||||||
|
build_discord_tools,
|
||||||
|
build_discord_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.dropbox import (
|
||||||
|
build_dropbox_tools,
|
||||||
|
build_dropbox_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.gmail import (
|
||||||
|
build_gmail_tools,
|
||||||
build_gmail_domain_agent,
|
build_gmail_domain_agent,
|
||||||
)
|
)
|
||||||
from app.agents.multi_agent_chat.integration import create_multi_agent_chat
|
from app.agents.multi_agent_chat.expert_agent.connectors.google_drive import (
|
||||||
from app.agents.multi_agent_chat.shared import (
|
build_google_drive_tools,
|
||||||
|
build_google_drive_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.luma import (
|
||||||
|
build_luma_tools,
|
||||||
|
build_luma_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.notion import (
|
||||||
|
build_notion_tools,
|
||||||
|
build_notion_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.onedrive import (
|
||||||
|
build_onedrive_tools,
|
||||||
|
build_onedrive_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.expert_agent.connectors.teams import (
|
||||||
|
build_teams_tools,
|
||||||
|
build_teams_domain_agent,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.core import (
|
||||||
|
REGISTRY_ROUTING_CATEGORY_KEYS,
|
||||||
|
TOOL_NAMES_BY_CATEGORY,
|
||||||
build_domain_agent,
|
build_domain_agent,
|
||||||
|
build_registry_dependencies,
|
||||||
|
build_registry_tools_for_category,
|
||||||
|
compose_child_task,
|
||||||
connector_binding,
|
connector_binding,
|
||||||
extract_last_assistant_text,
|
extract_last_assistant_text,
|
||||||
read_prompt_md,
|
read_prompt_md,
|
||||||
)
|
)
|
||||||
|
from app.agents.multi_agent_chat.integration import create_multi_agent_chat
|
||||||
from app.agents.multi_agent_chat.routing import (
|
from app.agents.multi_agent_chat.routing import (
|
||||||
|
DomainRoutingSpec,
|
||||||
build_supervisor_routing_tools,
|
build_supervisor_routing_tools,
|
||||||
routing_tools_from_domain_agents,
|
routing_tools_from_specs,
|
||||||
)
|
)
|
||||||
from app.agents.multi_agent_chat.supervisor import build_supervisor_agent
|
from app.agents.multi_agent_chat.supervisor import build_supervisor_agent
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"REGISTRY_ROUTING_CATEGORY_KEYS",
|
||||||
|
"TOOL_NAMES_BY_CATEGORY",
|
||||||
|
"DomainRoutingSpec",
|
||||||
"build_calendar_domain_agent",
|
"build_calendar_domain_agent",
|
||||||
|
"build_confluence_tools",
|
||||||
|
"build_confluence_domain_agent",
|
||||||
|
"build_deliverables_tools",
|
||||||
|
"build_deliverables_domain_agent",
|
||||||
|
"build_discord_tools",
|
||||||
|
"build_discord_domain_agent",
|
||||||
"build_domain_agent",
|
"build_domain_agent",
|
||||||
"build_gmail_connector_tools",
|
"build_dropbox_tools",
|
||||||
|
"build_dropbox_domain_agent",
|
||||||
|
"build_gmail_tools",
|
||||||
"build_gmail_domain_agent",
|
"build_gmail_domain_agent",
|
||||||
"build_google_calendar_connector_tools",
|
"build_calendar_tools",
|
||||||
|
"build_google_drive_tools",
|
||||||
|
"build_google_drive_domain_agent",
|
||||||
|
"build_luma_tools",
|
||||||
|
"build_luma_domain_agent",
|
||||||
|
"build_memory_tools",
|
||||||
|
"build_memory_domain_agent",
|
||||||
|
"build_notion_tools",
|
||||||
|
"build_notion_domain_agent",
|
||||||
|
"build_onedrive_tools",
|
||||||
|
"build_onedrive_domain_agent",
|
||||||
|
"build_registry_dependencies",
|
||||||
|
"build_registry_tools_for_category",
|
||||||
|
"build_research_tools",
|
||||||
|
"build_research_domain_agent",
|
||||||
"build_supervisor_agent",
|
"build_supervisor_agent",
|
||||||
"build_supervisor_routing_tools",
|
"build_supervisor_routing_tools",
|
||||||
|
"build_teams_tools",
|
||||||
|
"build_teams_domain_agent",
|
||||||
"connector_binding",
|
"connector_binding",
|
||||||
|
"compose_child_task",
|
||||||
"create_multi_agent_chat",
|
"create_multi_agent_chat",
|
||||||
"extract_last_assistant_text",
|
"extract_last_assistant_text",
|
||||||
"read_prompt_md",
|
"read_prompt_md",
|
||||||
"routing_tools_from_domain_agents",
|
"routing_tools_from_specs",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
"""Google Calendar vertical slice: connector tools, domain agent, ``domain_prompt.md``."""
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.calendar.agent import build_calendar_domain_agent
|
|
||||||
from app.agents.multi_agent_chat.calendar.connector_tools import (
|
|
||||||
build_google_calendar_connector_tools,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"build_calendar_domain_agent",
|
|
||||||
"build_google_calendar_connector_tools",
|
|
||||||
]
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
"""Google Calendar domain agent graph."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
import app.agents.multi_agent_chat.calendar as calendar_pkg
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.shared.domain_agent_factory import build_domain_agent
|
|
||||||
|
|
||||||
|
|
||||||
def build_calendar_domain_agent(llm: BaseChatModel, tools: Sequence[BaseTool]):
|
|
||||||
"""Compiled Calendar domain-agent graph (prompt + tools co-located under ``calendar``)."""
|
|
||||||
return build_domain_agent(
|
|
||||||
llm,
|
|
||||||
tools,
|
|
||||||
prompt_package=calendar_pkg.__name__,
|
|
||||||
prompt_stem="domain_prompt",
|
|
||||||
)
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
"""Google Calendar connector LangChain tools (``new_chat`` factories)."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.shared.deps import connector_binding
|
|
||||||
from app.agents.new_chat.tools.google_calendar import (
|
|
||||||
create_create_calendar_event_tool,
|
|
||||||
create_delete_calendar_event_tool,
|
|
||||||
create_search_calendar_events_tool,
|
|
||||||
create_update_calendar_event_tool,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_google_calendar_connector_tools(
|
|
||||||
*,
|
|
||||||
db_session: AsyncSession,
|
|
||||||
search_space_id: int,
|
|
||||||
user_id: str,
|
|
||||||
) -> list[BaseTool]:
|
|
||||||
d = connector_binding(
|
|
||||||
db_session=db_session,
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
user_id=user_id,
|
|
||||||
)
|
|
||||||
return [
|
|
||||||
create_search_calendar_events_tool(**d),
|
|
||||||
create_create_calendar_event_tool(**d),
|
|
||||||
create_update_calendar_event_tool(**d),
|
|
||||||
create_delete_calendar_event_tool(**d),
|
|
||||||
]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
You are the Google Calendar domain agent. Use only the tools provided to complete calendar-related tasks. Stay focused on scheduling and calendar operations and respond concisely.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
"""Gmail vertical slice: connector tools, domain agent, ``domain_prompt.md``."""
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.gmail.agent import build_gmail_domain_agent
|
|
||||||
from app.agents.multi_agent_chat.gmail.connector_tools import build_gmail_connector_tools
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"build_gmail_connector_tools",
|
|
||||||
"build_gmail_domain_agent",
|
|
||||||
]
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
"""Gmail domain agent graph."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
import app.agents.multi_agent_chat.gmail as gmail_pkg
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.shared.domain_agent_factory import build_domain_agent
|
|
||||||
|
|
||||||
|
|
||||||
def build_gmail_domain_agent(llm: BaseChatModel, tools: Sequence[BaseTool]):
|
|
||||||
"""Compiled Gmail domain-agent graph (prompt + tools co-located under ``gmail``)."""
|
|
||||||
return build_domain_agent(
|
|
||||||
llm,
|
|
||||||
tools,
|
|
||||||
prompt_package=gmail_pkg.__name__,
|
|
||||||
prompt_stem="domain_prompt",
|
|
||||||
)
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
"""Gmail connector LangChain tools (``new_chat`` factories; order matches registry)."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.shared.deps import connector_binding
|
|
||||||
from app.agents.new_chat.tools.gmail import (
|
|
||||||
create_create_gmail_draft_tool,
|
|
||||||
create_read_gmail_email_tool,
|
|
||||||
create_search_gmail_tool,
|
|
||||||
create_send_gmail_email_tool,
|
|
||||||
create_trash_gmail_email_tool,
|
|
||||||
create_update_gmail_draft_tool,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_gmail_connector_tools(
|
|
||||||
*,
|
|
||||||
db_session: AsyncSession,
|
|
||||||
search_space_id: int,
|
|
||||||
user_id: str,
|
|
||||||
) -> list[BaseTool]:
|
|
||||||
d = connector_binding(
|
|
||||||
db_session=db_session,
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
user_id=user_id,
|
|
||||||
)
|
|
||||||
return [
|
|
||||||
create_search_gmail_tool(**d),
|
|
||||||
create_read_gmail_email_tool(**d),
|
|
||||||
create_create_gmail_draft_tool(**d),
|
|
||||||
create_send_gmail_email_tool(**d),
|
|
||||||
create_trash_gmail_email_tool(**d),
|
|
||||||
create_update_gmail_draft_tool(**d),
|
|
||||||
]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
You are the Gmail domain agent. Use only the tools provided to complete Gmail-related tasks. Stay focused on email operations and respond concisely.
|
|
||||||
|
|
@ -2,36 +2,74 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
from langchain_core.language_models import BaseChatModel
|
||||||
|
from langchain_core.tools import BaseTool
|
||||||
from langgraph.types import Checkpointer
|
from langgraph.types import Checkpointer
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.calendar import build_google_calendar_connector_tools
|
from app.db import ChatVisibility
|
||||||
from app.agents.multi_agent_chat.gmail import build_gmail_connector_tools
|
|
||||||
|
from app.agents.new_chat.tools.mcp_tool import load_mcp_tools
|
||||||
|
|
||||||
|
from app.agents.multi_agent_chat.core.mcp_partition import (
|
||||||
|
fetch_mcp_connector_metadata_maps,
|
||||||
|
partition_mcp_tools_by_expert_route,
|
||||||
|
)
|
||||||
|
from app.agents.multi_agent_chat.core.registry import build_registry_dependencies
|
||||||
from app.agents.multi_agent_chat.routing.supervisor_routing import build_supervisor_routing_tools
|
from app.agents.multi_agent_chat.routing.supervisor_routing import build_supervisor_routing_tools
|
||||||
from app.agents.multi_agent_chat.supervisor import build_supervisor_agent
|
from app.agents.multi_agent_chat.supervisor import build_supervisor_agent
|
||||||
|
|
||||||
|
|
||||||
def create_multi_agent_chat(
|
async def create_multi_agent_chat(
|
||||||
llm: BaseChatModel,
|
llm: BaseChatModel,
|
||||||
*,
|
*,
|
||||||
db_session: AsyncSession,
|
db_session: AsyncSession,
|
||||||
search_space_id: int,
|
search_space_id: int,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
checkpointer: Checkpointer | None = None,
|
checkpointer: Checkpointer | None = None,
|
||||||
|
thread_id: str | None = None,
|
||||||
|
firecrawl_api_key: str | None = None,
|
||||||
|
connector_service: Any | None = None,
|
||||||
|
available_connectors: list[str] | None = None,
|
||||||
|
available_document_types: list[str] | None = None,
|
||||||
|
thread_visibility: ChatVisibility = ChatVisibility.PRIVATE,
|
||||||
|
include_mcp_tools: bool = True,
|
||||||
):
|
):
|
||||||
"""Build the full multi-agent chat graph (supervisor + Gmail + Calendar sub-agents via ``new_chat`` tools)."""
|
"""Build the full multi-agent chat graph (supervisor + domain subgraphs via routing tools).
|
||||||
|
|
||||||
|
**Builtins** (:mod:`expert_agent.builtins`): registry-grouped **categories** (research, memory, deliverables).
|
||||||
|
**Connectors** (:mod:`expert_agent.connectors`): **vendor integrations** — one subgraph each where split
|
||||||
|
(e.g. Gmail, Calendar, Discord, Teams, Notion, Confluence, Google Drive, Dropbox, OneDrive, Luma).
|
||||||
|
|
||||||
|
MCP tools from ``new_chat`` (``load_mcp_tools``) are partitioned inside this package and attached only
|
||||||
|
to the matching expert subgraphs — not to the supervisor tool list as raw MCP calls.
|
||||||
|
|
||||||
|
Deliverables (thread-scoped reports, podcasts, etc.) are registered only when ``thread_id`` is set.
|
||||||
|
"""
|
||||||
|
mcp_tools_by_route: dict[str, list[BaseTool]] | None = None
|
||||||
|
if include_mcp_tools:
|
||||||
|
mcp_flat = await load_mcp_tools(db_session, search_space_id)
|
||||||
|
id_map, name_map = await fetch_mcp_connector_metadata_maps(db_session, search_space_id)
|
||||||
|
mcp_tools_by_route = partition_mcp_tools_by_expert_route(mcp_flat, id_map, name_map)
|
||||||
|
|
||||||
|
registry_dependencies = build_registry_dependencies(
|
||||||
|
db_session=db_session,
|
||||||
|
search_space_id=search_space_id,
|
||||||
|
user_id=user_id,
|
||||||
|
thread_id=thread_id or "",
|
||||||
|
llm=llm,
|
||||||
|
firecrawl_api_key=firecrawl_api_key,
|
||||||
|
connector_service=connector_service,
|
||||||
|
available_connectors=available_connectors,
|
||||||
|
available_document_types=available_document_types,
|
||||||
|
thread_visibility=thread_visibility,
|
||||||
|
)
|
||||||
routing_tools = build_supervisor_routing_tools(
|
routing_tools = build_supervisor_routing_tools(
|
||||||
llm,
|
llm,
|
||||||
gmail_tools=build_gmail_connector_tools(
|
registry_dependencies=registry_dependencies,
|
||||||
db_session=db_session,
|
include_deliverables=thread_id is not None,
|
||||||
search_space_id=search_space_id,
|
mcp_tools_by_route=mcp_tools_by_route,
|
||||||
user_id=user_id,
|
|
||||||
),
|
|
||||||
calendar_tools=build_google_calendar_connector_tools(
|
|
||||||
db_session=db_session,
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
user_id=user_id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return build_supervisor_agent(llm, tools=routing_tools, checkpointer=checkpointer)
|
return build_supervisor_agent(llm, tools=routing_tools, checkpointer=checkpointer)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
"""Cross-cutting helpers: prompt loading, domain agent factory, connector deps."""
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.shared.deps import connector_binding
|
|
||||||
from app.agents.multi_agent_chat.shared.domain_agent_factory import build_domain_agent
|
|
||||||
from app.agents.multi_agent_chat.shared.invoke_output import extract_last_assistant_text
|
|
||||||
from app.agents.multi_agent_chat.shared.prompt_loader import read_prompt_md
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"build_domain_agent",
|
|
||||||
"connector_binding",
|
|
||||||
"extract_last_assistant_text",
|
|
||||||
"read_prompt_md",
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
"""Shared kwargs for ``new_chat`` connector tool factories."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
|
|
||||||
def connector_binding(
|
|
||||||
*,
|
|
||||||
db_session: AsyncSession,
|
|
||||||
search_space_id: int,
|
|
||||||
user_id: str,
|
|
||||||
) -> dict[str, AsyncSession | int | str]:
|
|
||||||
return {
|
|
||||||
"db_session": db_session,
|
|
||||||
"search_space_id": search_space_id,
|
|
||||||
"user_id": user_id,
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
"""Compile a domain agent graph from a co-located prompt + tool list."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
from langchain.agents import create_agent
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.shared.prompt_loader import read_prompt_md
|
|
||||||
|
|
||||||
|
|
||||||
def build_domain_agent(
|
|
||||||
llm: BaseChatModel,
|
|
||||||
tools: Sequence[BaseTool],
|
|
||||||
*,
|
|
||||||
prompt_package: str,
|
|
||||||
prompt_stem: str = "domain_prompt",
|
|
||||||
):
|
|
||||||
"""``create_agent`` + ``{prompt_stem}.md`` loaded from ``prompt_package``."""
|
|
||||||
system_prompt = read_prompt_md(prompt_package, prompt_stem)
|
|
||||||
return create_agent(
|
|
||||||
llm,
|
|
||||||
system_prompt=system_prompt,
|
|
||||||
tools=list(tools),
|
|
||||||
)
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
"""Extract displayable text from a LangGraph agent ``invoke`` / ``ainvoke`` result."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
|
|
||||||
def extract_last_assistant_text(result: dict[str, Any]) -> str:
|
|
||||||
"""Return the last message's string content, or ``\"\"`` if missing."""
|
|
||||||
messages = result.get("messages") or []
|
|
||||||
if not messages:
|
|
||||||
return ""
|
|
||||||
last = messages[-1]
|
|
||||||
content = getattr(last, "content", None)
|
|
||||||
if isinstance(content, str):
|
|
||||||
return content
|
|
||||||
return str(last)
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
"""Load ``*.md`` from any package (vertical slices use co-located prompts)."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from importlib import resources
|
|
||||||
|
|
||||||
|
|
||||||
def read_prompt_md(package: str, stem: str) -> str:
|
|
||||||
"""Read ``{stem}.md`` from the given import package (e.g. ``app.agents.multi_agent_chat.gmail``)."""
|
|
||||||
try:
|
|
||||||
ref = resources.files(package).joinpath(f"{stem}.md")
|
|
||||||
if not ref.is_file():
|
|
||||||
return ""
|
|
||||||
text = ref.read_text(encoding="utf-8")
|
|
||||||
except (FileNotFoundError, ModuleNotFoundError, OSError, TypeError):
|
|
||||||
return ""
|
|
||||||
if text.endswith("\n"):
|
|
||||||
text = text[:-1]
|
|
||||||
return text
|
|
||||||
|
|
@ -11,7 +11,7 @@ from langchain_core.language_models import BaseChatModel
|
||||||
from langchain_core.tools import BaseTool
|
from langchain_core.tools import BaseTool
|
||||||
from langgraph.types import Checkpointer
|
from langgraph.types import Checkpointer
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.shared.prompt_loader import read_prompt_md
|
from app.agents.multi_agent_chat.core.prompts import read_prompt_md
|
||||||
|
|
||||||
|
|
||||||
def build_supervisor_agent(
|
def build_supervisor_agent(
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
"""Baseline deep-agent factory without SurfSense specialist subagents.
|
|
||||||
|
|
||||||
Swap imports manually while building supervisor-style delegation::
|
|
||||||
|
|
||||||
# from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent
|
|
||||||
from app.agents.new_chat_supervisor_baseline.chat_deepagent import (
|
|
||||||
create_surfsense_deep_agent,
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from app.agents.new_chat_supervisor_baseline.chat_deepagent import (
|
|
||||||
create_surfsense_deep_agent,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ["create_surfsense_deep_agent"]
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
"""
|
|
||||||
Supervisor baseline: **no registry tools** and **no tool-injecting middleware**
|
|
||||||
(no ``task`` / subagents, filesystem, todos, skills, permission, pruning, repair, …).
|
|
||||||
|
|
||||||
Connector/document discovery still feeds :class:`KnowledgePriorityMiddleware` so turns
|
|
||||||
can include KB priority hints.
|
|
||||||
|
|
||||||
System prompt: :func:`build_supervisor_system_prompt` — SurfSense ``agent_*`` identity
|
|
||||||
fragments plus supervisor-scoped KB/memory text and composer citation/provider blocks,
|
|
||||||
without tool lists or ``tool_routing`` (see module docstring there).
|
|
||||||
|
|
||||||
See :mod:`app.agents.new_chat.chat_deepagent` for the full production agent.
|
|
||||||
|
|
||||||
Implementation: :mod:`app.agents.new_chat_supervisor_baseline.deep_agent`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
from langgraph.types import Checkpointer
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from app.agents.new_chat.feature_flags import AgentFeatureFlags, get_flags
|
|
||||||
from app.agents.new_chat.filesystem_selection import FilesystemSelection
|
|
||||||
from app.agents.new_chat.llm_config import AgentConfig
|
|
||||||
from app.db import ChatVisibility
|
|
||||||
from app.services.connector_service import ConnectorService
|
|
||||||
from app.utils.perf import get_perf_logger
|
|
||||||
|
|
||||||
from app.agents.new_chat_supervisor_baseline.deep_agent.compiled_agent import (
|
|
||||||
build_compiled_agent_blocking,
|
|
||||||
)
|
|
||||||
from app.agents.new_chat_supervisor_baseline.deep_agent.connector_searchable import (
|
|
||||||
map_connectors_to_searchable_types,
|
|
||||||
)
|
|
||||||
from app.agents.new_chat_supervisor_baseline.supervisor_system_prompt import (
|
|
||||||
build_supervisor_system_prompt,
|
|
||||||
)
|
|
||||||
|
|
||||||
_perf_log = get_perf_logger()
|
|
||||||
|
|
||||||
|
|
||||||
async def create_surfsense_deep_agent(
|
|
||||||
llm: BaseChatModel,
|
|
||||||
search_space_id: int,
|
|
||||||
db_session: AsyncSession,
|
|
||||||
connector_service: ConnectorService,
|
|
||||||
checkpointer: Checkpointer,
|
|
||||||
user_id: str | None = None,
|
|
||||||
thread_id: int | None = None,
|
|
||||||
agent_config: AgentConfig | None = None,
|
|
||||||
enabled_tools: list[str] | None = None,
|
|
||||||
disabled_tools: list[str] | None = None,
|
|
||||||
additional_tools: Sequence[BaseTool] | None = None,
|
|
||||||
firecrawl_api_key: str | None = None,
|
|
||||||
thread_visibility: ChatVisibility | None = None,
|
|
||||||
mentioned_document_ids: list[int] | None = None,
|
|
||||||
anon_session_id: str | None = None,
|
|
||||||
filesystem_selection: FilesystemSelection | None = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Build the supervisor baseline agent: registry tools are not loaded.
|
|
||||||
|
|
||||||
Parameters such as ``enabled_tools``, ``additional_tools``, and ``firecrawl_api_key``
|
|
||||||
are ignored for now; kept so call sites stay compatible.
|
|
||||||
"""
|
|
||||||
_ = (enabled_tools, disabled_tools, additional_tools, firecrawl_api_key, db_session)
|
|
||||||
|
|
||||||
_t_agent_total = time.perf_counter()
|
|
||||||
|
|
||||||
filesystem_selection = filesystem_selection or FilesystemSelection()
|
|
||||||
_fs_mode = filesystem_selection.mode
|
|
||||||
|
|
||||||
available_connectors: list[str] | None = None
|
|
||||||
available_document_types: list[str] | None = None
|
|
||||||
|
|
||||||
_t0 = time.perf_counter()
|
|
||||||
try:
|
|
||||||
connector_types = await connector_service.get_available_connectors(
|
|
||||||
search_space_id
|
|
||||||
)
|
|
||||||
if connector_types:
|
|
||||||
available_connectors = map_connectors_to_searchable_types(connector_types)
|
|
||||||
|
|
||||||
available_document_types = await connector_service.get_available_document_types(
|
|
||||||
search_space_id
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(f"Failed to discover available connectors/document types: {e}")
|
|
||||||
_perf_log.info(
|
|
||||||
"[create_agent] Connector/doc-type discovery in %.3fs",
|
|
||||||
time.perf_counter() - _t0,
|
|
||||||
)
|
|
||||||
|
|
||||||
visibility = thread_visibility or ChatVisibility.PRIVATE
|
|
||||||
|
|
||||||
tools: list[BaseTool] = []
|
|
||||||
|
|
||||||
_flags: AgentFeatureFlags = get_flags()
|
|
||||||
_perf_log.info("[create_agent] supervisor baseline: 0 registry tools")
|
|
||||||
|
|
||||||
_t0 = time.perf_counter()
|
|
||||||
|
|
||||||
final_system_prompt = build_supervisor_system_prompt(
|
|
||||||
agent_config=agent_config,
|
|
||||||
thread_visibility=thread_visibility,
|
|
||||||
llm=llm,
|
|
||||||
)
|
|
||||||
_perf_log.info(
|
|
||||||
"[create_agent] System prompt built in %.3fs", time.perf_counter() - _t0
|
|
||||||
)
|
|
||||||
|
|
||||||
_t0 = time.perf_counter()
|
|
||||||
agent = await asyncio.to_thread(
|
|
||||||
build_compiled_agent_blocking,
|
|
||||||
llm=llm,
|
|
||||||
tools=tools,
|
|
||||||
final_system_prompt=final_system_prompt,
|
|
||||||
filesystem_mode=_fs_mode,
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
user_id=user_id,
|
|
||||||
thread_id=thread_id,
|
|
||||||
visibility=visibility,
|
|
||||||
anon_session_id=anon_session_id,
|
|
||||||
available_connectors=available_connectors,
|
|
||||||
available_document_types=available_document_types,
|
|
||||||
mentioned_document_ids=mentioned_document_ids,
|
|
||||||
flags=_flags,
|
|
||||||
checkpointer=checkpointer,
|
|
||||||
)
|
|
||||||
_perf_log.info(
|
|
||||||
"[create_agent] Middleware stack + graph compiled in %.3fs",
|
|
||||||
time.perf_counter() - _t0,
|
|
||||||
)
|
|
||||||
|
|
||||||
_perf_log.info(
|
|
||||||
"[create_agent] Total agent creation in %.3fs",
|
|
||||||
time.perf_counter() - _t_agent_total,
|
|
||||||
)
|
|
||||||
return agent
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Helpers used only by :mod:`app.agents.new_chat_supervisor_baseline.chat_deepagent`."""
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
"""Compile a minimal supervisor graph: no bound tools, no tool-injecting middleware."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
from deepagents import __version__ as deepagents_version
|
|
||||||
from deepagents.backends import StateBackend
|
|
||||||
from langchain.agents import create_agent
|
|
||||||
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
from langgraph.types import Checkpointer
|
|
||||||
|
|
||||||
from app.agents.new_chat.context import SurfSenseContextSchema
|
|
||||||
from app.agents.new_chat.feature_flags import AgentFeatureFlags
|
|
||||||
from app.agents.new_chat.filesystem_selection import FilesystemMode
|
|
||||||
from app.agents.new_chat.middleware import (
|
|
||||||
AnonymousDocumentMiddleware,
|
|
||||||
FileIntentMiddleware,
|
|
||||||
KnowledgeBasePersistenceMiddleware,
|
|
||||||
KnowledgePriorityMiddleware,
|
|
||||||
KnowledgeTreeMiddleware,
|
|
||||||
MemoryInjectionMiddleware,
|
|
||||||
create_surfsense_compaction_middleware,
|
|
||||||
)
|
|
||||||
from app.db import ChatVisibility
|
|
||||||
|
|
||||||
|
|
||||||
def build_compiled_agent_blocking(
|
|
||||||
*,
|
|
||||||
llm: BaseChatModel,
|
|
||||||
tools: Sequence[BaseTool],
|
|
||||||
final_system_prompt: str,
|
|
||||||
filesystem_mode: FilesystemMode,
|
|
||||||
search_space_id: int,
|
|
||||||
user_id: str | None,
|
|
||||||
thread_id: int | None,
|
|
||||||
visibility: ChatVisibility,
|
|
||||||
anon_session_id: str | None,
|
|
||||||
available_connectors: list[str] | None,
|
|
||||||
available_document_types: list[str] | None,
|
|
||||||
mentioned_document_ids: list[int] | None,
|
|
||||||
flags: AgentFeatureFlags,
|
|
||||||
checkpointer: Checkpointer,
|
|
||||||
):
|
|
||||||
"""Build middleware + compile graph synchronously (typically ``asyncio.to_thread``).
|
|
||||||
|
|
||||||
Intentionally excludes registry tools (``tools`` should be ``[]``), SubAgent/task,
|
|
||||||
filesystem/todo/skills middleware, and tool-centric hygiene (repair, dedup, permission).
|
|
||||||
"""
|
|
||||||
_ = flags # retained for API parity with callers; stack is fixed minimal for now
|
|
||||||
|
|
||||||
_memory_middleware = MemoryInjectionMiddleware(
|
|
||||||
user_id=user_id,
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
thread_visibility=visibility,
|
|
||||||
)
|
|
||||||
|
|
||||||
summarization_mw = create_surfsense_compaction_middleware(llm, StateBackend)
|
|
||||||
|
|
||||||
deepagent_middleware = [
|
|
||||||
_memory_middleware,
|
|
||||||
AnonymousDocumentMiddleware(anon_session_id=anon_session_id)
|
|
||||||
if filesystem_mode == FilesystemMode.CLOUD
|
|
||||||
else None,
|
|
||||||
KnowledgeTreeMiddleware(
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
filesystem_mode=filesystem_mode,
|
|
||||||
llm=llm,
|
|
||||||
)
|
|
||||||
if filesystem_mode == FilesystemMode.CLOUD
|
|
||||||
else None,
|
|
||||||
KnowledgePriorityMiddleware(
|
|
||||||
llm=llm,
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
filesystem_mode=filesystem_mode,
|
|
||||||
available_connectors=available_connectors,
|
|
||||||
available_document_types=available_document_types,
|
|
||||||
mentioned_document_ids=mentioned_document_ids,
|
|
||||||
),
|
|
||||||
FileIntentMiddleware(llm=llm),
|
|
||||||
KnowledgeBasePersistenceMiddleware(
|
|
||||||
search_space_id=search_space_id,
|
|
||||||
created_by_id=user_id,
|
|
||||||
filesystem_mode=filesystem_mode,
|
|
||||||
thread_id=thread_id,
|
|
||||||
)
|
|
||||||
if filesystem_mode == FilesystemMode.CLOUD
|
|
||||||
else None,
|
|
||||||
summarization_mw,
|
|
||||||
AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
|
|
||||||
]
|
|
||||||
deepagent_middleware = [m for m in deepagent_middleware if m is not None]
|
|
||||||
|
|
||||||
agent = create_agent(
|
|
||||||
llm,
|
|
||||||
system_prompt=final_system_prompt,
|
|
||||||
tools=list(tools),
|
|
||||||
middleware=deepagent_middleware,
|
|
||||||
context_schema=SurfSenseContextSchema,
|
|
||||||
checkpointer=checkpointer,
|
|
||||||
)
|
|
||||||
return agent.with_config(
|
|
||||||
{
|
|
||||||
"recursion_limit": 10_000,
|
|
||||||
"metadata": {
|
|
||||||
"ls_integration": "deepagents",
|
|
||||||
"versions": {"deepagents": deepagents_version},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
"""Map connector enum values to searchable document/connector type strings."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
_CONNECTOR_TYPE_TO_SEARCHABLE: dict[str, str] = {
|
|
||||||
"TAVILY_API": "TAVILY_API",
|
|
||||||
"LINKUP_API": "LINKUP_API",
|
|
||||||
"BAIDU_SEARCH_API": "BAIDU_SEARCH_API",
|
|
||||||
"SLACK_CONNECTOR": "SLACK_CONNECTOR",
|
|
||||||
"TEAMS_CONNECTOR": "TEAMS_CONNECTOR",
|
|
||||||
"NOTION_CONNECTOR": "NOTION_CONNECTOR",
|
|
||||||
"GITHUB_CONNECTOR": "GITHUB_CONNECTOR",
|
|
||||||
"LINEAR_CONNECTOR": "LINEAR_CONNECTOR",
|
|
||||||
"DISCORD_CONNECTOR": "DISCORD_CONNECTOR",
|
|
||||||
"JIRA_CONNECTOR": "JIRA_CONNECTOR",
|
|
||||||
"CONFLUENCE_CONNECTOR": "CONFLUENCE_CONNECTOR",
|
|
||||||
"CLICKUP_CONNECTOR": "CLICKUP_CONNECTOR",
|
|
||||||
"GOOGLE_CALENDAR_CONNECTOR": "GOOGLE_CALENDAR_CONNECTOR",
|
|
||||||
"GOOGLE_GMAIL_CONNECTOR": "GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
"GOOGLE_DRIVE_CONNECTOR": "GOOGLE_DRIVE_FILE",
|
|
||||||
"AIRTABLE_CONNECTOR": "AIRTABLE_CONNECTOR",
|
|
||||||
"LUMA_CONNECTOR": "LUMA_CONNECTOR",
|
|
||||||
"ELASTICSEARCH_CONNECTOR": "ELASTICSEARCH_CONNECTOR",
|
|
||||||
"WEBCRAWLER_CONNECTOR": "CRAWLED_URL",
|
|
||||||
"BOOKSTACK_CONNECTOR": "BOOKSTACK_CONNECTOR",
|
|
||||||
"CIRCLEBACK_CONNECTOR": "CIRCLEBACK",
|
|
||||||
"OBSIDIAN_CONNECTOR": "OBSIDIAN_CONNECTOR",
|
|
||||||
"DROPBOX_CONNECTOR": "DROPBOX_FILE",
|
|
||||||
"ONEDRIVE_CONNECTOR": "ONEDRIVE_FILE",
|
|
||||||
"COMPOSIO_GOOGLE_DRIVE_CONNECTOR": "GOOGLE_DRIVE_FILE",
|
|
||||||
"COMPOSIO_GMAIL_CONNECTOR": "GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
"COMPOSIO_GOOGLE_CALENDAR_CONNECTOR": "GOOGLE_CALENDAR_CONNECTOR",
|
|
||||||
}
|
|
||||||
|
|
||||||
_ALWAYS_AVAILABLE_DOC_TYPES: tuple[str, ...] = (
|
|
||||||
"EXTENSION",
|
|
||||||
"FILE",
|
|
||||||
"NOTE",
|
|
||||||
"YOUTUBE_VIDEO",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def map_connectors_to_searchable_types(connector_types: list[Any]) -> list[str]:
|
|
||||||
"""Map connector types to searchable strings; dedupe preserving order."""
|
|
||||||
result_set: set[str] = set()
|
|
||||||
result_list: list[str] = []
|
|
||||||
|
|
||||||
for doc_type in _ALWAYS_AVAILABLE_DOC_TYPES:
|
|
||||||
if doc_type not in result_set:
|
|
||||||
result_set.add(doc_type)
|
|
||||||
result_list.append(doc_type)
|
|
||||||
|
|
||||||
for ct in connector_types:
|
|
||||||
ct_str = ct.value if hasattr(ct, "value") else str(ct)
|
|
||||||
searchable = _CONNECTOR_TYPE_TO_SEARCHABLE.get(ct_str)
|
|
||||||
if searchable and searchable not in result_set:
|
|
||||||
result_set.add(searchable)
|
|
||||||
result_list.append(searchable)
|
|
||||||
|
|
||||||
return result_list
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Supervisor-scoped prompt fragments (adaptations of ``new_chat/prompts/base``)."""
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<knowledge_base_only_policy>
|
|
||||||
Adapted from ``prompts/base/kb_only_policy_private.md`` for supervisor-only runs (no web
|
|
||||||
search / scrape / connector tools on this node).
|
|
||||||
|
|
||||||
CRITICAL RULE — KNOWLEDGE CONTEXT FIRST FOR FACTUAL QUESTIONS:
|
|
||||||
- For factual or informational questions, rely on information in this thread and on
|
|
||||||
knowledge SurfSense surfaces in your prompt (for example priority document excerpts
|
|
||||||
or injected memory text). Do not substitute unchecked general knowledge unless the
|
|
||||||
user explicitly opts in.
|
|
||||||
- If nothing in the conversation or injected context answers the question, you MUST:
|
|
||||||
1. Say you could not find it in the available SurfSense context for this turn.
|
|
||||||
2. Ask: "Would you like me to answer from my general knowledge instead?"
|
|
||||||
3. ONLY provide a general-knowledge answer AFTER the user explicitly says yes.
|
|
||||||
- This policy does NOT apply to:
|
|
||||||
* Casual conversation, greetings, or meta-questions about SurfSense itself
|
|
||||||
* Formatting, summarization, or analysis of content already present in the conversation
|
|
||||||
* Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points")
|
|
||||||
</knowledge_base_only_policy>
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<knowledge_base_only_policy>
|
|
||||||
Adapted from ``prompts/base/kb_only_policy_team.md`` for supervisor-only runs (no web
|
|
||||||
search / scrape / connector tools on this node).
|
|
||||||
|
|
||||||
CRITICAL RULE — TEAM KNOWLEDGE CONTEXT FIRST FOR FACTUAL QUESTIONS:
|
|
||||||
- For factual or informational questions, rely on information in this thread and on
|
|
||||||
knowledge SurfSense surfaces in your prompt from the shared space (for example
|
|
||||||
priority document excerpts or injected memory text). Do not substitute unchecked
|
|
||||||
general knowledge unless a team member explicitly opts in.
|
|
||||||
- If nothing in the conversation or injected context answers the question, you MUST:
|
|
||||||
1. Say you could not find it in the available SurfSense context for this turn.
|
|
||||||
2. Ask: "Would you like me to answer from my general knowledge instead?"
|
|
||||||
3. ONLY provide a general-knowledge answer AFTER a team member explicitly says yes.
|
|
||||||
- This policy does NOT apply to:
|
|
||||||
* Casual conversation, greetings, or meta-questions about SurfSense itself
|
|
||||||
* Formatting, summarization, or analysis of content already present in the conversation
|
|
||||||
* Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points")
|
|
||||||
</knowledge_base_only_policy>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<memory_context>
|
|
||||||
Derived from ``prompts/base/memory_protocol_*.md``, without requiring ``update_memory``
|
|
||||||
calls (this supervisor node does not expose that tool).
|
|
||||||
|
|
||||||
Personalized memory text may be injected into your prompt when configured. You cannot
|
|
||||||
persist new long-term memory from this supervisor node; if the user asks you to
|
|
||||||
remember something permanently, explain that doing so requires the full SurfSense
|
|
||||||
agent with memory tools enabled or another persistence path they configure.
|
|
||||||
</memory_context>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<supervisor_graph_role>
|
|
||||||
This node follows the LangGraph multi-agent **supervisor** pattern: the supervisor
|
|
||||||
language model responds from the current conversation and optional supervisor-scoped
|
|
||||||
system prompt (see LangChain Reference: ``langgraph_supervisor.create_supervisor``,
|
|
||||||
parameter ``prompt`` — typically a ``SystemMessage`` that scopes routing and handoff
|
|
||||||
behavior). In this SurfSense deployment the supervisor graph does **not** attach
|
|
||||||
registry tools or worker subgraphs—answer from messages and system-injected context,
|
|
||||||
and state plainly when the user expects tools or delegations that are not wired here.
|
|
||||||
</supervisor_graph_role>
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
"""Supervisor-scoped system prompt for ``new_chat_supervisor_baseline``.
|
|
||||||
|
|
||||||
Composition follows the same fragment discipline as
|
|
||||||
:func:`app.agents.new_chat.prompts.composer.compose_system_prompt`, but **omits**
|
|
||||||
sections that assume registry tools: ``base/tool_routing_*.md``, ``tools/_preamble.md``,
|
|
||||||
the tools/examples blocks, ``base/parameter_resolution.md`` (discovery lists concrete
|
|
||||||
tools), and ``base/memory_protocol_*.md`` (requires ``update_memory`` calls).
|
|
||||||
|
|
||||||
**Authoritative supervisor semantics:** LangChain Reference documents
|
|
||||||
``langgraph_supervisor.create_supervisor`` — the supervisor graph accepts an optional
|
|
||||||
``prompt`` (typically a ``SystemMessage``) that scopes the supervisor LLM alongside
|
|
||||||
managed worker graphs.
|
|
||||||
|
|
||||||
**SurfSense sources reused verbatim where applicable:** ``prompts/base/agent_private.md`` /
|
|
||||||
``agent_team.md`` from :mod:`app.agents.new_chat.prompts`. KB policy is adapted from
|
|
||||||
``base/kb_only_policy_*.md`` into supervisor-local fragments that reference injected
|
|
||||||
context instead of tool outputs. Provider and citation blocks reuse
|
|
||||||
``composer._build_provider_block`` / ``_build_citation_block`` and
|
|
||||||
``composer.detect_provider_variant`` unchanged.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import UTC, datetime
|
|
||||||
from importlib import resources
|
|
||||||
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
|
|
||||||
from app.agents.new_chat.llm_config import AgentConfig
|
|
||||||
from app.agents.new_chat.prompts import composer as pc
|
|
||||||
from app.db import ChatVisibility
|
|
||||||
|
|
||||||
_SUP_PROMPTS_PKG = "app.agents.new_chat_supervisor_baseline.prompts"
|
|
||||||
|
|
||||||
|
|
||||||
def _read_supervisor_fragment(filename: str) -> str:
|
|
||||||
try:
|
|
||||||
ref = resources.files(_SUP_PROMPTS_PKG).joinpath(filename)
|
|
||||||
if not ref.is_file():
|
|
||||||
return ""
|
|
||||||
text = ref.read_text(encoding="utf-8")
|
|
||||||
except (FileNotFoundError, ModuleNotFoundError, OSError):
|
|
||||||
return ""
|
|
||||||
if text.endswith("\n"):
|
|
||||||
text = text[:-1]
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def _build_supervisor_system_instruction_block(
|
|
||||||
*,
|
|
||||||
visibility: ChatVisibility,
|
|
||||||
resolved_today: str,
|
|
||||||
) -> str:
|
|
||||||
"""``<system_instruction>`` body: LangGraph supervisor scope + SurfSense identity + adapted KB + memory limits."""
|
|
||||||
variant = "team" if visibility == ChatVisibility.SEARCH_SPACE else "private"
|
|
||||||
sections = [
|
|
||||||
_read_supervisor_fragment("supervisor_graph_role.md"),
|
|
||||||
pc._read_fragment(f"base/agent_{variant}.md"),
|
|
||||||
_read_supervisor_fragment(f"kb_policy_supervisor_{variant}.md"),
|
|
||||||
_read_supervisor_fragment("memory_context_supervisor.md"),
|
|
||||||
]
|
|
||||||
body = "\n\n".join(s for s in sections if s)
|
|
||||||
block = f"\n<system_instruction>\n{body}\n\n</system_instruction>\n"
|
|
||||||
return block.format(resolved_today=resolved_today)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_llm_model_name(llm: BaseChatModel) -> str | None:
|
|
||||||
"""Best-effort model id string for :func:`composer.detect_provider_variant`."""
|
|
||||||
name = getattr(llm, "model_name", None)
|
|
||||||
if isinstance(name, str) and name.strip():
|
|
||||||
return name.strip()
|
|
||||||
model = getattr(llm, "model", None)
|
|
||||||
if isinstance(model, str) and model.strip():
|
|
||||||
return model.strip()
|
|
||||||
profile = getattr(llm, "profile", None)
|
|
||||||
if isinstance(profile, dict):
|
|
||||||
for key in ("model", "model_name"):
|
|
||||||
m = profile.get(key)
|
|
||||||
if isinstance(m, str) and m.strip():
|
|
||||||
return m.strip()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def build_supervisor_system_prompt(
|
|
||||||
*,
|
|
||||||
agent_config: AgentConfig | None,
|
|
||||||
thread_visibility: ChatVisibility | None,
|
|
||||||
llm: BaseChatModel,
|
|
||||||
) -> str:
|
|
||||||
"""Assemble the supervisor system prompt (no tool-list or tool-routing fragments)."""
|
|
||||||
resolved_today = datetime.now(UTC).astimezone(UTC).date().isoformat()
|
|
||||||
visibility = thread_visibility or ChatVisibility.PRIVATE
|
|
||||||
model_name = resolve_llm_model_name(llm)
|
|
||||||
|
|
||||||
if agent_config is not None:
|
|
||||||
custom = (agent_config.system_instructions or "").strip()
|
|
||||||
if custom:
|
|
||||||
sys_block = agent_config.system_instructions.format(resolved_today=resolved_today)
|
|
||||||
elif agent_config.use_default_system_instructions:
|
|
||||||
sys_block = _build_supervisor_system_instruction_block(
|
|
||||||
visibility=visibility,
|
|
||||||
resolved_today=resolved_today,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
sys_block = ""
|
|
||||||
else:
|
|
||||||
sys_block = _build_supervisor_system_instruction_block(
|
|
||||||
visibility=visibility,
|
|
||||||
resolved_today=resolved_today,
|
|
||||||
)
|
|
||||||
|
|
||||||
provider_variant = pc.detect_provider_variant(model_name)
|
|
||||||
sys_block += pc._build_provider_block(provider_variant)
|
|
||||||
|
|
||||||
if agent_config is None:
|
|
||||||
citations_enabled = True
|
|
||||||
else:
|
|
||||||
citations_enabled = agent_config.citations_enabled
|
|
||||||
|
|
||||||
sys_block += pc._build_citation_block(citations_enabled)
|
|
||||||
return sys_block
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue