From 34329840e1db8bb67c5bc010fedca2426dddacae Mon Sep 17 00:00:00 2001 From: Christian Glessner Date: Wed, 1 Apr 2026 12:23:04 +0200 Subject: [PATCH] Add Neo4j GraphRAG samples (#4994) * Add Neo4j GraphRAG samples * Fix sample CI issues * Address sample review feedback * Move Neo4j Python sample to end-to-end * Make Neo4j GraphRAG sample self-contained * Remove unused central package versions --- dotnet/agent-framework-dotnet.slnx | 1 + .../AgentWithRAG_Step05_Neo4jGraphRAG.csproj | 54 +++++++++ .../Program.cs | 77 ++++++++++++ .../README.md | 32 +++++ .../samples/02-agents/AgentWithRAG/README.md | 1 + .../05-end-to-end/neo4j_graphrag/README.md | 43 +++++++ .../05-end-to-end/neo4j_graphrag/main.py | 112 ++++++++++++++++++ 7 files changed, 320 insertions(+) create mode 100644 dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj create mode 100644 dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/Program.cs create mode 100644 dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/README.md create mode 100644 python/samples/05-end-to-end/neo4j_graphrag/README.md create mode 100644 python/samples/05-end-to-end/neo4j_graphrag/main.py diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index b9755dac83..9f0356fb87 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -171,6 +171,7 @@ + diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj new file mode 100644 index 0000000000..a25c626323 --- /dev/null +++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj @@ -0,0 +1,54 @@ + + + + Exe + net10.0 + + enable + enable + false + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/Program.cs b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/Program.cs new file mode 100644 index 0000000000..5bf0918ab3 --- /dev/null +++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/Program.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Neo4j.AgentFramework.GraphRAG; +using Neo4j.Driver; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +var neo4jUri = Environment.GetEnvironmentVariable("NEO4J_URI") ?? throw new InvalidOperationException("NEO4J_URI is not set."); +var neo4jUsername = Environment.GetEnvironmentVariable("NEO4J_USERNAME") ?? "neo4j"; +var neo4jPassword = Environment.GetEnvironmentVariable("NEO4J_PASSWORD") ?? throw new InvalidOperationException("NEO4J_PASSWORD is not set."); +var fulltextIndex = Environment.GetEnvironmentVariable("NEO4J_FULLTEXT_INDEX_NAME") ?? "search_chunks"; + +const string RetrievalQuery = """ + MATCH (node)-[:FROM_DOCUMENT]->(doc:Document)<-[:FILED]-(company:Company) + OPTIONAL MATCH (company)-[:FACES_RISK]->(risk:RiskFactor) + WITH node, score, company, doc, collect(DISTINCT risk.name)[0..5] AS risks + OPTIONAL MATCH (company)-[:MENTIONS]->(product:Product) + WITH node, score, company, doc, risks, collect(DISTINCT product.name)[0..5] AS products + RETURN + node.text AS text, + score, + company.name AS company, + company.ticker AS ticker, + doc.title AS title, + risks, + products + ORDER BY score DESC + """; + +await using var driver = GraphDatabase.Driver(new Uri(neo4jUri), AuthTokens.Basic(neo4jUsername, neo4jPassword)); +await driver.VerifyConnectivityAsync(); + +await using var provider = new Neo4jContextProvider( + driver, + new Neo4jContextProviderOptions + { + IndexName = fulltextIndex, + IndexType = IndexType.Fulltext, + RetrievalQuery = RetrievalQuery, + TopK = 5, + ContextPrompt = "Use the retrieved Neo4j graph context to answer accurately and call out when context is missing." + }); + +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIAgent agent = new AzureOpenAIClient( + new Uri(endpoint), + new DefaultAzureCredential()) + .GetChatClient(deploymentName) + .AsIChatClient() + .AsAIAgent(new ChatClientAgentOptions + { + ChatOptions = new() + { + Instructions = "You are a helpful assistant that answers questions using Neo4j graph context." + }, + AIContextProviders = [provider] + }); + +AgentSession session = await agent.CreateSessionAsync(); + +foreach (var question in new[] +{ + "What products does Microsoft offer?", + "What risks does Apple face?", + "Tell me about NVIDIA's AI business and risk factors." +}) +{ + Console.WriteLine($">> {question}\n"); + Console.WriteLine(await agent.RunAsync(question, session)); + Console.WriteLine(); +} diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/README.md b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/README.md new file mode 100644 index 0000000000..418407e831 --- /dev/null +++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/README.md @@ -0,0 +1,32 @@ +# Agent Framework Retrieval Augmented Generation (RAG) with Neo4j GraphRAG + +This sample demonstrates how to create and run an agent that uses the [Neo4j GraphRAG context provider](https://github.com/neo4j-labs/neo4j-maf-provider) with Microsoft Agent Framework for .NET. + +The sample uses a Neo4j fulltext index for retrieval and a Cypher `RetrievalQuery` to enrich results with related companies, products, and risk factors. + +## Prerequisites + +- .NET 10 SDK or later +- Azure OpenAI endpoint and chat deployment +- Azure CLI installed and authenticated +- A Neo4j database with chunked documents and a fulltext index such as `search_chunks` + +## Environment variables + +```powershell +$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" +$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" +$env:NEO4J_URI="neo4j+s://your-instance.databases.neo4j.io" +$env:NEO4J_USERNAME="neo4j" +$env:NEO4J_PASSWORD="your-password" +$env:NEO4J_FULLTEXT_INDEX_NAME="search_chunks" +``` + +## Build and run + +```powershell +dotnet build +dotnet run --framework net10.0 --no-build +``` + +The sample issues a few questions against the graph-backed retrieval provider and prints the responses to the console. diff --git a/dotnet/samples/02-agents/AgentWithRAG/README.md b/dotnet/samples/02-agents/AgentWithRAG/README.md index d606ac767c..9633220bf1 100644 --- a/dotnet/samples/02-agents/AgentWithRAG/README.md +++ b/dotnet/samples/02-agents/AgentWithRAG/README.md @@ -8,3 +8,4 @@ These samples show how to create an agent with the Agent Framework that uses Ret |[RAG with Vector Store and custom schema](./AgentWithRAG_Step02_CustomVectorStoreRAG/)|This sample demonstrates how to create and run an agent that uses Retrieval Augmented Generation (RAG) with a vector store. It also uses a custom schema for the documents stored in the vector store.| |[RAG with custom RAG data source](./AgentWithRAG_Step03_CustomRAGDataSource/)|This sample demonstrates how to create and run an agent that uses Retrieval Augmented Generation (RAG) with a custom RAG data source.| |[RAG with Foundry VectorStore service](./AgentWithRAG_Step04_FoundryServiceRAG/)|This sample demonstrates how to create and run an agent that uses Retrieval Augmented Generation (RAG) with the Foundry VectorStore service.| +|[RAG with Neo4j GraphRAG](./AgentWithRAG_Step05_Neo4jGraphRAG/)|This sample demonstrates how to create and run an agent that uses a Neo4j-backed GraphRAG context provider with graph-enriched retrieval.| diff --git a/python/samples/05-end-to-end/neo4j_graphrag/README.md b/python/samples/05-end-to-end/neo4j_graphrag/README.md new file mode 100644 index 0000000000..ab7f5e590e --- /dev/null +++ b/python/samples/05-end-to-end/neo4j_graphrag/README.md @@ -0,0 +1,43 @@ +# Neo4j GraphRAG Context Provider + +The [Neo4j GraphRAG context provider](https://github.com/neo4j-labs/neo4j-maf-provider) adds read-only retrieval from a Neo4j knowledge graph to an Agent Framework agent. It supports vector, fulltext, and hybrid retrieval, and can enrich search results by traversing graph relationships with a Cypher `retrieval_query`. + +This sample keeps setup lightweight by using a pre-built Neo4j fulltext index plus a graph-enrichment query. + +## Example + +| File | Description | +|---|---| +| [`main.py`](main.py) | Runnable GraphRAG sample using a Neo4j fulltext index and a Cypher enrichment query to surface related companies, products, and risk factors. | + +## Prerequisites + +1. A Neo4j database with document chunks already loaded +2. A Neo4j fulltext index over chunk text, such as `search_chunks` +3. An Azure AI Foundry project endpoint and chat deployment +4. Azure CLI authentication via `az login` + +## Environment variables + +This sample expects: + +- `FOUNDRY_PROJECT_ENDPOINT` +- `FOUNDRY_MODEL` +- `NEO4J_URI` +- `NEO4J_USERNAME` +- `NEO4J_PASSWORD` +- `NEO4J_FULLTEXT_INDEX_NAME` (optional, defaults to `search_chunks`) + +## Run with uv + +From the `python/` directory: + +```bash +uv run samples/05-end-to-end/neo4j_graphrag/main.py +``` + +## Notes + +- This sample uses the published `agent-framework-neo4j` package rather than code from this repository. +- The package also supports vector and hybrid retrieval when you configure embeddings and indexes in Neo4j. +- For memory-oriented scenarios, the Neo4j project also maintains companion examples in the external provider repository. diff --git a/python/samples/05-end-to-end/neo4j_graphrag/main.py b/python/samples/05-end-to-end/neo4j_graphrag/main.py new file mode 100644 index 0000000000..51e1154c69 --- /dev/null +++ b/python/samples/05-end-to-end/neo4j_graphrag/main.py @@ -0,0 +1,112 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "agent-framework-foundry", +# "agent-framework-neo4j", +# ] +# /// + +# Copyright (c) Microsoft. All rights reserved. + +import asyncio +import os + +from agent_framework import Agent +from agent_framework.foundry import FoundryChatClient +from agent_framework_neo4j import Neo4jContextProvider, Neo4jSettings +from azure.identity.aio import AzureCliCredential +from dotenv import load_dotenv + +load_dotenv() + +""" +This sample demonstrates how to use the Neo4j GraphRAG context provider with +Agent Framework and Azure AI Foundry. + +Environment variables: + FOUNDRY_PROJECT_ENDPOINT — Azure AI Foundry project endpoint + FOUNDRY_MODEL — Model deployment name (e.g. gpt-4o) + NEO4J_URI — Neo4j connection URI + NEO4J_USERNAME — Neo4j username + NEO4J_PASSWORD — Neo4j password + NEO4J_FULLTEXT_INDEX_NAME — Optional fulltext index name (defaults to search_chunks) +""" + +USER_INPUTS = [ + "What products does Microsoft offer?", + "What risks does Apple face?", + "Tell me about NVIDIA's AI business and risk factors.", +] + +# Optional graph-enrichment query: retrieval works without this, but supplying +# a query lets the sample attach related company, product, and risk metadata to +# each retrieved chunk. +RETRIEVAL_QUERY = """ +MATCH (node)-[:FROM_DOCUMENT]->(doc:Document)<-[:FILED]-(company:Company) +OPTIONAL MATCH (company)-[:FACES_RISK]->(risk:RiskFactor) +WITH node, score, company, doc, collect(DISTINCT risk.name)[0..5] AS risks +OPTIONAL MATCH (company)-[:MENTIONS]->(product:Product) +WITH node, score, company, doc, risks, collect(DISTINCT product.name)[0..5] AS products +RETURN + node.text AS text, + score, + company.name AS company, + company.ticker AS ticker, + doc.title AS title, + risks, + products +ORDER BY score DESC +""" + + +async def main() -> None: + # 1. Load and validate the Neo4j connection settings. + settings = Neo4jSettings() + if not settings.is_configured: + raise RuntimeError("Set NEO4J_URI, NEO4J_USERNAME, and NEO4J_PASSWORD before running this sample.") + + # 2. Read the Azure AI Foundry project endpoint and model configuration. + project_endpoint = os.environ.get("FOUNDRY_PROJECT_ENDPOINT") + if not project_endpoint: + raise RuntimeError("Set FOUNDRY_PROJECT_ENDPOINT before running this sample.") + + model = os.environ.get("FOUNDRY_MODEL") or "gpt-4o" + + # 3. Create the Neo4j context provider and Foundry-backed agent, then ask sample questions. + async with ( + AzureCliCredential() as credential, + Neo4jContextProvider( + source_id="neo4j_graphrag", + uri=settings.uri, + username=settings.username, + password=settings.get_password(), + index_name=settings.fulltext_index_name, + index_type="fulltext", + retrieval_query=RETRIEVAL_QUERY, + top_k=5, + ) as provider, + Agent( + client=FoundryChatClient( + project_endpoint=project_endpoint, + model=model, + credential=credential, + ), + name="Neo4jGraphRAGAgent", + instructions=( + "You are a helpful assistant. Use the Neo4j context provider results to answer accurately. " + "If the retrieved context is insufficient, say so plainly." + ), + context_providers=[provider], + ) as agent, + ): + session = agent.create_session() + print("=== Neo4j GraphRAG Context Provider ===\n") + + for user_input in USER_INPUTS: + print(f"User: {user_input}") + result = await agent.run(user_input, session=session) + print(f"Agent: {getattr(result, 'text', result)}\n") + + +if __name__ == "__main__": + asyncio.run(main())