feat(library-desk): add AI agent tools router

Tools Router:
- Expose library-desk capabilities as AI tool endpoints
- Support function calling for LLM agents
- Query wiki pages, search knowledge base
- Access graph entities and relationships

Tools Models:
- ToolDefinition for function schemas
- ToolParameter specifications
- ToolResponse format
- OpenAI function calling compatible
This commit is contained in:
2025-12-10 01:30:23 +01:00
parent dca8b63a50
commit 3ab44c2ab0
2 changed files with 395 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
"""
Tool catalog models for Library Desk.
Provides simplified tool definitions optimized for AI agent consumption.
"""
from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional
from enum import Enum
class ParameterType(str, Enum):
"""Parameter data types."""
STRING = "string"
INTEGER = "integer"
BOOLEAN = "boolean"
ARRAY = "array"
OBJECT = "object"
class ToolParameter(BaseModel):
"""Tool parameter definition."""
name: str = Field(..., description="Parameter name")
type: ParameterType = Field(..., description="Parameter type")
description: str = Field(..., description="Parameter description")
required: bool = Field(default=False, description="Whether parameter is required")
default: Optional[Any] = Field(default=None, description="Default value if not required")
example: Optional[Any] = Field(default=None, description="Example value")
class ToolDefinition(BaseModel):
"""Individual tool definition."""
name: str = Field(..., description="Tool identifier (e.g., 'wiki_create_page')")
category: str = Field(..., description="Tool category (e.g., 'wiki', 'graph')")
description: str = Field(..., description="What this tool does")
method: str = Field(..., description="HTTP method (GET, POST, PUT, DELETE)")
endpoint: str = Field(..., description="API endpoint path")
parameters: List[ToolParameter] = Field(default_factory=list, description="Tool parameters")
returns: str = Field(..., description="What the tool returns")
example: Optional[Dict[str, Any]] = Field(default=None, description="Example request")
fast: bool = Field(default=True, description="Whether operation completes quickly (<5s)")
class CategoryInfo(BaseModel):
"""Tool category information."""
name: str = Field(..., description="Category name")
description: str = Field(..., description="Category description")
tool_count: int = Field(..., description="Number of tools in category")
class ToolCatalog(BaseModel):
"""Complete tool catalog response."""
service: str = Field(default="library-desk", description="Service name")
version: str = Field(default="1.0.0", description="API version")
base_url: str = Field(..., description="Base URL for API")
categories: List[CategoryInfo] = Field(..., description="Available categories")
tools: List[ToolDefinition] = Field(..., description="All available tools")
authentication: str = Field(
default="Bearer token via Authorization header",
description="Authentication method"
)
+334
View File
@@ -0,0 +1,334 @@
"""
Tools router for Library Desk API.
Provides tool discovery endpoint for AI agents.
"""
from fastapi import APIRouter
from src.models.tools import (
ToolCatalog, ToolDefinition, ToolParameter, CategoryInfo, ParameterType
)
router = APIRouter(prefix="/tools", tags=["Tools"])
def get_wiki_tools() -> list[ToolDefinition]:
"""Get wiki tool definitions."""
return [
ToolDefinition(
name="wiki_list_pages",
category="wiki",
description="List wiki pages for a user with optional tag filtering",
method="GET",
endpoint="/wiki/pages",
parameters=[
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier",
required=False,
default="jpmschweitzer",
example="jpmschweitzer"
),
ToolParameter(
name="tag",
type=ParameterType.STRING,
description="Filter by tag (dossier)",
required=False,
example="projects"
),
ToolParameter(
name="limit",
type=ParameterType.INTEGER,
description="Maximum pages to return",
required=False,
default=50,
example=20
),
],
returns="List of pages with summaries",
fast=True
),
ToolDefinition(
name="wiki_get_page",
category="wiki",
description="Get a single wiki page by ID with full content",
method="GET",
endpoint="/wiki/pages/{page_id}",
parameters=[
ToolParameter(
name="page_id",
type=ParameterType.INTEGER,
description="Page ID to retrieve",
required=True,
example=123
),
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier for access control",
required=False,
default="jpmschweitzer"
),
],
returns="Complete page object with content",
fast=True
),
ToolDefinition(
name="wiki_create_page",
category="wiki",
description="Create a new wiki page in user's namespace",
method="POST",
endpoint="/wiki/pages",
parameters=[
ToolParameter(
name="title",
type=ParameterType.STRING,
description="Page title",
required=True,
example="Project Documentation"
),
ToolParameter(
name="path",
type=ParameterType.STRING,
description="Page path (will be prefixed with user namespace)",
required=True,
example="/projects/my-project"
),
ToolParameter(
name="content",
type=ParameterType.STRING,
description="Page content in Markdown format",
required=True,
example="# Overview\n\nThis is the content."
),
ToolParameter(
name="description",
type=ParameterType.STRING,
description="Short page description",
required=False,
example="Documentation for my project"
),
ToolParameter(
name="tags",
type=ParameterType.ARRAY,
description="Tags for categorization (dossiers)",
required=False,
example=["projects", "documentation"]
),
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier",
required=False,
default="jpmschweitzer"
),
],
returns="Created page object",
example={
"title": "My Project",
"path": "/projects/my-project",
"content": "# My Project\n\nProject description here.",
"tags": ["projects"],
"user": "jpmschweitzer"
},
fast=True
),
ToolDefinition(
name="wiki_update_page",
category="wiki",
description="Update an existing wiki page",
method="PUT",
endpoint="/wiki/pages/{page_id}",
parameters=[
ToolParameter(
name="page_id",
type=ParameterType.INTEGER,
description="Page ID to update",
required=True,
example=123
),
ToolParameter(
name="title",
type=ParameterType.STRING,
description="New page title",
required=False
),
ToolParameter(
name="content",
type=ParameterType.STRING,
description="New page content",
required=False
),
ToolParameter(
name="description",
type=ParameterType.STRING,
description="New description",
required=False
),
ToolParameter(
name="tags",
type=ParameterType.ARRAY,
description="New tags",
required=False
),
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier",
required=False,
default="jpmschweitzer"
),
],
returns="Updated page object",
fast=True
),
ToolDefinition(
name="wiki_delete_page",
category="wiki",
description="Delete a wiki page",
method="DELETE",
endpoint="/wiki/pages/{page_id}",
parameters=[
ToolParameter(
name="page_id",
type=ParameterType.INTEGER,
description="Page ID to delete",
required=True,
example=123
),
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier",
required=False,
default="jpmschweitzer"
),
],
returns="Success confirmation",
fast=True
),
ToolDefinition(
name="wiki_search_pages",
category="wiki",
description="Search wiki pages by content within user's namespace",
method="GET",
endpoint="/wiki/search",
parameters=[
ToolParameter(
name="q",
type=ParameterType.STRING,
description="Search query",
required=True,
example="docker configuration"
),
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier",
required=False,
default="jpmschweitzer"
),
ToolParameter(
name="limit",
type=ParameterType.INTEGER,
description="Maximum results",
required=False,
default=20
),
],
returns="List of matching pages",
fast=True
),
ToolDefinition(
name="wiki_list_dossiers",
category="wiki",
description="List all dossiers (unique tags) for a user with page counts",
method="GET",
endpoint="/wiki/dossiers",
parameters=[
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier",
required=False,
default="jpmschweitzer"
),
],
returns="List of dossiers with page counts",
fast=True
),
ToolDefinition(
name="wiki_get_dossier_pages",
category="wiki",
description="Get all pages in a specific dossier",
method="GET",
endpoint="/wiki/dossiers/{dossier_name}/pages",
parameters=[
ToolParameter(
name="dossier_name",
type=ParameterType.STRING,
description="Dossier name (tag)",
required=True,
example="projects"
),
ToolParameter(
name="user",
type=ParameterType.STRING,
description="User identifier",
required=False,
default="jpmschweitzer"
),
ToolParameter(
name="limit",
type=ParameterType.INTEGER,
description="Maximum pages",
required=False,
default=100
),
],
returns="List of pages in dossier",
fast=True
),
]
@router.get("", response_model=ToolCatalog)
async def get_tool_catalog() -> ToolCatalog:
"""
Get simplified tool catalog for AI agent consumption.
This endpoint provides a machine-readable catalog of all Library Desk tools,
optimized for discovery and use by AI agents like The Librarian.
Returns tool definitions with:
- Clear descriptions
- Parameter specifications
- Usage examples
- Performance characteristics
"""
wiki_tools = get_wiki_tools()
# Calculate category stats
categories = {}
for tool in wiki_tools:
if tool.category not in categories:
categories[tool.category] = 0
categories[tool.category] += 1
category_info = [
CategoryInfo(
name="wiki",
description="Wiki.js page and dossier management operations",
tool_count=categories.get("wiki", 0)
)
]
return ToolCatalog(
service="library-desk",
version="1.0.0",
base_url="http://library-desk:8089",
categories=category_info,
tools=wiki_tools,
authentication="Bearer token via Authorization header (LIBRARY_API_KEY)"
)