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]] = {
|
SUBAGENT_TO_REQUIRED_CONNECTOR_MAP: dict[str, frozenset[str]] = {
|
||||||
"deliverables": frozenset(),
|
"deliverables": frozenset(),
|
||||||
|
"knowledge_base": frozenset(),
|
||||||
"airtable": frozenset({"AIRTABLE_CONNECTOR"}),
|
"airtable": frozenset({"AIRTABLE_CONNECTOR"}),
|
||||||
"calendar": frozenset({"GOOGLE_CALENDAR_CONNECTOR"}),
|
"calendar": frozenset({"GOOGLE_CALENDAR_CONNECTOR"}),
|
||||||
"clickup": frozenset({"CLICKUP_CONNECTOR"}),
|
"clickup": frozenset({"CLICKUP_CONNECTOR"}),
|
||||||
|
|
|
||||||
|
|
@ -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.middleware import (
|
from app.agents.multi_agent_chat.middleware.stack import (
|
||||||
build_main_agent_deepagent_middleware,
|
build_main_agent_deepagent_middleware,
|
||||||
)
|
)
|
||||||
from app.agents.multi_agent_chat.subagents.shared.permissions import (
|
from app.agents.multi_agent_chat.subagents.shared.permissions import (
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import time
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents.graph import BASE_AGENT_PROMPT
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
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
|
||||||
|
|
@ -218,7 +217,7 @@ async def create_multi_agent_chat_deep_agent(
|
||||||
"[create_agent] System prompt built in %.3fs", time.perf_counter() - _t0
|
"[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
|
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(
|
def build_main_agent_system_prompt(
|
||||||
*,
|
*,
|
||||||
|
registry_subagent_prompt_lines: list[tuple[str, str]],
|
||||||
today: datetime | None = None,
|
today: datetime | None = None,
|
||||||
thread_visibility: ChatVisibility | None = None,
|
thread_visibility: ChatVisibility | None = None,
|
||||||
enabled_tool_names: set[str] | 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,
|
use_default_system_instructions: bool = True,
|
||||||
citations_enabled: bool = True,
|
citations_enabled: bool = True,
|
||||||
model_name: str | None = None, # noqa: ARG001 — kept for caller compatibility
|
model_name: str | None = None, # noqa: ARG001 — kept for caller compatibility
|
||||||
registry_subagent_prompt_lines: list[tuple[str, str]] | None = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
resolved_today = (today or datetime.now(UTC)).astimezone(UTC).date().isoformat()
|
resolved_today = (today or datetime.now(UTC)).astimezone(UTC).date().isoformat()
|
||||||
visibility = thread_visibility or ChatVisibility.PRIVATE
|
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
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
def build_specialists_section(
|
def build_specialists_section(
|
||||||
specialist_lines: list[tuple[str, str]] | None,
|
specialist_lines: list[tuple[str, 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)
|
bullets = "\n".join(f"- **{name}** — {desc}" for name, desc in specialist_lines)
|
||||||
return f"\n<specialists>\n{bullets}\n</specialists>\n"
|
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
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
"""Compose the ``<tools>`` block from per-tool vertical-slice folders.
|
"""Compose the ``<tools>`` block from per-tool vertical-slice folders.
|
||||||
|
|
||||||
Each tool lives in ``prompts/tools/<name>/`` with ``description.md`` and an
|
Each tool lives in ``prompts/tools/<name>/`` with ``description.md`` and an
|
||||||
inline-rendered ``example.md``. Visibility variants (currently only
|
``example.md``. Visibility variants live in ``{private,team}/`` subfolders.
|
||||||
``update_memory``) live in ``prompts/tools/<name>/{private,team}/``.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -31,6 +30,8 @@ def build_tools_instruction_block(
|
||||||
enabled_tool_names: set[str] | None,
|
enabled_tool_names: set[str] | None,
|
||||||
disabled_tool_names: set[str] | None,
|
disabled_tool_names: set[str] | None,
|
||||||
) -> str:
|
) -> 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"
|
variant = "team" if visibility == ChatVisibility.SEARCH_SPACE else "private"
|
||||||
|
|
||||||
parts: list[str] = ["\n<tools>\n"]
|
parts: list[str] = ["\n<tools>\n"]
|
||||||
|
|
@ -51,6 +52,14 @@ def build_tools_instruction_block(
|
||||||
parts.append("\n" + example + "\n")
|
parts.append("\n" + example + "\n")
|
||||||
parts.append("\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 = (
|
known_disabled = (
|
||||||
set(disabled_tool_names) & set(MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED)
|
set(disabled_tool_names) & set(MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED)
|
||||||
if disabled_tool_names
|
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 __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)
|
@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
|
retry: RetryAfterMiddleware | None
|
||||||
fallback: ScopedModelFallbackMiddleware | None
|
fallback: ScopedModelFallbackMiddleware | None
|
||||||
model_call_limit: ModelCallLimitMiddleware | None
|
model_call_limit: ModelCallLimitMiddleware | None
|
||||||
|
|
@ -42,8 +44,8 @@ class ResilienceBundle:
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def build_resilience_bundle(flags: AgentFeatureFlags) -> ResilienceBundle:
|
def build_resilience_middlewares(flags: AgentFeatureFlags) -> ResilienceMiddlewares:
|
||||||
return ResilienceBundle(
|
return ResilienceMiddlewares(
|
||||||
retry=build_retry_mw(flags),
|
retry=build_retry_mw(flags),
|
||||||
fallback=build_fallback_mw(flags),
|
fallback=build_fallback_mw(flags),
|
||||||
model_call_limit=build_model_call_limit_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,
|
build_subagents,
|
||||||
get_subagents_to_exclude,
|
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.multi_agent_chat.subagents.shared.permissions import ToolsPermissions
|
||||||
from app.agents.new_chat.feature_flags import AgentFeatureFlags
|
from app.agents.new_chat.feature_flags import AgentFeatureFlags
|
||||||
from app.agents.new_chat.filesystem_selection import FilesystemMode
|
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 (
|
from .main_agent.checkpointed_subagent_middleware import (
|
||||||
SurfSenseCheckpointedSubAgentMiddleware,
|
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.context_editing import build_context_editing_mw
|
||||||
from .main_agent.dedup_hitl import build_dedup_hitl_mw
|
from .main_agent.dedup_hitl import build_dedup_hitl_mw
|
||||||
from .main_agent.doom_loop import build_doom_loop_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.kb_context_projection import build_kb_context_projection_mw
|
||||||
from .shared.memory import build_memory_mw
|
from .shared.memory import build_memory_mw
|
||||||
from .shared.patch_tool_calls import build_patch_tool_calls_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 .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(
|
def build_main_agent_deepagent_middleware(
|
||||||
|
|
@ -80,7 +80,7 @@ def build_main_agent_deepagent_middleware(
|
||||||
disabled_tools: list[str] | None = None,
|
disabled_tools: list[str] | None = None,
|
||||||
) -> list[Any]:
|
) -> list[Any]:
|
||||||
"""Ordered middleware for ``create_agent`` (None entries already stripped)."""
|
"""Ordered middleware for ``create_agent`` (None entries already stripped)."""
|
||||||
resilience = build_resilience_bundle(flags)
|
resilience = build_resilience_middlewares(flags)
|
||||||
|
|
||||||
memory_mw = build_memory_mw(
|
memory_mw = build_memory_mw(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
|
@ -88,45 +88,21 @@ def build_main_agent_deepagent_middleware(
|
||||||
visibility=visibility,
|
visibility=visibility,
|
||||||
)
|
)
|
||||||
|
|
||||||
knowledge_base_subagent = build_knowledge_base_subagent(
|
subagent_dependencies = {
|
||||||
llm=llm,
|
**subagent_dependencies,
|
||||||
backend_resolver=backend_resolver,
|
"backend_resolver": backend_resolver,
|
||||||
filesystem_mode=filesystem_mode,
|
"filesystem_mode": filesystem_mode,
|
||||||
search_space_id=search_space_id,
|
}
|
||||||
user_id=user_id,
|
|
||||||
thread_id=thread_id,
|
subagents: list[SubAgent] = build_subagents(
|
||||||
resilience=resilience,
|
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,
|
||||||
)
|
)
|
||||||
|
logging.debug("Subagents registry: %s", [s["name"] for s in subagents])
|
||||||
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,
|
|
||||||
]
|
|
||||||
|
|
||||||
stack: list[Any] = [
|
stack: list[Any] = [
|
||||||
build_busy_mutex_mw(flags),
|
build_busy_mutex_mw(flags),
|
||||||
|
|
@ -165,6 +141,8 @@ def build_main_agent_deepagent_middleware(
|
||||||
checkpointer=checkpointer,
|
checkpointer=checkpointer,
|
||||||
backend=StateBackend,
|
backend=StateBackend,
|
||||||
subagents=subagents,
|
subagents=subagents,
|
||||||
|
system_prompt=None,
|
||||||
|
task_description=TASK_TOOL_DESCRIPTION,
|
||||||
),
|
),
|
||||||
resilience.model_call_limit,
|
resilience.model_call_limit,
|
||||||
resilience.tool_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 __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
"""`knowledge_base` route: ``SubAgent`` spec for the SurfSense KB specialist.
|
"""`knowledge_base` route: ``SubAgent`` spec for the SurfSense KB specialist.
|
||||||
|
|
||||||
The KB subagent owns the `/documents/` workspace: reading, writing, editing,
|
Owns the ``/documents/`` workspace (read, write, edit, search, organise)
|
||||||
searching, and organising user documents. It shares the orchestrator's
|
and shares the orchestrator's ``workspace_tree_text`` and ``kb_priority``
|
||||||
``workspace_tree_text`` and ``kb_priority`` via state and re-emits them as
|
via state. KB conforms to :class:`SubagentBuilder` but composes its
|
||||||
SystemMessages through the projection middleware (no extra DB / LLM calls).
|
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
|
from __future__ import annotations
|
||||||
|
|
@ -11,7 +14,6 @@ from __future__ import annotations
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
from langchain_core.language_models import BaseChatModel
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.middleware.shared.anthropic_cache import (
|
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 (
|
from app.agents.multi_agent_chat.middleware.shared.patch_tool_calls import (
|
||||||
build_patch_tool_calls_mw,
|
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 (
|
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
|
||||||
read_md_file,
|
read_md_file,
|
||||||
)
|
)
|
||||||
|
from app.agents.multi_agent_chat.subagents.shared.permissions import (
|
||||||
|
ToolsPermissions,
|
||||||
|
)
|
||||||
from app.agents.new_chat.filesystem_selection import FilesystemMode
|
from app.agents.new_chat.filesystem_selection import FilesystemMode
|
||||||
|
|
||||||
from .tools.index import destructive_fs_interrupt_on
|
from .tools.index import destructive_fs_interrupt_on
|
||||||
|
|
@ -45,20 +46,19 @@ NAME = "knowledge_base"
|
||||||
|
|
||||||
def build_subagent(
|
def build_subagent(
|
||||||
*,
|
*,
|
||||||
llm: BaseChatModel,
|
dependencies: dict[str, Any],
|
||||||
backend_resolver: Any,
|
model: BaseChatModel | None = None,
|
||||||
filesystem_mode: FilesystemMode,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
search_space_id: int,
|
extra_tools_bucket: ToolsPermissions | None = None, # noqa: ARG001 — KB ships fixed tools
|
||||||
user_id: str | None,
|
|
||||||
thread_id: int | None,
|
|
||||||
resilience: ResilienceBundle,
|
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
"""Resilience inserts encapsulated here so the orchestrator never mutates the list."""
|
"""Conforms to :class:`SubagentBuilder`; KB splices the shared stack itself."""
|
||||||
description = read_md_file(__package__, "description").strip()
|
llm = model if model is not None else dependencies["llm"]
|
||||||
if not description:
|
filesystem_mode: FilesystemMode = dependencies["filesystem_mode"]
|
||||||
description = (
|
mws = middleware_stack or {}
|
||||||
"Handles knowledge-base reads, writes, edits, and organisation."
|
|
||||||
)
|
description = read_md_file(__package__, "description").strip() or (
|
||||||
|
"Handles knowledge-base reads, writes, edits, and organisation."
|
||||||
|
)
|
||||||
prompt_stem = (
|
prompt_stem = (
|
||||||
"system_prompt_cloud"
|
"system_prompt_cloud"
|
||||||
if filesystem_mode == FilesystemMode.CLOUD
|
if filesystem_mode == FilesystemMode.CLOUD
|
||||||
|
|
@ -66,40 +66,39 @@ def build_subagent(
|
||||||
)
|
)
|
||||||
system_prompt = read_md_file(__package__, prompt_stem).strip()
|
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] = [
|
middleware: list[Any] = [
|
||||||
build_todos_mw(),
|
mws["todos"],
|
||||||
build_kb_context_projection_mw(),
|
build_kb_context_projection_mw(),
|
||||||
build_filesystem_mw(
|
build_filesystem_mw(
|
||||||
backend_resolver=backend_resolver,
|
backend_resolver=dependencies["backend_resolver"],
|
||||||
filesystem_mode=filesystem_mode,
|
filesystem_mode=filesystem_mode,
|
||||||
search_space_id=search_space_id,
|
search_space_id=dependencies["search_space_id"],
|
||||||
user_id=user_id,
|
user_id=dependencies.get("user_id"),
|
||||||
thread_id=thread_id,
|
thread_id=dependencies.get("thread_id"),
|
||||||
),
|
),
|
||||||
build_compaction_mw(llm),
|
build_compaction_mw(llm),
|
||||||
build_patch_tool_calls_mw(),
|
build_patch_tool_calls_mw(),
|
||||||
|
*resilience_mws,
|
||||||
build_anthropic_cache_mw(),
|
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] = {
|
spec: dict[str, Any] = {
|
||||||
"name": NAME,
|
"name": NAME,
|
||||||
"description": description,
|
"description": description,
|
||||||
"system_prompt": system_prompt,
|
"system_prompt": system_prompt,
|
||||||
"model": llm,
|
"model": llm,
|
||||||
"tools": [],
|
"tools": [], # KB virtual FS tools are injected at runtime by SurfSenseFilesystemMiddleware
|
||||||
"middleware": middleware,
|
"middleware": middleware,
|
||||||
"interrupt_on": destructive_fs_interrupt_on(),
|
"interrupt_on": destructive_fs_interrupt_on(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -29,7 +28,7 @@ def build_subagent(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent:
|
) -> SubAgent:
|
||||||
buckets = load_tools(dependencies=dependencies)
|
buckets = load_tools(dependencies=dependencies)
|
||||||
|
|
@ -51,5 +50,5 @@ def build_subagent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
interrupt_on=interrupt_on,
|
interrupt_on=interrupt_on,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any, Protocol
|
from typing import Any, Protocol
|
||||||
|
|
||||||
from deepagents import SubAgent
|
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 (
|
from app.agents.multi_agent_chat.subagents.builtins.deliverables.agent import (
|
||||||
build_subagent as build_deliverables_subagent,
|
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 (
|
from app.agents.multi_agent_chat.subagents.builtins.memory.agent import (
|
||||||
build_subagent as build_memory_subagent,
|
build_subagent as build_memory_subagent,
|
||||||
)
|
)
|
||||||
|
|
@ -79,7 +81,7 @@ class SubagentBuilder(Protocol):
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
extra_tools_bucket: ToolsPermissions | None = None,
|
extra_tools_bucket: ToolsPermissions | None = None,
|
||||||
) -> SubAgent: ...
|
) -> SubAgent: ...
|
||||||
|
|
||||||
|
|
@ -95,6 +97,7 @@ SUBAGENT_BUILDERS_BY_NAME: dict[str, SubagentBuilder] = {
|
||||||
"gmail": build_gmail_subagent,
|
"gmail": build_gmail_subagent,
|
||||||
"google_drive": build_google_drive_subagent,
|
"google_drive": build_google_drive_subagent,
|
||||||
"jira": build_jira_subagent,
|
"jira": build_jira_subagent,
|
||||||
|
"knowledge_base": build_knowledge_base_subagent,
|
||||||
"linear": build_linear_subagent,
|
"linear": build_linear_subagent,
|
||||||
"luma": build_luma_subagent,
|
"luma": build_luma_subagent,
|
||||||
"memory": build_memory_subagent,
|
"memory": build_memory_subagent,
|
||||||
|
|
@ -169,7 +172,7 @@ def build_subagents(
|
||||||
*,
|
*,
|
||||||
dependencies: dict[str, Any],
|
dependencies: dict[str, Any],
|
||||||
model: BaseChatModel | None = None,
|
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,
|
mcp_tools_by_agent: dict[str, ToolsPermissions] | None = None,
|
||||||
exclude: list[str] | None = None,
|
exclude: list[str] | None = None,
|
||||||
disabled_tools: list[str] | None = None,
|
disabled_tools: list[str] | None = None,
|
||||||
|
|
@ -188,7 +191,7 @@ def build_subagents(
|
||||||
spec = builder(
|
spec = builder(
|
||||||
dependencies=dependencies,
|
dependencies=dependencies,
|
||||||
model=model,
|
model=model,
|
||||||
extra_middleware=extra_middleware,
|
middleware_stack=middleware_stack,
|
||||||
extra_tools_bucket=mcp.get(name),
|
extra_tools_bucket=mcp.get(name),
|
||||||
)
|
)
|
||||||
_filter_disabled_tools_in_place(spec, disabled_names)
|
_filter_disabled_tools_in_place(spec, disabled_names)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from deepagents import SubAgent
|
from deepagents import SubAgent
|
||||||
|
|
@ -20,16 +19,22 @@ def pack_subagent(
|
||||||
system_prompt: str,
|
system_prompt: str,
|
||||||
tools: list[BaseTool],
|
tools: list[BaseTool],
|
||||||
model: BaseChatModel | None = None,
|
model: BaseChatModel | None = None,
|
||||||
extra_middleware: Sequence[Any] | None = None,
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
interrupt_on: dict[str, bool] | None = None,
|
interrupt_on: dict[str, bool] | None = None,
|
||||||
) -> SubAgent:
|
) -> 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():
|
if not system_prompt.strip():
|
||||||
msg = f"Subagent {name!r}: system_prompt is empty"
|
msg = f"Subagent {name!r}: system_prompt is empty"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
prepended = [m for m in (middleware_stack or {}).values() if m is not None]
|
||||||
middleware: list[Any] = [
|
middleware: list[Any] = [
|
||||||
*(extra_middleware or []),
|
*prepended,
|
||||||
PatchToolCallsMiddleware(),
|
PatchToolCallsMiddleware(),
|
||||||
DedupHITLToolCallsMiddleware(agent_tools=tools),
|
DedupHITLToolCallsMiddleware(agent_tools=tools),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Subagent resilience contract: ``extra_middleware`` reaches the agent chain."""
|
"""Subagent resilience contract: ``middleware_stack`` reaches the agent chain."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ class _AlwaysFailingChatModel(BaseChatModel):
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_subagent_recovers_when_primary_llm_fails():
|
async def test_subagent_recovers_when_primary_llm_fails():
|
||||||
"""Fallback in ``extra_middleware`` must finish the turn when primary raises."""
|
"""Fallback in ``middleware_stack`` must finish the turn when primary raises."""
|
||||||
primary = _AlwaysFailingChatModel()
|
primary = _AlwaysFailingChatModel()
|
||||||
fallback = FakeMessagesListChatModel(
|
fallback = FakeMessagesListChatModel(
|
||||||
responses=[AIMessage(content="recovered via fallback")]
|
responses=[AIMessage(content="recovered via fallback")]
|
||||||
|
|
@ -79,7 +79,7 @@ async def test_subagent_recovers_when_primary_llm_fails():
|
||||||
system_prompt="be helpful",
|
system_prompt="be helpful",
|
||||||
tools=[],
|
tools=[],
|
||||||
model=primary,
|
model=primary,
|
||||||
extra_middleware=[ModelFallbackMiddleware(fallback)],
|
middleware_stack={"fallback": ModelFallbackMiddleware(fallback)},
|
||||||
)
|
)
|
||||||
|
|
||||||
agent = create_agent(
|
agent = create_agent(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue