Java构建图数据库驱动的知识图谱Agent
·
一、为什么需要GraphRAG
传统RAG的局限非常明显:它只能找到文本相似的内容,无法理解实体间的关联。
| 场景 | Naive RAG | GraphRAG |
|---|---|---|
| “Spring Boot的作者还创建了哪些项目” | 只能搜到Spring Boot文档 | 通过图谱找到Rod Johnson→Spring Framework→其它项目 |
| “调用链中Service A挂了会影响哪些API” | 语义搜索不精确 | 图遍历精确找到所有下游依赖 |
| “这个Bug和三个月前的问题一样吗” | 向量相似不一定相关 | 通过实体(Bug→模块→开发者)关联判断 |
本文用Java+Spring Boot 3 + Neo4j + Spring AI,从零搭建一个GraphRAG Agent。
二、架构设计
+-------------------------------------------------+
| REST API |
| AIGraphController |
+-------------------------------------------------+
| GraphRAGService |
| +----------------+ +----------+ +-------------+ |
| |EntityExtractor | |GraphRepo | |VectorSearch | |
| | (LLM解析) | |(Neo4j) | |(Embedding) | |
| +----------------+ +----------+ +-------------+ |
+-------------------------------------------------+
| Spring AI ChatClient + EmbeddingClient |
+-------------------------------------------------+
三、依赖与配置
pom.xml 关键依赖:
<dependencies>
<!-- Spring Boot 3 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI (OpenAI兼容) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<!-- Neo4j -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<!-- 向量存储 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-neo4j-store-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
</dependencies>
application.yml:
spring:
neo4j:
uri: bolt://localhost:7687
authentication:
username: neo4j
password: ${NEO4J_PASSWORD}
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o-mini
embedding:
options:
model: text-embedding-3-small
四、图实体模型
package com.demo.graphrag.model;
import org.springframework.data.neo4j.core.schema.*;
import java.util.Set;
/**
* 知识图谱中的实体节点
* 例如:技术名词"Spring Boot"、人名"Rod Johnson"、项目"PetClinic"
*/
@Node("Entity")
public class KnowledgeEntity {
@Id
@GeneratedValue
private Long id;
@Property("name")
private String name; // 实体名称
@Property("type")
private String type; // 实体类型: PERSON/PROJECT/TECHNOLOGY/CONCEPT
@Property("description")
private String description; // LLM生成的实体描述
@Property("embedding")
private double[] embedding; // 向量嵌入(1536维)
@Relationship(type = "RELATED_TO", direction = Relationship.Direction.OUTGOING)
private Set<EntityRelation> relations;
// constructors
public KnowledgeEntity() {}
public KnowledgeEntity(String name, String type) {
this.name = name;
this.type = type;
}
// getters/setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public double[] getEmbedding() { return embedding; }
public void setEmbedding(double[] embedding) { this.embedding = embedding; }
public Set<EntityRelation> getRelations() { return relations; }
public void setRelations(Set<EntityRelation> relations) { this.relations = relations; }
}
package com.demo.graphrag.model;
import org.springframework.data.neo4j.core.schema.*;
/**
* 实体之间的关系边
*/
@RelationshipProperties
public class EntityRelation {
@RelationshipId
private Long id;
@TargetNode
private KnowledgeEntity target;
@Property("type")
private String type; // 关系类型: CREATED_BY, DEPENDS_ON, PART_OF, SIMILAR_TO
@Property("weight")
private double weight; // 关系权重 0.0~1.0
public EntityRelation() {}
public EntityRelation(KnowledgeEntity target, String type, double weight) {
this.target = target;
this.type = type;
this.weight = weight;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public KnowledgeEntity getTarget() { return target; }
public void setTarget(KnowledgeEntity target) { this.target = target; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public double getWeight() { return weight; }
public void setWeight(double weight) { this.weight = weight; }
}
五、Neo4j Repository层
package com.demo.graphrag.repository;
import com.demo.graphrag.model.KnowledgeEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface EntityRepository extends Neo4jRepository<KnowledgeEntity, Long> {
/**
* 按名称精确查找实体
*/
KnowledgeEntity findByName(String name);
/**
* 查找某实体的N度邻居
* MATCH (e:Entity {name: $name})-[r:RELATED_TO*1..$depth]-(related:Entity)
*/
@Query("MATCH (e:Entity {name: $name})-[r:RELATED_TO*1..$depth]-(related:Entity) " +
"RETURN DISTINCT related LIMIT $limit")
List<KnowledgeEntity> findNeighbors(
@Param("name") String name,
@Param("depth") int depth,
@Param("limit") int limit
);
/**
* 找出两个实体之间的最短路径
*/
@Query("MATCH p = shortestPath(" +
"(a:Entity {name: $from})-[*..5]-(b:Entity {name: $to})) " +
"RETURN nodes(p)")
List<KnowledgeEntity> findShortestPath(
@Param("from") String from,
@Param("to") String to
);
/**
* 向量相似搜索(需要neo4j-graph-data-science插件)
* 按余弦相似度排序返回最相似的实体
*/
@Query("MATCH (e:Entity) WHERE e.embedding IS NOT NULL " +
"WITH e, gds.similarity.cosine(e.embedding, $queryEmbedding) AS similarity " +
"WHERE similarity > $threshold " +
"RETURN e, similarity ORDER BY similarity DESC LIMIT $limit")
List<KnowledgeEntity> findSimilarByEmbedding(
@Param("queryEmbedding") double[] queryEmbedding,
@Param("threshold") double threshold,
@Param("limit") int limit
);
/**
* 按类型过滤实体
*/
List<KnowledgeEntity> findByType(String type);
}
六、核心服务:实体抽取 + 图谱构建
package com.demo.graphrag.service;
import com.demo.graphrag.model.EntityRelation;
import com.demo.graphrag.model.KnowledgeEntity;
import com.demo.graphrag.repository.EntityRepository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
public class GraphRAGService {
private final ChatClient chatClient;
private final EmbeddingClient embeddingClient;
private final EntityRepository entityRepository;
private final ObjectMapper objectMapper;
public GraphRAGService(ChatClient.Builder chatBuilder,
EmbeddingClient embeddingClient,
EntityRepository entityRepository,
ObjectMapper objectMapper) {
this.chatClient = chatBuilder.build();
this.embeddingClient = embeddingClient;
this.entityRepository = entityRepository;
this.objectMapper = objectMapper;
}
/**
* 从文本中抽取实体和关系,并写入Neo4j
*/
@Transactional
public List<KnowledgeEntity> ingestDocument(String documentText) {
// 1. 用LLM抽取实体和关系
String extractionResult = extractEntitiesAndRelations(documentText);
// 2. 解析JSON结果
List<ExtractedEntity> extracted;
try {
extracted = objectMapper.readValue(extractionResult,
new TypeReference<List<ExtractedEntity>>() {});
} catch (Exception e) {
throw new RuntimeException("LLM抽取结果解析失败", e);
}
// 3. 持久化到Neo4j(含向量嵌入)
List<KnowledgeEntity> saved = new ArrayList<>();
for (ExtractedEntity ee : extracted) {
KnowledgeEntity entity = upsertEntity(ee);
saved.add(entity);
// 4. 创建关系
if (ee.relations != null) {
for (ExtractedRelation rel : ee.relations) {
KnowledgeEntity target = upsertEntity(
new ExtractedEntity(rel.targetName, rel.targetType, null, null));
entity.getRelations().add(
new EntityRelation(target, rel.type, rel.weight));
}
}
// 保存关系和嵌入
entityRepository.save(entity);
}
return saved;
}
/**
* 用LLM抽取实体和关系(返回JSON格式)
*/
private String extractEntitiesAndRelations(String text) {
String prompt = "你是一个知识图谱构建助手。从以下文本中提取所有关键实体和它们之间的关系。\n" +
"以JSON数组格式返回,每个实体包含:\n" +
"{\n" +
" \"name\": \"实体名称\",\n" +
" \"type\": \"PERSON|PROJECT|TECHNOLOGY|CONCEPT|ORGANIZATION\",\n" +
" \"description\": \"一句话描述\",\n" +
" \"relations\": [\n" +
" {\"targetName\": \"关联实体名\", \"targetType\": \"类型\", " +
"\"type\": \"CREATED_BY|DEPENDS_ON|PART_OF|USES|SIMILAR_TO\", \"weight\": 0.8}\n" +
" ]\n" +
"}\n\n" +
"文本:\n" + text;
return chatClient.prompt()
.user(prompt)
.call()
.content();
}
/**
* 插入或更新实体(含向量嵌入生成)
*/
private KnowledgeEntity upsertEntity(ExtractedEntity ee) {
KnowledgeEntity existing = entityRepository.findByName(ee.name);
if (existing != null) {
if (ee.description != null) {
existing.setDescription(ee.description);
}
return existing;
}
KnowledgeEntity entity = new KnowledgeEntity(ee.name, ee.type);
entity.setDescription(ee.description);
// 生成向量嵌入
List<double[]> embeddings = embeddingClient.embed(
new EmbeddingRequest(List.of(ee.name + ": " + ee.description), null));
if (!embeddings.isEmpty()) {
entity.setEmbedding(embeddings.get(0));
}
entity.setRelations(new HashSet<>());
return entityRepository.save(entity);
}
// 内部DTO类
public static class ExtractedEntity {
public String name;
public String type;
public String description;
public List<ExtractedRelation> relations;
public ExtractedEntity() {}
public ExtractedEntity(String name, String type, String description,
List<ExtractedRelation> relations) {
this.name = name;
this.type = type;
this.description = description;
this.relations = relations;
}
}
public static class ExtractedRelation {
public String targetName;
public String targetType;
public String type;
public double weight;
}
}
七、混合检索:向量 + 图遍历
纯向量检索可能召回语义相似但逻辑无关的实体,GraphRAG结合图结构做多跳推理:
package com.demo.graphrag.service;
import com.demo.graphrag.model.KnowledgeEntity;
import com.demo.graphrag.repository.EntityRepository;
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class HybridSearchService {
private final EntityRepository entityRepository;
private final EmbeddingClient embeddingClient;
public HybridSearchService(EntityRepository entityRepository,
EmbeddingClient embeddingClient) {
this.entityRepository = entityRepository;
this.embeddingClient = embeddingClient;
}
/**
* 混合检索:向量相似 + 图扩展
* 1. 向量检索找到种子实体
* 2. 图遍历扩展关联实体
* 3. 合并去重排序
*/
public SearchResult hybridSearch(String query, int depth, int limit) {
// Step 1: 生成查询向量
List<double[]> queryEmb = embeddingClient.embed(
new EmbeddingRequest(List.of(query), null));
double[] queryVector = queryEmb.get(0);
// Step 2: 向量检索Top-K种子实体
List<KnowledgeEntity> seeds = entityRepository
.findSimilarByEmbedding(queryVector, 0.7, limit);
// Step 3: 图扩展——每个种子实体向外辐射N度
Set<KnowledgeEntity> allEntities = new LinkedHashSet<>(seeds);
for (KnowledgeEntity seed : seeds) {
List<KnowledgeEntity> neighbors = entityRepository
.findNeighbors(seed.getName(), depth, limit);
allEntities.addAll(neighbors);
}
// Step 4: 构建上下文文本
String context = buildContext(allEntities, seeds);
return new SearchResult(allEntities, context);
}
/**
* 将图数据转化为LLM可读的上下文文本
*/
private String buildContext(Set<KnowledgeEntity> entities,
List<KnowledgeEntity> seeds) {
StringBuilder sb = new StringBuilder();
sb.append("=== 核心相关实体 ===\n");
for (KnowledgeEntity seed : seeds) {
sb.append(formatEntity(seed)).append("\n");
}
sb.append("\n=== 关联实体 ===\n");
for (KnowledgeEntity entity : entities) {
if (!seeds.contains(entity)) {
sb.append(formatEntity(entity)).append("\n");
}
}
return sb.toString();
}
private String formatEntity(KnowledgeEntity e) {
StringBuilder sb = new StringBuilder();
sb.append("- ").append(e.getName())
.append(" [").append(e.getType()).append("]");
if (e.getDescription() != null) {
sb.append(": ").append(e.getDescription());
}
return sb.toString();
}
public record SearchResult(Set<KnowledgeEntity> entities, String context) {}
}
八、REST API层
package com.demo.graphrag.controller;
import com.demo.graphrag.model.KnowledgeEntity;
import com.demo.graphrag.service.GraphRAGService;
import com.demo.graphrag.service.HybridSearchService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/graphrag")
public class AIGraphController {
private final GraphRAGService graphRAGService;
private final HybridSearchService hybridSearchService;
public AIGraphController(GraphRAGService graphRAGService,
HybridSearchService hybridSearchService) {
this.graphRAGService = graphRAGService;
this.hybridSearchService = hybridSearchService;
}
/**
* 文档摄入:从文本中自动构建知识图谱
*/
@PostMapping("/ingest")
public List<KnowledgeEntity> ingest(@RequestBody IngestRequest request) {
return graphRAGService.ingestDocument(request.text());
}
/**
* 混合检索:结合向量和图的语义查询
*/
@PostMapping("/search")
public HybridSearchService.SearchResult search(@RequestBody SearchRequest request) {
return hybridSearchService.hybridSearch(
request.query(),
request.depth() != 0 ? request.depth() : 2,
request.limit() != 0 ? request.limit() : 10
);
}
public record IngestRequest(String text) {}
public record SearchRequest(String query, int depth, int limit) {}
}
九、端到端使用示例
# 1. 摄入技术文档构建知识图谱
curl -X POST http://localhost:8080/api/graphrag/ingest \
-H "Content-Type: application/json" \
-d '{"text": "Spring Boot是由Pivotal团队开发的Java框架。Rod Johnson是Spring框架的创始人。它内置了Tomcat并集成了Spring Data JPA和Spring Security。"}'
# 2. 图查询:谁是Spring框架的创始人?
curl -X POST http://localhost:8080/api/graphrag/search \
-H "Content-Type: application/json" \
-d '{"query": "Spring Boot的创始人和相关技术栈", "depth": 2, "limit": 10}'
响应示例:
{
"entities": [
{"name": "Spring Boot", "type": "TECHNOLOGY"},
{"name": "Rod Johnson", "type": "PERSON"},
{"name": "Spring Framework", "type": "TECHNOLOGY"},
{"name": "Spring Data JPA", "type": "TECHNOLOGY"},
{"name": "Spring Security", "type": "TECHNOLOGY"},
{"name": "Pivotal", "type": "ORGANIZATION"},
{"name": "Tomcat", "type": "TECHNOLOGY"}
],
"context": "=== 核心相关实体 ===\n- Spring Boot [TECHNOLOGY]: ..."
}
十、工程化要点
| 关注点 | 方案 | 细节 |
|---|---|---|
| 实体去重 | Neo4j MERGE语义 | findByName先查再插,防止重复节点 |
| 向量存储 | Neo4j原生数组属性 | 无需额外向量数据库,减少架构复杂度 |
| 事务一致性 | @Transactional |
实体+关系原子写入 |
| LLM容错 | JSON解析+try/catch | LLM输出格式不稳定时降级处理 |
| 扩展性 | 分片+索引 | Neo4j支持集群部署,支持亿级实体 |
更多推荐


所有评论(0)