- search_places: detect HTTP 429 and mark retryable=False to stop the
retry loop immediately instead of spiraling until MAX_ITERATIONS
- get_weather: extract collection coordinates (lat/lng from first
location with coords) and retry when LLM omits required params;
uses sync_to_async for the DB query in the async view
- AITravelChat: deduplicate context-only tools (get_trip_details,
get_weather) in the render pipeline to prevent duplicate place cards
from appearing when the retry loop causes multiple get_trip_details calls
- Tests: 5 new tests covering 429 non-retryable path and weather
coord fallback; all 39 chat tests pass
Prevent API key and sensitive info leakage through exception messages:
- Replace str(exc) with generic error messages in all catch-all handlers
- Add server-side exception logging via logger.exception()
- Add ALLOWED_KWARGS per-tool allowlist to filter untrusted LLM kwargs
- Bound tool execution loop to MAX_TOOL_ITERATIONS=10
- Fix tool_call delta merge to use tool_call index
Implement a full chat-based travel agent using LiteLLM for multi-provider
LLM support (OpenAI, Anthropic, Gemini, Ollama, Groq, Mistral, etc.).
Backend:
- New 'chat' Django app with ChatConversation and ChatMessage models
- Streaming SSE endpoint via StreamingHttpResponse
- 5 agent tools: search_places, list_trips, get_trip_details,
add_to_itinerary, get_weather
- LiteLLM client wrapper with per-user API key retrieval
- System prompt with user preference context injection
Frontend:
- New /chat route with full-page chat UI (DaisyUI + Tailwind)
- Collapsible conversation sidebar with CRUD
- SSE streaming response display with tool call visualization
- Provider selector dropdown
- SSE proxy fix to stream text/event-stream without buffering
- Navbar link and i18n keys