Switch default model to qwen3 and add intent routing for reliable tool calling
Mistral didn't reliably use Ollama's structured tool calling API — it wrote tool calls as JSON text instead of invoking them. This adds an intent routing layer that classifies user intent via a fast non-streaming LLM call before streaming, executing detected tools directly and bypassing native tool calling. - Change default OLLAMA_MODEL from mistral to qwen3 - Add intent.py: classify_intent() with JSON parsing and fallback regex - Integrate intent routing into generation_task.py round 0 - Add all-day event support (iCalendar DATE values) to CalDAV service - Add recurring event support (RRULE) to CalDAV service and tool definition - Improve create_event tool description for descriptive titles - Enhance system prompt with structured tool usage guidance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import date as date_type, datetime, timedelta
|
||||
|
||||
import caldav
|
||||
import icalendar
|
||||
@@ -76,30 +76,61 @@ async def create_event(
|
||||
duration: int | None = None,
|
||||
description: str | None = None,
|
||||
location: str | None = None,
|
||||
all_day: bool = False,
|
||||
recurrence: str | None = None,
|
||||
) -> dict:
|
||||
"""Create a calendar event. start/end are ISO datetime strings."""
|
||||
"""Create a calendar event.
|
||||
|
||||
start/end are ISO date (YYYY-MM-DD) or datetime strings.
|
||||
If all_day is True, DTSTART/DTEND use DATE values.
|
||||
recurrence is an iCalendar RRULE string (e.g. "FREQ=YEARLY").
|
||||
"""
|
||||
config = await get_caldav_config(user_id)
|
||||
if not (config.get("caldav_url") and config.get("caldav_username") and config.get("caldav_password")):
|
||||
raise ValueError("CalDAV is not configured. Go to Settings → Calendar to set it up.")
|
||||
|
||||
dt_start = datetime.fromisoformat(start)
|
||||
if end:
|
||||
dt_end = datetime.fromisoformat(end)
|
||||
else:
|
||||
dt_end = dt_start + timedelta(minutes=duration or 60)
|
||||
|
||||
cal = icalendar.Calendar()
|
||||
cal.add("prodid", "-//FabledAssistant//EN")
|
||||
cal.add("version", "2.0")
|
||||
|
||||
event = icalendar.Event()
|
||||
event.add("summary", title)
|
||||
event.add("dtstart", dt_start)
|
||||
event.add("dtend", dt_end)
|
||||
|
||||
if all_day:
|
||||
# All-day events use DATE values (no time component)
|
||||
d_start = datetime.fromisoformat(start).date() if "T" in start else date_type.fromisoformat(start)
|
||||
if end:
|
||||
d_end = datetime.fromisoformat(end).date() if "T" in end else date_type.fromisoformat(end)
|
||||
else:
|
||||
d_end = d_start + timedelta(days=1)
|
||||
event.add("dtstart", d_start)
|
||||
event.add("dtend", d_end)
|
||||
result_start = d_start.isoformat()
|
||||
result_end = d_end.isoformat()
|
||||
else:
|
||||
dt_start = datetime.fromisoformat(start)
|
||||
if end:
|
||||
dt_end = datetime.fromisoformat(end)
|
||||
else:
|
||||
dt_end = dt_start + timedelta(minutes=duration or 60)
|
||||
event.add("dtstart", dt_start)
|
||||
event.add("dtend", dt_end)
|
||||
result_start = dt_start.isoformat()
|
||||
result_end = dt_end.isoformat()
|
||||
|
||||
if description:
|
||||
event.add("description", description)
|
||||
if location:
|
||||
event.add("location", location)
|
||||
if recurrence:
|
||||
# Parse RRULE string like "FREQ=YEARLY" into a vRecur dict
|
||||
rrule_parts = {}
|
||||
for part in recurrence.split(";"):
|
||||
if "=" in part:
|
||||
key, value = part.split("=", 1)
|
||||
rrule_parts[key.strip().lower()] = value.strip()
|
||||
event.add("rrule", rrule_parts)
|
||||
|
||||
cal.add_component(event)
|
||||
|
||||
ical_str = cal.to_ical().decode("utf-8")
|
||||
@@ -111,11 +142,15 @@ async def create_event(
|
||||
|
||||
await asyncio.to_thread(_save)
|
||||
|
||||
return {
|
||||
result = {
|
||||
"title": title,
|
||||
"start": dt_start.isoformat(),
|
||||
"end": dt_end.isoformat(),
|
||||
"start": result_start,
|
||||
"end": result_end,
|
||||
"all_day": all_day,
|
||||
}
|
||||
if recurrence:
|
||||
result["recurrence"] = recurrence
|
||||
return result
|
||||
|
||||
|
||||
async def list_events(user_id: int, date_from: str, date_to: str) -> list[dict]:
|
||||
|
||||
Reference in New Issue
Block a user