Initial commit
This commit is contained in:
129
server/tools/brain_tool.py
Normal file
129
server/tools/brain_tool.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
tools/brain_tool.py — 2nd Brain tool for Jarvis.
|
||||
|
||||
Gives Jarvis (and any agent with brain access) two operations:
|
||||
capture — save a thought to the brain
|
||||
search — retrieve thoughts by semantic similarity
|
||||
browse — list recent thoughts (optional type filter)
|
||||
stats — database statistics
|
||||
|
||||
Capture is the only write operation and requires no confirmation (it's
|
||||
non-destructive and the user expects it to be instant).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from ..context_vars import current_user as _current_user_var
|
||||
from .base import BaseTool, ToolResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BrainTool(BaseTool):
|
||||
name = "brain"
|
||||
description = (
|
||||
"Access the 2nd Brain knowledge base. "
|
||||
"Operations: capture (save a thought), search (semantic search by meaning), "
|
||||
"browse (recent thoughts), stats (database overview). "
|
||||
"Use 'capture' to save anything worth remembering. "
|
||||
"Use 'search' to find relevant past thoughts by meaning, not just keywords."
|
||||
)
|
||||
input_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["capture", "search", "browse", "stats"],
|
||||
"description": "Operation to perform.",
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Text to capture (required for 'capture').",
|
||||
},
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search query (required for 'search').",
|
||||
},
|
||||
"threshold": {
|
||||
"type": "number",
|
||||
"description": "Similarity threshold 0-1 for 'search' (default 0.7). Lower = broader results.",
|
||||
"default": 0.7,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Max results for 'search' or 'browse' (default 10).",
|
||||
"default": 10,
|
||||
},
|
||||
"type_filter": {
|
||||
"type": "string",
|
||||
"description": "Filter 'browse' by thought type: insight, person_note, task, reference, idea, other.",
|
||||
},
|
||||
},
|
||||
"required": ["operation"],
|
||||
}
|
||||
|
||||
requires_confirmation = False
|
||||
allowed_in_scheduled_tasks = True
|
||||
|
||||
async def execute(self, **kwargs) -> ToolResult:
|
||||
operation = kwargs.get("operation")
|
||||
# Resolve current user for brain namespace scoping (3-G)
|
||||
_user = _current_user_var.get()
|
||||
user_id = _user.id if _user else None
|
||||
|
||||
try:
|
||||
from ..brain.database import get_pool
|
||||
if get_pool() is None:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error="Brain DB is not available. Check BRAIN_DB_URL in .env.",
|
||||
)
|
||||
|
||||
if operation == "capture":
|
||||
return await self._capture(kwargs.get("content", ""), user_id=user_id)
|
||||
elif operation == "search":
|
||||
return await self._search(
|
||||
kwargs.get("query", ""),
|
||||
float(kwargs.get("threshold", 0.7)),
|
||||
int(kwargs.get("limit", 10)),
|
||||
user_id=user_id,
|
||||
)
|
||||
elif operation == "browse":
|
||||
return await self._browse(
|
||||
int(kwargs.get("limit", 10)),
|
||||
kwargs.get("type_filter"),
|
||||
user_id=user_id,
|
||||
)
|
||||
elif operation == "stats":
|
||||
return await self._stats(user_id=user_id)
|
||||
else:
|
||||
return ToolResult(success=False, error=f"Unknown operation: {operation}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error("BrainTool error (%s): %s", operation, e)
|
||||
return ToolResult(success=False, error=str(e))
|
||||
|
||||
async def _capture(self, content: str, user_id: str | None = None) -> ToolResult:
|
||||
if not content.strip():
|
||||
return ToolResult(success=False, error="content is required for capture")
|
||||
from ..brain.ingest import ingest_thought
|
||||
result = await ingest_thought(content, user_id=user_id)
|
||||
return ToolResult(success=True, data=result)
|
||||
|
||||
async def _search(self, query: str, threshold: float, limit: int, user_id: str | None = None) -> ToolResult:
|
||||
if not query.strip():
|
||||
return ToolResult(success=False, error="query is required for search")
|
||||
from ..brain.search import semantic_search
|
||||
results = await semantic_search(query, threshold=threshold, limit=limit, user_id=user_id)
|
||||
return ToolResult(success=True, data={"results": results, "count": len(results)})
|
||||
|
||||
async def _browse(self, limit: int, type_filter: str | None, user_id: str | None = None) -> ToolResult:
|
||||
from ..brain.database import browse_thoughts
|
||||
results = await browse_thoughts(limit=limit, type_filter=type_filter, user_id=user_id)
|
||||
return ToolResult(success=True, data={"results": results, "count": len(results)})
|
||||
|
||||
async def _stats(self, user_id: str | None = None) -> ToolResult:
|
||||
from ..brain.database import get_stats
|
||||
stats = await get_stats(user_id=user_id)
|
||||
return ToolResult(success=True, data=stats)
|
||||
Reference in New Issue
Block a user