From 23802a74bb08b10002e24b47b5491e056fd87f36 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 2 Jul 2026 00:16:55 +0200 Subject: [PATCH] feat(capabilities): document verbs with descriptions and field docs --- surfsense_backend/app/capabilities/types.py | 1 + .../capabilities/web/discover/definition.py | 6 +++ .../app/capabilities/web/discover/schemas.py | 24 +++++++---- .../app/capabilities/web/scrape/definition.py | 6 +++ .../app/capabilities/web/scrape/schemas.py | 42 +++++++++++++++---- .../capabilities/access/test_rest_router.py | 1 + 6 files changed, 64 insertions(+), 16 deletions(-) diff --git a/surfsense_backend/app/capabilities/types.py b/surfsense_backend/app/capabilities/types.py index 4a8b57cbf..14c1f17e7 100644 --- a/surfsense_backend/app/capabilities/types.py +++ b/surfsense_backend/app/capabilities/types.py @@ -48,6 +48,7 @@ class Capability: """One typed verb; the source of truth the doors (05) and agent (07) read.""" name: str + description: str input_schema: type[BaseModel] output_schema: type[BaseModel] executor: Executor diff --git a/surfsense_backend/app/capabilities/web/discover/definition.py b/surfsense_backend/app/capabilities/web/discover/definition.py index 1c3b4ec28..e4e779307 100644 --- a/surfsense_backend/app/capabilities/web/discover/definition.py +++ b/surfsense_backend/app/capabilities/web/discover/definition.py @@ -9,6 +9,12 @@ from app.capabilities.web.discover.schemas import DiscoverInput, DiscoverOutput WEB_DISCOVER = Capability( name="web.discover", + description=( + "Search the web for a query and return ranked results. Use it to find " + "pages when you don't already have exact URLs. Returns a list of hits " + "(url, title, snippet, provider); pass the chosen url(s) to web.scrape " + "to read their full content." + ), input_schema=DiscoverInput, output_schema=DiscoverOutput, executor=build_discover_executor(), diff --git a/surfsense_backend/app/capabilities/web/discover/schemas.py b/surfsense_backend/app/capabilities/web/discover/schemas.py index 77622b210..ae74d2c56 100644 --- a/surfsense_backend/app/capabilities/web/discover/schemas.py +++ b/surfsense_backend/app/capabilities/web/discover/schemas.py @@ -2,20 +2,28 @@ from __future__ import annotations -from pydantic import BaseModel +from pydantic import BaseModel, Field class DiscoverInput(BaseModel): - query: str - top_k: int = 10 + query: str = Field( + description="What to search the web for, phrased in natural language." + ) + top_k: int = Field( + default=10, description="Maximum number of results to return (1-50)." + ) class DiscoverHit(BaseModel): - url: str - title: str - snippet: str | None = None - provider: str + url: str = Field( + description="The result's page URL; pass it to web.scrape to read it." + ) + title: str = Field(description="The result's page title.") + snippet: str | None = Field( + default=None, description="A short extract summarizing the page." + ) + provider: str = Field(description="Which search engine returned this hit.") class DiscoverOutput(BaseModel): - hits: list[DiscoverHit] + hits: list[DiscoverHit] = Field(description="Ranked search results, best first.") diff --git a/surfsense_backend/app/capabilities/web/scrape/definition.py b/surfsense_backend/app/capabilities/web/scrape/definition.py index 295795066..5ceb0b3a5 100644 --- a/surfsense_backend/app/capabilities/web/scrape/definition.py +++ b/surfsense_backend/app/capabilities/web/scrape/definition.py @@ -9,6 +9,12 @@ from app.capabilities.web.scrape.schemas import ScrapeInput, ScrapeOutput WEB_SCRAPE = Capability( name="web.scrape", + description=( + "Fetch one or more web pages and return their clean, readable content. " + "Give it the exact URLs to read (use web.discover first if you don't have " + "them). Returns one row per URL with a status (success/empty/failed), the " + "page content, and metadata such as title and description." + ), input_schema=ScrapeInput, output_schema=ScrapeOutput, executor=build_scrape_executor(), diff --git a/surfsense_backend/app/capabilities/web/scrape/schemas.py b/surfsense_backend/app/capabilities/web/scrape/schemas.py index 91de17a53..415a64554 100644 --- a/surfsense_backend/app/capabilities/web/scrape/schemas.py +++ b/surfsense_backend/app/capabilities/web/scrape/schemas.py @@ -11,8 +11,21 @@ MAX_SCRAPE_URLS = 20 class ScrapeInput(BaseModel): - urls: list[str] = Field(min_length=1, max_length=MAX_SCRAPE_URLS) - max_length: int = 50_000 + urls: list[str] = Field( + min_length=1, + max_length=MAX_SCRAPE_URLS, + description=( + "Full page URLs to fetch and read (1-20), each starting with " + "http:// or https://. Pass the exact URLs you want the content of." + ), + ) + max_length: int = Field( + default=50_000, + description=( + "Maximum characters of cleaned content returned per page; " + "content longer than this is truncated." + ), + ) @property def estimated_units(self) -> int: @@ -21,15 +34,28 @@ class ScrapeInput(BaseModel): class ScrapeRow(BaseModel): - url: str - status: Literal["success", "empty", "failed"] - content: str | None = None - metadata: dict[str, str] | None = None - error: str | None = None + url: str = Field(description="The requested URL this result is for.") + status: Literal["success", "empty", "failed"] = Field( + description=( + "'success' = content returned; 'empty' = page reached but no " + "readable content; 'failed' = could not be fetched (see error)." + ) + ) + content: str | None = Field( + default=None, description="Cleaned, readable page text (present on success)." + ) + metadata: dict[str, str] | None = Field( + default=None, description="Page metadata such as title and description." + ) + error: str | None = Field( + default=None, description="Why the fetch failed (present on 'failed')." + ) class ScrapeOutput(BaseModel): - rows: list[ScrapeRow] + rows: list[ScrapeRow] = Field( + description="One result per requested URL, in the same order." + ) @property def billable_units(self) -> int: diff --git a/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py b/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py index 0f4366c4b..211ce9854 100644 --- a/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py +++ b/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py @@ -26,6 +26,7 @@ async def _echo_executor(payload: _EchoInput) -> _EchoOutput: _ECHO = Capability( name="test.echo", + description="Echo the input back for tests.", input_schema=_EchoInput, output_schema=_EchoOutput, executor=_echo_executor,