Expose manage_notes via native function calling (#759)
The agent's RAG tool selector retrieves manage_notes as relevant for note / todo / reminder requests, but two gaps stopped it from actually firing on local llama.cpp / vLLM endpoints: 1. FUNCTION_TOOL_SCHEMAS had no entry for manage_notes. Even when the tool was marked relevant, no JSON schema was sent on the function tools list, so native-function-calling models had nothing to call. In practice the model would describe creating the note in prose while the actual note stayed blank — the symptom reported in #713 ("checklist hallucinated as blank"). 2. _API_HOSTS only listed hosted providers (OpenAI, Anthropic, etc.). For local endpoints like http://localhost:8080 or http://host.docker.internal:8000, _is_api_model fell back to keyword-sniffing the model name, so any model whose slug didn't happen to match the keyword list silently lost native tool schemas entirely. Fixes: - src/tool_schemas.py: add a manage_notes function schema covering list/add/update/delete/toggle_item with the full Keep-style field set. note_type is exposed as an enum ("note" | "checklist") so the model picks the mode explicitly instead of inferring it from content shape. Items are named checklist_items in the schema — consistent with the description's wording and avoiding the Python-built-in name clash that #713 calls out. - src/tool_implementations.py: do_manage_notes accepts both checklist_items (new, schema-exposed) and items (legacy / internal). Direct API callers and existing code paths keep working unchanged; native function calls following the new schema route through the same path. - src/agent_loop.py: add localhost, 127.0.0.1, and host.docker.internal to _API_HOSTS so the function-tool path is not gated behind model-name guessing for local servers. Closes #174. Closes #713.
This commit is contained in:
@@ -1853,7 +1853,13 @@ async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
|
||||
title = text_raw.strip()
|
||||
elif not content_raw and text_raw:
|
||||
content_raw = text_raw
|
||||
items_raw = args.get("items")
|
||||
# Accept both `items` (legacy/internal field) and `checklist_items`
|
||||
# (the schema-exposed name used by native function calls). Models
|
||||
# following the schema emit `checklist_items`; older code paths
|
||||
# and direct API callers still use `items`.
|
||||
items_raw = args.get("checklist_items")
|
||||
if items_raw is None:
|
||||
items_raw = args.get("items")
|
||||
items_json = json.dumps(items_raw) if items_raw is not None else None
|
||||
note_type = args.get("note_type", "checklist" if items_raw else "note")
|
||||
# Accept natural-language due_date ("tomorrow at 1pm") in
|
||||
@@ -1918,8 +1924,11 @@ async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
|
||||
for field in ("title", "content", "note_type", "color", "label", "due_date"):
|
||||
if field in args and args[field] is not None:
|
||||
setattr(note, field, args[field])
|
||||
if "items" in args and args["items"] is not None:
|
||||
note.items = json.dumps(args["items"])
|
||||
new_items = args.get("checklist_items")
|
||||
if new_items is None:
|
||||
new_items = args.get("items")
|
||||
if new_items is not None:
|
||||
note.items = json.dumps(new_items)
|
||||
flag_modified(note, "items")
|
||||
if "pinned" in args:
|
||||
note.pinned = args["pinned"]
|
||||
|
||||
Reference in New Issue
Block a user