feat: improved ae_journal_search + AE integration docs
Search improvements: - Switched from LIKE on default_qry_str to query_string path (fulltext MATCH/AGAINST IN BOOLEAN MODE — uses the index, supports +/- boolean ops) - Added tag filter (icontains on tags field) - Added date_from / date_to filters (created_on gte/lte) - Added type_code / topic_code exact-match filters - Added sort_by / sort_order control (updated, created, name, priority) - Added status / priority filters - Added page parameter for pagination - Richer output: updated date, tags, pagination hint - Updated Gemini tool declaration with all new params Docs: - documentation/ARCH__AE_INTEGRATION.md — journal_entry full schema, search operator reference, current tool inventory, planned phases (broader AE integration: tasks, people, calendar, knowledge import) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -94,8 +94,9 @@ _ae_journal_list_declaration = types.FunctionDeclaration(
|
||||
_ae_journal_search_declaration = types.FunctionDeclaration(
|
||||
name="ae_journal_search",
|
||||
description=(
|
||||
"Search the Aether Journals knowledge base by keyword. "
|
||||
"Use this to look up notes, documentation, meeting summaries, or any saved knowledge. "
|
||||
"Search Aether Journal entries. All parameters are optional — combine freely. "
|
||||
"Use 'query' for fulltext keyword search (supports boolean: +required -excluded \"phrase\"). "
|
||||
"Use 'tags' to filter by tag substring. Use 'date_from'/'date_to' for date ranges (YYYY-MM-DD). "
|
||||
"Always search before creating a new entry to avoid duplicates."
|
||||
),
|
||||
parameters=types.Schema(
|
||||
@@ -103,21 +104,58 @@ _ae_journal_search_declaration = types.FunctionDeclaration(
|
||||
properties={
|
||||
"query": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Keyword or phrase to search for",
|
||||
description="Fulltext keyword search. Supports boolean mode: +required -excluded \"exact phrase\".",
|
||||
),
|
||||
"journal_id": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description=(
|
||||
"Optional: scope search to a specific journal by its id_random. "
|
||||
"Omit to search all journals."
|
||||
),
|
||||
description="Scope results to a specific journal by its id_random. Omit to search all journals.",
|
||||
),
|
||||
"tags": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Filter by tag substring (e.g. 'networking' matches entries tagged 'networking' or 'home-networking').",
|
||||
),
|
||||
"type_code": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Filter by exact type_code (e.g. 'note', 'meeting', 'log').",
|
||||
),
|
||||
"topic_code": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Filter by exact topic_code.",
|
||||
),
|
||||
"date_from": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Return entries created on or after this date (YYYY-MM-DD).",
|
||||
),
|
||||
"date_to": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Return entries created on or before this date (YYYY-MM-DD).",
|
||||
),
|
||||
"sort_by": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Sort field: 'updated' (default), 'created', 'name', or 'priority'.",
|
||||
),
|
||||
"sort_order": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Sort direction: 'desc' (default, newest first) or 'asc'.",
|
||||
),
|
||||
"status": types.Schema(
|
||||
type=types.Type.INTEGER,
|
||||
description="Filter by exact status code.",
|
||||
),
|
||||
"priority": types.Schema(
|
||||
type=types.Type.INTEGER,
|
||||
description="Filter by exact priority (1=low, 5=high).",
|
||||
),
|
||||
"max_results": types.Schema(
|
||||
type=types.Type.INTEGER,
|
||||
description="Maximum number of entries to return (default 10)",
|
||||
description="Number of results per page (default 10).",
|
||||
),
|
||||
"page": types.Schema(
|
||||
type=types.Type.INTEGER,
|
||||
description="Page number for pagination (default 1).",
|
||||
),
|
||||
},
|
||||
required=["query"],
|
||||
required=[],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -41,36 +41,95 @@ def _check_config() -> str | None:
|
||||
# Tool: ae_journal_search
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def journal_search(query: str, journal_id: str | None = None, max_results: int = 10) -> str:
|
||||
"""Search AE Journal entries by keyword.
|
||||
async def journal_search(
|
||||
query: str = "",
|
||||
journal_id: str = "",
|
||||
tags: str = "",
|
||||
type_code: str = "",
|
||||
topic_code: str = "",
|
||||
date_from: str = "",
|
||||
date_to: str = "",
|
||||
sort_by: str = "updated",
|
||||
sort_order: str = "desc",
|
||||
status: int | None = None,
|
||||
priority: int | None = None,
|
||||
max_results: int = 10,
|
||||
page: int = 1,
|
||||
) -> str:
|
||||
"""Search AE Journal entries.
|
||||
|
||||
Searches across the default_qry_str field (title + content excerpt).
|
||||
Optionally scoped to a specific journal by journal_id (id_random).
|
||||
Returns a markdown-formatted list of matching entries.
|
||||
At least one of query, tags, type_code, topic_code, date_from, or journal_id
|
||||
should be provided. All filters combine with AND.
|
||||
"""
|
||||
err = _check_config()
|
||||
if err:
|
||||
return err
|
||||
|
||||
return await asyncio.to_thread(_sync_journal_search, query, journal_id, max_results)
|
||||
return await asyncio.to_thread(
|
||||
_sync_journal_search,
|
||||
query, journal_id, tags, type_code, topic_code,
|
||||
date_from, date_to, sort_by, sort_order,
|
||||
status, priority, max_results, page,
|
||||
)
|
||||
|
||||
|
||||
def _sync_journal_search(query: str, journal_id: str | None, max_results: int) -> str:
|
||||
def _sync_journal_search(
|
||||
query: str,
|
||||
journal_id: str,
|
||||
tags: str,
|
||||
type_code: str,
|
||||
topic_code: str,
|
||||
date_from: str,
|
||||
date_to: str,
|
||||
sort_by: str,
|
||||
sort_order: str,
|
||||
status: int | None,
|
||||
priority: int | None,
|
||||
max_results: int,
|
||||
page: int,
|
||||
) -> str:
|
||||
import requests
|
||||
|
||||
url = f"{settings.ae_api_url}/v3/crud/journal_entry/search"
|
||||
search_body = {
|
||||
"and_filters": [
|
||||
{"field": "default_qry_str", "op": "icontains", "value": query}
|
||||
],
|
||||
"page_size": max_results,
|
||||
# Build sort field
|
||||
sort_field_map = {
|
||||
"updated": "updated_on",
|
||||
"created": "created_on",
|
||||
"name": "name",
|
||||
"priority": "priority",
|
||||
}
|
||||
sort_field = sort_field_map.get(sort_by, "updated_on")
|
||||
order_by = f"{'-' if sort_order == 'desc' else ''}{sort_field}"
|
||||
|
||||
params = {}
|
||||
search_body: dict = {"page_size": max_results, "page": page, "order_by": order_by}
|
||||
|
||||
# Fulltext keyword — uses MATCH/AGAINST index
|
||||
if query:
|
||||
search_body["query_string"] = query
|
||||
|
||||
# Additional AND filters
|
||||
and_filters: list[dict] = []
|
||||
if tags:
|
||||
and_filters.append({"field": "tags", "op": "icontains", "value": tags})
|
||||
if type_code:
|
||||
and_filters.append({"field": "type_code", "op": "eq", "value": type_code})
|
||||
if topic_code:
|
||||
and_filters.append({"field": "topic_code", "op": "eq", "value": topic_code})
|
||||
if date_from:
|
||||
and_filters.append({"field": "created_on", "op": "gte", "value": date_from})
|
||||
if date_to:
|
||||
and_filters.append({"field": "created_on", "op": "lte", "value": date_to})
|
||||
if status is not None:
|
||||
and_filters.append({"field": "status", "op": "eq", "value": status})
|
||||
if priority is not None:
|
||||
and_filters.append({"field": "priority", "op": "eq", "value": priority})
|
||||
if and_filters:
|
||||
search_body["and_filters"] = and_filters
|
||||
|
||||
params: dict = {}
|
||||
if journal_id:
|
||||
params["for_obj_type"] = "journal"
|
||||
params["for_obj_id"] = journal_id
|
||||
|
||||
url = f"{settings.ae_api_url}/v3/crud/journal_entry/search"
|
||||
try:
|
||||
resp = requests.post(
|
||||
url,
|
||||
@@ -86,17 +145,23 @@ def _sync_journal_search(query: str, journal_id: str | None, max_results: int) -
|
||||
return f"Journal search error: {e}"
|
||||
|
||||
entries = data.get("data", [])
|
||||
if not entries:
|
||||
return f"No journal entries found matching: {query}"
|
||||
total = data.get("total") or data.get("count") or len(entries)
|
||||
|
||||
if not entries:
|
||||
desc = query or tags or type_code or topic_code or f"journal {journal_id}"
|
||||
return f"No journal entries found for: {desc}"
|
||||
|
||||
label = query or tags or f"{len(entries)} entries"
|
||||
lines = [f"Journal entries — **{label}** ({total} total, page {page}):\n"]
|
||||
|
||||
lines = [f"Journal entries matching **{query}** ({len(entries)} result(s)):\n"]
|
||||
for entry in entries:
|
||||
title = entry.get("name") or "(untitled)"
|
||||
entry_id = entry.get("id_random", "")
|
||||
journal_name = entry.get("journal_name") or entry.get("parent_name") or ""
|
||||
summary = entry.get("summary") or ""
|
||||
tags = entry.get("tags") or []
|
||||
updated = (entry.get("updated_at") or entry.get("created_at") or "")[:10]
|
||||
entry_tags = entry.get("tags") or []
|
||||
updated = (entry.get("updated_on") or entry.get("updated_at") or
|
||||
entry.get("created_on") or entry.get("created_at") or "")[:10]
|
||||
content_preview = (entry.get("content") or "")[:400].replace("\n", " ")
|
||||
|
||||
header = f"**{title}**"
|
||||
@@ -106,14 +171,18 @@ def _sync_journal_search(query: str, journal_id: str | None, max_results: int) -
|
||||
if updated:
|
||||
header += f" [{updated}]"
|
||||
lines.append(header)
|
||||
if tags:
|
||||
lines.append(f" Tags: {', '.join(tags)}")
|
||||
if entry_tags:
|
||||
tag_list = entry_tags if isinstance(entry_tags, list) else [t.strip() for t in str(entry_tags).split(",")]
|
||||
lines.append(f" Tags: {', '.join(tag_list)}")
|
||||
if summary:
|
||||
lines.append(f" Summary: {summary}")
|
||||
lines.append(f" {summary}")
|
||||
elif content_preview:
|
||||
lines.append(f" {content_preview}{'…' if len(entry.get('content','')) > 400 else ''}")
|
||||
lines.append(f" {content_preview}{'…' if len(entry.get('content', '')) > 400 else ''}")
|
||||
lines.append("")
|
||||
|
||||
if total > page * max_results:
|
||||
lines.append(f"(More results — call again with page={page + 1})")
|
||||
|
||||
return "\n".join(lines).strip()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user