feat(agents): consolidate connectors under mcp_discovery; route web search through google_search

MCP consolidation:
- Route all MCP-capable connectors (Slack, Jira, Linear, ClickUp, Airtable,
  Notion, Confluence, interim Gmail/Calendar, custom MCP) through a single
  `mcp_discovery` subagent. Drive/OneDrive/Dropbox stay native to enrich the KB.
- Deprecate Discord/Teams/Luma: no viable official MCP server.

Google-only web search:
- Remove the main-agent `web_search` tool and the SearXNG platform service;
  all public web search now flows through the `google_search` subagent via task().
- Deprecate the Tavily/SearXNG/Linkup/Baidu search connectors (HTTP 410 on
  create, "Deprecated" badge); guide heavy users to the custom MCP connector.
- Remove web search from anonymous chat (pure Q&A).
- Tear SearXNG out of docker compose + install scripts; drop tavily-python
  and linkup-sdk deps and their config/env vars.

Fix:
- metrics._package_version() now swallows any metadata lookup failure. A
  malformed editable-install distribution with no `Version` field raised
  KeyError deep in importlib.metadata, and since it runs on every
  record_subagent_invoke_duration call it was crashing every task()
  delegation. Verified end-to-end against live GPT-5.4.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-07-04 21:06:04 -07:00
parent ff2e5f390f
commit ab747e7a49
206 changed files with 1704 additions and 7223 deletions

View file

@ -0,0 +1,86 @@
"""Backend E2E: public web search now flows through the ``google_search`` route.
After the Google-only consolidation the main agent has no ``web_search`` tool;
a web query must be delegated to the ``google_search`` subagent via ``task``.
This drives the *assembled* production graph (real DB, scripted LLM) end to end:
main agent emits a ``task(google_search, ...)`` call, the subagent runs a turn,
and the main agent resumes to a final answer. Proves the delegation path the
teardown relies on actually executes -- not just that the constants changed.
"""
from __future__ import annotations
import pytest
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langgraph.checkpoint.memory import InMemorySaver
from app.agents.chat.multi_agent_chat import create_multi_agent_chat_deep_agent
from app.services.connector_service import ConnectorService
from tests.integration.harness import ScriptedTurn, build_scripted_harness
pytestmark = pytest.mark.integration
def _last_ai_text(messages: list) -> str | None:
for m in reversed(messages):
if isinstance(m, AIMessage) and isinstance(m.content, str) and m.content:
return m.content
return None
@pytest.mark.asyncio
async def test_web_query_delegates_to_google_search(
db_session, db_user, db_workspace
):
"""A web-search query routes through ``task(google_search)`` and resumes.
Scripted sequence (the fake model is shared and consumed in order across the
main agent and the delegated subagent):
1. main agent -> task(subagent_type="google_search")
2. subagent -> plain text (never touches the scrape tool, so no network)
3. main agent -> final answer
"""
harness = build_scripted_harness(
turns=[
ScriptedTurn(
tool_calls=[
{
"name": "task",
"args": {
"subagent_type": "google_search",
"description": "latest SurfSense release notes",
},
"id": "call_ws_task",
}
]
),
ScriptedTurn(text="SERP: SurfSense v2 shipped Google-only web search."),
ScriptedTurn(text="SurfSense v2 shipped Google-only web search."),
]
)
agent = await create_multi_agent_chat_deep_agent(
llm=harness.model,
workspace_id=db_workspace.id,
db_session=db_session,
connector_service=ConnectorService(db_session),
checkpointer=InMemorySaver(),
user_id=str(db_user.id),
thread_id=db_workspace.id,
agent_config=None,
)
result = await agent.ainvoke(
{"messages": [HumanMessage(content="search the web for SurfSense news")]},
config={"configurable": {"thread_id": "ws-google-delegation-1"}},
)
task_tool_messages = [
m
for m in result["messages"]
if isinstance(m, ToolMessage) and m.name == "task"
]
assert task_tool_messages, "web query did not delegate through the task tool"
assert _last_ai_text(result["messages"]) == (
"SurfSense v2 shipped Google-only web search."
)