MicroFish/.kiro/specs/graphiti-neo4j-finalize/requirements.md

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:

  1. docker-compose.yml does not include Neo4j, so docker compose up -d from a clean checkout cannot bring up the stack as advertised in the README.
  2. 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 and depends_on.
    • Making the Graphiti LLM client and embedder configurable (OpenAI-compatible vs Gemini).
    • Decoupling embedding credentials from chat LLM credentials.
    • Refreshing .env.example to 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 dev path still works against a host-installed Neo4j.
  • 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).
  • 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

  1. The docker-compose.yml shall declare a service named neo4j using image neo4j:5-community.
  2. The neo4j service shall expose ports 7474 (HTTP browser) and 7687 (Bolt) to the host.
  3. The neo4j service shall authenticate with neo4j/${NEO4J_PASSWORD:-mirofish123} sourced from the project env file.
  4. The neo4j service shall mount named Docker volumes for /data and /logs so graph state persists across container restarts.
  5. The neo4j service shall declare a healthcheck that succeeds only when Bolt is ready (e.g. via cypher-shell).
  6. The mirofish application service shall declare depends_on: { neo4j: { condition: service_healthy } } so the app starts only after Neo4j is ready.
  7. While running inside the Docker network, the mirofish service shall use NEO4J_URI=bolt://neo4j:7687 (overriding the host-mode default of bolt://localhost:7687).
  8. The docker-compose.yml shall not include the obsolete top-level version: key (Compose v2 syntax).
  9. When docker compose up -d is run on a clean checkout, the system shall start both services and POST /api/graph/build shall 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

  1. When NEO4J_URI is unset, the application shall default to bolt://localhost:7687.
  2. When npm run dev is 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.
  3. The Docker-only NEO4J_URI=bolt://neo4j:7687 override 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

  1. The system shall read a configuration value Config.GRAPHITI_LLM_PROVIDER with allowed values openai and gemini.
  2. When GRAPHITI_LLM_PROVIDER is unset, the system shall default to openai.
  3. When GRAPHITI_LLM_PROVIDER=openai, the Graphiti adapter shall instantiate an OpenAI-compatible LLM client using LLM_API_KEY, LLM_BASE_URL, and LLM_MODEL_NAME (the same triple consumed by LLMClient).
  4. When GRAPHITI_LLM_PROVIDER=gemini, the Graphiti adapter shall instantiate GeminiClient with the existing configuration (preserving today's behaviour).
  5. If GRAPHITI_LLM_PROVIDER is 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.
  6. When the provider is openai and LLM_API_KEY is a Qwen/Dashscope key, the system shall complete a graph build for a small .txt document 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

  1. When GRAPHITI_LLM_PROVIDER=openai, the Graphiti adapter shall instantiate an OpenAI-compatible embedder using the embedding-specific credentials (see Requirement 5) and EMBEDDING_MODEL.
  2. When GRAPHITI_LLM_PROVIDER=gemini, the Graphiti adapter shall instantiate GeminiEmbedder using LLM_API_KEY and EMBEDDING_MODEL (preserving today's behaviour).
  3. 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

  1. The system shall read optional configuration values EMBEDDING_API_KEY and EMBEDDING_BASE_URL.
  2. When EMBEDDING_API_KEY is unset, the system shall fall back to LLM_API_KEY.
  3. When EMBEDDING_BASE_URL is unset, the system shall fall back to LLM_BASE_URL.
  4. When EMBEDDING_API_KEY is set, the embedder shall use the embedding key for embedding calls and the chat LLM key shall be untouched for chat calls.
  5. The embedder shall use EMBEDDING_MODEL for the model name independently of LLM_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

  1. The .env.example file shall include NEO4J_URI, NEO4J_USER, and NEO4J_PASSWORD with sensible defaults matching backend/app/config.py.
  2. The .env.example file shall include EMBEDDING_MODEL with a default consistent with Config.EMBEDDING_MODEL.
  3. The .env.example file shall include the optional GRAPHITI_LLM_PROVIDER, EMBEDDING_API_KEY, and EMBEDDING_BASE_URL keys with comments explaining their fallback behaviour.
  4. The .env.example file 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.
  5. The .env.example file shall either drop ZEP_API_KEY entirely or keep it as a single commented line marked deprecated for users with old setups.
  6. The .env.example file shall not contain any real secret values.
  7. Where .env.example lists 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

  1. The _GeminiReranker no-op stub shall be removed from backend/app/services/graphiti_adapter.py.
  2. The _GraphNamespace.search method shall not accept a reranker keyword argument it silently ignores; the parameter shall either be removed or honoured.
  3. The ZepToolsService.search_graph call site in backend/app/services/zep_tools.py shall not pass reranker="cross_encoder" if the adapter cannot honour it.
  4. 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

  1. When GRAPHITI_LLM_PROVIDER=gemini and the existing LLM_API_KEY/EMBEDDING_MODEL are unchanged, the Graphiti adapter shall behave identically to the pre-change implementation for graph build, search, and report.
  2. When the env file does not declare GRAPHITI_LLM_PROVIDER, the system shall pick openai (matching the documented default provider) and shall not silently switch existing Gemini deployments.
  3. 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

  1. When a fresh checkout is configured with a Qwen LLM_API_KEY, an OpenAI EMBEDDING_API_KEY, and GRAPHITI_LLM_PROVIDER=openai (default), uploading a small .txt and calling /api/graph/build shall complete successfully.
  2. After a successful graph build, querying the graph data endpoint shall return a non-zero count of nodes and edges.
  3. After a successful graph build, generating a report with InsightForge/Panorama shall return non-empty results.
  4. If the smoke test fails, the system shall surface the underlying provider error (not a 500) so the operator can correct configuration.