mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-10 22:32:16 +02:00
feat(chat): add intelligence_agent subagent over capability verbs
This commit is contained in:
parent
110beb4dd1
commit
7ad83760b5
9 changed files with 135 additions and 1 deletions
|
|
@ -26,6 +26,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(),
|
"knowledge_base": frozenset(),
|
||||||
|
"intelligence_agent": 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"}),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""``intelligence_agent`` route: ``SurfSenseSubagentSpec`` builder for deepagents."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from langchain_core.language_models import BaseChatModel
|
||||||
|
from langchain_core.tools import BaseTool
|
||||||
|
|
||||||
|
from app.agents.chat.multi_agent_chat.subagents.shared.md_file_reader import (
|
||||||
|
read_md_file,
|
||||||
|
)
|
||||||
|
from app.agents.chat.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
|
||||||
|
from app.agents.chat.multi_agent_chat.subagents.shared.subagent_builder import (
|
||||||
|
pack_subagent,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .tools.index import NAME, RULESET, load_tools
|
||||||
|
|
||||||
|
|
||||||
|
def build_subagent(
|
||||||
|
*,
|
||||||
|
dependencies: dict[str, Any],
|
||||||
|
model: BaseChatModel | None = None,
|
||||||
|
middleware_stack: dict[str, Any] | None = None,
|
||||||
|
mcp_tools: list[BaseTool] | None = None,
|
||||||
|
) -> SurfSenseSubagentSpec:
|
||||||
|
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
|
||||||
|
description = (
|
||||||
|
read_md_file(__package__, "description").strip()
|
||||||
|
or "Gathers and compares web intelligence for this workspace."
|
||||||
|
)
|
||||||
|
system_prompt = read_md_file(__package__, "system_prompt").strip()
|
||||||
|
return pack_subagent(
|
||||||
|
name=NAME,
|
||||||
|
description=description,
|
||||||
|
system_prompt=system_prompt,
|
||||||
|
tools=tools,
|
||||||
|
ruleset=RULESET,
|
||||||
|
dependencies=dependencies,
|
||||||
|
model=model,
|
||||||
|
middleware_stack=middleware_stack,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Specialist for web intelligence: fetch live pages and search the web, then compare against earlier findings.
|
||||||
|
Use whenever a task needs current data pulled from specific URLs or discovered on the web — competitor/product monitoring, pricing, listings, "what changed since last time" — and answered from that evidence.
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
You are the SurfSense intelligence sub-agent.
|
||||||
|
You receive delegated instructions from a supervisor agent and return structured results for supervisor synthesis.
|
||||||
|
|
||||||
|
<goal>
|
||||||
|
Pull evidence from the live web with the web.* capability verbs and answer the delegated question, including "what changed" comparisons against evidence already in this conversation.
|
||||||
|
</goal>
|
||||||
|
|
||||||
|
<available_tools>
|
||||||
|
- `web_discover` — search the web for a query; returns ranked hits (url, title, snippet, provider).
|
||||||
|
- `web_scrape` — fetch specific URLs; returns clean page content and metadata per URL.
|
||||||
|
</available_tools>
|
||||||
|
|
||||||
|
<playbook>
|
||||||
|
- If the request names exact URLs, call `web_scrape` on them directly.
|
||||||
|
- If it does not, call `web_discover` first, pick the most relevant hits, then `web_scrape` those URLs to read them.
|
||||||
|
- Batch URLs into a single `web_scrape` call when reading several pages (up to its limit) instead of many one-URL calls.
|
||||||
|
- For "what changed" / monitoring requests, compare the freshly scraped values against the prior values already present in this conversation's earlier tool results, and report the concrete deltas (added, removed, changed old -> new). Do not claim a change you cannot point to.
|
||||||
|
</playbook>
|
||||||
|
|
||||||
|
<tool_policy>
|
||||||
|
- Use only tools in `<available_tools>`.
|
||||||
|
- A `web_scrape` row with `status` other than `success` yielded no content — do not invent its content; report it as unavailable.
|
||||||
|
- Never fabricate facts, URLs, prices, or quotes.
|
||||||
|
</tool_policy>
|
||||||
|
|
||||||
|
<safety>
|
||||||
|
- Report uncertainty explicitly when evidence is incomplete or conflicting.
|
||||||
|
- Never present unverified claims as facts.
|
||||||
|
</safety>
|
||||||
|
|
||||||
|
<failure_policy>
|
||||||
|
- On tool failure, return `status=error` with a concise recovery `next_step`.
|
||||||
|
- On no useful evidence, return `status=blocked` with a narrower query or the URLs you still need.
|
||||||
|
</failure_policy>
|
||||||
|
|
||||||
|
<output_contract>
|
||||||
|
Return **only** one JSON object (no markdown/prose):
|
||||||
|
{
|
||||||
|
"status": "success" | "partial" | "blocked" | "error",
|
||||||
|
"action_summary": string,
|
||||||
|
"evidence": {
|
||||||
|
"findings": string[],
|
||||||
|
"sources": string[],
|
||||||
|
"confidence": "high" | "medium" | "low"
|
||||||
|
},
|
||||||
|
"next_step": string | null,
|
||||||
|
"missing_fields": string[] | null,
|
||||||
|
"assumptions": string[] | null
|
||||||
|
}
|
||||||
|
<include snippet="output_contract_base"/>
|
||||||
|
Route-specific rules:
|
||||||
|
- `evidence.findings`: max 10 entries, each a single sentence stating one distinct fact or delta. Do not paste raw scraped pages.
|
||||||
|
- `evidence.sources`: max 10 URLs, one per finding when applicable. List each URL once.
|
||||||
|
</output_contract>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""Intelligence-agent tools: the web.* capability verbs, generated from the registry."""
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
"""``intelligence_agent`` tools: web.* verbs generated from the capability registry."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from langchain_core.tools import BaseTool
|
||||||
|
|
||||||
|
from app.agents.chat.multi_agent_chat.shared.permissions import Ruleset
|
||||||
|
from app.capabilities.access.agent import build_capability_tools
|
||||||
|
from app.capabilities.web.discover.definition import WEB_DISCOVER
|
||||||
|
from app.capabilities.web.scrape.definition import WEB_SCRAPE
|
||||||
|
|
||||||
|
NAME = "intelligence_agent"
|
||||||
|
|
||||||
|
RULESET = Ruleset(origin=NAME, rules=[])
|
||||||
|
|
||||||
|
_CI_VERBS = [WEB_DISCOVER, WEB_SCRAPE]
|
||||||
|
|
||||||
|
|
||||||
|
def load_tools(
|
||||||
|
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
|
||||||
|
) -> list[BaseTool]:
|
||||||
|
d = {**(dependencies or {}), **kwargs}
|
||||||
|
return build_capability_tools(
|
||||||
|
workspace_id=d.get("workspace_id"),
|
||||||
|
capabilities=_CI_VERBS,
|
||||||
|
)
|
||||||
|
|
@ -15,6 +15,9 @@ from app.agents.chat.multi_agent_chat.constants import (
|
||||||
from app.agents.chat.multi_agent_chat.subagents.builtins.deliverables.agent import (
|
from app.agents.chat.multi_agent_chat.subagents.builtins.deliverables.agent import (
|
||||||
build_subagent as build_deliverables_subagent,
|
build_subagent as build_deliverables_subagent,
|
||||||
)
|
)
|
||||||
|
from app.agents.chat.multi_agent_chat.subagents.builtins.intelligence_agent.agent import (
|
||||||
|
build_subagent as build_intelligence_agent_subagent,
|
||||||
|
)
|
||||||
from app.agents.chat.multi_agent_chat.subagents.builtins.knowledge_base.agent import (
|
from app.agents.chat.multi_agent_chat.subagents.builtins.knowledge_base.agent import (
|
||||||
build_subagent as build_knowledge_base_subagent,
|
build_subagent as build_knowledge_base_subagent,
|
||||||
)
|
)
|
||||||
|
|
@ -99,6 +102,7 @@ SUBAGENT_BUILDERS_BY_NAME: dict[str, SubagentBuilder] = {
|
||||||
"dropbox": build_dropbox_subagent,
|
"dropbox": build_dropbox_subagent,
|
||||||
"gmail": build_gmail_subagent,
|
"gmail": build_gmail_subagent,
|
||||||
"google_drive": build_google_drive_subagent,
|
"google_drive": build_google_drive_subagent,
|
||||||
|
"intelligence_agent": build_intelligence_agent_subagent,
|
||||||
"jira": build_jira_subagent,
|
"jira": build_jira_subagent,
|
||||||
"knowledge_base": build_knowledge_base_subagent,
|
"knowledge_base": build_knowledge_base_subagent,
|
||||||
"linear": build_linear_subagent,
|
"linear": build_linear_subagent,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from app.agents.chat.multi_agent_chat.subagents.registry import (
|
||||||
|
|
||||||
pytestmark = pytest.mark.unit
|
pytestmark = pytest.mark.unit
|
||||||
|
|
||||||
# The full specialist roster the main agent composes from: 4 builtins + 15
|
# The full specialist roster the main agent composes from: 5 builtins + 15
|
||||||
# connector routes. Adding/removing a specialist is a deliberate product change
|
# connector routes. Adding/removing a specialist is a deliberate product change
|
||||||
# and must be reflected here.
|
# and must be reflected here.
|
||||||
_EXPECTED_SUBAGENTS = frozenset(
|
_EXPECTED_SUBAGENTS = frozenset(
|
||||||
|
|
@ -33,6 +33,7 @@ _EXPECTED_SUBAGENTS = frozenset(
|
||||||
"dropbox",
|
"dropbox",
|
||||||
"gmail",
|
"gmail",
|
||||||
"google_drive",
|
"google_drive",
|
||||||
|
"intelligence_agent",
|
||||||
"jira",
|
"jira",
|
||||||
"knowledge_base",
|
"knowledge_base",
|
||||||
"linear",
|
"linear",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue