feat: query param validation block label, name, and search (#6397)
* add block label, name, and search query param validation * finishing touches on blocks * remove default for blocks * query changes to api spec * openapi changes * change descriptions
This commit is contained in:
@@ -10045,16 +10045,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50,
|
||||
"pattern": "^[a-zA-Z0-9_-]+$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Labels to include (e.g. human, persona)",
|
||||
"description": "Label to include (alphanumeric, hyphens, underscores only)",
|
||||
"examples": ["human", "persona", "the_label_of-a-block"],
|
||||
"title": "Label"
|
||||
},
|
||||
"description": "Labels to include (e.g. human, persona)"
|
||||
"description": "Label to include (alphanumeric, hyphens, underscores only)"
|
||||
},
|
||||
{
|
||||
"name": "templates_only",
|
||||
@@ -10075,16 +10079,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100,
|
||||
"pattern": "^[a-zA-Z0-9 _-]+$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Name of the block",
|
||||
"description": "Name filter (alphanumeric, spaces, hyphens, underscores)",
|
||||
"examples": ["My Agent", "test_tool", "default-config"],
|
||||
"title": "Name"
|
||||
},
|
||||
"description": "Name of the block"
|
||||
"description": "Name filter (alphanumeric, spaces, hyphens, underscores)"
|
||||
},
|
||||
{
|
||||
"name": "identity_id",
|
||||
@@ -10093,16 +10101,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 45,
|
||||
"maxLength": 45,
|
||||
"pattern": "^identity-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search agents by identifier id",
|
||||
"description": "The ID of the identity in the format 'identity-<uuid4>'",
|
||||
"examples": ["identity-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Identity Id"
|
||||
},
|
||||
"description": "Search agents by identifier id"
|
||||
"description": "The ID of the identity in the format 'identity-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "identifier_keys",
|
||||
@@ -10231,16 +10243,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50,
|
||||
"pattern": "^[a-zA-Z0-9_-]+$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels.",
|
||||
"description": "Search blocks by label. If provided, returns blocks whose label matches the search query. This is a full-text search on block labels.",
|
||||
"examples": ["human", "persona", "the_label_of-a-block"],
|
||||
"title": "Label Search"
|
||||
},
|
||||
"description": "Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels."
|
||||
"description": "Search blocks by label. If provided, returns blocks whose label matches the search query. This is a full-text search on block labels."
|
||||
},
|
||||
{
|
||||
"name": "description_search",
|
||||
@@ -10249,16 +10265,18 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search blocks by description. If provided, returns blocks that match this description. This is a full-text search on block descriptions.",
|
||||
"description": "Search blocks by description. If provided, returns blocks whose description matches the search query. This is a full-text search on block descriptions.",
|
||||
"title": "Description Search"
|
||||
},
|
||||
"description": "Search blocks by description. If provided, returns blocks that match this description. This is a full-text search on block descriptions."
|
||||
"description": "Search blocks by description. If provided, returns blocks whose description matches the search query. This is a full-text search on block descriptions."
|
||||
},
|
||||
{
|
||||
"name": "value_search",
|
||||
@@ -10267,16 +10285,18 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search blocks by value. If provided, returns blocks that match this value.",
|
||||
"description": "Search blocks by value. If provided, returns blocks whose value matches the search query. This is a full-text search on block values.",
|
||||
"title": "Value Search"
|
||||
},
|
||||
"description": "Search blocks by value. If provided, returns blocks that match this value."
|
||||
"description": "Search blocks by value. If provided, returns blocks whose value matches the search query. This is a full-text search on block values."
|
||||
},
|
||||
{
|
||||
"name": "connected_to_agents_count_gt",
|
||||
@@ -11939,16 +11959,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50,
|
||||
"pattern": "^[a-zA-Z0-9_-]+$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Labels to include (e.g. human, persona)",
|
||||
"description": "Label to include (alphanumeric, hyphens, underscores only)",
|
||||
"examples": ["human", "persona", "the_label_of-a-block"],
|
||||
"title": "Label"
|
||||
},
|
||||
"description": "Labels to include (e.g. human, persona)"
|
||||
"description": "Label to include (alphanumeric, hyphens, underscores only)"
|
||||
},
|
||||
{
|
||||
"name": "templates_only",
|
||||
@@ -11969,16 +11993,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100,
|
||||
"pattern": "^[a-zA-Z0-9 _-]+$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Name of the block",
|
||||
"description": "Name filter (alphanumeric, spaces, hyphens, underscores)",
|
||||
"examples": ["My Agent", "test_tool", "default-config"],
|
||||
"title": "Name"
|
||||
},
|
||||
"description": "Name of the block"
|
||||
"description": "Name filter (alphanumeric, spaces, hyphens, underscores)"
|
||||
},
|
||||
{
|
||||
"name": "identity_id",
|
||||
@@ -11987,16 +12015,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 45,
|
||||
"maxLength": 45,
|
||||
"pattern": "^identity-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search agents by identifier id",
|
||||
"description": "The ID of the identity in the format 'identity-<uuid4>'",
|
||||
"examples": ["identity-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Identity Id"
|
||||
},
|
||||
"description": "Search agents by identifier id"
|
||||
"description": "The ID of the identity in the format 'identity-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "identifier_keys",
|
||||
@@ -12125,16 +12157,20 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50,
|
||||
"pattern": "^[a-zA-Z0-9_-]+$"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels.",
|
||||
"description": "Search blocks by label. If provided, returns blocks whose label matches the search query. This is a full-text search on block labels.",
|
||||
"examples": ["human", "persona", "the_label_of-a-block"],
|
||||
"title": "Label Search"
|
||||
},
|
||||
"description": "Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels."
|
||||
"description": "Search blocks by label. If provided, returns blocks whose label matches the search query. This is a full-text search on block labels."
|
||||
},
|
||||
{
|
||||
"name": "description_search",
|
||||
@@ -12143,16 +12179,18 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search blocks by description. If provided, returns blocks that match this description. This is a full-text search on block descriptions.",
|
||||
"description": "Search blocks by description. If provided, returns blocks whose description matches the search query. This is a full-text search on block descriptions.",
|
||||
"title": "Description Search"
|
||||
},
|
||||
"description": "Search blocks by description. If provided, returns blocks that match this description. This is a full-text search on block descriptions."
|
||||
"description": "Search blocks by description. If provided, returns blocks whose description matches the search query. This is a full-text search on block descriptions."
|
||||
},
|
||||
{
|
||||
"name": "value_search",
|
||||
@@ -12161,16 +12199,18 @@
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Search blocks by value. If provided, returns blocks that match this value.",
|
||||
"description": "Search blocks by value. If provided, returns blocks whose value matches the search query. This is a full-text search on block values.",
|
||||
"title": "Value Search"
|
||||
},
|
||||
"description": "Search blocks by value. If provided, returns blocks that match this value."
|
||||
"description": "Search blocks by value. If provided, returns blocks whose value matches the search query. This is a full-text search on block values."
|
||||
},
|
||||
{
|
||||
"name": "connected_to_agents_count_gt",
|
||||
|
||||
@@ -8,7 +8,15 @@ from letta.schemas.block import BaseBlock, Block, BlockResponse, BlockUpdate, Cr
|
||||
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
||||
from letta.server.server import SyncServer
|
||||
from letta.utils import is_1_0_sdk_version
|
||||
from letta.validators import BlockId
|
||||
from letta.validators import (
|
||||
BlockDescriptionSearchQuery,
|
||||
BlockId,
|
||||
BlockLabelQuery,
|
||||
BlockLabelSearchQuery,
|
||||
BlockNameQuery,
|
||||
BlockValueSearchQuery,
|
||||
IdentityIdQuery,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
@@ -19,10 +27,10 @@ router = APIRouter(prefix="/blocks", tags=["blocks"])
|
||||
@router.get("/", response_model=List[BlockResponse], operation_id="list_blocks")
|
||||
async def list_blocks(
|
||||
# query parameters
|
||||
label: Optional[str] = Query(None, description="Labels to include (e.g. human, persona)"),
|
||||
label: BlockLabelQuery = None,
|
||||
templates_only: bool = Query(False, description="Whether to include only templates"),
|
||||
name: Optional[str] = Query(None, description="Name of the block"),
|
||||
identity_id: Optional[str] = Query(None, description="Search agents by identifier id"),
|
||||
name: BlockNameQuery = None,
|
||||
identity_id: IdentityIdQuery = None,
|
||||
identifier_keys: Optional[List[str]] = Query(None, description="Search agents by identifier keys"),
|
||||
project_id: Optional[str] = Query(None, description="Search blocks by project id"),
|
||||
limit: Optional[int] = Query(50, description="Number of blocks to return"),
|
||||
@@ -38,21 +46,9 @@ async def list_blocks(
|
||||
"asc", description="Sort order for blocks by creation time. 'asc' for oldest first, 'desc' for newest first"
|
||||
),
|
||||
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
||||
label_search: Optional[str] = Query(
|
||||
None,
|
||||
description=("Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels."),
|
||||
),
|
||||
description_search: Optional[str] = Query(
|
||||
None,
|
||||
description=(
|
||||
"Search blocks by description. If provided, returns blocks that match this description. "
|
||||
"This is a full-text search on block descriptions."
|
||||
),
|
||||
),
|
||||
value_search: Optional[str] = Query(
|
||||
None,
|
||||
description=("Search blocks by value. If provided, returns blocks that match this value."),
|
||||
),
|
||||
label_search: BlockLabelSearchQuery = None,
|
||||
description_search: BlockDescriptionSearchQuery = None,
|
||||
value_search: BlockValueSearchQuery = None,
|
||||
connected_to_agents_count_gt: Optional[int] = Query(
|
||||
None,
|
||||
description=(
|
||||
|
||||
@@ -7,7 +7,15 @@ from letta.schemas.block import Block, CreateBlock
|
||||
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
||||
from letta.server.server import SyncServer
|
||||
from letta.utils import is_1_0_sdk_version
|
||||
from letta.validators import BlockId
|
||||
from letta.validators import (
|
||||
BlockDescriptionSearchQuery,
|
||||
BlockId,
|
||||
BlockLabelQuery,
|
||||
BlockLabelSearchQuery,
|
||||
BlockNameQuery,
|
||||
BlockValueSearchQuery,
|
||||
IdentityIdQuery,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
@@ -18,10 +26,10 @@ router = APIRouter(prefix="/_internal_blocks", tags=["_internal_blocks"])
|
||||
@router.get("/", response_model=List[Block], operation_id="list_internal_blocks")
|
||||
async def list_blocks(
|
||||
# query parameters
|
||||
label: Optional[str] = Query(None, description="Labels to include (e.g. human, persona)"),
|
||||
label: BlockLabelQuery = None,
|
||||
templates_only: bool = Query(False, description="Whether to include only templates"),
|
||||
name: Optional[str] = Query(None, description="Name of the block"),
|
||||
identity_id: Optional[str] = Query(None, description="Search agents by identifier id"),
|
||||
name: BlockNameQuery = None,
|
||||
identity_id: IdentityIdQuery = None,
|
||||
identifier_keys: Optional[List[str]] = Query(None, description="Search agents by identifier keys"),
|
||||
project_id: Optional[str] = Query(None, description="Search blocks by project id"),
|
||||
limit: Optional[int] = Query(50, description="Number of blocks to return"),
|
||||
@@ -37,21 +45,9 @@ async def list_blocks(
|
||||
"asc", description="Sort order for blocks by creation time. 'asc' for oldest first, 'desc' for newest first"
|
||||
),
|
||||
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
||||
label_search: Optional[str] = Query(
|
||||
None,
|
||||
description=("Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels."),
|
||||
),
|
||||
description_search: Optional[str] = Query(
|
||||
None,
|
||||
description=(
|
||||
"Search blocks by description. If provided, returns blocks that match this description. "
|
||||
"This is a full-text search on block descriptions."
|
||||
),
|
||||
),
|
||||
value_search: Optional[str] = Query(
|
||||
None,
|
||||
description=("Search blocks by value. If provided, returns blocks that match this value."),
|
||||
),
|
||||
label_search: BlockLabelSearchQuery = None,
|
||||
description_search: BlockDescriptionSearchQuery = None,
|
||||
value_search: BlockValueSearchQuery = None,
|
||||
connected_to_agents_count_gt: Optional[int] = Query(
|
||||
None,
|
||||
description=(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import inspect
|
||||
import re
|
||||
from functools import wraps
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from fastapi import Path
|
||||
from fastapi import Path, Query
|
||||
|
||||
from letta.errors import LettaInvalidArgumentError
|
||||
from letta.schemas.enums import PrimitiveType # PrimitiveType is now in schemas.enums
|
||||
@@ -124,3 +124,101 @@ def raise_on_invalid_id(param_name: str, expected_prefix: PrimitiveType):
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Query Parameter Validators
|
||||
# =============================================================================
|
||||
# Format validators for common query parameters to match frontend constraints
|
||||
|
||||
|
||||
def _create_id_query_validator(primitive: str):
|
||||
"""
|
||||
Creates a Query validator for ID parameters with format validation.
|
||||
|
||||
Args:
|
||||
primitive: The primitive type prefix (e.g., "agent", "tool")
|
||||
|
||||
Returns:
|
||||
A Query validator with pattern matching
|
||||
"""
|
||||
return Query(
|
||||
description=f"The ID of the {primitive} in the format '{primitive}-<uuid4>'",
|
||||
pattern=PRIMITIVE_ID_PATTERNS[primitive].pattern,
|
||||
examples=[f"{primitive}-123e4567-e89b-42d3-8456-426614174000"],
|
||||
min_length=len(primitive) + 1 + 36,
|
||||
max_length=len(primitive) + 1 + 36,
|
||||
)
|
||||
|
||||
|
||||
# Query parameter ID validators with format checking
|
||||
AgentIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.AGENT.value)]
|
||||
ToolIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.TOOL.value)]
|
||||
SourceIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.SOURCE.value)]
|
||||
BlockIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.BLOCK.value)]
|
||||
MessageIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.MESSAGE.value)]
|
||||
RunIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.RUN.value)]
|
||||
JobIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.JOB.value)]
|
||||
GroupIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.GROUP.value)]
|
||||
IdentityIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.IDENTITY.value)]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# String Field Validators
|
||||
# =============================================================================
|
||||
# Format validators for common string fields
|
||||
|
||||
# Label validator: alphanumeric, hyphens, underscores, max 50 chars
|
||||
BlockLabelQuery = Annotated[
|
||||
Optional[str],
|
||||
Query(
|
||||
description="Label to include (alphanumeric, hyphens, underscores only)",
|
||||
pattern=r"^[a-zA-Z0-9_-]+$",
|
||||
min_length=1,
|
||||
max_length=50,
|
||||
examples=["human", "persona", "the_label_of-a-block"],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# Name validator: similar to label but allows spaces, max 100 chars
|
||||
BlockNameQuery = Annotated[
|
||||
Optional[str],
|
||||
Query(
|
||||
description="Name filter (alphanumeric, spaces, hyphens, underscores)",
|
||||
pattern=r"^[a-zA-Z0-9 _-]+$",
|
||||
min_length=1,
|
||||
max_length=100,
|
||||
examples=["My Agent", "test_tool", "default-config"],
|
||||
),
|
||||
]
|
||||
|
||||
# Search query validator: general text search, max 200 chars
|
||||
BlockLabelSearchQuery = Annotated[
|
||||
Optional[str],
|
||||
Query(
|
||||
description="Search blocks by label. If provided, returns blocks whose label matches the search query. This is a full-text search on block labels.",
|
||||
pattern=r"^[a-zA-Z0-9_-]+$",
|
||||
min_length=1,
|
||||
max_length=50,
|
||||
examples=["human", "persona", "the_label_of-a-block"],
|
||||
),
|
||||
]
|
||||
|
||||
BlockValueSearchQuery = Annotated[
|
||||
Optional[str],
|
||||
Query(
|
||||
description="Search blocks by value. If provided, returns blocks whose value matches the search query. This is a full-text search on block values.",
|
||||
min_length=1,
|
||||
max_length=200,
|
||||
),
|
||||
]
|
||||
|
||||
BlockDescriptionSearchQuery = Annotated[
|
||||
Optional[str],
|
||||
Query(
|
||||
description="Search blocks by description. If provided, returns blocks whose description matches the search query. This is a full-text search on block descriptions.",
|
||||
min_length=1,
|
||||
max_length=200,
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user