mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-07 23:02: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).
|
||||
|
||||
**Vertical slices**
|
||||
**Layout (SRP)**
|
||||
|
||||
- :mod:`gmail` — connector tools, domain agent, ``domain_prompt.md``
|
||||
- :mod:`calendar` — connector tools, domain agent, ``domain_prompt.md``
|
||||
|
||||
**Shared**
|
||||
|
||||
- :mod:`shared` — prompt loader, ``build_domain_agent``, connector deps, invoke result parsing
|
||||
|
||||
**Cross-cutting**
|
||||
|
||||
- :mod:`routing` — supervisor routing tools + invoke helpers
|
||||
- :mod:`supervisor` — top graph + ``supervisor_prompt.md``
|
||||
- :mod:`integration` — ``create_multi_agent_chat``
|
||||
- :mod:`expert_agent.builtins` — general categories from the tool registry (research, memory, deliverables — not tied to one vendor).
|
||||
- :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`).
|
||||
- :mod:`routing` — supervisor-facing ``@tool`` routers → domain invoke.
|
||||
- :mod:`supervisor` — orchestrator graph + ``supervisor_prompt.md``.
|
||||
- :mod:`integration` — async ``create_multi_agent_chat`` composer (partitions MCP tools into experts).
|
||||
|
||||
Documentation:
|
||||
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``.
|
||||
"""
|
||||
|
||||
from app.agents.multi_agent_chat.calendar import (
|
||||
build_calendar_domain_agent,
|
||||
build_google_calendar_connector_tools,
|
||||
from app.agents.multi_agent_chat.expert_agent.builtins.deliverables import (
|
||||
build_deliverables_tools,
|
||||
build_deliverables_domain_agent,
|
||||
)
|
||||
from app.agents.multi_agent_chat.gmail import (
|
||||
build_gmail_connector_tools,
|
||||
from app.agents.multi_agent_chat.expert_agent.builtins.memory import (
|
||||
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,
|
||||
)
|
||||
from app.agents.multi_agent_chat.integration import create_multi_agent_chat
|
||||
from app.agents.multi_agent_chat.shared import (
|
||||
from app.agents.multi_agent_chat.expert_agent.connectors.google_drive 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_registry_dependencies,
|
||||
build_registry_tools_for_category,
|
||||
compose_child_task,
|
||||
connector_binding,
|
||||
extract_last_assistant_text,
|
||||
read_prompt_md,
|
||||
)
|
||||
from app.agents.multi_agent_chat.integration import create_multi_agent_chat
|
||||
from app.agents.multi_agent_chat.routing import (
|
||||
DomainRoutingSpec,
|
||||
build_supervisor_routing_tools,
|
||||
routing_tools_from_domain_agents,
|
||||
routing_tools_from_specs,
|
||||
)
|
||||
from app.agents.multi_agent_chat.supervisor import build_supervisor_agent
|
||||
|
||||
__all__ = [
|
||||
"REGISTRY_ROUTING_CATEGORY_KEYS",
|
||||
"TOOL_NAMES_BY_CATEGORY",
|
||||
"DomainRoutingSpec",
|
||||
"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_gmail_connector_tools",
|
||||
"build_dropbox_tools",
|
||||
"build_dropbox_domain_agent",
|
||||
"build_gmail_tools",
|
||||
"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_routing_tools",
|
||||
"build_teams_tools",
|
||||
"build_teams_domain_agent",
|
||||
"connector_binding",
|
||||
"compose_child_task",
|
||||
"create_multi_agent_chat",
|
||||
"extract_last_assistant_text",
|
||||
"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 typing import Any
|
||||
|
||||
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.multi_agent_chat.calendar import build_google_calendar_connector_tools
|
||||
from app.agents.multi_agent_chat.gmail import build_gmail_connector_tools
|
||||
from app.db import ChatVisibility
|
||||
|
||||
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.supervisor import build_supervisor_agent
|
||||
|
||||
|
||||
def create_multi_agent_chat(
|
||||
async def create_multi_agent_chat(
|
||||
llm: BaseChatModel,
|
||||
*,
|
||||
db_session: AsyncSession,
|
||||
search_space_id: int,
|
||||
user_id: str,
|
||||
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(
|
||||
llm,
|
||||
gmail_tools=build_gmail_connector_tools(
|
||||
db_session=db_session,
|
||||
search_space_id=search_space_id,
|
||||
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,
|
||||
),
|
||||
registry_dependencies=registry_dependencies,
|
||||
include_deliverables=thread_id is not None,
|
||||
mcp_tools_by_route=mcp_tools_by_route,
|
||||
)
|
||||
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 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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue