KB Agent runs on Azure App Service which cold-starts after idle periods,
taking 20-30s to respond. The previous 15s timeout caused httpx.ReadTimeout
that crashed the ReAct agent tool node and killed the SSE stream.
Changes:
- Increase kb_agent_search_timeout_sec default from 15 to 30 in config.py
- Add single retry with 2s backoff on ReadTimeout in kb_search tool
- Catch all exceptions and return friendly Chinese error messages instead
of propagating exceptions to the agent loop
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Add return_dto=None to delete_attachment decorator to fix Litestar
ImproperlyConfiguredException on 204 status code with None return type
- Catch BaseException instead of Exception in create_tables() to handle
anyio ExceptionGroup from concurrent gunicorn workers
- Enable debug=True temporarily to capture detailed error traces
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
When 2 gunicorn workers start simultaneously, both call create_all()
which races on CREATE TABLE attachments. The second worker hits a
PostgreSQL UniqueViolation on pg_type_typname_nsp_index because the
type already exists. Wrap create_tables() in try/except to handle
this gracefully.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Litestar validates that handlers with 204 status codes cannot return
a response body. The delete_attachment handler was declared as
-> Response and returned Response(content=None, status_code=204),
which caused ImproperlyConfiguredException at startup and crashed
all workers. Change return type to None and move status_code to the
@delete decorator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Attachment ORM model (id, conversation_id, message_id, filename, content_type, blob_url, size_bytes, created_at) with FK to conversations
- Add POST /api/attachments/upload (multipart, 50MB limit), GET /api/attachments/{id}, GET /api/attachments/{id}/download (302 to SAS URL), DELETE /api/attachments/{id}
- Add delete_blob() and generate_sas_url() to storage/blob.py for download redirect and cleanup
- Dispatch parse_attachment task to Service Bus for parseable types (PDF, images, CSV, DOCX, XLSX)
- Pre-create blob container on startup (best-effort)
- Add AttachmentOut schema and full API docs in doc/api.md
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Litestar's {ticket_id:uuid} path parameter requires the handler
parameter to be typed as UUID, not str. This caused a 400 validation
error on GET /api/tickets/{uuid}.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
These packages were listed locally but never committed, causing
ModuleNotFoundError: No module named 'azure' on Azure deployment.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The app.storage and app.tasks packages were never committed to git,
causing ModuleNotFoundError on Azure deployment. Also adds the
document and sandbox tool modules with their config fields.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
{ticket_id:str} was matching /api/tickets/summary before the literal route.
Changing to {ticket_id:uuid} restricts matching to UUID format only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace module-level _current_model global with contextvars.ContextVar
to prevent concurrent requests from overwriting each other's search
strategy (flash vs pro)
- Add GET /api/tickets/summary returning {total, by_status, by_priority}
aggregated from Gongdan API, registered before parameterized ticket routes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ticketNumber (e.g. TK-2026-296691) was being used as id, causing
GET /api/tickets/{id} to 404 on upstream Gongdan API which expects UUID.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Azure Oryx could not find packages installed to .python_packages/.
Add a startup.sh script that sets PYTHONPATH to include the bundled
package directory before launching gunicorn. Update workflow to set
the Web App startup command to use startup.sh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add web_search tool with Jina Search/Reader/Rerank
- Flash mode: top 3, 8s timeout, no Reader/Rerank
- Pro mode: top 10, 20s timeout, concurrent Reader + Rerank top 5
- Add Redis async cache (TTL=300s) for search results
- Register "search" in ALL_TOOLS mapping
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add complete Python backend (Litestar + LangGraph) with chat, conversations, tickets APIs
- Add GitHub Actions workflow for auto-deploying backend to Azure Web App (soc-backend)
- Add gunicorn to requirements.txt for production serving
- Update CLAUDE.md and EXTERNAL_SERVICES.md with latest config
- Remove obsolete claudehd.md (merged into gpthd.md)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>