mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-17 18:35:19 +02:00
multi_agent_chat/subagents: dict-keyed middleware_stack + always-on KB
This commit is contained in:
parent
eee861bb3d
commit
d843468256
39 changed files with 232 additions and 203 deletions
|
|
@ -25,6 +25,7 @@ CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS: dict[str, str] = {
|
|||
|
||||
SUBAGENT_TO_REQUIRED_CONNECTOR_MAP: dict[str, frozenset[str]] = {
|
||||
"deliverables": frozenset(),
|
||||
"knowledge_base": frozenset(),
|
||||
"airtable": frozenset({"AIRTABLE_CONNECTOR"}),
|
||||
"calendar": frozenset({"GOOGLE_CALENDAR_CONNECTOR"}),
|
||||
"clickup": frozenset({"CLICKUP_CONNECTOR"}),
|
||||
|
|
|
|||
|
|
@ -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.middleware import (
|
||||
from app.agents.multi_agent_chat.middleware.stack import (
|
||||
build_main_agent_deepagent_middleware,
|
||||
)
|
||||
from app.agents.multi_agent_chat.subagents.shared.permissions import (
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import time
|
|||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents.graph import BASE_AGENT_PROMPT
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
from langchain_core.tools import BaseTool
|
||||
from langgraph.types import Checkpointer
|
||||
|
|
@ -218,7 +217,7 @@ async def create_multi_agent_chat_deep_agent(
|
|||
"[create_agent] System prompt built in %.3fs", time.perf_counter() - _t0
|
||||
)
|
||||
|
||||
final_system_prompt = system_prompt + "\n\n" + BASE_AGENT_PROMPT
|
||||
final_system_prompt = system_prompt
|
||||
|
||||
config_id = agent_config.config_id if agent_config is not None else None
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ from .sections.tools import build_tools_section
|
|||
|
||||
def build_main_agent_system_prompt(
|
||||
*,
|
||||
registry_subagent_prompt_lines: list[tuple[str, str]],
|
||||
today: datetime | None = None,
|
||||
thread_visibility: ChatVisibility | None = None,
|
||||
enabled_tool_names: set[str] | None = None,
|
||||
|
|
@ -49,7 +50,6 @@ def build_main_agent_system_prompt(
|
|||
use_default_system_instructions: bool = True,
|
||||
citations_enabled: bool = True,
|
||||
model_name: str | None = None, # noqa: ARG001 — kept for caller compatibility
|
||||
registry_subagent_prompt_lines: list[tuple[str, str]] | None = None,
|
||||
) -> str:
|
||||
resolved_today = (today or datetime.now(UTC)).astimezone(UTC).date().isoformat()
|
||||
visibility = thread_visibility or ChatVisibility.PRIVATE
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
"""``<specialists>`` section — live ``task`` roster for this workspace."""
|
||||
"""``<specialists>`` section — live ``task`` roster for this workspace.
|
||||
|
||||
The roster is non-empty by contract: ``deliverables`` and ``knowledge_base``
|
||||
both declare ``frozenset()`` in ``SUBAGENT_TO_REQUIRED_CONNECTOR_MAP``, so
|
||||
they survive every connector-based exclusion pass.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def build_specialists_section(
|
||||
specialist_lines: list[tuple[str, str]] | None,
|
||||
specialist_lines: list[tuple[str, str]],
|
||||
) -> str:
|
||||
if specialist_lines is None:
|
||||
return ""
|
||||
if not specialist_lines:
|
||||
return (
|
||||
"\n<specialists>\n"
|
||||
"No specialists are available for `task` in this workspace.\n"
|
||||
"</specialists>\n"
|
||||
)
|
||||
bullets = "\n".join(f"- **{name}** — {desc}" for name, desc in specialist_lines)
|
||||
return f"\n<specialists>\n{bullets}\n</specialists>\n"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Main-agent ``<tools>`` block (memory + research builtins only; see ``main_agent.tools``)."""
|
||||
"""Main-agent ``<tools>`` block (memory + research builtins + ``task``)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
"""Compose the ``<tools>`` block from per-tool vertical-slice folders.
|
||||
|
||||
Each tool lives in ``prompts/tools/<name>/`` with ``description.md`` and an
|
||||
inline-rendered ``example.md``. Visibility variants (currently only
|
||||
``update_memory``) live in ``prompts/tools/<name>/{private,team}/``.
|
||||
``example.md``. Visibility variants live in ``{private,team}/`` subfolders.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -31,6 +30,8 @@ def build_tools_instruction_block(
|
|||
enabled_tool_names: set[str] | None,
|
||||
disabled_tool_names: set[str] | None,
|
||||
) -> str:
|
||||
"""Render ``<tools>``. ``task`` is always included: at least ``deliverables``
|
||||
and ``knowledge_base`` are always in ``<specialists>`` (see constants)."""
|
||||
variant = "team" if visibility == ChatVisibility.SEARCH_SPACE else "private"
|
||||
|
||||
parts: list[str] = ["\n<tools>\n"]
|
||||
|
|
@ -51,6 +52,14 @@ def build_tools_instruction_block(
|
|||
parts.append("\n" + example + "\n")
|
||||
parts.append("\n")
|
||||
|
||||
task_description = read_prompt_md("tools/task/description.md")
|
||||
task_example = read_prompt_md("tools/task/example.md")
|
||||
if task_description:
|
||||
parts.append(task_description + "\n")
|
||||
if task_example:
|
||||
parts.append("\n" + task_example + "\n")
|
||||
parts.append("\n")
|
||||
|
||||
known_disabled = (
|
||||
set(disabled_tool_names) & set(MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED)
|
||||
if disabled_tool_names
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
"""``task`` — description + few-shot examples for the specialist-delegation tool."""
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
- `task` — Invoke a specialist subagent.
|
||||
- Specialists own workspace knowledge-base operations and connected
|
||||
third-party services (Slack, Notion, Jira, Gmail, etc.). See
|
||||
`<specialists>` for the live roster.
|
||||
- Each subagent runs in isolation with its own tool stack and context,
|
||||
and returns a single synthesized result.
|
||||
- Args:
|
||||
- `subagent_type` — name of the specialist to invoke (must match an
|
||||
entry in `<specialists>`).
|
||||
- `description` — the FULL task prompt. The specialist cannot see this
|
||||
thread, so include all context and constraints, plus what you need
|
||||
back. The specialist will respond in its own format — don't dictate
|
||||
one.
|
||||
- Rules:
|
||||
- One `task` call per turn. Bundle related work for the same specialist
|
||||
into one invocation; the parent graph cannot coordinate human
|
||||
approvals across parallel subagents.
|
||||
- Don't claim to already know what a specialist's source contains;
|
||||
invoke it and use what it returns.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<example>
|
||||
user: "Save these meeting notes to my KB: …"
|
||||
→ task(subagent_type="knowledge_base", description="Save the notes below to
|
||||
a new document under /documents/notes/. Pick a sensible title and folder;
|
||||
tell me the path you used.\n\n<notes>…</notes>")
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: "What did Maya say about the Q2 roadmap in Slack last week?"
|
||||
→ task(subagent_type="slack", description="Find messages from Maya about
|
||||
the Q2 roadmap from the past week. Return the most relevant quotes with
|
||||
channel and timestamp.")
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: "Find my Q2 roadmap and summarise the milestones."
|
||||
→ task(subagent_type="knowledge_base", description="Locate the Q2 roadmap
|
||||
document under /documents and summarise its milestones. Use glob or grep
|
||||
if the path isn't obvious from the workspace tree.")
|
||||
</example>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
"""Multi-agent middleware stack assembly."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .stack import build_main_agent_deepagent_middleware
|
||||
|
||||
__all__ = ["build_main_agent_deepagent_middleware"]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
"""Schema-level description for the ``task`` tool.
|
||||
|
||||
Loaded from ``prompts/tools/task/description.md`` so the tool-schema text
|
||||
and the ``<tools>`` block render from the same source.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.agents.multi_agent_chat.main_agent.system_prompt.builder.load_md import (
|
||||
read_prompt_md,
|
||||
)
|
||||
|
||||
TASK_TOOL_DESCRIPTION: str = read_prompt_md("tools/task/description.md")
|
||||
|
||||
__all__ = ["TASK_TOOL_DESCRIPTION"]
|
||||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from .bundle import ResilienceBundle, build_resilience_bundle
|
||||
from .bundle import ResilienceMiddlewares, build_resilience_middlewares
|
||||
|
||||
__all__ = ["ResilienceBundle", "build_resilience_bundle"]
|
||||
__all__ = ["ResilienceMiddlewares", "build_resilience_middlewares"]
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ from .tool_call_limit import build_tool_call_limit_mw
|
|||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ResilienceBundle:
|
||||
class ResilienceMiddlewares:
|
||||
"""The four resilience middleware instances, any of which may be ``None`` when disabled by flags."""
|
||||
|
||||
retry: RetryAfterMiddleware | None
|
||||
fallback: ScopedModelFallbackMiddleware | None
|
||||
model_call_limit: ModelCallLimitMiddleware | None
|
||||
|
|
@ -42,8 +44,8 @@ class ResilienceBundle:
|
|||
]
|
||||
|
||||
|
||||
def build_resilience_bundle(flags: AgentFeatureFlags) -> ResilienceBundle:
|
||||
return ResilienceBundle(
|
||||
def build_resilience_middlewares(flags: AgentFeatureFlags) -> ResilienceMiddlewares:
|
||||
return ResilienceMiddlewares(
|
||||
retry=build_retry_mw(flags),
|
||||
fallback=build_fallback_mw(flags),
|
||||
model_call_limit=build_model_call_limit_mw(flags),
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ from app.agents.multi_agent_chat.subagents import (
|
|||
build_subagents,
|
||||
get_subagents_to_exclude,
|
||||
)
|
||||
from app.agents.multi_agent_chat.subagents.builtins.knowledge_base.agent import (
|
||||
build_subagent as build_knowledge_base_subagent,
|
||||
)
|
||||
from app.agents.multi_agent_chat.subagents.shared.permissions import ToolsPermissions
|
||||
from app.agents.new_chat.feature_flags import AgentFeatureFlags
|
||||
from app.agents.new_chat.filesystem_selection import FilesystemMode
|
||||
|
|
@ -37,6 +34,9 @@ from .main_agent.busy_mutex import build_busy_mutex_mw
|
|||
from .main_agent.checkpointed_subagent_middleware import (
|
||||
SurfSenseCheckpointedSubAgentMiddleware,
|
||||
)
|
||||
from .main_agent.checkpointed_subagent_middleware.task_description import (
|
||||
TASK_TOOL_DESCRIPTION,
|
||||
)
|
||||
from .main_agent.context_editing import build_context_editing_mw
|
||||
from .main_agent.dedup_hitl import build_dedup_hitl_mw
|
||||
from .main_agent.doom_loop import build_doom_loop_mw
|
||||
|
|
@ -53,9 +53,9 @@ from .shared.compaction import build_compaction_mw
|
|||
from .shared.kb_context_projection import build_kb_context_projection_mw
|
||||
from .shared.memory import build_memory_mw
|
||||
from .shared.patch_tool_calls import build_patch_tool_calls_mw
|
||||
from .shared.resilience import build_resilience_bundle
|
||||
from .shared.resilience import build_resilience_middlewares
|
||||
from .shared.todos import build_todos_mw
|
||||
from .subagent.extras import build_subagent_extras
|
||||
from .subagent.middleware_stack import build_subagent_middleware_stack
|
||||
|
||||
|
||||
def build_main_agent_deepagent_middleware(
|
||||
|
|
@ -80,7 +80,7 @@ def build_main_agent_deepagent_middleware(
|
|||
disabled_tools: list[str] | None = None,
|
||||
) -> list[Any]:
|
||||
"""Ordered middleware for ``create_agent`` (None entries already stripped)."""
|
||||
resilience = build_resilience_bundle(flags)
|
||||
resilience = build_resilience_middlewares(flags)
|
||||
|
||||
memory_mw = build_memory_mw(
|
||||
user_id=user_id,
|
||||
|
|
@ -88,45 +88,21 @@ def build_main_agent_deepagent_middleware(
|
|||
visibility=visibility,
|
||||
)
|
||||
|
||||
knowledge_base_subagent = build_knowledge_base_subagent(
|
||||
llm=llm,
|
||||
backend_resolver=backend_resolver,
|
||||
filesystem_mode=filesystem_mode,
|
||||
search_space_id=search_space_id,
|
||||
user_id=user_id,
|
||||
thread_id=thread_id,
|
||||
resilience=resilience,
|
||||
subagent_dependencies = {
|
||||
**subagent_dependencies,
|
||||
"backend_resolver": backend_resolver,
|
||||
"filesystem_mode": filesystem_mode,
|
||||
}
|
||||
|
||||
subagents: list[SubAgent] = build_subagents(
|
||||
dependencies=subagent_dependencies,
|
||||
model=llm,
|
||||
middleware_stack=build_subagent_middleware_stack(resilience=resilience),
|
||||
mcp_tools_by_agent=mcp_tools_by_agent or {},
|
||||
exclude=get_subagents_to_exclude(available_connectors),
|
||||
disabled_tools=disabled_tools,
|
||||
)
|
||||
|
||||
subagents_registry: list[SubAgent] = []
|
||||
try:
|
||||
subagent_extras = build_subagent_extras(
|
||||
resilience=resilience,
|
||||
)
|
||||
subagents_registry = build_subagents(
|
||||
dependencies=subagent_dependencies,
|
||||
model=llm,
|
||||
extra_middleware=subagent_extras,
|
||||
mcp_tools_by_agent=mcp_tools_by_agent or {},
|
||||
exclude=get_subagents_to_exclude(available_connectors),
|
||||
disabled_tools=disabled_tools,
|
||||
)
|
||||
logging.debug(
|
||||
"Subagents registry: %s",
|
||||
[s["name"] for s in subagents_registry],
|
||||
)
|
||||
except Exception:
|
||||
# Degrade to KB-only rather than aborting the turn:
|
||||
# one bad subagent dep should not deny the user a response.
|
||||
logging.exception(
|
||||
"Subagents registry build failed; falling back to knowledge_base only"
|
||||
)
|
||||
subagents_registry = []
|
||||
|
||||
subagents: list[SubAgent] = [
|
||||
knowledge_base_subagent,
|
||||
*subagents_registry,
|
||||
]
|
||||
logging.debug("Subagents registry: %s", [s["name"] for s in subagents])
|
||||
|
||||
stack: list[Any] = [
|
||||
build_busy_mutex_mw(flags),
|
||||
|
|
@ -165,6 +141,8 @@ def build_main_agent_deepagent_middleware(
|
|||
checkpointer=checkpointer,
|
||||
backend=StateBackend,
|
||||
subagents=subagents,
|
||||
system_prompt=None,
|
||||
task_description=TASK_TOOL_DESCRIPTION,
|
||||
),
|
||||
resilience.model_call_limit,
|
||||
resilience.tool_call_limit,
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
"""Extra middleware threaded into every registry subagent's stack.
|
||||
|
||||
Registry subagents are scoped to one domain (deliverables, research, memory,
|
||||
connectors, MCP) and never read or write the SurfSense filesystem — that
|
||||
capability belongs to the ``knowledge_base`` subagent. Keeping FS off the
|
||||
registry stacks avoids polluting their tool surface with FS tools they
|
||||
never act on.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ..shared.resilience import ResilienceBundle
|
||||
from ..shared.todos import build_todos_mw
|
||||
|
||||
|
||||
def build_subagent_extras(
|
||||
*,
|
||||
resilience: ResilienceBundle,
|
||||
) -> list[Any]:
|
||||
extras: list[Any] = [build_todos_mw()]
|
||||
extras.extend(resilience.as_list())
|
||||
return extras
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
"""Shared middleware stack threaded into every subagent.
|
||||
|
||||
Mirrors ``middleware/stack.py`` (the orchestrator's middleware stack) but
|
||||
exposes its contents as a dict keyed by purpose so specialists can pick
|
||||
the entries they need and decide ordering. The default consumer
|
||||
(``pack_subagent``) prepends every non-``None`` value in insertion order.
|
||||
|
||||
Registry subagents never touch the SurfSense filesystem — that capability
|
||||
belongs to ``knowledge_base`` — so no FS middleware is exposed here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ..shared.resilience import ResilienceMiddlewares
|
||||
from ..shared.todos import build_todos_mw
|
||||
|
||||
|
||||
def build_subagent_middleware_stack(
|
||||
*,
|
||||
resilience: ResilienceMiddlewares,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"todos": build_todos_mw(),
|
||||
"retry": resilience.retry,
|
||||
"fallback": resilience.fallback,
|
||||
"model_call_limit": resilience.model_call_limit,
|
||||
"tool_call_limit": resilience.tool_call_limit,
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
"""`knowledge_base` route: ``SubAgent`` spec for the SurfSense KB specialist.
|
||||
|
||||
The KB subagent owns the `/documents/` workspace: reading, writing, editing,
|
||||
searching, and organising user documents. It shares the orchestrator's
|
||||
``workspace_tree_text`` and ``kb_priority`` via state and re-emits them as
|
||||
SystemMessages through the projection middleware (no extra DB / LLM calls).
|
||||
Owns the ``/documents/`` workspace (read, write, edit, search, organise)
|
||||
and shares the orchestrator's ``workspace_tree_text`` and ``kb_priority``
|
||||
via state. KB conforms to :class:`SubagentBuilder` but composes its
|
||||
middleware list itself: it picks individual entries from
|
||||
``middleware_stack`` by key so resilience lands just outside the
|
||||
Anthropic cache (inside the filesystem and projection middlewares),
|
||||
which a flat prepend can't satisfy.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -11,7 +14,6 @@ from __future__ import annotations
|
|||
from typing import Any, cast
|
||||
|
||||
from deepagents import SubAgent
|
||||
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
|
||||
from app.agents.multi_agent_chat.middleware.shared.anthropic_cache import (
|
||||
|
|
@ -29,13 +31,12 @@ from app.agents.multi_agent_chat.middleware.shared.kb_context_projection import
|
|||
from app.agents.multi_agent_chat.middleware.shared.patch_tool_calls import (
|
||||
build_patch_tool_calls_mw,
|
||||
)
|
||||
from app.agents.multi_agent_chat.middleware.shared.resilience import (
|
||||
ResilienceBundle,
|
||||
)
|
||||
from app.agents.multi_agent_chat.middleware.shared.todos import build_todos_mw
|
||||
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
|
||||
read_md_file,
|
||||
)
|
||||
from app.agents.multi_agent_chat.subagents.shared.permissions import (
|
||||
ToolsPermissions,
|
||||
)
|
||||
from app.agents.new_chat.filesystem_selection import FilesystemMode
|
||||
|
||||
from .tools.index import destructive_fs_interrupt_on
|
||||
|
|
@ -45,20 +46,19 @@ NAME = "knowledge_base"
|
|||
|
||||
def build_subagent(
|
||||
*,
|
||||
llm: BaseChatModel,
|
||||
backend_resolver: Any,
|
||||
filesystem_mode: FilesystemMode,
|
||||
search_space_id: int,
|
||||
user_id: str | None,
|
||||
thread_id: int | None,
|
||||
resilience: ResilienceBundle,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None, # noqa: ARG001 — KB ships fixed tools
|
||||
) -> SubAgent:
|
||||
"""Resilience inserts encapsulated here so the orchestrator never mutates the list."""
|
||||
description = read_md_file(__package__, "description").strip()
|
||||
if not description:
|
||||
description = (
|
||||
"Handles knowledge-base reads, writes, edits, and organisation."
|
||||
)
|
||||
"""Conforms to :class:`SubagentBuilder`; KB splices the shared stack itself."""
|
||||
llm = model if model is not None else dependencies["llm"]
|
||||
filesystem_mode: FilesystemMode = dependencies["filesystem_mode"]
|
||||
mws = middleware_stack or {}
|
||||
|
||||
description = read_md_file(__package__, "description").strip() or (
|
||||
"Handles knowledge-base reads, writes, edits, and organisation."
|
||||
)
|
||||
prompt_stem = (
|
||||
"system_prompt_cloud"
|
||||
if filesystem_mode == FilesystemMode.CLOUD
|
||||
|
|
@ -66,40 +66,39 @@ def build_subagent(
|
|||
)
|
||||
system_prompt = read_md_file(__package__, prompt_stem).strip()
|
||||
|
||||
resilience_mws = [
|
||||
m
|
||||
for m in (
|
||||
mws.get("retry"),
|
||||
mws.get("fallback"),
|
||||
mws.get("model_call_limit"),
|
||||
mws.get("tool_call_limit"),
|
||||
)
|
||||
if m is not None
|
||||
]
|
||||
|
||||
middleware: list[Any] = [
|
||||
build_todos_mw(),
|
||||
mws["todos"],
|
||||
build_kb_context_projection_mw(),
|
||||
build_filesystem_mw(
|
||||
backend_resolver=backend_resolver,
|
||||
backend_resolver=dependencies["backend_resolver"],
|
||||
filesystem_mode=filesystem_mode,
|
||||
search_space_id=search_space_id,
|
||||
user_id=user_id,
|
||||
thread_id=thread_id,
|
||||
search_space_id=dependencies["search_space_id"],
|
||||
user_id=dependencies.get("user_id"),
|
||||
thread_id=dependencies.get("thread_id"),
|
||||
),
|
||||
build_compaction_mw(llm),
|
||||
build_patch_tool_calls_mw(),
|
||||
*resilience_mws,
|
||||
build_anthropic_cache_mw(),
|
||||
]
|
||||
|
||||
resilience_mws = resilience.as_list()
|
||||
if resilience_mws:
|
||||
cache_idx = next(
|
||||
(
|
||||
i
|
||||
for i, m in enumerate(middleware)
|
||||
if isinstance(m, AnthropicPromptCachingMiddleware)
|
||||
),
|
||||
len(middleware),
|
||||
)
|
||||
for offset, mw in enumerate(resilience_mws):
|
||||
middleware.insert(cache_idx + offset, mw)
|
||||
|
||||
spec: dict[str, Any] = {
|
||||
"name": NAME,
|
||||
"description": description,
|
||||
"system_prompt": system_prompt,
|
||||
"model": llm,
|
||||
"tools": [],
|
||||
"tools": [], # KB virtual FS tools are injected at runtime by SurfSenseFilesystemMiddleware
|
||||
"middleware": middleware,
|
||||
"interrupt_on": destructive_fs_interrupt_on(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent:
|
||||
buckets = load_tools(dependencies=dependencies)
|
||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
|||
tools=tools,
|
||||
interrupt_on=interrupt_on,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Protocol
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -14,6 +13,9 @@ from app.agents.multi_agent_chat.constants import (
|
|||
from app.agents.multi_agent_chat.subagents.builtins.deliverables.agent import (
|
||||
build_subagent as build_deliverables_subagent,
|
||||
)
|
||||
from app.agents.multi_agent_chat.subagents.builtins.knowledge_base.agent import (
|
||||
build_subagent as build_knowledge_base_subagent,
|
||||
)
|
||||
from app.agents.multi_agent_chat.subagents.builtins.memory.agent import (
|
||||
build_subagent as build_memory_subagent,
|
||||
)
|
||||
|
|
@ -79,7 +81,7 @@ class SubagentBuilder(Protocol):
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
extra_tools_bucket: ToolsPermissions | None = None,
|
||||
) -> SubAgent: ...
|
||||
|
||||
|
|
@ -95,6 +97,7 @@ SUBAGENT_BUILDERS_BY_NAME: dict[str, SubagentBuilder] = {
|
|||
"gmail": build_gmail_subagent,
|
||||
"google_drive": build_google_drive_subagent,
|
||||
"jira": build_jira_subagent,
|
||||
"knowledge_base": build_knowledge_base_subagent,
|
||||
"linear": build_linear_subagent,
|
||||
"luma": build_luma_subagent,
|
||||
"memory": build_memory_subagent,
|
||||
|
|
@ -169,7 +172,7 @@ def build_subagents(
|
|||
*,
|
||||
dependencies: dict[str, Any],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
mcp_tools_by_agent: dict[str, ToolsPermissions] | None = None,
|
||||
exclude: list[str] | None = None,
|
||||
disabled_tools: list[str] | None = None,
|
||||
|
|
@ -188,7 +191,7 @@ def build_subagents(
|
|||
spec = builder(
|
||||
dependencies=dependencies,
|
||||
model=model,
|
||||
extra_middleware=extra_middleware,
|
||||
middleware_stack=middleware_stack,
|
||||
extra_tools_bucket=mcp.get(name),
|
||||
)
|
||||
_filter_disabled_tools_in_place(spec, disabled_names)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, cast
|
||||
|
||||
from deepagents import SubAgent
|
||||
|
|
@ -20,16 +19,22 @@ def pack_subagent(
|
|||
system_prompt: str,
|
||||
tools: list[BaseTool],
|
||||
model: BaseChatModel | None = None,
|
||||
extra_middleware: Sequence[Any] | None = None,
|
||||
middleware_stack: dict[str, Any] | None = None,
|
||||
interrupt_on: dict[str, bool] | None = None,
|
||||
) -> SubAgent:
|
||||
"""Pack the route-local pieces passed in into one sub-agent spec."""
|
||||
"""Pack the route-local pieces passed in into one sub-agent spec.
|
||||
|
||||
``middleware_stack`` is the shared subagent middleware stack (see
|
||||
``build_subagent_middleware_stack``). Every non-``None`` value is
|
||||
prepended to this subagent's middleware list in insertion order.
|
||||
"""
|
||||
if not system_prompt.strip():
|
||||
msg = f"Subagent {name!r}: system_prompt is empty"
|
||||
raise ValueError(msg)
|
||||
|
||||
prepended = [m for m in (middleware_stack or {}).values() if m is not None]
|
||||
middleware: list[Any] = [
|
||||
*(extra_middleware or []),
|
||||
*prepended,
|
||||
PatchToolCallsMiddleware(),
|
||||
DedupHITLToolCallsMiddleware(agent_tools=tools),
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue