Skip to content

Commit 3432984

Browse files
authored
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
1 parent 2a8c3e2 commit 3432984

7 files changed

Lines changed: 320 additions & 0 deletions

File tree

dotnet/agent-framework-dotnet.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/AgentWithRAG_Step02_CustomVectorStoreRAG.csproj" />
172172
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj" />
173173
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj" />
174+
<Project Path="samples/02-agents/AgentWithRAG/AgentWithRAG_Step05_Neo4jGraphRAG/AgentWithRAG_Step05_Neo4jGraphRAG.csproj" />
174175
</Folder>
175176
<Folder Name="/Samples/02-agents/ModelContextProtocol/">
176177
<File Path="samples/02-agents/ModelContextProtocol/README.md" />
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>net10.0</TargetFrameworks>
6+
7+
<Nullable>enable</Nullable>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Remove="Microsoft.CodeAnalysis.NetAnalyzers" />
14+
<PackageReference Remove="Microsoft.VisualStudio.Threading.Analyzers" />
15+
<PackageReference Remove="xunit.analyzers" />
16+
<PackageReference Remove="Moq.Analyzers" />
17+
<PackageReference Remove="Roslynator.Analyzers" />
18+
<PackageReference Remove="Roslynator.CodeAnalysis.Analyzers" />
19+
<PackageReference Remove="Roslynator.Formatting.Analyzers" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
24+
<PackageReference Include="Azure.Identity" Version="1.19.0" />
25+
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc4" />
26+
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.4.0" />
27+
<PackageReference Include="Neo4j.AgentFramework.GraphRAG" Version="0.1.0-preview.2" />
28+
<PackageReference Include="Neo4j.Driver" Version="5.28.0" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.100">
33+
<PrivateAssets>all</PrivateAssets>
34+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
35+
</PackageReference>
36+
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15">
37+
<PrivateAssets>all</PrivateAssets>
38+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
39+
</PackageReference>
40+
<PackageReference Include="Roslynator.Analyzers" Version="4.14.1">
41+
<PrivateAssets>all</PrivateAssets>
42+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
43+
</PackageReference>
44+
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.1">
45+
<PrivateAssets>all</PrivateAssets>
46+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
47+
</PackageReference>
48+
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.14.1">
49+
<PrivateAssets>all</PrivateAssets>
50+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
51+
</PackageReference>
52+
</ItemGroup>
53+
54+
</Project>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Azure.AI.OpenAI;
4+
using Azure.Identity;
5+
using Microsoft.Agents.AI;
6+
using Microsoft.Extensions.AI;
7+
using Neo4j.AgentFramework.GraphRAG;
8+
using Neo4j.Driver;
9+
10+
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
11+
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
12+
var neo4jUri = Environment.GetEnvironmentVariable("NEO4J_URI") ?? throw new InvalidOperationException("NEO4J_URI is not set.");
13+
var neo4jUsername = Environment.GetEnvironmentVariable("NEO4J_USERNAME") ?? "neo4j";
14+
var neo4jPassword = Environment.GetEnvironmentVariable("NEO4J_PASSWORD") ?? throw new InvalidOperationException("NEO4J_PASSWORD is not set.");
15+
var fulltextIndex = Environment.GetEnvironmentVariable("NEO4J_FULLTEXT_INDEX_NAME") ?? "search_chunks";
16+
17+
const string RetrievalQuery = """
18+
MATCH (node)-[:FROM_DOCUMENT]->(doc:Document)<-[:FILED]-(company:Company)
19+
OPTIONAL MATCH (company)-[:FACES_RISK]->(risk:RiskFactor)
20+
WITH node, score, company, doc, collect(DISTINCT risk.name)[0..5] AS risks
21+
OPTIONAL MATCH (company)-[:MENTIONS]->(product:Product)
22+
WITH node, score, company, doc, risks, collect(DISTINCT product.name)[0..5] AS products
23+
RETURN
24+
node.text AS text,
25+
score,
26+
company.name AS company,
27+
company.ticker AS ticker,
28+
doc.title AS title,
29+
risks,
30+
products
31+
ORDER BY score DESC
32+
""";
33+
34+
await using var driver = GraphDatabase.Driver(new Uri(neo4jUri), AuthTokens.Basic(neo4jUsername, neo4jPassword));
35+
await driver.VerifyConnectivityAsync();
36+
37+
await using var provider = new Neo4jContextProvider(
38+
driver,
39+
new Neo4jContextProviderOptions
40+
{
41+
IndexName = fulltextIndex,
42+
IndexType = IndexType.Fulltext,
43+
RetrievalQuery = RetrievalQuery,
44+
TopK = 5,
45+
ContextPrompt = "Use the retrieved Neo4j graph context to answer accurately and call out when context is missing."
46+
});
47+
48+
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
49+
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
50+
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
51+
AIAgent agent = new AzureOpenAIClient(
52+
new Uri(endpoint),
53+
new DefaultAzureCredential())
54+
.GetChatClient(deploymentName)
55+
.AsIChatClient()
56+
.AsAIAgent(new ChatClientAgentOptions
57+
{
58+
ChatOptions = new()
59+
{
60+
Instructions = "You are a helpful assistant that answers questions using Neo4j graph context."
61+
},
62+
AIContextProviders = [provider]
63+
});
64+
65+
AgentSession session = await agent.CreateSessionAsync();
66+
67+
foreach (var question in new[]
68+
{
69+
"What products does Microsoft offer?",
70+
"What risks does Apple face?",
71+
"Tell me about NVIDIA's AI business and risk factors."
72+
})
73+
{
74+
Console.WriteLine($">> {question}\n");
75+
Console.WriteLine(await agent.RunAsync(question, session));
76+
Console.WriteLine();
77+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Agent Framework Retrieval Augmented Generation (RAG) with Neo4j GraphRAG
2+
3+
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.
4+
5+
The sample uses a Neo4j fulltext index for retrieval and a Cypher `RetrievalQuery` to enrich results with related companies, products, and risk factors.
6+
7+
## Prerequisites
8+
9+
- .NET 10 SDK or later
10+
- Azure OpenAI endpoint and chat deployment
11+
- Azure CLI installed and authenticated
12+
- A Neo4j database with chunked documents and a fulltext index such as `search_chunks`
13+
14+
## Environment variables
15+
16+
```powershell
17+
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
18+
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
19+
$env:NEO4J_URI="neo4j+s://your-instance.databases.neo4j.io"
20+
$env:NEO4J_USERNAME="neo4j"
21+
$env:NEO4J_PASSWORD="your-password"
22+
$env:NEO4J_FULLTEXT_INDEX_NAME="search_chunks"
23+
```
24+
25+
## Build and run
26+
27+
```powershell
28+
dotnet build
29+
dotnet run --framework net10.0 --no-build
30+
```
31+
32+
The sample issues a few questions against the graph-backed retrieval provider and prints the responses to the console.

dotnet/samples/02-agents/AgentWithRAG/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ These samples show how to create an agent with the Agent Framework that uses Ret
88
|[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.|
99
|[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.|
1010
|[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.|
11+
|[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.|
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Neo4j GraphRAG Context Provider
2+
3+
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`.
4+
5+
This sample keeps setup lightweight by using a pre-built Neo4j fulltext index plus a graph-enrichment query.
6+
7+
## Example
8+
9+
| File | Description |
10+
|---|---|
11+
| [`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. |
12+
13+
## Prerequisites
14+
15+
1. A Neo4j database with document chunks already loaded
16+
2. A Neo4j fulltext index over chunk text, such as `search_chunks`
17+
3. An Azure AI Foundry project endpoint and chat deployment
18+
4. Azure CLI authentication via `az login`
19+
20+
## Environment variables
21+
22+
This sample expects:
23+
24+
- `FOUNDRY_PROJECT_ENDPOINT`
25+
- `FOUNDRY_MODEL`
26+
- `NEO4J_URI`
27+
- `NEO4J_USERNAME`
28+
- `NEO4J_PASSWORD`
29+
- `NEO4J_FULLTEXT_INDEX_NAME` (optional, defaults to `search_chunks`)
30+
31+
## Run with uv
32+
33+
From the `python/` directory:
34+
35+
```bash
36+
uv run samples/05-end-to-end/neo4j_graphrag/main.py
37+
```
38+
39+
## Notes
40+
41+
- This sample uses the published `agent-framework-neo4j` package rather than code from this repository.
42+
- The package also supports vector and hybrid retrieval when you configure embeddings and indexes in Neo4j.
43+
- For memory-oriented scenarios, the Neo4j project also maintains companion examples in the external provider repository.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# /// script
2+
# requires-python = ">=3.10"
3+
# dependencies = [
4+
# "agent-framework-foundry",
5+
# "agent-framework-neo4j",
6+
# ]
7+
# ///
8+
9+
# Copyright (c) Microsoft. All rights reserved.
10+
11+
import asyncio
12+
import os
13+
14+
from agent_framework import Agent
15+
from agent_framework.foundry import FoundryChatClient
16+
from agent_framework_neo4j import Neo4jContextProvider, Neo4jSettings
17+
from azure.identity.aio import AzureCliCredential
18+
from dotenv import load_dotenv
19+
20+
load_dotenv()
21+
22+
"""
23+
This sample demonstrates how to use the Neo4j GraphRAG context provider with
24+
Agent Framework and Azure AI Foundry.
25+
26+
Environment variables:
27+
FOUNDRY_PROJECT_ENDPOINT — Azure AI Foundry project endpoint
28+
FOUNDRY_MODEL — Model deployment name (e.g. gpt-4o)
29+
NEO4J_URI — Neo4j connection URI
30+
NEO4J_USERNAME — Neo4j username
31+
NEO4J_PASSWORD — Neo4j password
32+
NEO4J_FULLTEXT_INDEX_NAME — Optional fulltext index name (defaults to search_chunks)
33+
"""
34+
35+
USER_INPUTS = [
36+
"What products does Microsoft offer?",
37+
"What risks does Apple face?",
38+
"Tell me about NVIDIA's AI business and risk factors.",
39+
]
40+
41+
# Optional graph-enrichment query: retrieval works without this, but supplying
42+
# a query lets the sample attach related company, product, and risk metadata to
43+
# each retrieved chunk.
44+
RETRIEVAL_QUERY = """
45+
MATCH (node)-[:FROM_DOCUMENT]->(doc:Document)<-[:FILED]-(company:Company)
46+
OPTIONAL MATCH (company)-[:FACES_RISK]->(risk:RiskFactor)
47+
WITH node, score, company, doc, collect(DISTINCT risk.name)[0..5] AS risks
48+
OPTIONAL MATCH (company)-[:MENTIONS]->(product:Product)
49+
WITH node, score, company, doc, risks, collect(DISTINCT product.name)[0..5] AS products
50+
RETURN
51+
node.text AS text,
52+
score,
53+
company.name AS company,
54+
company.ticker AS ticker,
55+
doc.title AS title,
56+
risks,
57+
products
58+
ORDER BY score DESC
59+
"""
60+
61+
62+
async def main() -> None:
63+
# 1. Load and validate the Neo4j connection settings.
64+
settings = Neo4jSettings()
65+
if not settings.is_configured:
66+
raise RuntimeError("Set NEO4J_URI, NEO4J_USERNAME, and NEO4J_PASSWORD before running this sample.")
67+
68+
# 2. Read the Azure AI Foundry project endpoint and model configuration.
69+
project_endpoint = os.environ.get("FOUNDRY_PROJECT_ENDPOINT")
70+
if not project_endpoint:
71+
raise RuntimeError("Set FOUNDRY_PROJECT_ENDPOINT before running this sample.")
72+
73+
model = os.environ.get("FOUNDRY_MODEL") or "gpt-4o"
74+
75+
# 3. Create the Neo4j context provider and Foundry-backed agent, then ask sample questions.
76+
async with (
77+
AzureCliCredential() as credential,
78+
Neo4jContextProvider(
79+
source_id="neo4j_graphrag",
80+
uri=settings.uri,
81+
username=settings.username,
82+
password=settings.get_password(),
83+
index_name=settings.fulltext_index_name,
84+
index_type="fulltext",
85+
retrieval_query=RETRIEVAL_QUERY,
86+
top_k=5,
87+
) as provider,
88+
Agent(
89+
client=FoundryChatClient(
90+
project_endpoint=project_endpoint,
91+
model=model,
92+
credential=credential,
93+
),
94+
name="Neo4jGraphRAGAgent",
95+
instructions=(
96+
"You are a helpful assistant. Use the Neo4j context provider results to answer accurately. "
97+
"If the retrieved context is insufficient, say so plainly."
98+
),
99+
context_providers=[provider],
100+
) as agent,
101+
):
102+
session = agent.create_session()
103+
print("=== Neo4j GraphRAG Context Provider ===\n")
104+
105+
for user_input in USER_INPUTS:
106+
print(f"User: {user_input}")
107+
result = await agent.run(user_input, session=session)
108+
print(f"Agent: {getattr(result, 'text', result)}\n")
109+
110+
111+
if __name__ == "__main__":
112+
asyncio.run(main())

0 commit comments

Comments
 (0)