Add CalDAV calendar integration, LLM-suggested tags, and settings refinements
- CalDAV integration: per-user calendar config, create/list/search events via caldav library, LLM tools for calendar operations from chat - LLM-suggested tags: new tag_suggestions service prompts LLM with existing tags and note content to suggest 3-5 relevant tags; exposed via API endpoints (suggest-tags, append-tag); integrated into editor views (suggest button + clickable pills) and chat tool calls (pills in ToolCallCard with one-click apply) - Settings/model UI refinements, generation task improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ from typing import Literal
|
||||
import httpx
|
||||
|
||||
from fabledassistant.config import Config
|
||||
from fabledassistant.services.caldav import is_caldav_configured
|
||||
from fabledassistant.services.notes import get_note, search_notes_for_context
|
||||
from fabledassistant.services.settings import get_setting
|
||||
|
||||
@@ -79,9 +80,10 @@ async def stream_chat(
|
||||
options: dict | None = None,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""Stream chat completion from Ollama, yielding content chunks."""
|
||||
payload: dict = {"model": model, "messages": messages, "stream": True}
|
||||
merged_options = {"num_ctx": 16384}
|
||||
if options:
|
||||
payload["options"] = options
|
||||
merged_options.update(options)
|
||||
payload: dict = {"model": model, "messages": messages, "stream": True, "options": merged_options}
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=30.0, read=300.0)) as client:
|
||||
async with client.stream(
|
||||
"POST",
|
||||
@@ -119,7 +121,17 @@ async def stream_chat_with_tools(
|
||||
ChatChunk(type="tool_calls") is yielded. Always ends with
|
||||
ChatChunk(type="done").
|
||||
"""
|
||||
payload: dict = {"model": model, "messages": messages, "stream": True}
|
||||
options: dict = {"num_ctx": 16384}
|
||||
# Disable thinking mode for models like qwen3 — it interferes with tool calling
|
||||
if tools:
|
||||
options["num_predict"] = 4096
|
||||
payload: dict = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"stream": True,
|
||||
"options": options,
|
||||
"think": False,
|
||||
}
|
||||
if tools:
|
||||
payload["tools"] = tools
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=30.0, read=300.0)) as client:
|
||||
@@ -129,6 +141,7 @@ async def stream_chat_with_tools(
|
||||
json=payload,
|
||||
) as resp:
|
||||
resp.raise_for_status()
|
||||
accumulated_tool_calls: list[dict] = []
|
||||
async for line in resp.aiter_lines():
|
||||
if not line.strip():
|
||||
continue
|
||||
@@ -140,11 +153,22 @@ async def stream_chat_with_tools(
|
||||
if chunk:
|
||||
yield ChatChunk(type="content", content=chunk)
|
||||
|
||||
# Check for tool calls on done
|
||||
# Collect tool calls from any message (some models
|
||||
# emit them before the done flag)
|
||||
tc = msg.get("tool_calls")
|
||||
if tc:
|
||||
accumulated_tool_calls.extend(tc)
|
||||
|
||||
if data.get("done"):
|
||||
tool_calls = msg.get("tool_calls")
|
||||
if tool_calls:
|
||||
yield ChatChunk(type="tool_calls", tool_calls=tool_calls)
|
||||
if accumulated_tool_calls:
|
||||
logger.info(
|
||||
"Ollama returned %d tool call(s): %s",
|
||||
len(accumulated_tool_calls),
|
||||
json.dumps(accumulated_tool_calls)[:500],
|
||||
)
|
||||
yield ChatChunk(type="tool_calls", tool_calls=accumulated_tool_calls)
|
||||
else:
|
||||
logger.debug("Ollama done with no tool calls")
|
||||
yield ChatChunk(type="done")
|
||||
break
|
||||
|
||||
@@ -220,14 +244,19 @@ async def build_context(
|
||||
from datetime import date as date_type
|
||||
assistant_name = await get_setting(user_id, "assistant_name", "Fable")
|
||||
today = date_type.today().isoformat()
|
||||
has_caldav = await is_caldav_configured(user_id)
|
||||
date_guidance = "For relative dates like 'Friday' or 'next week', resolve them to YYYY-MM-DD format."
|
||||
if has_caldav:
|
||||
date_guidance += " For calendar events, use ISO 8601 datetime format (e.g. 2025-01-15T14:00:00)."
|
||||
|
||||
system_parts = [
|
||||
f"You are a helpful assistant named {assistant_name}, integrated into a note-taking and task-tracking app called Fabled Assistant. "
|
||||
"Help users with their notes, tasks, and general questions. "
|
||||
"When note context is provided, use it to give relevant answers. "
|
||||
f"Today's date is {today}. "
|
||||
"You have tools available to create tasks, create notes, and search the user's notes. "
|
||||
"Use them when the user asks you to create, add, or find tasks and notes. "
|
||||
"For relative dates like 'Friday' or 'next week', resolve them to YYYY-MM-DD format."
|
||||
"When the user asks you to create, add, or find something, use the provided tool functions. "
|
||||
"Do not describe or write out function calls as text — actually invoke the tools. "
|
||||
f"{date_guidance}"
|
||||
]
|
||||
|
||||
context_meta: dict = {
|
||||
|
||||
Reference in New Issue
Block a user