10 KiB
Implementation Plan — graphiti-neo4j-finalize
Two-phase ordering: Foundation tasks (Config, Compose, env example) unblock the core adapter rewrite. Core tasks rewrite the Graphiti adapter and clean up the misleading reranker kwarg in callers. Validation closes the loop with a structural review and a manual smoke test.
1. Foundation — runtime configuration and infrastructure wiring
-
1.1 (P) Extend the central configuration module with the new provider switch and decoupled embedder credentials
- Add a
GRAPHITI_LLM_PROVIDERconfiguration knob with allowed valuesopenaiandgemini, defaulting toopenaiwhen the environment variable is unset. - Add optional
EMBEDDING_API_KEYandEMBEDDING_BASE_URLfields that fall back to the existing chat-LLM credentials when unset. - Preserve every existing
Configattribute exactly (no removals, no renames); existing Gemini deployments must keep reading the same env vars. - Observable completion: importing the configuration module exposes the three new attributes with documented defaults, and existing attributes report identical values to before.
- Requirements: 3.1, 3.2, 5.1, 5.2, 5.3, 5.5, 8.3
- Boundary: Config
- Add a
-
1.2 (P) Add a healthchecked Neo4j service to the Docker Compose stack
- Declare a
neo4jservice usingneo4j:5-community, exposing the HTTP browser and Bolt ports, mounting named volumes for data and logs, and reading its admin password from the same project env file. - Add a Bolt-level healthcheck (using
cypher-shell) so dependent services start only after Neo4j accepts queries. - Wire the existing application service so that it depends on Neo4j being healthy and overrides the connection URI for the in-container case while leaving the host-mode default untouched.
- Keep Compose v2 syntax: do not introduce a top-level
version:key. - Observable completion:
docker compose configparses cleanly, anddocker compose up -dfrom a clean checkout brings both services up with Neo4j reportinghealthybefore the application starts. - Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 2.3
- Boundary: docker-compose.yml
- Declare a
-
1.3 (P) Refresh the env example to mirror the README and the code's actual reads
- Add the Neo4j connection variables, the embedding model, and the new optional provider/embedder variables alongside their fallback rules.
- Drop the deprecated Zep variable (or keep a single commented "deprecated" line) and add a comment guiding Qwen/Dashscope users to point the embedder at OpenAI directly.
- Ensure no real secret values are present.
- If the environment-guard hook blocks editing the example file, document the same content in the README's environment section instead and note the discrepancy in the PR description.
- Observable completion: copying the example to a fresh
.envplus filling in only the LLM key is sufficient to boot the stack against the documented default provider; the variable set in the example matches the variable set in the README's environment section. - Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7
- Boundary: .env.example, README
2. Core — Graphiti adapter rewrite
-
2.1 Replace the no-op Gemini reranker with a provider-agnostic passthrough
- Remove the
_GeminiRerankerclass that depends on a Gemini client and replace it with a renamed passthrough that returns its input list with synthetic descending scores and holds no provider-specific state. - Always inject this passthrough into the Graphiti constructor so the framework does not silently fall back to its OpenAI-only default reranker (which would 401 against Qwen/Dashscope keys).
- Observable completion: the graph adapter module exposes a passthrough reranker with no Gemini dependency, and a grep for
_GeminiRerankerinbackend/app/services/returns zero hits. - Requirements: 7.1, 7.4
- Remove the
-
2.2 Implement the Graphiti provider switch inside the singleton factory
- Read the new provider configuration once when constructing the singleton; branch between an OpenAI-compatible client/embedder pair and the existing Gemini client/embedder pair.
- Lazy-import the provider-specific Graphiti classes inside their respective branches so a missing optional dependency for one provider does not break the other.
- For the OpenAI-compatible branch, use the chat triple (
api_key,base_url,model) for the LLM client and the embedder credentials with fallback to the chat triple for the embedder. - For the Gemini branch, preserve the current behaviour byte-for-byte.
- When the provider value is unrecognised, raise an error that names the offending value and lists the allowed set, so misconfiguration is surfaced loudly rather than silently.
- Preserve the existing singleton pattern, double-checked lock, persistent event-loop binding, and
build_indices_and_constraints()call exactly. - Observable completion: with the documented default configuration plus a Qwen/Dashscope key, the adapter constructs a Graphiti instance whose internal LLM client targets the configured base URL; with
GRAPHITI_LLM_PROVIDER=geminiand an existing Gemini setup, the constructed instance is functionally identical to the pre-change behaviour. - Requirements: 3.3, 3.4, 3.5, 4.1, 4.2, 4.3, 5.4, 8.1, 8.2, 9.4
- Depends: 1.1, 2.1
-
2.3 Make the search namespace honest about reranker support
- Drop the
rerankerkeyword argument from the adapter's search method since the adapter has never honoured it. - Observable completion: the adapter's search method signature contains no
rerankerparameter, and a grep forreranker=inbackend/app/services/graphiti_adapter.pyreturns zero hits. - Requirements: 7.2
- Depends: 2.2
- Drop the
3. Core — caller cleanup so the new signature stays consistent
-
3.1 (P) Remove the misleading reranker keyword from the report-tool search call
- Update the graph search invocation that asks for a
cross_encoderreranker (which the adapter never honoured) so it no longer passes the keyword. - Leave behaviour unchanged otherwise; the reranker argument was already a no-op.
- Observable completion: a grep for
reranker=inbackend/app/services/zep_tools.pyreturns zero hits, and report-tool search code paths still execute end-to-end withoutTypeError. - Requirements: 7.3, 7.4
- Boundary: zep_tools.py
- Depends: 2.3
- Update the graph search invocation that asks for a
-
3.2 (P) Remove the misleading reranker keyword from the profile-generator search calls
- Update both of the graph search invocations that ask for an
rrfreranker (also a no-op in the adapter) so they no longer pass the keyword. - Observable completion: a grep for
reranker=inbackend/app/services/oasis_profile_generator.pyreturns zero hits, and profile-generation search code paths still execute end-to-end withoutTypeError. - Requirements: 7.3, 7.4
- Boundary: oasis_profile_generator.py
- Depends: 2.3
- Update both of the graph search invocations that ask for an
4. Validation — structural checks and manual smoke
-
4.1 Static verification of the rewrite
- Confirm that no references to
_GeminiRerankerremain anywhere underbackend/. - Confirm that no
reranker=keyword arguments remain anywhere underbackend/app/services/. - Confirm that
docker compose configparses the new compose file without warnings about deprecated keys. - Confirm that the host-mode default for the Neo4j URI in the configuration is
bolt://localhost:7687(Requirement 2.1) and is not mutated by the Compose service-level override. - Observable completion: all four checks pass and their commands exit zero / produce empty greps; results captured in the PR description.
- Requirements: 1.8, 2.1, 7.1, 7.2, 7.3
- Depends: 3.1, 3.2
- Confirm that no references to
-
4.2 Compose stack smoke (no LLM keys required)
- Boot the full stack via
docker compose up -dfrom a clean state (volumes pruned). - Confirm Neo4j reaches
healthystatus before the application container starts (verifies thedepends_onwiring). - Confirm
cypher-shellagainst the running Neo4j accepts a trivialRETURN 1using the configured password. - Confirm the application's
/healthendpoint returns OK after Neo4j is healthy. - Observable completion:
docker compose psshows both services running with Neo4j healthy;curl localhost:5001/healthreturns the expected JSON. - Requirements: 1.9, 2.2
- Depends: 1.2, 4.1
- Boot the full stack via
-
4.3 Provider misconfiguration smoke
- Set
GRAPHITI_LLM_PROVIDERto an unrecognised value with an LLM key configured and trigger a graph-build request. - Confirm the adapter raises an error that names the offending value and lists the allowed providers.
- Observable completion: the application logs contain the expected named-and-allowed error message; the failure surface is the provider error itself, not a generic 500.
- Requirements: 3.5, 9.4
- Depends: 2.2, 4.2
- Set
-
* 4.4 End-to-end pipeline smoke against the documented default provider
- Run by the ticket reviewer using real keys (Qwen for chat, OpenAI for embeddings).
- Configure
LLM_API_KEY(Qwen),EMBEDDING_API_KEY(OpenAI), keepGRAPHITI_LLM_PROVIDERat its default (openai), then upload a small.txtand run ontology generation followed by graph build. - Verify the graph data endpoint returns a non-zero count of nodes and edges and that report tools (
InsightForge,Panorama) return non-empty results. - Marked optional because it depends on real API keys not present in the implementation environment; required for ticket reviewer sign-off.
- Observable completion: graph build completes within ~10 minutes; data and report endpoints return non-empty payloads.
- Requirements: 9.1, 9.2, 9.3
- Depends: 2.2, 4.2
-
* 4.5 Backwards-compatibility smoke against Gemini
- Run by a reviewer with a Gemini key.
- Set
GRAPHITI_LLM_PROVIDER=gemini, leaveLLM_API_KEYas the Gemini key, and setEMBEDDING_MODEL=text-embedding-004. - Run the same upload + build flow and confirm completion.
- Marked optional for the same reason as 4.4 (no Gemini key in implementation environment); required for ticket reviewer sign-off.
- Observable completion: graph build completes successfully with no behavioural difference from the pre-change implementation.
- Requirements: 8.1
- Depends: 2.2, 4.2