- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
在基于向量检索的 RAG(Retrieval-Augmented Generation)方案中,纯文本块召回难以捕捉"实体—关系—实体"之间的结构化语义。com.openjiuwen.retrieval.common.Triple是 openJiuwen agent-core-java 检索模块中承载知识图谱三元组的核心数据模型,它以subject(主语)、predicate(谓词)、object(宾语)三个必填字段和可选的metadata元数据,为GraphKnowledgeBase的图模式索引构建与图扩展检索提供了统一的数据载体。读完本文,你可以掌握Triple的字段定义、构造与校验规则、元数据使用惯例,以及它在三元组抽取、图索引构建、图扩展检索整条链路中的具体作用,从而在 Java 项目里正确构建并调试图谱增强检索能力。
一、Triple 在检索模块中的定位
Triple位于 Triple.java,其类注释标明它镜像 Python 版openjiuwen/core/retrieval/common/triple.py中的同名类,属于 Java 版与 Python 版行为对齐(parity)的实现。该模型在整个检索链路中承担"关系原子"的角色:
- 索引侧:
GraphKnowledgeBase开启图模式(useGraph=true)后,切分出的TextChunk会经由Extractor抽取为List<Triple>,再由知识库转换为文本块写入独立的三元组索引; - 检索侧:
GraphRetriever通过三元组索引召回相关关系,并借助三元组中的实体首尾做图扩展(beam search),最终把扩展到的文本块与原召回结果做 RRF 融合; - 配套模型:与
Triple同包下的 TripleBeam.java(一条三元组路径及其评分)和 TripleMemory.java(AgenticRetriever 中用于多轮改写时缓存已见三元组)都围绕该模型工作。
原文档(Triple.md)说明:"subject、predicate、object均不能为空;GraphKnowledgeBase图模式索引与检索依赖该模型。"下面结合源码逐层展开。
二、字段结构与构造规则
2.1 字段一览
从 Triple.java 的源码看,Triple是一个普通 POJO,包含四个成员:
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
subject | String | 是 | 主语,即关系的起点实体 |
predicate | String | 是 | 谓词,即实体间的关系描述 |
object | String | 是 | 宾语,即关系的终点实体 |
metadata | Map<String, Object> | 否 | 附加元数据,默认初始化为空的LinkedHashMap,用于携带doc_id、chunk_id等溯源信息 |
需要说明的是:原文档描述中提到"可选置信度",但从当前 Java 源码看,Triple并未单独定义 confidence 字段;如果需要保存置信度一类的数值信息,可以推断应通过metadata中的自定义键承载,这一点请以实际代码为准。
2.2 构造器与校验
Triple提供三个构造入口(见 Triple.java):
public Triple() { } public Triple(String subject, String predicate, String object) { this(subject, predicate, object, null); } public Triple(String subject, String predicate, String object, Map<String, Object> metadata) { this.subject = requireText(subject, "subject"); this.predicate = requireText(predicate, "predicate"); this.object = requireText(object, "object"); setMetadata(metadata); }校验逻辑由私有静态方法requireText完成(Triple.java):
private static String requireText(String value, String fieldName) { if (value == null) { throw RetrievalExceptions.validation(fieldName + " is required"); } return value; }几个使用上需要注意的行为:
- 空值快速失败:
subject、predicate、object任一为null时,构造器立即抛出RetrievalExceptions.validation生成的BaseError(错误信息形如subject is required),属于"构造即校验"的防御式设计; - metadata 防御性拷贝:
setMetadata会对入参做一次浅拷贝(new LinkedHashMap<>(metadata)),传null时回退为空LinkedHashMap。这保证外部修改原始 Map 不会影响已构造的Triple内部状态; - LinkedHashMap 保持插入序:元数据采用
LinkedHashMap而非HashMap,遍历metadata时顺序与插入顺序一致,便于日志排查和序列化输出稳定; - 全量 getter/setter:三个文本字段与
metadata均提供标准访问器,setter 不做二次校验,因此在运行时通过setSubject(null)写入空值是允许的,校验只发生在构造阶段。
基本用法示例:
// 最简构造,metadata 为空 Map Triple triple = new Triple("Alice", "knows", "Bob"); // 带溯源元数据的构造 Triple withMeta = new Triple( "Elden Ring", "publisher", "FromSoftware", Map.of("doc_id", "doc_1", "chunk_id", "chunk_7"));以上用法与单元测试 TripleTest.java 完全一致:测试createTripleUsesEmptyMetadataDefault验证了三参构造后metadata为空,createTripleWithMetadataStoresMetadata验证了元数据完整存储,missingRequiredFieldsAreRejected则断言(null, null, null)、("Alice", null, null)、("Alice", "knows", null)三种缺字段场景均抛出BaseError。
三、metadata 的约定键:溯源三元组来源
Triple本身只是载体,真正的语义来自metadata中约定俗成的键。在仓库中,两个抽取器和知识库都统一使用了doc_id与chunk_id两个键,用于把三元组回溯到来源文档与文本块:
TripleExtractor.parseTriples在解析 LLM 输出后,为每个三元组写入Map.of("doc_id", docId, "chunk_id", chunkId)(见 TripleExtractor.java);LLMTripleExtractor.parseTriples在复制 chunk 原有 metadata 的基础上再覆盖写入doc_id、chunk_id(见 LLMTripleExtractor.java);SimpleTripleExtractor同样遵循该惯例,并额外写入序列化后的triple键(见下文)。
这种"约定键"机制使三元组索引中的每条记录都能反查回kb_<kbId>_chunks中的原文块,是后续图扩展检索(由三元组找回关联 chunk)能够成立的根基。
四、Triple 的生成:三种 Extractor 实现
Triple实例不是手工逐条构造的,而是由索引管线的Extractor从TextChunk批量抽取而来。retrieval模块提供了三种实现(基类为 Extractor.java):
4.1 TripleExtractor:基于 LlmInvoker 的 LLM 抽取
TripleExtractor.java 是功能最完整的实现:
- 提示词工程:内置
PROMPT_TEMPLATE(L39-L144),要求 LLM 从"标题 + 段落"中构造 RDF 风格图,输出严格 JSON:顶层恰好含named_entities与triples两个键,triples中每项是恰好三个字符串的数组;模板还内置了两个 few-shot 示例(Magic Johnson、Elden Ring)并要求"能解析代词为具体名称""不要输出重复三元组"等; - 并发控制:每个 chunk 的抽取任务提交到名为
triple-extractor-io的有界模块线程池(OpenJiuwenExecutors.newBoundedModulePool),并用Semaphore限制最大并发(默认 50); - 容错解析:
parseTriples先做normalizeJsonContent清理(剥离 Markdown 代码围栏、去除尾逗号),再逐项校验三元组是否为恰好三元素的数组且三个元素均非 null/容器节点,非法项计数并告警跳过;解析整体失败时抛出RETRIEVAL_KB_TRIPLE_EXTRACTION_PROCESS_ERROR; - 单 chunk 失败策略:单个 chunk 抽取失败会被记录为该 chunk 的错误并在
mergeResults中抛出首个错误,同时保留其他 chunk 已成功抽取的三元组逻辑上可见于日志。
构造参数为TripleExtractor(LlmInvoker llmClient, String modelName)或带temperature(默认 0.0)与maxConcurrent的四参形式,其中LlmInvoker是类内定义的函数式接口,便于测试注入 mock 客户端。
4.2 LLMTripleExtractor:直连 BaseModelClient 的变体
LLMTripleExtractor.java 与上述实现同源,区别在于它直接依赖BaseModelClient,并通过自建的ThreadPoolExecutor(核心 0、最大maxConcurrent、SynchronousQueue、CallerRunsPolicy)并发抽取。其extractJson/repairJson对 LLM 输出做了更强的兜底:先剥离代码围栏,再按"最外层数组优先、否则取最外层对象"的策略截取 JSON 片段,最后用正则修复尾逗号。解析成功后同样以node.get(0/1/2).asText().trim()生成Triple,并把 chunk 原有 metadata 合并进三元组元数据。
4.3 SimpleTripleExtractor:无 LLM 的规则兜底
SimpleTripleExtractor.java 不调用任何模型,按句子边界(.!?。!?之后的空白,见SENTENCE_SPLIT)切分文本,将每句按空白拆词:第 1 个词作subject、第 2 个词作predicate、其余拼作object。它显式把序列化后的三元组写入metadata.put("triple", ...),供下游直接解析。该实现适合作为离线测试、低质量语料的兜底方案,其输出质量明显低于 LLM 抽取,选型时应注意。
三者对应的 API 文档见 LLMTripleExtractor.md 与 SimpleTripleExtractor.md。
五、下游消费:GraphKnowledgeBase 如何把 Triple 变成索引
GraphKnowledgeBase.java 是Triple最主要的生产者和消费者。
5.1 索引构建链路
addDocuments的流程(L76-L129)为:
- 校验
chunker、indexManager非空,缺少时抛出RETRIEVAL_KB_CHUNKER_NOT_FOUND/RETRIEVAL_KB_INDEX_MANAGER_NOT_FOUND; - 若文档无 id,则用
UUID.randomUUID()补齐; chunker.chunkDocuments切分后,以kb_<kbId>_chunks为索引名构建 chunk 索引;- 仅当
config.isUseGraph() == true且注入了extractor时,才执行extractor.extract(chunks),并把得到的List<Triple>交给buildTripleIndex。
buildTripleIndex与tripleChunks展示了Triple到存储层的转换细节(L313-L352):
private List<TextChunk> tripleChunks(List<Triple> triples) { for (int index = 0; index < triples.size(); index++) { Triple triple = triples.get(index); String text = triple.getSubject() + " " + triple.getPredicate() + " " + triple.getObject(); Map<String, Object> metadata = new LinkedHashMap<>(triple.getMetadata()); metadata.put("triple", serializeTriple(triple)); // JSON: ["s","p","o"] metadata.put("chunk_index", index); ... chunks.add(new TextChunk(UUID.randomUUID().toString(), text, docId, metadata)); } }要点:
- 每个
Triple被拼成"subject predicate object"形式的纯文本作为向量化/BM25 的索引文本; - 原始三元组以 JSON 数组字符串形式存入
metadata.triple,这是GraphRetriever后续 beam 搜索时重建实体边的关键(parseTripleStrict会按 JSON 数组反解析该字段); metadata.chunk_id直接继承自抽取阶段写入的来源 chunk id,形成"三元组 → 原文块"的反向指针;- 三元组索引用
kb_<kbId>_triples命名(tripleIndexName()),与 chunk 索引kb_<kbId>_chunks分离存储。
5.2 删除链路
deleteDocuments(L191-L225)对每个docId先删 chunk 索引,若useGraph为真则再删kb_<kbId>_triples中的对应记录;三元组删除失败仅记录warn日志,不阻断整体流程。updateDocuments则是"先删后加"的组合操作,天然保证三元组索引随文档更新而刷新。
5.3 统计能力
getStatisticsAsync会在useGraph=true时同时返回chunk_index_info与triple_index_info,便于运维侧核对三元组索引规模(L237-L261)。
六、检索侧:三元组索引如何驱动图扩展
GraphKnowledgeBase.retrieve在useGraph生效时构造 GraphRetriever,其graphExpansion逻辑(L191-L287)完整体现了三元组索引的检索价值:
- 先召回 chunk:按
vector/sparse/hybrid模式从 chunk 索引取初始结果; - 由 chunk 反查三元组:
fetchTriples收集命中 chunk 的chunk_id,以chunk_ids过滤条件从三元组索引拉取候选三元组; - beam search 图扩展:
TripleBeamSearch(同文件内 L581 起)把每条三元组的metadata.triple解析回[subject, predicate, object],取首尾两个实体作为查询词继续检索相邻三元组,逐跳扩展出TripleBeam路径(默认num_beams=10、num_candidates_per_beam=100、max_length即graph_hops默认 2,encoder_batch_size=256),扩展路径的得分用 query 向量与路径拼接文本向量的余弦相似度计算; - 回捞并融合:扩展出的三元组再经
fetchChunks反查回原文块,与新 chunk 一起通过FusionUtils.rrfFusionRetrieval(RRF k=60)融合,最后截断到topK。
整个过程中Triple的metadata.chunk_id与metadata.triple两个约定键被反复使用——前者保证"三元组↔原文块"的双向可达,后者保证实体边的可解析性。此外RetrievalConfig中的graphExpansion开关会经optionsFrom透传给检索器(L354-L364),开启agentic时则由AgenticRetriever接管,其中TripleMemory(TripleMemory.java)以小写归一化的三元组字符串去重,把已见关系以(s p o)文本形式喂给查询改写模型,避免多轮循环检索重复引用同一关系。
相关 API 文档可进一步参阅 GraphRetriever.md、TripleBeamSearch.md、TripleBeam.md、TripleMemory.md 与 GraphKnowledgeBase.md。
七、工程实践要点小结
结合源码与测试,使用Triple时有几条可直接落地的建议:
- 构造时机:优先使用带 metadata 的四参构造器;若手工构造三元组(如从外部图谱导入),务必自行补齐
doc_id/chunk_id,否则图扩展阶段将失去回捞原文块的能力; - 空值语义:校验只拦
null不拦空串,若上游 LLM 可能返回空字符串三元组,建议在进入GraphKnowledgeBase前自行过滤; - metadata 可变性:
getMetadata()返回的是内部引用(setter 做了拷贝,getter 未做),外部代码应避免直接修改已注册三元组的 metadata,以免污染索引构建时的序列化输出; - 抽取器选型:正式图谱构建用
TripleExtractor/LLMTripleExtractor(质量高、有并发与容错),离线调试或无模型环境用SimpleTripleExtractor兜底; - 验证方式:修改
Triple相关逻辑后,可运行 TripleTest.java 与 GraphKnowledgeBaseTest.java(其中使用KnowledgeBaseConfig.builder().kbId("graph").useGraph(true).build()验证图模式链路)做回归确认。
八、相关文档索引
- 模型文档:Triple.md、TextChunk.md、RetrievalConfig.md、KnowledgeBaseConfig.md
- 抽取器文档:Extractor.md、LLMTripleExtractor.md、SimpleTripleExtractor.md
- 检索文档:GraphKnowledgeBase.md、GraphRetriever.md
综上,Triple虽是一个字段极少的轻量模型,但它是 agent-core-java 图谱增强检索的枢纽数据结构:其"构造即校验"保证了索引数据的干净性,metadata中doc_id/chunk_id/triple三枚约定键串联起"抽取 → 索引 → 图扩展 → 回捞融合"的完整链路,是理解并定制GraphKnowledgeBase行为不可绕开的一环。
- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
相关推荐
openJiuwen agent-core 知识图谱三元组数据模型 Triple 深度解析:从字段定义到图检索全链路实践
openJiuwen agent core 知识图谱三元组数据模型 Triple 深度解析:从字段定义到图检索全链路实践 本文围绕 openJiuwen age
人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习openJiuwen agent-core-java 图知识库 GraphKnowledgeBase 实战指南:三元组抽取、图索引与多跳扩展检索
openJiuwen agent core java 图知识库 GraphKnowledgeBase 实战指南:三元组抽取、图索引与多跳扩展检索 本文以 com
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习openJiuwen agent-core Extractor 抽象基类解析:LLM 驱动的 RDF 三元组抽取与知识图谱索引
openJiuwen agent core Extractor 抽象基类解析:LLM 驱动的 RDF 三元组抽取与知识图谱索引 本篇文章聚焦 openJiuwe
人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考