{"openapi":"3.1.0","info":{"title":"Haiven Knowledge API","description":"Semantic knowledge base API for the Haiven platform.\n\n## Overview\nHaiven Knowledge provides semantic search and multi-source ingestion over infrastructure\ndocumentation, operational lessons, research findings, meeting notes, emails, calendar\nevents, AI conversation exports, and web clips.\n\n## Pipeline\n**Search:** Query → Qwen3-Embedding-4B (embed) → Qdrant (vector search, top 20) → Qwen3-Reranker-4B (cross-encoder) → top-N results\n**Ingest:** Content → GLM-4.7-Flash (dual-text generation) → Qwen3-Embedding-4B (embed) → Qdrant upsert\n\n## Features\n- Semantic search with cross-encoder reranking (RAGAS Context Precision: 0.672)\n- Dual-text pipeline: separate embedding and generation texts for higher retrieval quality\n- Source connectors: email, calendar, AI conversations, web clipper\n- Multi-format ingestion: Markdown, PDF, DOCX via Docling, URLs via crawl4ai\n- Meeting transcript ingestion with action item extraction\n- Graceful degradation: reranker unavailable → embedding-only (degraded=true)\n- Full Langfuse tracing for all search and ingest operations\n\n## Collection\n- **Name**: haiven-knowledge_v3\n- **Points**: 6299+\n- **Embedding model**: qwen3-embedding-4b (2560-dim, Cosine, vLLM Alpha GPU)\n- **Reranker**: Qwen3-Reranker-4B-seq-cls (Delta GPU, port 8460)\n\n## Base URLs\n- Production (Traefik): https://knowledge.haiven.site\n- Internal (Docker): http://haiven-knowledge:8022\n\n## Authentication\nNo authentication required for internal LAN access.\n\n## Source Application Values\nFilter search by origin: `email`, `calendar`, `ai_conversation`, `web_clip`,\n`research_agent`, `manual`, `docs`, `langfuse`\n","version":"2.0.0","contact":{"name":"Haiven Infrastructure","url":"https://home.haiven.site"}},"servers":[{"url":"https://knowledge.haiven.site","description":"Production (Traefik)"},{"url":"http://haiven-knowledge:8022","description":"Internal (Docker network)"}],"tags":[{"name":"Health","description":"Service health and Prometheus metrics"},{"name":"Search","description":"Semantic search with optional reranking and filters"},{"name":"Ingest","description":"Knowledge ingestion — text, directory, meeting, file upload"},{"name":"Clip","description":"Web URL clipping via crawl4ai"},{"name":"Admin","description":"Collection statistics and source file management"}],"paths":{"/health":{"get":{"summary":"Health check","description":"Returns service health with dependency status.\n\nChecks: Qdrant connectivity, LiteLLM embedding endpoint, Qdrant collection\nexistence, reranker availability (optional).\n\nUsed as Docker healthcheck: `curl -f http://localhost:8022/health`\n","operationId":"getHealth","tags":["Health"],"responses":{"200":{"description":"Service health (healthy or degraded)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"},"examples":{"healthy":{"summary":"All systems operational","value":{"status":"healthy","qdrant":true,"litellm":true,"collection":"haiven-knowledge_v3","version":"1.0.0","reranker":true}},"degraded":{"summary":"Reranker unavailable (search continues)","value":{"status":"healthy","qdrant":true,"litellm":true,"collection":"haiven-knowledge_v3","version":"1.0.0","reranker":false}}}}}}}}},"/metrics":{"get":{"summary":"Prometheus metrics","description":"Prometheus-formatted metrics for monitoring.\n\n**Exported metrics:**\n- `knowledge_points_total` (gauge): Total knowledge points in Qdrant collection\n\n**Scrape config (Docker labels):**\n- `prometheus.scrape=true`\n- `prometheus.port=8022`\n- `prometheus.path=/metrics`\n","operationId":"getMetrics","tags":["Health"],"responses":{"200":{"description":"Prometheus text format","content":{"text/plain":{"schema":{"type":"string"},"example":"# HELP knowledge_points_total Total knowledge points in Qdrant\n# TYPE knowledge_points_total gauge\nknowledge_points_total 6299\n"}}}}}},"/v1/search":{"post":{"summary":"Semantic search","description":"Perform semantic search over the knowledge base.\n\n**Pipeline:**\n1. Query embedded via Qwen3-Embedding-4B (2560-dim, instruction prefix applied)\n2. Qdrant cosine similarity search with optional filters\n3. If reranker enabled: top 20 candidates re-scored by Qwen3-Reranker-4B (cross-encoder)\n4. Results sorted by reranker score (or vector score if degraded)\n\n**Filters (all optional):**\n- `category`: reference, research, lesson, plan\n- `source`: docs, langfuse, manual\n- `source_application`: email, calendar, ai_conversation, web_clip, research_agent, manual\n- `score_threshold`: minimum vector similarity (0.0–1.0)\n\n**Degraded mode:** If reranker is unavailable, results are returned by vector score only\nand `degraded: true` is set in the response.\n\n**Score guidance:**\n- 0.8–1.0: Highly relevant, direct topic match\n- 0.6–0.8: Related concepts, use with confidence\n- 0.4–0.6: Loosely related, useful background\n- <0.4: Likely not relevant (default threshold: none — returns all hits)\n","operationId":"searchKnowledge","tags":["Search"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"},"examples":{"basic":{"summary":"Basic search","value":{"query":"How do I configure Traefik dynamic routing?","limit":10}},"filtered":{"summary":"Filter by category and score","value":{"query":"GPU Blackwell kernel issues","limit":5,"category":"lesson","score_threshold":0.5}},"by_source_application":{"summary":"Filter by connector source","value":{"query":"project deadline discussion","source_application":"email","limit":10}},"research_output":{"summary":"Search research agent outputs","value":{"query":"RAGAS evaluation methodology","source_application":"research_agent","limit":5}}}}}},"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"},"examples":{"results_found":{"summary":"Successful search with reranking","value":{"results":[{"content":"Traefik dynamic configuration files are loaded from /mnt/apps/docker/infrastructure/traefik/dynamic/ with 2-second polling.","text_for_generation":"Traefik loads dynamic routing configuration from YAML files in /mnt/apps/docker/infrastructure/traefik/dynamic/. These files are polled every 2 seconds and support routers, services, and middlewares sections.","score":0.783,"reranker_score":0.924,"source":"docs","source_file":"/mnt/apps/docs/infrastructure/traefik-routing.md","topic":"traefik-routing","category":"reference","tags":["traefik","routing","docker"],"created_at":"2026-02-10T15:30:00Z","doc_type":"reference"}],"total":1,"query_time_ms":145.3,"degraded":false,"no_relevant_results":false}},"degraded":{"summary":"Reranker unavailable (embedding-only)","value":{"results":[],"total":0,"query_time_ms":89.2,"degraded":true,"no_relevant_results":true}}}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"Qdrant vector store unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"service":{"type":"string"}}},"example":{"error":"Vector store unavailable","service":"qdrant"}}}}}}},"/v1/ingest/text":{"post":{"summary":"Ingest single text entry","description":"Ingest a single knowledge point from text.\n\n**Processing pipeline:**\n1. GLM-4.7-Flash generates `text_for_embedding` (keyword-dense) and `text_for_generation` (LLM-ready)\n2. Qwen3-Embedding-4B embeds `text_for_embedding` (2560-dim)\n3. Vector + both texts stored in Qdrant with metadata\n\n**source_application values:** email, calendar, ai_conversation, web_clip,\nresearch_agent, manual (default)\n\n**Categories:** reference, research, lesson, plan\n","operationId":"ingestText","tags":["Ingest"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestTextRequest"},"examples":{"simple":{"summary":"Simple manual entry","value":{"content":"Always use qwen3-embedding-4b for embeddings. It routes to vLLM Alpha GPU (always-on). Never use qwen3-embedding-8b-f16-instruct — routes to CPU llama-swap and hangs.","topic":"embedding-model-routing"}},"detailed":{"summary":"Full metadata","value":{"content":"Seed-36B uses thinking_budget (not enable_thinking) to control reasoning. Set chat_template_kwargs: {thinking_budget: 0} to disable. With budget 0, still returns 1 line of reasoning_content.","topic":"llm-thinking-mode","category":"lesson","tags":["seed","thinking","vllm","glm"],"source":"manual","source_application":"manual","doc_type":"reference"}},"from_connector":{"summary":"Connector-sourced entry","value":{"content":"Meeting notes: Q1 review scheduled for March 15...","topic":"calendar-events","category":"reference","source_application":"calendar"}}}}}},"responses":{"200":{"description":"Text ingested successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResponse"},"example":{"point_id":"a3f7c829-4d5e-4b2a-8f3c-7e9d1a5b4c82","content_preview":"Always use qwen3-embedding-4b for embeddings. It routes to vLLM Alpha GPU...","topic":"embedding-model-routing"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/ingest/directory":{"post":{"summary":"Bulk ingest from directory","description":"Ingest all Markdown files from a directory path.\n\nThe directory must be inside a mounted volume (`/data/docs` or `/data/memory`).\nEach file is chunked, enriched via the dual-text pipeline, embedded, and stored.\n\n**Duplicate detection:** Files are tracked by `source_file` path. Set\n`skip_existing: false` to force re-ingest of already-indexed files.\n\n**Supported formats:** `.md`, `.txt` (native chunking)\nFor PDF/DOCX, use `/v1/ingest/file` (routes through Docling).\n","operationId":"ingestDirectory","tags":["Ingest"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestDirectoryRequest"},"examples":{"default":{"summary":"Ingest docs directory","value":{"path":"/data/docs","recursive":true,"skip_existing":true}},"force_reindex":{"summary":"Force re-ingest (e.g. after content update)","value":{"path":"/data/docs/gpu-management","recursive":false,"skip_existing":false}}}}}},"responses":{"200":{"description":"Directory ingestion completed (partial failures allowed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestDirectoryResponse"},"examples":{"success":{"summary":"Full success","value":{"files_processed":47,"chunks_ingested":312,"files_skipped":5,"errors":[]}},"partial":{"summary":"Partial failure","value":{"files_processed":23,"chunks_ingested":156,"files_skipped":2,"errors":["/data/docs/corrupted.md: UnicodeDecodeError"]}}}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/ingest/meeting":{"post":{"summary":"Ingest meeting transcript","description":"Ingest a meeting transcript with typed chunk extraction.\n\nThe meeting ingester extracts distinct chunk types from the transcript:\n- `decision`: Decisions made (DECISION: prefix)\n- `action_item`: Action items assigned (ACTION: prefix)\n- `discussion`: Discussion points and context\n\nIf `KNOW_WORKHUB_TASK_SYNC_ENABLED=true`, action items are automatically\ncreated as tasks in work-hub.\n\nEach chunk is independently embedded and stored with `doc_type` metadata\nset to the chunk type.\n","operationId":"ingestMeeting","tags":["Ingest"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeetingIngestRequest"},"examples":{"standard":{"summary":"Meeting transcript","value":{"text":"ACTION Elijah: Set up Authentik SSO by March 1.\nDECISION: We will use Seed-36B for structured output tasks.\nDiscussion about RAGAS quality gate — current 0.672, target 0.80 by Q2.","meeting_title":"Infrastructure Review 2026-02-25","source_file":"meetings/2026-02-25-infra-review.md"}}}}}},"responses":{"200":{"description":"Meeting ingested successfully","content":{"application/json":{"schema":{"type":"object","description":"Ingestion result with chunk breakdown"},"example":{"chunks_ingested":3,"action_items":1,"decisions":1,"discussion_points":1,"tasks_created":1}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/ingest/file":{"post":{"summary":"Upload file for ingestion","description":"Upload a file for ingestion. Supports multiple formats.\n\n**Format routing:**\n- `.md`, `.txt`: Native chunking (no external service required)\n- `.pdf`, `.docx`, `.pptx`, `.xlsx`, `.html`: Converted via Docling\n  (haiven-ingest-docling at port 5001) to Markdown, then chunked\n\nAfter format conversion, the same dual-text enrichment + embedding pipeline\napplies as for `/v1/ingest/text`.\n\n**Request:** multipart/form-data with `file`, `topic`, `category` fields.\n","operationId":"ingestFile","tags":["Ingest"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to ingest (MD, TXT, PDF, DOCX, PPTX, XLSX, HTML)"},"topic":{"type":"string","default":"general","description":"Topic classification"},"category":{"type":"string","default":"reference","description":"Category classification"}}}}}},"responses":{"200":{"description":"File ingested successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestDirectoryResponse"},"example":{"files_processed":1,"chunks_ingested":23,"files_skipped":0,"errors":[]}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/clip":{"post":{"summary":"Clip a web URL","description":"Fetch a URL via crawl4ai and ingest the Markdown content into the knowledge base.\n\n**Deduplication:** SHA-256 of `\"web_clip\" + url` is used as `document_id`.\nRe-clipping the same URL returns `\"already_exists\"` without fetching.\n\n**Processing:**\n1. Check Qdrant for existing document_id (dedup)\n2. POST to crawl4ai `/crawl` with the URL\n3. Extract `fit_markdown` from crawl response\n4. Run dual-text enrichment (GLM-4.7-Flash)\n5. Embed and store with `source_application=\"web_clip\"`\n\nRequires `KNOW_CRAWL4AI_URL` to be configured and crawl4ai running.\n","operationId":"clipUrl","tags":["Clip"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipRequest"},"examples":{"standard":{"summary":"Clip a documentation page","value":{"url":"https://docs.qdrant.tech/concepts/filtering/","tags":["qdrant","filtering","payload"],"domain":"work","topic":"qdrant-filtering","category":"reference"}},"blog":{"summary":"Clip a technical blog post","value":{"url":"https://huggingface.co/blog/qwen3","tags":["qwen","embedding","llm"],"domain":"work","topic":"embedding-models"}}}}}},"responses":{"200":{"description":"Clip result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipResponse"},"examples":{"ingested":{"summary":"New URL ingested","value":{"status":"ingested","point_id":"b4e8d923-5f6a-4c3b-9e2d-8f0e2b6c5d94","title":"Filtering - Qdrant","content_preview":"Filtering in Qdrant allows you to narrow search results using payload conditions..."}},"already_exists":{"summary":"URL already clipped","value":{"status":"already_exists","point_id":null,"title":null,"content_preview":null}}}}}},"422":{"$ref":"#/components/responses/ValidationError"},"502":{"description":"crawl4ai unreachable or returned error","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}},"example":{"detail":"crawl4ai unreachable: Connection refused"}}}},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/stats":{"get":{"summary":"Collection statistics","description":"Returns statistics about the knowledge base: total points, breakdown by source\nand category, and list of all distinct topics.\n","operationId":"getStats","tags":["Admin"],"responses":{"200":{"description":"Collection statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsResponse"},"example":{"total_points":6299,"sources":{"docs":5800,"langfuse":450,"manual":49},"categories":{"reference":4200,"research":1100,"lesson":700,"plan":299},"topics":["gpu-allocation","vllm-deployment","traefik-routing","llm-thinking-mode","embedding-model-routing"]}}}},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/admin/source-file":{"delete":{"summary":"Delete knowledge by source file","description":"Delete all knowledge points with a matching `source_file` payload field.\n\nUse this to remove outdated documentation before re-ingesting, or to clean up\ntest data. This operation is irreversible — re-ingest to restore.\n\n**Query parameter:** `source_file` — exact path as stored in the Qdrant payload.\n","operationId":"deleteSourceFile","tags":["Admin"],"parameters":[{"name":"source_file","in":"query","required":true,"schema":{"type":"string"},"description":"Exact source_file value stored in Qdrant payload","example":"/mnt/apps/docs/deprecated/old-guide.md"}],"responses":{"200":{"description":"Points deleted (count may be 0 if not found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSourceFileResponse"},"examples":{"deleted":{"summary":"Points removed","value":{"source_file":"/mnt/apps/docs/deprecated/old-guide.md","deleted_count":15}},"not_found":{"summary":"No points with that source","value":{"source_file":"/mnt/apps/docs/nonexistent.md","deleted_count":0}}}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/ServerError"}}}}},"components":{"schemas":{"HealthResponse":{"type":"object","required":["status","qdrant","litellm","collection","version"],"properties":{"status":{"type":"string","enum":["healthy","degraded","unhealthy"],"description":"Overall health status"},"qdrant":{"type":"boolean","description":"Qdrant vector database reachable"},"litellm":{"type":"boolean","description":"LiteLLM embedding endpoint reachable"},"collection":{"type":"string","nullable":true,"description":"Active Qdrant collection name"},"version":{"type":"string","description":"Service version"},"reranker":{"type":"boolean","nullable":true,"description":"Reranker service health (null if disabled)"}}},"SearchRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":1,"description":"Natural language search query"},"limit":{"type":"integer","default":10,"minimum":1,"maximum":50,"description":"Maximum results to return"},"category":{"type":"string","nullable":true,"description":"Filter by category","enum":["reference","research","lesson","plan"]},"source":{"type":"string","nullable":true,"description":"Filter by source identifier"},"source_application":{"type":"string","nullable":true,"description":"Filter by connector source (email, calendar, ai_conversation, web_clip, research_agent, manual, docs, langfuse)"},"score_threshold":{"type":"number","format":"float","nullable":true,"minimum":0.0,"maximum":1.0,"description":"Minimum vector similarity score (0.0–1.0)"}}},"SearchResultItem":{"type":"object","required":["content","score","source"],"properties":{"content":{"type":"string","description":"Raw knowledge text"},"text_for_generation":{"type":"string","nullable":true,"description":"LLM-optimized version of content (generated by enrichment pipeline)"},"score":{"type":"number","format":"float","description":"Qdrant cosine similarity score (0.0–1.0)"},"reranker_score":{"type":"number","format":"float","nullable":true,"description":"Cross-encoder reranker score (present when reranker is active)"},"source":{"type":"string","description":"Origin identifier (docs, langfuse, manual, etc.)"},"source_file":{"type":"string","nullable":true,"description":"Original source file path"},"topic":{"type":"string","nullable":true,"description":"Topic classification"},"category":{"type":"string","nullable":true,"description":"Category classification"},"tags":{"type":"array","items":{"type":"string"},"description":"Associated tags"},"created_at":{"type":"string","nullable":true,"description":"ISO-8601 creation timestamp"},"doc_type":{"type":"string","nullable":true,"description":"Document type (reference, lesson, action_item, decision, etc.)"}}},"SearchResponse":{"type":"object","required":["results","total","query_time_ms"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/SearchResultItem"}},"total":{"type":"integer","description":"Number of results returned"},"query_time_ms":{"type":"number","format":"float","description":"Total pipeline execution time in milliseconds"},"degraded":{"type":"boolean","default":false,"description":"True if reranker was unavailable and results are embedding-only"},"no_relevant_results":{"type":"boolean","default":false,"description":"True if the result set is empty"}}},"IngestTextRequest":{"type":"object","required":["content","topic"],"properties":{"content":{"type":"string","minLength":1,"description":"Knowledge text to ingest"},"topic":{"type":"string","minLength":1,"description":"Topic classification"},"category":{"type":"string","default":"reference","description":"Category: reference, research, lesson, plan"},"tags":{"type":"array","items":{"type":"string"},"default":[],"description":"Tags for filtering"},"source":{"type":"string","default":"manual","description":"Source: docs, langfuse, manual"},"source_file":{"type":"string","nullable":true,"description":"Original source file path (optional)"},"source_application":{"type":"string","nullable":true,"description":"Application that produced this content (email, calendar, web_clip, etc.)"},"doc_type":{"type":"string","default":"reference","description":"Document type classification"}}},"IngestResponse":{"type":"object","required":["point_id","content_preview","topic"],"properties":{"point_id":{"type":"string","description":"UUID of the stored Qdrant point"},"content_preview":{"type":"string","description":"First 100 characters of content"},"topic":{"type":"string","description":"Assigned topic"}}},"IngestDirectoryRequest":{"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"Directory path to ingest (must be within a mounted volume)"},"recursive":{"type":"boolean","default":true,"description":"Walk subdirectories recursively"},"skip_existing":{"type":"boolean","default":true,"description":"Skip files already indexed (by source_file path)"}}},"IngestDirectoryResponse":{"type":"object","required":["files_processed","chunks_ingested","files_skipped","errors"],"properties":{"files_processed":{"type":"integer","description":"Number of files successfully processed"},"chunks_ingested":{"type":"integer","description":"Total knowledge chunks stored in Qdrant"},"files_skipped":{"type":"integer","description":"Files skipped (already indexed, or non-supported format)"},"errors":{"type":"array","items":{"type":"string"},"description":"Error messages for failed files (partial failures allowed)"}}},"MeetingIngestRequest":{"type":"object","required":["text","source_file"],"properties":{"text":{"type":"string","minLength":1,"description":"Meeting transcript text"},"meeting_title":{"type":"string","default":"Meeting","description":"Meeting title for metadata context"},"source_file":{"type":"string","description":"Source file path for deduplication tracking"}}},"ClipRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"URL to fetch and ingest"},"tags":{"type":"array","items":{"type":"string"},"default":[],"description":"Tags to apply to the ingested content"},"domain":{"type":"string","default":"work","description":"Domain classification (work, personal)"},"topic":{"type":"string","default":"web-clip","description":"Topic for the ingested entry"},"category":{"type":"string","default":"reference","description":"Category classification"}}},"ClipResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["ingested","already_exists","error"],"description":"Result of the clip operation"},"point_id":{"type":"string","nullable":true,"description":"Qdrant point ID (present when status=ingested)"},"title":{"type":"string","nullable":true,"description":"Page title extracted from crawl response"},"content_preview":{"type":"string","nullable":true,"description":"First 200 characters of fetched content"}}},"StatsResponse":{"type":"object","required":["total_points","sources","categories","topics"],"properties":{"total_points":{"type":"integer","description":"Total knowledge points in collection"},"sources":{"type":"object","additionalProperties":{"type":"integer"},"description":"Breakdown by source field"},"categories":{"type":"object","additionalProperties":{"type":"integer"},"description":"Breakdown by category field"},"topics":{"type":"array","items":{"type":"string"},"description":"All distinct topic values"}}},"DeleteSourceFileResponse":{"type":"object","required":["source_file","deleted_count"],"properties":{"source_file":{"type":"string","description":"Source file path that was targeted"},"deleted_count":{"type":"integer","description":"Number of Qdrant points deleted"}}},"ErrorResponse":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string","description":"Error description"}}}},"responses":{"ValidationError":{"description":"Request validation failed (FastAPI 422)","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"array","items":{"type":"object","properties":{"loc":{"type":"array","items":{"type":"string"}},"msg":{"type":"string"},"type":{"type":"string"}}}}}}}}},"ServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"embedding_error":{"summary":"Embedding service failure","value":{"detail":"Embedding failed: Connection timeout"}},"qdrant_error":{"summary":"Qdrant failure","value":{"detail":"Vector store unavailable"}}}}}}}}}