diff --git a/docs/architecture.md b/docs/architecture.md index 91ab316..9069ee9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -212,8 +212,8 @@ Permission resolution is centralised in `services/access.py`. `get_project_permi | `services/api_keys.py` | `generate_key()`, `create_api_key()`, `list_api_keys()`, `revoke_api_key()`, `lookup_key()` (SHA-256 hash lookup) | | `services/llm.py` | `build_context()`, RAG injection, history summarisation, `stream_chat_with_tools()`, URL fetching, SSRF guard | | `services/generation_task.py` | `run_generation()` — full chat pipeline: intent routing, tool loop, SSE fan-out, push notification; `run_assist_generation()` | -| `services/intent.py` | `classify_intent()` — fast non-streaming LLM call; intent skip heuristic; `_PRIOR_WORK_REFS` fast-path | -| `services/tools.py` | All LLM tool definitions + `execute_tool(user_id, tool_name, arguments, conv_id=None, workspace_project_id=None)` dispatcher; duplicate guards; `_resolve_project()` 4-step lookup; `search_projects` and `set_rag_scope` tools | +| ~~`services/intent.py`~~ | Removed — intent routing eliminated; the main model handles all tool routing directly | +| `services/tools/` | LLM tool package — decorator-based registry (`_registry.py`), shared helpers (`_helpers.py`), and one module per domain: `notes.py` (create/update/delete/search/list/read), `tasks.py` (list/log_work), `entities.py` (save_person/save_place/lists), `projects.py`, `calendar.py`, `web.py`, `rag.py`, `profile.py`, `rss.py`, `weather.py`, `utility.py` (calculate). `execute_tool()` and `get_tools_for_user()` are the public API. | | `services/projects.py` | Project CRUD + `generate_project_summary()` (Ollama, fire-and-forget) + `backfill_project_summaries()` (startup) | | `services/embeddings.py` | `upsert_note_embedding()`, `semantic_search_notes(orphan_only=False)` (pgvector cosine similarity) | | `services/generation_buffer.py` | In-memory SSE event buffer; `cancel_event`; 60s cleanup; supports both chat (int keys) and assist (string keys) | @@ -231,7 +231,7 @@ Permission resolution is centralised in `services/access.py`. `get_project_permi | `services/briefing_scheduler.py` | APScheduler `BackgroundScheduler`; slots with catch-up logic; async-safe via `asyncio.create_task` | | `services/briefing_conversations.py` | Briefing conversation persistence and history queries | | `services/briefing_profile.py` | Per-user profile note that the assistant updates over time | -| `services/research.py` | SearXNG research pipeline: 5 sub-queries → parallel fetch → synthesis; `search_images` for image category | +| `services/research.py` | SearXNG research pipeline: sub-queries → parallel fetch → outline → section synthesis → executive summary → index note with linked section notes | | `services/events.py` | Internal events CRUD: `list_events`, `create_event`, `update_event`, `delete_event`, `get_event`; source of truth for all event LLM tools | | `routes/events.py` | `/api/events` — event CRUD routes | | `services/caldav.py` | Optional CalDAV sync — user-configured external server; syncs to/from internal store via `caldav_uid` FK; `is_caldav_configured()` guards tool activation | @@ -291,7 +291,7 @@ Permission resolution is centralised in `services/access.py`. `get_project_permi | `services/access.py` | Permission resolution for all shared resources | | `services/llm.py` | `build_context()`, RAG injection, history summarisation | | `services/generation_task.py` | SSE streaming, tool-call loop, GenerationBuffer management | -| `services/tools.py` | All LLM tool implementations (`create_note`, `search_notes`, `get_weather`, …) | +| `services/tools/` | LLM tool implementations (38 tools across 11 modules); decorator-based registry | | `services/embeddings.py` | `upsert_note_embedding()`, `semantic_search_notes()` | | `services/briefing_pipeline.py` | Two-lane parallel gather → LLM synthesis → briefing output | | `services/briefing_scheduler.py` | APScheduler integration, catch-up logic for missed slots | @@ -311,26 +311,22 @@ See [sso-oauth.md](sso-oauth.md) for provider-specific setup instructions. ## LLM Pipeline Internals -### Intent Routing +### Tool Routing -Before the main model runs, a lightweight intent classifier (`services/intent.py`) runs concurrently with `build_context()`. It makes a fast non-streaming call using a smaller dedicated model (`OLLAMA_INTENT_MODEL`, default `qwen2.5:7b`) to determine if the message requires a tool call. - -**Skip heuristic** — Intent classification is skipped entirely for short messages (≤10 words) with no action/object keywords, saving 400–800ms on conversational replies. - -**Prior-work fast-path** — `_PRIOR_WORK_REFS` regex detects phrases like "research you did", "note you made", "using your research" and returns no-tool immediately, preventing `search_web` from firing when the user references existing notes. - -If a tool is detected, the intent's one-sentence `ack` field is streamed as the first chunk (TTFT), the tool executes, then the main model generates a follow-up with the tool result. For chat-only responses the main model streams directly. +No separate intent router — the main model handles all tool routing directly via Ollama's structured tool-calling output. The model receives the full tool schema list and decides whether to call a tool or respond conversationally. A thinking-mode heuristic (`_should_think()`) detects complex prompts and enables extended reasoning. ### Tool Loop -Multi-round tool loop (max 5 rounds). All implementations in `services/tools.py`; `execute_tool(user_id, tool_name, arguments, conv_id=None, workspace_project_id=None)` is the dispatcher. `conv_id` and `workspace_project_id` are threaded in from `run_generation()` so tools like `set_rag_scope` can write to the current conversation. +Multi-round tool loop (max 5 rounds). All implementations in `services/tools/` (decorator-based registry); `execute_tool(user_id, tool_name, arguments, conv_id=None, workspace_project_id=None)` is the dispatcher. `conv_id` and `workspace_project_id` are threaded in from `run_generation()` so tools like `set_rag_scope` can write to the current conversation. -**Duplicate protection on `create_note` / `create_task`:** +**Unified `create_note` tool** — creates both notes and tasks. Setting `status` (e.g. `"todo"`) creates a task; omitting it creates a knowledge note. All task fields (due_date, priority, milestone, parent_task, recurrence_rule) are available on the single tool. + +**Duplicate protection on `create_note`:** 1. Exact title match (case-insensitive) → hard block, redirect to `update_note` 2. Fuzzy title match (SequenceMatcher ≥ 82%; punctuation stripped before candidate search) → hard block -3. Semantic content similarity (threshold 0.90, body ≥ 200 chars) → soft block with `requires_confirmation: true` +3. Semantic content similarity (threshold 0.90, body ≥ 80 chars) → soft block with `requires_confirmation: true` -**Project resolution** (`_resolve_project`): 4-step lookup — (1) exact DB match, (2) `query in title` substring, (3) `title in query` reverse substring, (4) SequenceMatcher ≥ 0.55. +**Project resolution** (`_helpers.resolve_project`): 4-step lookup — (1) exact DB match, (2) `query in title` substring, (3) `title in query` reverse substring, (4) SequenceMatcher ≥ 0.55. ### Context Window and Summarisation @@ -344,8 +340,11 @@ History summarisation threshold: 30 messages. Keeps 8 recent messages. Summary m 1. Intent model generates 5 focused sub-queries 2. All 5 SearXNG queries run in parallel (200ms stagger to avoid rate limiter) 3. Up to 15 unique URLs fetched in parallel -4. Up to 12 sources passed to synthesis LLM -5. Result saved as a note with `tags=["research"]` +4. LLM generates an outline (2–8 sections with title + focus) +5. Each section synthesised in parallel from relevant sources +6. Executive summary generated from all section content +7. Index note created with executive summary + links to section notes; section notes linked back via `parent_id` +8. Falls back to single-note synthesis if outline generation fails SearXNG tip: add the app server IP to `botdetection.ip_lists.pass_ip` in SearXNG `settings.yml` to bypass the rate limiter for trusted backend requests. diff --git a/docs/features.md b/docs/features.md index 6c1ad9a..e9bbec4 100644 --- a/docs/features.md +++ b/docs/features.md @@ -68,7 +68,7 @@ Full conversation history with SSE streaming. Features: ## Web Research -The assistant can search the web (SearXNG) and fetch pages, synthesising findings into notes. A lightweight `search_web` tool answers quick questions inline without saving. Requires `SEARXNG_URL` to be configured. +The assistant can search the web (SearXNG) and fetch pages, synthesising findings into a structured multi-note research output: an index note with an executive summary and links to focused section notes. Each section covers a distinct aspect of the topic with cited sources. Falls back to a single note when outline generation fails. A lightweight `search_web` tool answers quick questions inline without saving. Requires `SEARXNG_URL` to be configured. ## Calendar