Skip to content

Commit d8a05ae

Browse files
committed
feat: refactor agent tools management and add UI integration
- Added endpoint to list agent tools with metadata, excluding hidden tools. - Updated NewChatRequest and RegenerateRequest schemas to include disabled tools. - Integrated disabled tools management in the NewChatPage and Composer components. - Improved tool instructions and visibility in the system prompt. - Refactored tool registration to support hidden tools and default enabled states. - Enhanced document chunk creation to handle strict zip behavior. - Cleaned up imports and formatting across various files for consistency.
1 parent c131912 commit d8a05ae

File tree

20 files changed

+538
-283
lines changed

20 files changed

+538
-283
lines changed

surfsense_backend/app/agents/new_chat/chat_deepagent.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,21 +303,27 @@ async def create_surfsense_deep_agent(
303303
len(tools),
304304
)
305305

306-
# Build system prompt based on agent_config
306+
# Build system prompt based on agent_config, scoped to the tools actually enabled
307307
_t0 = time.perf_counter()
308308
_sandbox_enabled = sandbox_backend is not None
309+
_enabled_tool_names = {t.name for t in tools}
310+
_user_disabled_tool_names = set(disabled_tools) if disabled_tools else set()
309311
if agent_config is not None:
310312
system_prompt = build_configurable_system_prompt(
311313
custom_system_instructions=agent_config.system_instructions,
312314
use_default_system_instructions=agent_config.use_default_system_instructions,
313315
citations_enabled=agent_config.citations_enabled,
314316
thread_visibility=thread_visibility,
315317
sandbox_enabled=_sandbox_enabled,
318+
enabled_tool_names=_enabled_tool_names,
319+
disabled_tool_names=_user_disabled_tool_names,
316320
)
317321
else:
318322
system_prompt = build_surfsense_system_prompt(
319323
thread_visibility=thread_visibility,
320324
sandbox_enabled=_sandbox_enabled,
325+
enabled_tool_names=_enabled_tool_names,
326+
disabled_tool_names=_user_disabled_tool_names,
321327
)
322328
_perf_log.info(
323329
"[create_agent] System prompt built in %.3fs", time.perf_counter() - _t0

surfsense_backend/app/agents/new_chat/system_prompt.py

Lines changed: 228 additions & 207 deletions
Large diffs are not rendered by default.

surfsense_backend/app/agents/new_chat/tools/registry.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class ToolDefinition:
9797
factory: Callable[[dict[str, Any]], BaseTool]
9898
requires: list[str] = field(default_factory=list)
9999
enabled_by_default: bool = True
100+
hidden: bool = False
100101

101102

102103
# =============================================================================
@@ -139,7 +140,7 @@ class ToolDefinition:
139140
# need to call search_knowledge_base separately before generating.
140141
ToolDefinition(
141142
name="generate_report",
142-
description="Generate a structured Markdown report from provided content",
143+
description="Generate a structured report from provided content and export it",
143144
factory=lambda deps: create_generate_report_tool(
144145
search_space_id=deps["search_space_id"],
145146
thread_id=deps["thread_id"],
@@ -234,7 +235,7 @@ class ToolDefinition:
234235
requires=["user_id", "search_space_id", "db_session", "thread_visibility"],
235236
),
236237
# =========================================================================
237-
# LINEAR TOOLS - create, update, delete issues
238+
# LINEAR TOOLS - create, update, delete issues (WIP - hidden from UI)
238239
# =========================================================================
239240
ToolDefinition(
240241
name="create_linear_issue",
@@ -245,6 +246,8 @@ class ToolDefinition:
245246
user_id=deps["user_id"],
246247
),
247248
requires=["db_session", "search_space_id", "user_id"],
249+
enabled_by_default=False,
250+
hidden=True,
248251
),
249252
ToolDefinition(
250253
name="update_linear_issue",
@@ -255,6 +258,8 @@ class ToolDefinition:
255258
user_id=deps["user_id"],
256259
),
257260
requires=["db_session", "search_space_id", "user_id"],
261+
enabled_by_default=False,
262+
hidden=True,
258263
),
259264
ToolDefinition(
260265
name="delete_linear_issue",
@@ -265,9 +270,11 @@ class ToolDefinition:
265270
user_id=deps["user_id"],
266271
),
267272
requires=["db_session", "search_space_id", "user_id"],
273+
enabled_by_default=False,
274+
hidden=True,
268275
),
269276
# =========================================================================
270-
# NOTION TOOLS - create, update, delete pages
277+
# NOTION TOOLS - create, update, delete pages (WIP - hidden from UI)
271278
# =========================================================================
272279
ToolDefinition(
273280
name="create_notion_page",
@@ -278,6 +285,8 @@ class ToolDefinition:
278285
user_id=deps["user_id"],
279286
),
280287
requires=["db_session", "search_space_id", "user_id"],
288+
enabled_by_default=False,
289+
hidden=True,
281290
),
282291
ToolDefinition(
283292
name="update_notion_page",
@@ -288,6 +297,8 @@ class ToolDefinition:
288297
user_id=deps["user_id"],
289298
),
290299
requires=["db_session", "search_space_id", "user_id"],
300+
enabled_by_default=False,
301+
hidden=True,
291302
),
292303
ToolDefinition(
293304
name="delete_notion_page",
@@ -298,9 +309,11 @@ class ToolDefinition:
298309
user_id=deps["user_id"],
299310
),
300311
requires=["db_session", "search_space_id", "user_id"],
312+
enabled_by_default=False,
313+
hidden=True,
301314
),
302315
# =========================================================================
303-
# GOOGLE DRIVE TOOLS - create files, delete files
316+
# GOOGLE DRIVE TOOLS - create files, delete files (WIP - hidden from UI)
304317
# =========================================================================
305318
ToolDefinition(
306319
name="create_google_drive_file",
@@ -311,6 +324,8 @@ class ToolDefinition:
311324
user_id=deps["user_id"],
312325
),
313326
requires=["db_session", "search_space_id", "user_id"],
327+
enabled_by_default=False,
328+
hidden=True,
314329
),
315330
ToolDefinition(
316331
name="delete_google_drive_file",
@@ -321,6 +336,8 @@ class ToolDefinition:
321336
user_id=deps["user_id"],
322337
),
323338
requires=["db_session", "search_space_id", "user_id"],
339+
enabled_by_default=False,
340+
hidden=True,
324341
),
325342
]
326343

