12 KiB
Requirements Document
Project Description (Input)
Complete Zep → Neo4j/Graphiti migration: add Neo4j to docker-compose.yml (Part 1) and fix the data-processing pipeline so the documented default LLM provider (Qwen via Dashscope) works end-to-end (Part 2). Tracked by GitHub issue #1.
Introduction
Commit 6264828 replaced Zep Cloud with Graphiti + Neo4j as the knowledge-graph backend, but the migration left two functional gaps that block first-time setup and the documented default LLM provider:
docker-compose.ymldoes not include Neo4j, sodocker compose up -dfrom a clean checkout cannot bring up the stack as advertised in the README.- Graphiti is hard-wired to Gemini for both LLM and embeddings, so the documented default provider (Qwen via Dashscope) — which is OpenAI-SDK-compatible — fails Step 1 of the pipeline (Graph Build) with a 401.
Adjacent issues block the same path: the env example file is stale (still references ZEP_API_KEY, missing Neo4j and embedding vars), the embedder credentials are coupled to the chat LLM credentials, and a no-op reranker stub silently degrades search quality while the call site at backend/app/services/zep_tools.py:504 requests a cross_encoder reranker that the adapter accepts and ignores.
This spec finalises the migration so a fresh checkout with a Qwen LLM_API_KEY works end-to-end via Docker, without regressing the existing Gemini path.
Boundary Context
-
In scope:
- Adding a Neo4j service to
docker-compose.yml, wired with healthcheck anddepends_on. - Making the Graphiti LLM client and embedder configurable (OpenAI-compatible vs Gemini).
- Decoupling embedding credentials from chat LLM credentials.
- Refreshing
.env.exampleto mirror the README and the code's actual env reads. - Cleaning up the reranker situation (no-op stub + ignored kwarg + misleading caller).
- Verifying the host-mode
npm run devpath still works against a host-installed Neo4j.
- Adding a Neo4j service to
-
Out of scope:
- Renaming
zep_*files (legacy prefix) — tracked separately. - Migrating data from existing Zep deployments.
- Frontend changes.
- Adding a real cross-encoder reranker implementation (we choose to remove rather than reimplement; future ticket may add one).
- Pagination cleanup of
_NodeNamespace.get_by_graph_id/_EdgeNamespace.get_by_graph_id(low priority, deferred).
- Renaming
-
Adjacent expectations:
_recover_stuck_projects(backend/app/__init__.py) talks to Neo4j at startup and must continue to function once Neo4j is reachable inside Docker.- All Graphiti reads/writes remain scoped by per-project
group_id(no change to isolation semantics). - The single-source-of-truth config remains
backend/app/config.py; new knobs are added there, not scattered.
Requirements
Requirement 1: Dockerised Neo4j Service
Objective: As a new contributor, I want docker compose up -d from a clean checkout to bring up Neo4j alongside the application, so that I can follow the README's "Quick Deploy via Docker" path without installing Neo4j manually.
Acceptance Criteria
- The
docker-compose.ymlshall declare a service namedneo4jusing imageneo4j:5-community. - The
neo4jservice shall expose ports7474(HTTP browser) and7687(Bolt) to the host. - The
neo4jservice shall authenticate withneo4j/${NEO4J_PASSWORD:-mirofish123}sourced from the project env file. - The
neo4jservice shall mount named Docker volumes for/dataand/logsso graph state persists across container restarts. - The
neo4jservice shall declare a healthcheck that succeeds only when Bolt is ready (e.g. viacypher-shell). - The
mirofishapplication service shall declaredepends_on: { neo4j: { condition: service_healthy } }so the app starts only after Neo4j is ready. - While running inside the Docker network, the
mirofishservice shall useNEO4J_URI=bolt://neo4j:7687(overriding the host-mode default ofbolt://localhost:7687). - The
docker-compose.ymlshall not include the obsolete top-levelversion:key (Compose v2 syntax). - When
docker compose up -dis run on a clean checkout, the system shall start both services andPOST /api/graph/buildshall succeed end-to-end against the in-stack Neo4j.
Requirement 2: Host-Mode Compatibility (No Regression)
Objective: As a developer running npm run dev against a host-installed Neo4j, I want my workflow to keep working unchanged, so that the Docker addition does not regress the host-mode dev loop.
Acceptance Criteria
- When
NEO4J_URIis unset, the application shall default tobolt://localhost:7687. - When
npm run devis run with a host-installed Neo4j on the default port, the application shall connect successfully without any new configuration steps compared to before this change. - The Docker-only
NEO4J_URI=bolt://neo4j:7687override shall not appear in any non-Docker code path.
Requirement 3: Configurable Graphiti LLM Provider
Objective: As an operator using the documented default LLM provider (Qwen via Dashscope), I want Graphiti to use the same OpenAI-SDK-compatible endpoint as the rest of the app, so that Step 1 of the pipeline succeeds without my key being sent to Google.
Acceptance Criteria
- The system shall read a configuration value
Config.GRAPHITI_LLM_PROVIDERwith allowed valuesopenaiandgemini. - When
GRAPHITI_LLM_PROVIDERis unset, the system shall default toopenai. - When
GRAPHITI_LLM_PROVIDER=openai, the Graphiti adapter shall instantiate an OpenAI-compatible LLM client usingLLM_API_KEY,LLM_BASE_URL, andLLM_MODEL_NAME(the same triple consumed byLLMClient). - When
GRAPHITI_LLM_PROVIDER=gemini, the Graphiti adapter shall instantiateGeminiClientwith the existing configuration (preserving today's behaviour). - If
GRAPHITI_LLM_PROVIDERis set to an unrecognised value, the Graphiti adapter shall raise a configuration error at startup with a message naming the offending value and the allowed set. - When the provider is
openaiandLLM_API_KEYis a Qwen/Dashscope key, the system shall complete a graph build for a small.txtdocument end-to-end without hitting Gemini endpoints.
Requirement 4: Configurable Graphiti Embedder
Objective: As an operator, I want the embedder to follow the same provider switch as the LLM client, so that I can run a fully OpenAI-compatible stack or a fully Gemini stack without code edits.
Acceptance Criteria
- When
GRAPHITI_LLM_PROVIDER=openai, the Graphiti adapter shall instantiate an OpenAI-compatible embedder using the embedding-specific credentials (see Requirement 5) andEMBEDDING_MODEL. - When
GRAPHITI_LLM_PROVIDER=gemini, the Graphiti adapter shall instantiateGeminiEmbedderusingLLM_API_KEYandEMBEDDING_MODEL(preserving today's behaviour). - The Graphiti adapter shall not import provider-specific embedder classes that are unused at runtime for the selected provider (lazy import or guarded selection).
Requirement 5: Decoupled Embedding Credentials
Objective: As an operator running Qwen for chat (which does not expose text-embedding-3-small), I want to point the embedder at a separate provider/key, so that embeddings work without forcing me to use a single provider for everything.
Acceptance Criteria
- The system shall read optional configuration values
EMBEDDING_API_KEYandEMBEDDING_BASE_URL. - When
EMBEDDING_API_KEYis unset, the system shall fall back toLLM_API_KEY. - When
EMBEDDING_BASE_URLis unset, the system shall fall back toLLM_BASE_URL. - When
EMBEDDING_API_KEYis set, the embedder shall use the embedding key for embedding calls and the chat LLM key shall be untouched for chat calls. - The embedder shall use
EMBEDDING_MODELfor the model name independently ofLLM_MODEL_NAME.
Requirement 6: Refreshed Env Example
Objective: As a new contributor copying .env.example to .env, I want the example to reflect what the code actually reads, so that following the README produces a working configuration.
Acceptance Criteria
- The
.env.examplefile shall includeNEO4J_URI,NEO4J_USER, andNEO4J_PASSWORDwith sensible defaults matchingbackend/app/config.py. - The
.env.examplefile shall includeEMBEDDING_MODELwith a default consistent withConfig.EMBEDDING_MODEL. - The
.env.examplefile shall include the optionalGRAPHITI_LLM_PROVIDER,EMBEDDING_API_KEY, andEMBEDDING_BASE_URLkeys with comments explaining their fallback behaviour. - The
.env.examplefile shall annotate that Dashscope/Qwen does not expose OpenAI-compatible embeddings and shall recommend pointing the embedder at OpenAI directly when chat is Dashscope/Qwen. - The
.env.examplefile shall either dropZEP_API_KEYentirely or keep it as a single commented line marked deprecated for users with old setups. - The
.env.examplefile shall not contain any real secret values. - Where
.env.examplelists an env var, the README's environment-variable section shall list the same var (no drift between the two surfaces).
Requirement 7: Reranker Cleanup
Objective: As a developer reading the search code path, I want the reranker situation to be honest, so that I am not misled into believing search results are reranked when they are not.
Acceptance Criteria
- The
_GeminiRerankerno-op stub shall be removed frombackend/app/services/graphiti_adapter.py. - The
_GraphNamespace.searchmethod shall not accept arerankerkeyword argument it silently ignores; the parameter shall either be removed or honoured. - The
ZepToolsService.search_graphcall site inbackend/app/services/zep_tools.pyshall not passreranker="cross_encoder"if the adapter cannot honour it. - After cleanup, every search code path (
InsightForge,PanoramaSearch,QuickSearch, the report-agent tools) shall return Graphiti's default-ranked results without the misleading no-op layer.
Requirement 8: Provider Backwards Compatibility
Objective: As an existing operator running with Gemini, I want my deployment to keep working without changes after this migration is finalised, so that I am not forced to migrate providers.
Acceptance Criteria
- When
GRAPHITI_LLM_PROVIDER=geminiand the existingLLM_API_KEY/EMBEDDING_MODELare unchanged, the Graphiti adapter shall behave identically to the pre-change implementation for graph build, search, and report. - When the env file does not declare
GRAPHITI_LLM_PROVIDER, the system shall pickopenai(matching the documented default provider) and shall not silently switch existing Gemini deployments. - The migration shall not remove any env var an existing Gemini deployment relies on (
LLM_API_KEY,LLM_BASE_URL,LLM_MODEL_NAME,EMBEDDING_MODEL,NEO4J_*).
Requirement 9: End-to-End Acceptance via Qwen
Objective: As a reviewer of this migration, I want a smoke test that a fresh checkout with the documented default provider can complete the pipeline, so that I have confidence the fix actually unblocks the README path.
Acceptance Criteria
- When a fresh checkout is configured with a Qwen
LLM_API_KEY, an OpenAIEMBEDDING_API_KEY, andGRAPHITI_LLM_PROVIDER=openai(default), uploading a small.txtand calling/api/graph/buildshall complete successfully. - After a successful graph build, querying the graph data endpoint shall return a non-zero count of nodes and edges.
- After a successful graph build, generating a report with
InsightForge/Panoramashall return non-empty results. - If the smoke test fails, the system shall surface the underlying provider error (not a 500) so the operator can correct configuration.