Skip to content

Commit a8d384e

Browse files
authored
test(neo4j): add integration tests and docs for vector search (googleapis#2902)
1 parent a843d57 commit a8d384e

File tree

3 files changed

+90
-8
lines changed

3 files changed

+90
-8
lines changed

.ci/integration.cloudbuild.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ steps:
536536
- "GOPATH=/gopath"
537537
- "NEO4J_DATABASE=$_NEO4J_DATABASE"
538538
- "NEO4J_URI=$_NEO4J_URI"
539-
secretEnv: ["NEO4J_USER", "NEO4J_PASS"]
539+
secretEnv: ["NEO4J_USER", "NEO4J_PASS", "API_KEY"]
540540
volumes:
541541
- name: "go"
542542
path: "/gopath"

docs/en/integrations/neo4j/tools/neo4j-cypher.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,71 @@ parameters:
6767
description: 4 digit number starting in 1900 up to the current year
6868
```
6969
70+
### Vector Search
71+
72+
Neo4j supports vector similarity search. When using an `embeddingModel` with a `neo4j-cypher` tool, the tool automatically converts text parameters into the vector format required by Neo4j.
73+
74+
#### Define the Embedding Model
75+
76+
See [EmbeddingModels](../../../documentation/configuration/embedding-models/_index.md) for more information.
77+
78+
kind: embeddingModel
79+
name: gemini-model
80+
type: gemini
81+
model: gemini-embedding-001
82+
apiKey: ${GOOGLE_API_KEY}
83+
dimension: 768
84+
85+
#### Vector Ingestion Tool
86+
87+
This tool stores both the raw text and its vector representation. It uses `valueFromParam` to hide the vector conversion logic from the LLM, ensuring the Agent only has to provide the content once.
88+
```yaml
89+
kind: tool
90+
name: insert_doc_neo4j
91+
type: neo4j-cypher
92+
source: my-neo4j-source
93+
statement: |
94+
CREATE (n:Document {content: $content, embedding: $text_to_embed})
95+
RETURN 1 as result
96+
description: |
97+
Index new documents for semantic search in Neo4j.
98+
parameters:
99+
- name: content
100+
type: string
101+
description: The text content to store.
102+
- name: text_to_embed
103+
type: string
104+
# Automatically copies 'content' and converts it to a vector array
105+
valueFromParam: content
106+
embeddedBy: gemini-model
107+
```
108+
109+
#### Vector Search Tool
110+
111+
This tool allows the Agent to perform a natural language search. The query string provided by the Agent is converted into a vector before the Cypher statement is executed.
112+
113+
```yaml
114+
kind: tool
115+
name: search_docs_neo4j
116+
type: neo4j-cypher
117+
source: my-neo4j-source
118+
statement: |
119+
MATCH (n:Document)
120+
WITH n, vector.similarity.cosine(n.embedding, $query) AS score
121+
WHERE score IS NOT NULL
122+
ORDER BY score DESC
123+
LIMIT 1
124+
RETURN n.content as content
125+
description: |
126+
Search for documents in Neo4j using natural language.
127+
Returns the most semantically similar result.
128+
parameters:
129+
- name: query
130+
type: string
131+
description: The search query to be converted to a vector.
132+
embeddedBy: gemini-model
133+
```
134+
70135
## Reference
71136

72137
| **field** | **type** | **required** | **description** |

tests/neo4j/neo4j_integration_test.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,44 +77,56 @@ func TestNeo4jToolEndpoints(t *testing.T) {
7777
// This configuration defines the data source and the tools to be tested.
7878
toolsFile := map[string]any{
7979
"sources": map[string]any{
80-
"my-neo4j-instance": sourceConfig,
80+
"my-instance": sourceConfig,
8181
},
8282
"tools": map[string]any{
8383
"my-simple-cypher-tool": map[string]any{
8484
"type": "neo4j-cypher",
85-
"source": "my-neo4j-instance",
85+
"source": "my-instance",
8686
"description": "Simple tool to test end to end functionality.",
8787
"statement": "RETURN 1 as a;",
8888
},
8989
"my-simple-execute-cypher-tool": map[string]any{
9090
"type": "neo4j-execute-cypher",
91-
"source": "my-neo4j-instance",
91+
"source": "my-instance",
9292
"description": "Simple tool to test end to end functionality.",
9393
},
9494
"my-readonly-execute-cypher-tool": map[string]any{
9595
"type": "neo4j-execute-cypher",
96-
"source": "my-neo4j-instance",
96+
"source": "my-instance",
9797
"description": "A readonly cypher execution tool.",
9898
"readOnly": true,
9999
},
100100
"my-schema-tool": map[string]any{
101101
"type": "neo4j-schema",
102-
"source": "my-neo4j-instance",
102+
"source": "my-instance",
103103
"description": "A tool to get the Neo4j schema.",
104104
},
105105
"my-schema-tool-with-cache": map[string]any{
106106
"type": "neo4j-schema",
107-
"source": "my-neo4j-instance",
107+
"source": "my-instance",
108108
"description": "A schema tool with a custom cache expiration.",
109109
"cacheExpireMinutes": 10,
110110
},
111111
"my-populated-schema-tool": map[string]any{
112112
"type": "neo4j-schema",
113-
"source": "my-neo4j-instance",
113+
"source": "my-instance",
114114
"description": "A tool to get the Neo4j schema from a populated DB.",
115115
},
116116
},
117117
}
118+
119+
insertStmt := `CREATE (n:SenseAIDocument {content: $content, embedding: $text_to_embed}) RETURN 1 as result`
120+
searchStmt := `
121+
MATCH (n:SenseAIDocument)
122+
WITH n, vector.similarity.cosine(n.embedding, $query) AS score
123+
WHERE score IS NOT NULL
124+
ORDER BY score DESC
125+
LIMIT 1
126+
RETURN n.content as content
127+
`
128+
toolsFile = tests.AddSemanticSearchConfig(t, toolsFile, "neo4j-cypher", insertStmt, searchStmt)
129+
118130
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
119131
if err != nil {
120132
t.Fatalf("command initialization returned an error: %s", err)
@@ -578,4 +590,9 @@ func TestNeo4jToolEndpoints(t *testing.T) {
578590
}
579591
})
580592
}
593+
594+
// Semantic search tests
595+
semanticInsertWant := `[{"result":1}]`
596+
semanticSearchWant := `[{"content":"The quick brown fox jumps over the lazy dog"}]`
597+
tests.RunSemanticSearchToolInvokeTest(t, semanticInsertWant, semanticInsertWant, semanticSearchWant)
581598
}

0 commit comments

Comments
 (0)