This is a spoke of a 5-part GraphRAG cluster. Start with the hub, which teaches the core pattern — a knowledge base that answers multi-hop questions — then see the other spokes: fraud-ring detection, supply-chain impact analysis, and product recommendations. For the architecture argument in depth, read the pillar: GraphRAG vs Neo4j.
Objective
A support engineer opens an at-risk enterprise account and sees a fresh ticket: "double-charged on our latest invoice." The fastest useful answer isn't a knowledge-base article — it's what actually resolved a similar case before. But whether you drop everything for it depends on something the ticket text can't tell you: is this a $240k enterprise account or a free-tier trial?
Vector-only RAG can find tickets that read like this one, but it throws away the account's value and tier, because those are edges in a graph, not words in the ticket. Neo4j can hold the account graph, but it has no native embedding hop, so ticket-text similarity has to live in a separate vector database with an app-side merge on top. The classic build is a four-system stack: Neo4j for the customer/ticket/order graph, a vector DB (Pinecone / Weaviate / pgvector) for ticket embeddings, an orchestration layer (LangChain / LlamaIndex) to glue them, and an LLM API to grade how useful each past resolution is.
SynapCores collapses that into one query on one engine: tickets carry their own
embedding property, [:SIMILAR_TO > 0.8] is a semantic hop inside Cypher
(HNSW under the hood), and llm_score(prompt, node) grades each candidate inline.
This recipe builds a small customer-360 graph and answers "what should we tell
this at-risk account, and how much does it matter?" with a single Cypher
statement — then shows you exactly what the Neo4j stack version would take.
Step 1: Build the customer-360 graph
Customers PLACED orders and OPENED tickets. Each C360Ticket carries an
embedding of its text, a status ("open" / "resolved"), and — when resolved
— the resolution that fixed it. Embedding values here are 5-dimensional demo
vectors so the recipe is fast and deterministic; in production you generate them
with EMBED('the ticket text') (384-dim by default). Tickets about the same topic
share a vector direction, so [:SIMILAR_TO > 0.8] returns same-topic neighbors.
// Customers — tier + ARR are the structural signal vector search can't see
MERGE (c1:C360Customer {id: "CUST-77", name: "Northwind Logistics",
tier: "enterprise", arr: 240000, health: "at-risk"})
MERGE (c2:C360Customer {id: "CUST-12", name: "Acme Retail",
tier: "pro", arr: 24000, health: "healthy"})
MERGE (c3:C360Customer {id: "CUST-30", name: "Bluebird Studios",
tier: "free", arr: 0, health: "trial"})
// Tickets — topic A = billing, B = SSO/login, C = API rate limits.
// Same-topic vectors point the same direction (within +/-0.02).
MERGE (t1:C360Ticket {id: "TCK-1001", status: "open", resolution: "",
text: "We were double-charged on our latest enterprise invoice and need a refund.",
embedding: [0.80, 0.10, 0.05, -0.05, 0.20]}) // topic A (billing)
MERGE (t2:C360Ticket {id: "TCK-1002", status: "resolved",
text: "Invoice showed a duplicate charge for the monthly plan.",
resolution: "Issued a credit note for the duplicate line item and turned on invoice de-duplication.",
embedding: [0.79, 0.12, 0.06, -0.06, 0.21]}) // topic A (billing)
MERGE (t3:C360Ticket {id: "TCK-1003", status: "resolved",
text: "Billing overcharge after a mid-cycle plan upgrade.",
resolution: "Applied a proration credit for the mid-cycle upgrade and documented the billing cutover.",
embedding: [0.81, 0.09, 0.04, -0.04, 0.19]}) // topic A (billing)
MERGE (t4:C360Ticket {id: "TCK-2001", status: "resolved",
text: "SAML SSO login hit a redirect loop after our IdP change.",
resolution: "Updated the ACS URL in the SAML config to match the new IdP.",
embedding: [-0.30, 0.60, 0.20, 0.05, 0.10]}) // topic B (SSO)
MERGE (t5:C360Ticket {id: "TCK-2002", status: "open", resolution: "",
text: "Users can't log in via Okta SSO this morning.",
embedding: [-0.31, 0.61, 0.19, 0.06, 0.11]}) // topic B (SSO)
MERGE (t6:C360Ticket {id: "TCK-3001", status: "resolved",
text: "Hitting 429 rate-limit errors on the export API.",
resolution: "Raised the account's API rate-limit tier and added retry-after backoff guidance.",
embedding: [0.05, -0.40, 0.65, 0.10, -0.17]}) // topic C (API limits)
// Orders — structural account value
MERGE (o1:C360Order {id: "ORD-9001", item: "Annual enterprise license", amount: 240000})
MERGE (o2:C360Order {id: "ORD-9002", item: "Pro plan renewal", amount: 24000})
// Structure: customers OPENED tickets and PLACED orders
MERGE (c1)-[:OPENED]->(t1)
MERGE (c2)-[:OPENED]->(t2)
MERGE (c3)-[:OPENED]->(t3)
MERGE (c2)-[:OPENED]->(t4)
MERGE (c3)-[:OPENED]->(t5)
MERGE (c2)-[:OPENED]->(t6)
MERGE (c1)-[:PLACED]->(o1)
MERGE (c2)-[:PLACED]->(o2);
Step 2: The vector half — semantic recall alone
[:SIMILAR_TO > 0.8] walks from the at-risk account's open ticket to the tickets
whose text is near it. This is the "vector database" part — except it's a Cypher
edge, not a separate service.
// "Northwind's open ticket is about a double-charge.
// What past tickets are semantically related?"
MATCH (c:C360Customer {id: "CUST-77"})-[:OPENED]->(open:C360Ticket {status: "open"})
MATCH (open)-[:SIMILAR_TO > 0.8]->(similar:C360Ticket)
RETURN similar.id AS id, similar.status AS status, similar.text AS text;
Expected: TCK-1002 (duplicate-charge invoice) and TCK-1003 (mid-cycle overcharge) — the other two billing tickets. The SSO and API-limit tickets point in different directions, so they don't come back. The seed ticket (TCK-1001) is excluded from its own SIMILAR_TO set, so you get related tickets, not the one you started from.