@@ -344,7 +361,7 @@ def get_all_tool_names() -> list[str]:
344361

345362

346363
def get_default_enabled_tools() -> list[str]:
347-
"""Get names of tools that are enabled by default."""
364+
"""Get names of tools that are enabled by default (excludes hidden tools)."""
348365
return [tool_def.name for tool_def in BUILTIN_TOOLS if tool_def.enabled_by_default]
349366

350367

@@ -393,10 +410,10 @@ def build_tools(
393410
if disabled_tools:
394411
tool_names_to_use -= set(disabled_tools)
395412

396-
# Build the tools
413+
# Build the tools (skip hidden/WIP tools unconditionally)
397414
tools: list[BaseTool] = []
398415
for tool_def in BUILTIN_TOOLS:
399-
if tool_def.name not in tool_names_to_use:
416+
if tool_def.hidden or tool_def.name not in tool_names_to_use:
400417
continue
401418

402419
# Check that all required dependencies are provided

surfsense_backend/app/connectors/composio_gmail_connector.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
from bs4 import BeautifulSoup
1414
from markdownify import markdownify as md
15-
1615
from sqlalchemy.ext.asyncio import AsyncSession
1716
from sqlalchemy.future import select
1817
from sqlalchemy.orm import selectinload
@@ -139,7 +138,9 @@ def _html_to_markdown(html: str) -> str:
139138
soup = BeautifulSoup(html, "html.parser")
140139
for tag in soup.find_all(["style", "script", "img"]):
141140
tag.decompose()
142-
for tag in soup.find_all(["table", "thead", "tbody", "tfoot", "tr", "td", "th"]):
141+
for tag in soup.find_all(
142+
["table", "thead", "tbody", "tfoot", "tr", "td", "th"]
143+
):
143144
tag.unwrap()
144145
return md(str(soup)).strip()
145146

surfsense_backend/app/connectors/google_gmail_connector.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
from typing import Any
1111

1212
from bs4 import BeautifulSoup
13-
from markdownify import markdownify as md
14-
1513
from google.auth.transport.requests import Request
1614
from google.oauth2.credentials import Credentials
1715
from googleapiclient.discovery import build
16+
from markdownify import markdownify as md
1817
from sqlalchemy.ext.asyncio import AsyncSession
1918
from sqlalchemy.future import select
2019
from sqlalchemy.orm.attributes import flag_modified
@@ -356,7 +355,9 @@ def _html_to_markdown(html: str) -> str:
356355
soup = BeautifulSoup(html, "html.parser")
357356
for tag in soup.find_all(["style", "script", "img"]):
358357
tag.decompose()
359-
for tag in soup.find_all(["table", "thead", "tbody", "tfoot", "tr", "td", "th"]):
358+
for tag in soup.find_all(
359+
["table", "thead", "tbody", "tfoot", "tr", "td", "th"]
360+
):
360361
tag.unwrap()
361362
return md(str(soup)).strip()
362363

surfsense_backend/app/db.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,10 @@ class Chunk(BaseModel, TimestampMixin):
960960
embedding = Column(Vector(config.embedding_model_instance.dimension))
961961

962962
document_id = Column(
963-
Integer, ForeignKey("documents.id", ondelete="CASCADE"), nullable=False, index=True
963+
Integer,
964+
ForeignKey("documents.id", ondelete="CASCADE"),
965+
nullable=False,
966+
index=True,
964967
)
965968
document = relationship("Document", back_populates="chunks")
966969

surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ async def index(
211211

212212
chunks = [
213213
Chunk(content=text, embedding=emb)
214-
for text, emb in zip(chunk_texts, chunk_embeddings)
214+
for text, emb in zip(chunk_texts, chunk_embeddings, strict=False)
215215
]
216216
perf.info(
217217
"[indexing] chunk+embed doc=%d chunks=%d in %.3fs",

surfsense_backend/app/routes/new_chat_routes.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
shielded_async_session,
3636
)
3737
from app.schemas.new_chat import (
38+
AgentToolInfo,
3839
NewChatMessageRead,
3940
NewChatRequest,
4041
NewChatThreadCreate,
@@ -1024,6 +1025,32 @@ async def list_messages(
10241025
) from None
10251026

10261027

1028+
# =============================================================================
1029+
# Agent Tools Endpoint
1030+
# =============================================================================
1031+
1032+
1033+
@router.get("/agent/tools", response_model=list[AgentToolInfo])
1034+
async def list_agent_tools(
1035+
_user: User = Depends(current_active_user),
1036+
):
1037+
"""Return the list of built-in agent tools with their metadata.
1038+
1039+
Hidden (WIP) tools are excluded from the response.
1040+
"""
1041+
from app.agents.new_chat.tools.registry import BUILTIN_TOOLS
1042+
1043+
return [
1044+
AgentToolInfo(
1045+
name=t.name,
1046+
description=t.description,
1047+
enabled_by_default=t.enabled_by_default,
1048+
)
1049+
for t in BUILTIN_TOOLS
1050+
if not t.hidden
1051+
]
1052+
1053+
10271054
# =============================================================================
10281055
# Chat Streaming Endpoint
10291056
# =============================================================================
@@ -1108,6 +1135,7 @@ async def handle_new_chat(
11081135
needs_history_bootstrap=thread.needs_history_bootstrap,
11091136
thread_visibility=thread.visibility,
11101137
current_user_display_name=user.display_name or "A team member",
1138+
disabled_tools=request.disabled_tools,
11111139
),
11121140
media_type="text/event-stream",
11131141
headers={
@@ -1344,6 +1372,7 @@ async def stream_with_cleanup():
13441372
needs_history_bootstrap=thread.needs_history_bootstrap,
13451373
thread_visibility=thread.visibility,
13461374
current_user_display_name=user.display_name or "A team member",
1375+
disabled_tools=request.disabled_tools,
13471376
):
13481377
yield chunk
13491378
streaming_completed = True

surfsense_backend/app/routes/search_source_connectors_routes.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Non-OAuth connectors (BookStack, GitHub, etc.) are limited to one per search space.
1919
"""
2020

21+
import asyncio
2122
import logging
2223
from contextlib import suppress
2324
from datetime import UTC, datetime, timedelta
@@ -52,8 +53,6 @@
5253
SearchSourceConnectorRead,
5354
SearchSourceConnectorUpdate,
5455
)
55-
import asyncio
56-
5756
from app.services.composio_service import ComposioService, get_composio_service
5857
from app.services.notification_service import NotificationService
5958
from app.tasks.connector_indexers import (
@@ -3124,7 +3123,9 @@ async def get_drive_picker_token(
31243123
detail="Composio connected account not found. Please reconnect.",
31253124
)
31263125
service = get_composio_service()
3127-
access_token = await asyncio.to_thread(service.get_access_token, composio_account_id)
3126+
access_token = await asyncio.to_thread(
3127+
service.get_access_token, composio_account_id
3128+
)
31283129
return {
31293130
"access_token": access_token,
31303131
"client_id": config.GOOGLE_OAUTH_CLIENT_ID,

surfsense_backend/app/schemas/new_chat.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ class NewChatRequest(BaseModel):
172172
mentioned_surfsense_doc_ids: list[int] | None = (
173173
None # Optional SurfSense documentation IDs mentioned with @ in the chat
174174
)
175+
disabled_tools: list[str] | None = (
176+
None # Optional list of tool names the user has disabled from the UI
177+
)
175178

176179

177180
class RegenerateRequest(BaseModel):
@@ -191,6 +194,20 @@ class RegenerateRequest(BaseModel):
191194
)
192195
mentioned_document_ids: list[int] | None = None
193196
mentioned_surfsense_doc_ids: list[int] | None = None
197+
disabled_tools: list[str] | None = None
198+
199+
200+
# =============================================================================
201+
# Agent Tools Schemas
202+
# =============================================================================
203+
204+
205+
class AgentToolInfo(BaseModel):
206+
"""Schema for a single agent tool's public metadata."""
207+
208+
name: str
209+
description: str
210+
enabled_by_default: bool
194211

195212

196213
class ResumeDecision(BaseModel):

0 commit comments

Comments
 (0)