Cortex (Neo4j + Haystack powered GraphRAG) is a knowledge base system that combines:
- Document Processing: PDF, TXT, MD, DOCX, XLSX file upload and processing
- GraphRAG: Entity and relationship extraction from documents using LLM
- Semantic Search: Vector embeddings with hybrid search (vector + keyword + graph)
- Knowledge Graph: Neo4j-based graph storage with community detection
- Collections: Organization of documents into collections with separate knowledge graphs
- API Key Management: Admin-controlled API key system with permissions
Description: Health check endpoint
Authentication: None
Response: HealthResponse
status: "healthy" | "degraded"neo4j_connected: boolversion: str
Description: Get knowledge base and knowledge graph statistics
Authentication: require_read_permission
Response: GraphStatsResponse
document_count: intchunk_count: intentity_count: intrelationship_count: inttotal_size: intcommunity_count: intcollection_count: intpending_count: intentity_relationship_ratio: floatrelationship_target_ratio: float
Description: Upload a file to the knowledge base
Authentication: require_manage_permission
Parameters:
file: UploadFile (multipart/form-data)collection_id: Optional[str] (query param)start_processing: bool (query param, default: false)
Response: UploadResponse
document_id: strfilename: strstatus: ProcessingStatusmessage: str
Notes: For bulk uploads, set start_processing=false and call /api/documents/process-pending later.
Description: List all documents in the knowledge base
Authentication: require_read_permission
Response: {"documents": List[dict], "total": int}
Description: Get a specific document
Authentication: require_read_permission
Response: Document metadata dict
Description: Get document with full content (all chunks concatenated)
Authentication: None
Response: Document with chunks array and full_content string
Description: Delete a document and clean up orphaned entities
Authentication: require_manage_permission
Response: {"message": str, "orphaned_entities_removed": int, "orphaned_communities_removed": int}
Description: Delete multiple documents
Authentication: require_manage_permission
Request: DeleteRequest
document_ids: List[str]
Response: {"message": str, "deleted_count": int, "orphaned_entities_removed": int, "orphaned_communities_removed": int}
Description: Delete all documents (destructive operation)
Authentication: require_manage_permission
Response: {"message": str, "deleted_count": int, "entities_removed": int, "communities_removed": int}
Description: Reprocess a single document
Authentication: None
Parameters:
file: Optional[UploadFile] - If provided, updates stored file and reprocesses
Response: {"document_id": str, "filename": str, "status": ProcessingStatus, "message": str}
Description: Reprocess multiple documents using stored files
Authentication: None
Request: ReprocessRequest
document_ids: List[str]
Parameters:
concurrency: Optional[int] (query param, 1-50)
Response: {"results": List[dict], "total_queued": int, "task_id": str, "concurrency": int, "message": str}
Description: Get all documents with 'pending' status
Authentication: None
Response: {"pending_count": int, "documents": List[dict]}
Description: Start processing all pending documents as background task
Authentication: None
Parameters:
concurrency: Optional[int] (query param, 1-50, defaults to config)
Response: {"task_id": str, "status": TaskStatus, "pending_count": int, "concurrency": int, "message": str}
Description: Generate a topic hint for custom content using LLM
Authentication: None
Request: TopicHintRequest
content: str (min 10 chars)answer: Optional[str]input_type: str ("qa" | "text" | "markdown")
Response: TopicHintResponse
topic_hint: strexisting_similar: List[str]
Description: Create a custom knowledge input (Q&A, text, or markdown)
Authentication: require_manage_permission
Request: CustomInputCreate
input_type: CustomInputType ("qa" | "text" | "markdown")content: str (min 10 chars)answer: Optional[str] (required for Q&A type)title: Optional[str] (max 200 chars)collection_id: Optional[str]start_processing: bool (default: true)
Response: CustomInputResponse
document_id: strfilename: strstatus: ProcessingStatusmessage: strinput_type: CustomInputType
Description: List all custom inputs with optional search
Authentication: None
Parameters:
search: Optional[str] (query param)limit: int (query param, default: 50, max: 200)
Response: {"custom_inputs": List[dict], "total": int}
Description: Get a custom input's full data for editing
Authentication: None
Response: Custom input dict with content, answer, input_type, topic_hint
Description: Perform hybrid search (vector + keyword + metadata)
Authentication: require_read_permission
Request: SearchRequest
query: strtop_k: int (default: 5, min: 1, max: 50)filters: Optional[dict]collection_id: Optional[str] — top-level scope; legacyfilters.collection_idis also supported (400 if both disagree)
Response: SearchResponse
query: strresults: List[SearchResult]total_results: inttotal: int — alias oftotal_results, the number of returned results
This endpoint uses fixed vector/keyword/metadata weights (0.5/0.3/0.2), RRF
fusion, and no cross-encoder reranking. ENABLE_HYBRID_SEARCH and the weight
environment variables govern the different Ask AI/context fusion, not this route.
Description: Ask a question using enhanced GraphRAG — non-streaming, recommended for quick chat. To retrieve knowledge from the Cortex, the recommended first call is a streaming Deep Research query on POST /api/ask/stream with use_agentic: true; use this endpoint for quick single-shot answers from callers that cannot consume SSE.
Authentication: require_read_permission
Request: RAGRequest
question: strtop_k: int (default: 5, min: 1, max: 20)use_graph: bool (default: true)max_hops: int (default: 2, min: 1, max: 3)conversation_history: Optional[List[ConversationMessage]]use_reranking: bool (default: true)use_agentic: bool (default: false) — withENABLE_AGENT_RESEARCH=true(default), returns400 {"detail": {"error": "agentic_requires_streaming", "use_endpoint": "/api/ask/stream", ...}}. With the flag false, the legacy_agentic_rag_queryruns within the same deadline. Its local unreleased forwarding repair carries effective scalar/multi/empty scope through chunk/community-access builders and the no-LLM-key recursive fallback. Use streaming Deep Research in either configuration.use_fast_search: bool (default: false)
Constraints:
- Bounded by a server-side wall-clock deadline (
ASK_DEADLINE_SECONDS, default 28s, kept just below the edge-proxy read timeout). On expiry the request returns504 {"error": "deadline_exceeded"}with guidance to retry, simplify, or switch to/api/ask/stream(which is not subject to this deadline). - Any other failure before an answer is produced returns
500 {"detail": {"error": "ask_failed", "message": ..., "use_endpoint": "/api/ask/stream"}}(plusexceptionoutside production). Both structured 5xx bodies survive production sanitization (dict details carrying anerrorcode are passed through; free-text details are still replaced by the generic message). Every 5xx carriesrequest_id.
Response: RAGResponse
question: stranswer: strsources: List[SearchResult]graph_context: Optional[GraphContext]reranked: boolreasoning_steps: Optional[List[str]]sub_questions: Optional[List[str]] — decomposed sub-questions (string list). Populated only on the flag-off legacy deep-research path (local unreleased projection; earlier inspected handler snapshots omitted the values — check version/capability). Nonempty and literal[]lists are preserved. Null on the standard chat path, the no-LLM-key fallback, and the input-screen refusals (constructed before retrieval); a model refusal runs the real helper, so the populated values are projected alongsiderefused: true/refusal_source: "model".communities_used: Optional[List[int]] — integer community IDs used during the legacy deep retrieval; same branch and null rules assub_questions(including the model-refusal projection).retrieval_stats: Optional[dict] — on the flag-off legacy deep path exactly four keys:total_sources_considered,unique_sources,sub_questions_researched,communities_referenced(the schema accepts{}, but the real helper always returns all four keys populated). Null on the standard chat path, the no-key fallback and input-screen refusals; projected populated on model refusals (seesub_questions). Distinct from the streaming endpoints' SSE retrieval statistics.structured: Optional[dict] — parsed JSON answer whenresponse_formatwas setfinish_reason: Optional[str] — provider finish reason of the answer completion (stop,length, …): the standard answer on the standard chat path, the synthesis completion on the flag-off legacy agentic path (local unreleased propagation; the decomposition call's reason is never used). Null when the provider reports none.truncated: bool —truewhenfinish_reason == "length", i.e. the answer hit the writer's output-token cap (1,200 tokens on the standard non-streaming chat path; 2,000 on the flag-off legacy agentic synthesis call)refused: bool —truewhenansweris the prompt-injection safe refusal rather than knowledgerefusal_source: Optional[str] — set whenrefused:heuristic(pattern validator),classifier(prompt-guard model — may be a false positive, rephrase), ormodel(the writer emitted the canned deflection itself). Both input gates run on this endpoint exactly as on/api/ask/stream; a refused ask is a normal200with emptysourcesandfinish_reason: "stop".
Description: Stream RAG response (Server-Sent Events) — the primary retrieval endpoint. "Ask the Cortex" / "find something in the Cortex" should start here with use_agentic: true (streaming Deep Research); the SSE heartbeats keep long agentic runs alive where the non-streaming endpoint would time out.
Authentication: require_read_permission
Request: RAGRequest (same as /api/ask)
Response: SSE stream with events:
content: Streamed answer tokenssources: Retrieved sources (at end)graph_context: Graph context (at end)thinking: Reasoning steps (if agentic mode)sub_questions: Decomposed questions (if agentic mode)retrieval: Source retrieval progressretrieval_stats: Final statisticsdone: Completion signal
Notes:
- When
use_agentic=true: Includes extended thinking visibility - When
use_fast_search=true: Uses simple vector search only (fastest) - Local unreleased legacy scope repairs (
ENABLE_AGENT_RESEARCH=false): both streaming endpoints (agentic_rag_stream) and non-streaming/api/ask(_agentic_rag_query) forward the effective scalar/allowlist to sub-question chunk queries and community selection/summary access. Only nonstream has the no-LLM-key recursive fallback, which also preserves scope. Shared full community summaries and global entity/relationship metadata remain broader limits; this is not complete graph-context privacy. - Local unreleased legacy streaming done-flag (
ENABLE_AGENT_RESEARCH=false): on both streaming endpoints, the legacy synthesis stream's providerfinish_reasonprojects onto the publicdoneframe astruncated—length→truncated: true;stop, a null reason, and a missing reason attribute never set the flag, and the decomposition call's reason is never used. Additive only — no new SSE field, no publicfinish_reasonon SSE — and this legacy path appends no "cut short" notice; its synthesis call carries its own 2,000-token output cap. - Local unreleased standard streaming done-flag (
ENABLE_AGENT_CHAT=false, the default): on the standard chat branch ofPOST /api/ask/stream(depth: "standard"or the equivalent agreeing legacy flags), the standard writer's providerfinish_reasonis captured from the synthesis stream before the text-only security filter and projects onto the publicdoneframe astruncated—length→truncated: true;stop, a null reason, and a missing reason attribute never set the flag. Additive only — no new SSE field, no publicfinish_reasonon SSE, no "cut short" notice; the writer cap isWRITER_MAX_TOKENS_SPEED(default 1200), distinct from the flag-off legacy agentic synthesis call's literal 2000 and the non-streaming chat path's literal 1200. - Local unreleased fast streaming done-flag (the fast branch of
POST /api/ask/stream, reached withdepth: "fast"oruse_fast_search: true— always its own branch, independent ofENABLE_AGENT_CHAT;/api/ask/stream/thinkinghas no fast branch): the fast writer's providerfinish_reasonis captured from the synthesis stream before the text-only security filter and projects onto the publicdoneframe astruncated—length→truncated: true;stop, a null reason, and a missing reason attribute never set the flag. Additive only — no new SSE field, no publicfinish_reasonon SSE, no "cut short" notice; the output cap is a literal 600 tokens — distinct fromWRITER_MAX_TOKENS_SPEEDand the other writers' caps — and the model is the Fast Mode model (OPENAI_MODEL_FAST_MODE, defaultOPENAI_MODEL), not theWRITER_MODELoverride.
Description: Stream RAG with extended thinking visibility
Authentication: require_read_permission
Request: RAGRequest
Response: SSE stream with thinking events (same as agentic mode in /api/ask/stream)
Description: Get knowledge graph data for visualization
Authentication: None
Parameters:
limit: int (query param, default: 100, max: 10000, 0 = all)include_neighbors: bool (query param, default: true)
Response: {"nodes": List[dict], "edges": List[dict], "stats": dict}
Description: Get entity and all relationships up to max_depth hops
Authentication: None
Parameters:
max_depth: int (query param, default: 2, min: 1, max: 3)limit: int (query param, default: 50, max: 200)
Response: {"entity": dict, "relationships": List[dict], "related_entities": List[dict]}
Description: Get subgraph containing specified entities
Authentication: None
Request: List[str] (entity names)
Parameters:
include_connections: bool (query param, default: true)
Response: {"nodes": List[dict], "edges": List[dict]}
Description: List entities in the knowledge graph
Authentication: None
Parameters:
entity_type: Optional[str] (query param, filter by type)limit: int (query param, default: 50, max: 200)
Response: {"entities": List[dict], "total": int}
Description: Get details about a specific entity
Authentication: None
Parameters:
max_hops: int (query param, default: 2, min: 1, max: 3)
Response: GraphContext with entities and relationships
Description: Search for entities by name
Authentication: None
Parameters:
query: str (query param, min length: 1)
Response: {"query": str, "results": List[dict]}
Description: Get GraphRAG system status
Authentication: None
Response: { "graph_extraction_enabled": bool, "llm_available": bool, "model": Optional[str], "entity_count": int, "relationship_count": int, "community_count": int, "collection_count": int, "community_detection_enabled": bool, "graph_summarization_enabled": bool, "semantic_entity_resolution_enabled": bool, "collections_enabled": bool }
Description: List all collections
Authentication: None
Response: {"collections": List[Collection], "total": int}
Description: Create a new collection
Authentication: require_manage_permission
Request: CollectionCreate
name: str (min: 1, max: 100)description: Optional[str] (max: 500)
Response: Collection
Description: Get a specific collection with stats
Authentication: None
Response: Collection
Description: Delete a collection (moves documents to default collection)
Authentication: require_manage_permission
Response: {"message": str, "documents_moved": int}
Notes: Cannot delete the "default" collection.
Description: Add a document to a collection
Authentication: None
Response: {"message": str}
Description: Move multiple documents to a collection
Authentication: None
Request: MoveDocumentsRequest
document_ids: List[str]target_collection_id: str
Response: {"message": str, "moved_count": int}
Description: Get entities in a collection's knowledge graph
Authentication: None
Parameters:
limit: int (query param, default: 100, max: 500)
Response: {"entities": List[dict], "total": int}
Description: List all detected communities
Authentication: None
Parameters:
limit: int (query param, default: 50, max: 200)
Response: {"communities": List[Community], "total": int}
Description: Start community detection as background task
Authentication: None
Parameters:
min_size: int (query param, default: 3, min: 2, max: 20)collection_id: Optional[str] (query param)
Response: {"task_id": str, "status": TaskStatus, "message": str}
Notes: Poll /api/tasks/{task_id} for progress.
Description: Get a specific community with entities and relationships
Authentication: None
Response: Community
Description: Generate or regenerate summaries for communities
Authentication: None
Request: CommunitySummaryRequest
community_ids: Optional[List[int]] (if None, summarizes all)force_regenerate: bool (default: false)
Response: {"results": List[dict], "total_processed": int}
Description: Search communities by summary content
Authentication: None
Parameters:
query: str (query param, min length: 1)limit: int (query param, default: 5, max: 20)
Response: {"query": str, "results": List[dict]}
Description: Get task status and progress
Authentication: None
Response: TaskProgress
Description: Get task result (returns 202 if still running)
Authentication: None
Response: Task result dict or 202 status
Description: List all active tasks
Authentication: None
Parameters:
status: Optional[str] (query param)task_type: Optional[str] (query param)
Response: {"tasks": List[TaskProgress], "total": int}
Description: Cancel/remove a task
Authentication: None
Response: {"message": str}
Description: Remove old completed/failed tasks
Authentication: None
Parameters:
max_age_hours: int (query param, default: 24, min: 1, max: 168)
Response: {"removed": int, "remaining": int}
Description: Clean up orphaned entities from knowledge graph
Authentication: None
Response: {"message": str, "orphaned_entities_removed": int}
Description: Raw chat completion on the instance's primary model — no retrieval, no prompt security, the caller owns the full prompt. Built for trusted first-party services (e.g. cortex-chat's personality generator) so operators keep one model configuration. Admin-gated precisely because prompt security is bypassed.
Authentication: require_admin
Request Body: LLMCompletionRequest — {messages: [{role: system|user|assistant, content}] (1–50), temperature? (0–2), max_tokens? (≤16000), stream: bool = true}
Reasoning: hidden chain-of-thought follows DEFAULT_REASONING_MODE exactly like /api/ask (default off — Venice disable_thinking, vLLM enable_thinking=False, OpenAI reasoning_effort, auto-fallback when a gateway rejects the params). Without this, thinking-by-default models (Qwen3.x) spend the whole max_tokens budget in a reasoning_content channel and the stream ends with no visible content.
Response: streaming (default) — OpenAI-compatible chat chunks as SSE data: frames terminated by data: [DONE], with heartbeat comments and a terminal event: shutdown on restart; errors stream as sanitized data: {"error": ...} frames. Non-streaming — {"content": str, "model": str}.
Quota/metering: gated by MAX_QUERIES_PER_MONTH (429 + Retry-After when exhausted); completions are unit-metered and Langfuse-traced via the client factory like every other completion. Subject to RATE_LIMIT_QPM.
Description: Get system configuration (read-only, no secrets exposed)
Authentication: require_admin
Response: SystemConfigResponse
Returns current system settings grouped into:
- LLM:
openai_model,openai_api_base,relationship_max_context,parallel_relationship_batches,relationship_target_ratio,relationship_max_rounds,relationship_max_hours - Extraction:
extraction_model,extraction_api_base,extraction_max_context,batch_processing_concurrency - Vision:
vision_model,vision_api_base,vision_max_concurrent,vision_model_available - Embeddings:
embedding_model,embedding_dimension,embedding_api_base,embedding_send_dimensions - Plus: chunking, search/RAG, graph, community detection, entity resolution, collections, features settings
Note: API keys, passwords, and secrets are never included in the response.
Description: List all API keys
Authentication: require_admin
Response: List[APIKeyListItem]
Description: Create a new API key
Authentication: require_admin
Request: CreateAPIKeyRequest
name: str (min: 1, max: 100)permissions: List[APIKeyPermission] (min: 1)
Response: CreateAPIKeyResponse (includes actual key - shown only once!)
Description: Get a specific API key by ID
Authentication: require_admin
Response: APIKeyListItem
Description: Update an API key
Authentication: require_admin
Request: UpdateAPIKeyRequest
name: Optional[str]permissions: Optional[List[APIKeyPermission]]is_active: Optional[bool]
Response: APIKeyListItem
Description: Delete an API key permanently
Authentication: require_admin
Response: {"message": str}
Description: Revoke an API key (deactivate)
Authentication: require_admin
Response: APIKeyListItem
Description: Reactivate a revoked API key
Authentication: require_admin
Response: APIKeyListItem
PENDINGPROCESSINGEXTRACTINGCOMPLETEDFAILED
filename: strfile_type: strfile_size: intfile_path: Optional[str]upload_date: datetimechunk_count: intprocessing_status: ProcessingStatuserror_message: Optional[str]progress_current: intprogress_total: intprogress_message: str
id: strdocument_id: strcontent: strembedding: Optional[List[float]]chunk_index: intmetadata: dict
id: strmetadata: DocumentMetadatachunks: List[DocumentChunk]
name: strtype: str (Person, Organization, Concept, Location, Event, Technology, etc.)description: str
source: strtarget: strrelationship_type: str (WORKS_FOR, LOCATED_IN, USES, RELATED_TO, etc. — 14 standard types; MENTIONS removed)description: strweight: float (0-10)confidence: float (0.0-1.0, relationships below 0.5 are filtered before storage)
entities: List[Entity]relationships: List[Relationship]
entities: List[dict]relationships: List[dict]chunks: List[dict]communities: List[dict]
id: strname: strdescription: Optional[str]created_at: datetimedocument_count: intentity_count: int
name: str (min: 1, max: 100)description: Optional[str] (max: 500)
name: Optional[str] (min: 1, max: 100)description: Optional[str] (max: 500)
id: intname: Optional[str]summary: Optional[str]entity_count: intentities: List[dict]key_relationships: List[dict]collection_id: Optional[str]
community_ids: Optional[List[int]]force_regenerate: bool
query: strtop_k: int (default: 5, min: 1, max: 50)filters: Optional[dict]
document_id: strchunk_id: strcontent: strscore: floatmetadata: dict
query: strresults: List[SearchResult]total_results: int
role: str ("user" | "assistant")content: str
question: strtop_k: int (default: 5, min: 1, max: 20)use_graph: bool (default: true)max_hops: int (default: 2, min: 1, max: 3)conversation_history: Optional[List[ConversationMessage]]use_reranking: bool (default: true)use_agentic: bool (default: false)use_fast_search: bool (default: false)
question: stranswer: strsources: List[SearchResult]graph_context: Optional[GraphContext]reranked: boolreasoning_steps: Optional[List[str]]sub_questions: Optional[List[str]] — legacy deep-research projection (seePOST /api/ask)communities_used: Optional[List[int]] — legacy deep-research projection (seePOST /api/ask)retrieval_stats: Optional[dict] — legacy deep-research projection, four fixed keys (seePOST /api/ask)collection_id: Optional[str]structured: Optional[dict]finish_reason: Optional[str]truncated: bool (default: false)refused: bool (default: false)refusal_source: Optional[str] —heuristic|classifier|model, set whenrefused(on the streamingcontentanddonerefusal frames as well)
QATEXTMARKDOWN
input_type: CustomInputTypecontent: str (min: 10)answer: Optional[str]title: Optional[str] (max: 200)collection_id: Optional[str]start_processing: bool (default: true)
document_id: strfilename: strstatus: ProcessingStatusmessage: strinput_type: CustomInputType
PENDINGRUNNINGCOMPLETEDFAILED
task_id: strtask_type: strstatus: TaskStatusprogress_current: intprogress_total: intprogress_percent: floatmessage: strstarted_at: Optional[datetime]completed_at: Optional[datetime]error: Optional[str]result: Optional[dict]
READ- Can use Ask AI, search, view graphsMANAGE- Can upload, edit, delete documents and collections
id: strname: strkey_prefix: strkey_hash: strpermissions: List[APIKeyPermission]is_active: boolcreated_at: datetimelast_used_at: Optional[datetime]created_by: str
name: str (min: 1, max: 100)permissions: List[APIKeyPermission] (min: 1)
id: strname: strkey: str (actual API key - shown only once!)key_prefix: strpermissions: List[APIKeyPermission]created_at: datetime
id: strname: strkey_prefix: strpermissions: List[APIKeyPermission]is_active: boolcreated_at: datetimelast_used_at: Optional[datetime]created_by: str
name: Optional[str] (min: 1, max: 100)permissions: Optional[List[APIKeyPermission]]is_active: Optional[bool]
document_id: strfilename: strstatus: ProcessingStatusmessage: str
document_ids: List[str]
document_ids: List[str]
document_ids: List[str]target_collection_id: str
status: strneo4j_connected: boolversion: str
document_count: intchunk_count: intentity_count: intrelationship_count: inttotal_size: intcommunity_count: intcollection_count: intpending_count: intentity_relationship_ratio: floatrelationship_target_ratio: float
Location: app/services/document_processor.py
Key Methods:
process_file(file_path, filename, file_size, collection_id) -> str: Process a file and store itstore_file_only(file_path, filename, file_size, collection_id) -> str: Store file without processingget_pending_documents() -> List[dict]: Get all pending documentsprocess_pending_documents(concurrency, progress_callback) -> dict: Process pending documents with concurrency controlqueue_document_for_reprocessing(doc_id) -> bool: Queue document for reprocessingreprocess_document(doc_id) -> bool: Reprocess a document immediatelyreprocess_document_from_file(doc_id, file_path, file_type) -> bool: Reprocess from file
Features:
- URL protection during chunking (prevents URLs from being split)
- Sentence-based or word-based chunking
- OpenAI or SentenceTransformers embeddings
- Graph extraction with concurrent processing
- Document summary generation for extraction context
Location: app/services/document_processor.py
Key Methods:
search(query, top_k, filters) -> List[dict]: Semantic vector searchhybrid_search(query, top_k, vector_weight, keyword_weight, metadata_weight) -> List[dict]: Hybrid search with RRFgraph_search_async(query, top_k, max_hops, use_hybrid_rrf, collection_id, allowed_collection_ids) -> dict: Graph-enhanced searchrerank_results(query, results, top_k) -> List[dict]: Cross-encoder rerankingrag_query(question, top_k, use_graph, max_hops, conversation_history, use_reranking, use_agentic) -> dict: RAG queryagentic_rag_stream(question, top_k, max_hops, conversation_history, collection_id) -> AsyncGenerator: Streaming agentic RAG (legacy)agent_rag_stream(question, mode, conversation_history, collection_id) -> AsyncGenerator: Agent-based research pipeline (researcher/writer)agent_rag_query(question, mode, conversation_history, collection_id) -> dict: Non-streaming agent RAG query
Features:
- Hybrid search combining vector, keyword, and graph traversal (RRF)
- Cross-encoder reranking for precision
- Conversation memory support
- Agent-based research pipeline with LLM-driven tool-calling (researcher/writer architecture)
- Community-aware retrieval via
community_searchtool - Entity exploration via
entity_lookuptool - Reasoning transparency via
reasoningtool (quality mode)
Location: app/services/graph_extractor.py
Key Methods:
extract_from_text(text, document_summary, entity_types, relation_types) -> ExtractionResult: Extract entities and relationshipsextract_from_text_async(...) -> ExtractionResult: Async version for concurrent processingextract_entities_from_query(query) -> List[str]: Extract entities from user queryextract_entities_from_query_async(query) -> List[str]: Async versiongenerate_document_summary(document) -> str: Generate document summarygenerate_document_summary_async(document) -> str: Async versiongenerate_community_summary(entities, relationships) -> dict: Generate community summarygenerate_community_summary_async(entities, relationships) -> dict: Async versiongenerate_entity_embedding(entity_name, entity_type, description) -> Optional[List[float]]: Generate entity embedding
Features:
- XML-formatted prompts for better parsing
- Entity and relationship extraction with weights
- Document summary generation for context
- Community summarization
- Entity embedding generation for semantic resolution
Location: app/services/neo4j_service.py
Key Methods:
initialize_schema(): Create indexes and constraintsstore_document(doc_id, metadata) -> str: Store document nodestore_chunk(chunk) -> str: Store chunk with embeddingvector_search(query_embedding, top_k, filters) -> List[dict]: Vector similarity searchhybrid_search_rrf(query_embedding, query_text, entity_names, top_k, max_hops, vector_weight, keyword_weight, graph_weight) -> dict: Hybrid search with RRFstore_graph_extraction(chunk_id, extraction) -> dict: Store entities and relationshipstraverse_from_entities(entity_names, max_hops) -> dict: Graph traversaldetect_communities(min_size, collection_id) -> List[dict]: Community detectionstore_community(community_id, entity_names, summary, name): Store communitysearch_communities_by_content(query, limit) -> List[dict]: Search communitiescreate_collection(name, description) -> Optional[Collection]: Create collectionlist_collections() -> List[Collection]: List collectionsadd_document_to_collection(doc_id, collection_id) -> bool: Add document to collectiondelete_document(doc_id) -> dict: Delete document and cleanupcleanup_orphaned_entities() -> int: Cleanup orphaned entities
Legacy Neo4jService.hybrid_search (vector + traversal when hybrid RRF is
disabled) accepts optional collection_id/allowed_collection_ids after
max_hops and forwards them to both chunk-query builders. Graph entity and
relationship metadata remains global in the legacy and RRF traversal paths.
Features:
- Vector indexes for embeddings
- Full-text indexes for content search
- Community detection using graph algorithms
- Collection-level knowledge graphs
- Semantic entity resolution with embeddings
- Graph visualization data generation
Location: app/services/auth_service.py
Key Functions:
validate_api_key(api_key) -> AuthResult: Validate API keyhash_api_key(api_key) -> str: Hash API key for storageverify_api_key_hash(api_key, stored_hash) -> bool: Verify API key hashgenerate_api_key(prefix) -> Tuple[str, str]: Generate new API key
Dependencies (FastAPI):
require_api_key: Requires any valid API keyrequire_read_permission: Requires READ permissionrequire_manage_permission: Requires MANAGE permissionrequire_admin: Requires admin API key
AuthResult:
is_authenticated: boolis_admin: boolpermissions: List[APIKeyPermission]key_id: Optional[str]error: Optional[str]has_permission(permission) -> bool: Check permission
Location: app/services/api_key_service.py
Key Methods:
create_api_key(name, permissions, created_by) -> Optional[CreateAPIKeyResponse]: Create API keylist_api_keys() -> List[APIKeyListItem]: List all API keysget_api_key(key_id) -> Optional[APIKeyListItem]: Get API key by IDupdate_api_key(key_id, request) -> Optional[APIKeyListItem]: Update API keydelete_api_key(key_id) -> bool: Delete API keyrevoke_api_key(key_id) -> Optional[APIKeyListItem]: Revoke API keyactivate_api_key(key_id) -> Optional[APIKeyListItem]: Activate API key
Location: app/services/prompt_security.py
Key Functions:
detect_injection_attempt(user_input) -> Tuple[bool, Optional[str]]: Detect prompt injectionsanitize_user_input(user_input) -> str: Sanitize user inputfilter_output(response, system_prompt) -> str: Filter output for prompt leakageget_anti_injection_instruction(enabled) -> str: Get anti-injection instruction for promptsget_safe_refusal_message() -> str: Get safe refusal messagevalidate_and_process_input(user_input, strict_mode, enabled) -> Tuple[str, bool, Optional[str]]: Main validation entry point
Features:
- Pattern-based injection detection
- Character replacement attack detection
- Output filtering for prompt leakage
- Configurable strict/sanitize modes
The API uses API Key authentication via the X-API-Key header.
-
Admin API Key (from environment):
- Set via
ADMIN_API_KEYenvironment variable - Has full access (READ + MANAGE permissions)
- Checked first
- Set via
-
Generated API Keys (stored in Neo4j):
- Created via
/api/admin/api-keysendpoint - Stored with SHA-256 hash
- Has assigned permissions (READ or MANAGE)
- Validated against stored hash
- Created via
- READ: Can use Ask AI, search, view graphs
- MANAGE: Can upload, edit, delete documents and collections
- ADMIN: Full access (only admin API key)
- Read-only keys:
cortex_ro_<64 hex chars> - Read-write keys:
cortex_rw_<64 hex chars> - Prefix (first 12 chars) used for lookup
curl -H "X-API-Key: cortex_ro_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
https://api.example.com/api/ask- API keys are hashed with SHA-256 before storage
- Constant-time comparison for hash verification
- Last used timestamp tracking
- Revocation support (deactivate without deletion)
- Admin-only key management endpoints
Location: app/config.py
NEO4J_URI: Neo4j connection URI (default:bolt://localhost:7687)NEO4J_USER: Neo4j username (default:neo4j)NEO4J_PASSWORD: Neo4j password (default:password123)
OPENAI_API_KEY: API key for LLM callsOPENAI_API_BASE: API base URL (default:https://api.openai.com/v1)OPENAI_MODEL: Model for LLM calls (default:qwen3-6-35b-a3b)OPENAI_MODEL_FAST_MODE: Model for Fast Mode (defaults toOPENAI_MODEL)
UPLOAD_DIR: Upload directory (default:./uploads)CUSTOM_INPUTS_DIR: Custom inputs directory (default:./custom_inputs)MAX_FILE_SIZE_MB: Maximum file size in MB (default:50)ALLOWED_EXTENSIONS: List of allowed extensions (default:[".pdf", ".txt", ".md", ".docx", ".xlsx"])
EMBEDDING_MODEL: Embedding model (default:text-embedding-3-small)EMBEDDING_DIMENSION: Embedding dimension (default:1536)USE_OPENAI_EMBEDDINGS: Use OpenAI embeddings (default:True)
CHUNK_SIZE: Chunk size in words (default:500)CHUNK_OVERLAP: Chunk overlap in words (default:50)CHUNK_BY: Chunking method - "word" or "sentence" (default:"sentence")SENTENCES_PER_CHUNK: Sentences per chunk when using sentence splitting (default:5)
ENABLE_GRAPH_EXTRACTION: Enable LLM-based extraction (default:True)GRAPH_EXTRACTION_MODEL: Model for extraction (defaults toOPENAI_MODEL)MAX_GRAPH_HOPS: Maximum hops for graph traversal (default:2)CONCURRENT_EXTRACTIONS: Concurrent chunks for extraction (default:3)
BATCH_PROCESSING_CONCURRENCY: Concurrent documents in batch mode (default:2)PROCESSING_THREAD_WORKERS: Thread pool workers for CPU operations (default:4)
ENABLE_RERANKING: Enable cross-encoder reranking (default:True)RERANKING_MODEL: Cross-encoder model (default:cross-encoder/ms-marco-MiniLM-L-6-v2)ENABLE_HYBRID_SEARCH: Enable hybrid search (default:True)VECTOR_WEIGHT: Weight for vector search (default:0.5)KEYWORD_WEIGHT: Weight for keyword search (default:0.3)GRAPH_WEIGHT: Weight for graph context (default:0.2)MAX_CONVERSATION_HISTORY: Max messages in conversation (default:6)ENABLE_AGENTIC_RAG: Enable multi-step agentic RAG (default:True)MAX_AGENTIC_STEPS: Maximum steps in agentic RAG — legacy (default:3)
ENABLE_AGENT_RESEARCH: Use agent pipeline for deep research mode (default:True)ENABLE_AGENT_CHAT: Use agent pipeline for standard chat mode (default:True)RESEARCHER_MAX_ITERATIONS_SPEED: Max agent iterations for chat (default:3)RESEARCHER_MAX_ITERATIONS_QUALITY: Max agent iterations for deep research (default:8)WRITER_MAX_TOKENS_SPEED: Max output tokens for chat answers (default:1200)WRITER_MAX_TOKENS_QUALITY: Max output tokens for deep research answers (default:8000)
PARALLEL_RELATIONSHIP_BATCHES: Batches to process in parallel, 0 = auto (default:0)RELATIONSHIP_TARGET_RATIO: Target entity-to-relationship ratio (default:1.0)RELATIONSHIP_MAX_ROUNDS: Maximum analysis rounds (default:3)RELATIONSHIP_MAX_HOURS: Maximum hours for relationship analysis (default: not set)RELATIONSHIP_MAX_PER_ENTITY: Soft cap on relationships per entity (default:50, 0 = no cap)RELATIONSHIP_MAX_OUTPUT_TOKENS: Max output tokens per relationship batch (default:16000)
ENABLE_COMMUNITY_DETECTION: Enable community detection (default:True)MIN_COMMUNITY_SIZE: Minimum entities for community (default:3)MAX_COMMUNITIES: Maximum communities to track (default:50)ENABLE_GRAPH_SUMMARIZATION: Generate LLM summaries (default:True)COMMUNITY_SUMMARY_MODEL: Model for summaries (defaults toOPENAI_MODEL)
ENABLE_SEMANTIC_ENTITY_RESOLUTION: Use embeddings for entity matching (default:True)ENTITY_SIMILARITY_THRESHOLD: Threshold for deduplication (default:0.85)ENTITY_EMBEDDING_MODEL: Model for entity embeddings (defaults toEMBEDDING_MODEL)
ENABLE_COLLECTIONS: Enable collections (default:True)DEFAULT_COLLECTION: Default collection name (default:"default")
STREAM_REASONING_STEPS: Stream reasoning steps (default:True)SHOW_RETRIEVAL_STATS: Show retrieval statistics (default:True)
PROMPT_SECURITY: Enable prompt injection protection (default:True)
ADMIN_EMAIL: Admin login email (default:admin@example.com)ADMIN_PASSWORD: Admin login password (required for auth)ADMIN_API_KEY: Admin API key for full backend accessSESSION_SECRET: Secret for JWT session encryption (min 32 chars)
Configuration is loaded from:
- Environment variables (uppercase, e.g.,
NEO4J_URI) .envfile (checked in multiple locations)- Default values (as shown above)
fast_mode_model: Get model for Fast Modeextraction_model: Get model for graph extractionsummary_model: Get model for community summarizationentity_embed_model: Get model for entity embeddings
- Entity Extraction: Extracts entities (Person, Organization, Technology, etc.) from documents
- Relationship Extraction: Extracts relationships between entities with weights
- Community Detection: Detects communities of related entities using graph algorithms
- Community Summarization: Generates LLM summaries for communities
- Semantic Entity Resolution: Uses embeddings to deduplicate similar entities
- Collection-Level Graphs: Separate knowledge graphs per collection
- Hybrid Search: Combines vector similarity, keyword matching, and metadata search
- Reciprocal Rank Fusion (RRF): Merges results from multiple search methods
- Cross-Encoder Reranking: Improves precision with cross-encoder models
- Graph-Enhanced Retrieval: Uses knowledge graph for context-aware search
- Agentic RAG: Multi-step reasoning for complex questions
- Batch Processing: Process multiple documents with controlled concurrency
- Progress Tracking: Real-time progress updates for document processing
- Background Tasks: Long-running operations (community detection, batch processing)
- URL Protection: Prevents URLs from being split during chunking
- Permanent File Storage: Original files kept for reprocessing
All endpoints return standard HTTP status codes:
200: Success400: Bad Request (validation errors)401: Unauthorized (invalid/missing API key)403: Forbidden (insufficient permissions)404: Not Found500: Internal Server Error
Error responses include a detail field with the error message.
Currently, there is no rate limiting implemented. Consider implementing rate limiting for production deployments.
CORS is enabled for all origins (allow_origins=["*"]). Configure appropriately for production.
API Version: 2.0.0
Generated from backend codebase analysis