第2478篇:知识工程在AI时代的复兴——本体论和推理在LLM时代的新价值
2026/4/30大约 6 分钟
第2478篇:知识工程在AI时代的复兴——本体论和推理在LLM时代的新价值
适读人群:架构师、AI工程师、对知识图谱感兴趣的工程师 | 阅读时长:约16分钟 | 核心价值:理解知识图谱和本体论如何与LLM互补,构建更可靠的企业AI系统
知识图谱这个词,在LLM出现之前是AI领域的一个大方向。谷歌、百度都在大力建设知识图谱。但LLM大爆发之后,很多人觉得知识图谱"没用了"——反正LLM什么都知道,要知识图谱干什么?
这个判断是片面的。我见过把知识图谱和LLM结合起来的系统,比纯LLM的效果好很多,特别是在需要精确推理的企业场景里。
LLM的局限和知识图谱的补充
LLM有几个系统性的局限:
局限一:知识是静态的。LLM的训练数据有截止时间,对于实时变化的数据(如当前的产品库存、最新的政策文件),LLM不知道。
局限二:关系推理不可靠。LLM在处理需要精确推理的问题时容易出错,比如"A大于B,B大于C,A和C是什么关系"——这类简单的传递性推理,LLM有时候会答错。
局限三:可解释性差。LLM给出答案但无法解释推理路径,在金融、法律、医疗等需要解释决策的场景里不被接受。
局限四:幻觉问题。LLM会生成听起来合理但实际错误的信息,在需要高精度的场景里代价很高。
知识图谱能补充这些局限:它存储精确的实体关系,能做确定性推理,有完整的推理链条,且数据是可以实时更新的。
GraphRAG:知识图谱+LLM的实践
GraphRAG是微软提出的一种将知识图谱和RAG结合的方法。基本思路:在RAG的检索阶段,不只检索文本片段,还检索知识图谱中的相关实体和关系,把图谱信息加入到LLM的上下文。
核心实现
1. 知识图谱构建
@Service
public class KnowledgeGraphBuilder {
private final Neo4jDriver neo4jDriver;
private final ChatClient chatClient;
/**
* 从非结构化文本中提取实体和关系,构建知识图谱
*/
public void buildFromDocuments(List<Document> documents) {
for (Document doc : documents) {
// 用LLM提取实体和关系
KnowledgeTriples triples = extractTriples(doc.getContent());
// 存入Neo4j
storeTriples(triples, doc.getId());
}
}
private KnowledgeTriples extractTriples(String text) {
String extractionPrompt = """
从以下文本中提取知识三元组(实体-关系-实体):
文本:
%s
提取规则:
1. 只提取明确陈述的事实,不要推断
2. 实体要规范化(同义词用同一个名称)
3. 关系要具体(不要用"相关"这样模糊的关系)
4. 属性关系也要提取(如"产品X的价格是100元")
返回JSON:
{
"entities": [{"name": "实体名", "type": "类型", "properties": {}}],
"relations": [{"subject": "实体A", "predicate": "关系", "object": "实体B"}]
}
""".formatted(text);
ChatResponse response = chatClient.call(new Prompt(
new UserMessage(extractionPrompt),
OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.1f)
.withResponseFormat(new ResponseFormat(ResponseFormat.Type.JSON_OBJECT))
.build()
));
return parseKnowledgeTriples(response.getResult().getOutput().getContent());
}
private void storeTriples(KnowledgeTriples triples, String sourceDocId) {
try (Session session = neo4jDriver.session()) {
// 存储实体
for (Entity entity : triples.getEntities()) {
session.run(
"MERGE (e:Entity {name: $name, type: $type}) " +
"SET e += $properties, e.sourceDoc = $sourceDoc",
Map.of(
"name", entity.getName(),
"type", entity.getType(),
"properties", entity.getProperties(),
"sourceDoc", sourceDocId
)
);
}
// 存储关系
for (Relation relation : triples.getRelations()) {
session.run(
"MATCH (a:Entity {name: $subject}), (b:Entity {name: $object}) " +
"MERGE (a)-[r:RELATION {type: $predicate}]->(b) " +
"SET r.sourceDoc = $sourceDoc",
Map.of(
"subject", relation.getSubject(),
"predicate", relation.getPredicate(),
"object", relation.getObject(),
"sourceDoc", sourceDocId
)
);
}
}
}
}2. GraphRAG检索
@Service
public class GraphRAGRetriever {
private final Neo4jDriver neo4jDriver;
private final VectorStore vectorStore;
private final ChatClient chatClient;
/**
* 结合知识图谱和向量检索,获取更丰富的上下文
*/
public RetrievedContext retrieve(String userQuery) {
// 1. 从问题中提取关键实体
List<String> queryEntities = extractQueryEntities(userQuery);
// 2. 在知识图谱中查找实体及其相关关系
GraphContext graphContext = queryGraphForEntities(queryEntities);
// 3. 传统向量检索
List<Document> vectorDocs = vectorStore.similaritySearch(
SearchRequest.query(userQuery).withTopK(5)
);
// 4. 融合两种检索结果
return RetrievedContext.builder()
.graphContext(graphContext)
.vectorDocuments(vectorDocs)
.entities(queryEntities)
.build();
}
private List<String> extractQueryEntities(String query) {
String prompt = """
从以下问题中提取关键实体名称(人名、产品名、组织名、地名等):
问题: %s
只返回实体名称列表,用逗号分隔。
""".formatted(query);
String result = chatClient.call(new Prompt(new UserMessage(prompt)))
.getResult().getOutput().getContent();
return Arrays.stream(result.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(toList());
}
private GraphContext queryGraphForEntities(List<String> entities) {
if (entities.isEmpty()) {
return GraphContext.empty();
}
try (Session session = neo4jDriver.session()) {
// 查询实体及其2跳以内的关系
Result result = session.run(
"MATCH (e:Entity) WHERE e.name IN $names " +
"OPTIONAL MATCH path = (e)-[r*1..2]-(related:Entity) " +
"RETURN e, path, related LIMIT 100",
Map.of("names", entities)
);
return buildGraphContext(result);
}
}
private GraphContext buildGraphContext(Result neo4jResult) {
List<String> facts = new ArrayList<>();
Set<String> processedRelations = new HashSet<>();
while (neo4jResult.hasNext()) {
Record record = neo4jResult.next();
// 提取路径中的关系
if (record.containsKey("path") && !record.get("path").isNull()) {
Path path = record.get("path").asPath();
path.relationships().forEach(rel -> {
String relKey = rel.startNodeId() + "->" + rel.endNodeId();
if (!processedRelations.contains(relKey)) {
processedRelations.add(relKey);
String startName = rel.startNode().get("name").asString();
String relType = rel.get("type").asString();
String endName = rel.endNode().get("name").asString();
facts.add(String.format("%s %s %s", startName, relType, endName));
}
});
}
}
return GraphContext.of(facts);
}
}3. 融合生成
@Service
public class GraphRAGGenerator {
private final ChatClient chatClient;
private final GraphRAGRetriever retriever;
public GraphRAGResponse generate(String userQuery) {
// 检索上下文
RetrievedContext context = retriever.retrieve(userQuery);
// 构建增强的prompt
String augmentedPrompt = buildAugmentedPrompt(userQuery, context);
ChatResponse response = chatClient.call(new Prompt(
List.of(
new SystemMessage("""
你是一个知识问答助手。回答问题时:
1. 优先使用知识图谱中的精确事实
2. 对于不在知识图谱中的信息,使用参考文档
3. 如果两者信息冲突,以知识图谱为准(更可靠)
4. 明确区分确定的事实和推断的内容
"""),
new UserMessage(augmentedPrompt)
)
));
return GraphRAGResponse.builder()
.answer(response.getResult().getOutput().getContent())
.usedGraphFacts(context.getGraphContext().getFacts())
.usedDocuments(context.getVectorDocuments().stream()
.map(Document::getId).collect(toList()))
.build();
}
private String buildAugmentedPrompt(String query, RetrievedContext context) {
StringBuilder sb = new StringBuilder();
sb.append("问题: ").append(query).append("\n\n");
if (!context.getGraphContext().isEmpty()) {
sb.append("知识图谱中的相关事实(高可信度):\n");
context.getGraphContext().getFacts().forEach(fact ->
sb.append("- ").append(fact).append("\n")
);
sb.append("\n");
}
if (!context.getVectorDocuments().isEmpty()) {
sb.append("参考文档(可能相关):\n");
context.getVectorDocuments().forEach(doc ->
sb.append(doc.getContent()).append("\n---\n")
);
}
return sb.toString();
}
}知识图谱+LLM的适用场景
并不是所有场景都需要知识图谱。适合引入知识图谱的场景特征:
- 实体关系复杂:产品、人员、组织、合同之间有多种关系,需要精确追踪
- 需要推理链:不只是"查什么",还需要"通过几步推理得出结论"
- 数据频繁更新:产品价格、库存、政策规定等实时变化的信息
- 可解释性要求高:需要展示答案是从哪些事实推导出来的
适合纯LLM的场景:开放性的知识问答、内容创作、代码辅助——这些不需要精确的实体追踪。
