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
|
|
@ -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,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue