dev.langchain4j.data.document.Metadata是 LangChain4j 在 RAG / 文档处理链路里的元数据对象,用于给Document或TextSegment挂载结构化附加信息,比如文件名、来源、更新时间、owner、业务标签等。官方 RAG 教程对它的定义很明确:Metadata是一个key-value map,key 是String,value 支持String、Integer、Long、Float、Double、UUID。(GitHub)
1. 这个类是什么
在 LangChain4j 官方 RAG 文档里,Document持有一个Metadata,它用来保存文档的补充信息,例如名称、来源、最后更新时间、owner,或者任何对检索和注入有帮助的上下文。官方还说明了它不只用于存储,还用于:
- 把元数据一并注入 prompt,帮助模型理解上下文
- 在检索阶段按 metadata 过滤内容
- 在源文档更新时,依据 metadata 快速定位并同步 embedding store 中的对应记录 (GitHub)
所以一句话讲:
Metadata不是正文内容本身,而是文档/分块的“结构化上下文标签”。(GitHub)
2. 关键参数 / 数据结构
严格说,Metadata不是“带很多构造参数的配置类”,而是一个键值容器。它的关键点主要在“允许存什么”和“怎么组织 key”。
支持的 value 类型
官方文档明确列出的支持类型有:
StringIntegerLongFloatDoubleUUID(GitHub)
这意味着你不应该随便往里塞复杂 POJO、List、Map 之类的深层对象,至少从官方公开教程来看,这些不在标准支持集合里。做检索过滤时,优先用简单标量字段最稳。(GitHub)
常见 metadata key
官方没有强制统一 schema,但从教程和示例里,常见 key 包括:
file_nameindexanimaluserId(GitHub)
其中,文档分块后每个TextSegment会继承原始Document的 metadata,并额外带一个index,表示它在文档中的分块位置,从 0 开始。(Javadoc)
3. 方法接口
官方 RAG 教程直接列出了Metadata的常用方法。你可以把它当成这个类最核心的 API 面。(GitHub)
静态工厂
Metadata.from(Map)从一个
Map创建Metadata。(GitHub)
写入方法
Metadata.put(String key, String value)put(String, int)以及其他对应类型的重载
Metadata.putAll(Map)一次性写入多个条目。(GitHub)
读取方法
Metadata.getString(String key)getInteger(String key)以及其他类型读取方法
官方说明这些方法会按所需类型返回对应 entry。(GitHub)
判断 / 删除 / 复制 / 转换
Metadata.containsKey(String key)Metadata.remove(String key)Metadata.copy()Metadata.toMap()Metadata.merge(Metadata)(GitHub)
兼容性提醒
官方旧版 deprecated 列表显示,早期一些基于Object的重载,例如Metadata.metadata(String, Object)、from(String, Object)之类后来被弃用,官方建议改用更明确的 typed 版本。这个信号很重要:LangChain4j 在逐步收紧 Metadata 的类型边界。(Javadoc)
4. 核心方法的具体使用
下面我用接近官方风格的方式讲几个最常用方法。
4.1from(Map):批量创建
适合在 ingestion 时一次性挂上完整元数据。
importdev.langchain4j.data.document.Metadata;Metadatametadata=Metadata.from(Map.of("file_name","employee-handbook.pdf","department","HR","version",3));依据官方教程,这种方式是标准入口之一。(GitHub)
4.2put(...)/putAll(...):逐步补充
适合文档经过多阶段处理时逐层补标签。
Metadatametadata=Metadata.from(Map.of("file_name","policy.txt"));metadata.put("owner","legal-team");metadata.put("year",2025);如果一开始只知道一部分字段,后面再补source、biz_unit、doc_type,这种方式很自然。官方教程明确把put、putAll列为常用方法。(GitHub)
4.3getString(...)/getInteger(...):按类型读取
适合 prompt 注入、自定义过滤、日志审计。
StringfileName=metadata.getString("file_name");Integerversion=metadata.getInteger("version");这里要注意:读取时最好和写入类型保持一致。你如果把某个 key 作为Integer写入,后续就不要把它当String读,这样最稳。官方虽然没在教程里展开异常语义,但 typed getter 的设计本身就说明应当遵守类型一致性。(GitHub)
4.4containsKey(...)/remove(...)
适合做健壮性处理和 metadata 清洗。
if(metadata.containsKey("temp_flag")){metadata.remove("temp_flag");}这类方法在做 ingestion pipeline 时很常见,比如先打一个临时标签,最终写库前清掉。(GitHub)
4.5copy()/merge(...)
适合“基础元数据 + 阶段元数据”的场景。
Metadatabase=Metadata.from(Map.of("file_name","faq.md","lang","zh-CN"));Metadataextra=Metadata.from(Map.of("channel","wechat","biz","support"));Metadatamerged=base.merge(extra);官方把copy()和merge(Metadata)都列为常用方法,说明这是设计时就考虑到的用法。(GitHub)
5. 典型调用链
Metadata单独看很简单,但它真正的价值在调用链里。
5.1 文档加载 → metadata 生成
官方 RAG 教程指出,Document包含Metadata;文档加载器会创建Document,其中 metadata 往往会携带来源信息。(GitHub)
典型形态:
Documentdocument=Document.from(text,metadata);官方教程明确列出了Document.from(String, Metadata)。(GitHub)
5.2 文档切分 → segment 继承 metadata
官方DocumentBySentenceSplitter的 Javadoc 说明:每个TextSegment会继承原始Document的全部 metadata,并新增indexkey。(Javadoc)
这一步很关键,因为后续检索、过滤、prompt 注入,实际往往作用在TextSegment级别,而不是整篇Document。(Javadoc)
5.3 入库 → 向量库携带 metadata
官方 advanced RAG 示例里,TextSegment会被加到EmbeddingStore;示例同时展示了 metadata 被用于过滤和 prompt 注入。(GitHub)
5.4 检索 → 按 metadata 过滤
官方示例中直接用:
FilteronlyDogs=metadataKey("animal").isEqualTo("dog");然后把这个 filter 传给EmbeddingStoreContentRetriever.builder().filter(...),从而把检索限制在某类 segment 上。(GitHub)
5.5 注入 prompt → 指定 metadataKeysToInclude
官方_04_Advanced_RAG_with_Metadata_Example里,用DefaultContentInjector.builder().metadataKeysToInclude(asList("file_name", "index")),把指定 metadata 字段一起注入 prompt,让模型知道内容来自哪个文件、属于第几个片段。(GitHub)
一条完整典型链路
可以概括成:
原始文档 → Document(text + Metadata) → Splitter 切分 → TextSegment 继承 Metadata + index → EmbeddingStore 入库 → ContentRetriever 按 Metadata 检索/过滤 → ContentInjector 把 Metadata 注入 prompt → LLM 回答
这条链路是官方 RAG 教程和两个 advanced metadata 示例共同展示出来的。(GitHub)
6. 适用边界
6.1 适合的场景
文档来源追踪
比如记录:
- 文件名
- URL
- owner
- 部门
- 版本
- 业务线
这正是官方列举 Metadata 用途的核心场景。(GitHub)
检索过滤
比如只检索:
- 某个用户的数据
- 某种文档类型
- 某个主题
- 某个租户的数据
官方示例已给出animal=dog和userId=1这类过滤思路。(GitHub)
Prompt 增强
例如把file_name、index、source一起注入 prompt,帮助模型回答“这条规则出自哪个文件”。官方示例就是这么做的。(GitHub)
6.2 不适合的场景
不适合塞复杂嵌套业务对象
从官方教程公开的支持类型看,Metadata 设计目标是简单标量键值,不是通用 JSON 文档容器。(GitHub)
不适合承载正文
正文仍然应放在Document.text()/TextSegment.text(),不要把一大段文本塞进 metadata。否则既影响过滤价值,也会让注入 prompt 时噪声很大。这个结论是基于官方设计职责作出的工程推断。(GitHub)
不适合 schema 混乱
同一个 key 一会儿写"2025",一会儿写2025,会让 typed getter、过滤条件和下游存储都变得不稳定。虽然官方没有专门写“禁止”,但从 typed API 和过滤示例看,保持 schema 稳定是必要的。(GitHub)
7. 中文场景优化
官方没有单列“中文优化”章节,但结合它给出的 Metadata 用法,可以提炼出几条对中文业务非常实用的做法。
7.1 key 用英文稳定命名,value 保留中文业务语义
推荐:
doc_type = "合同"department = "法务"lang = "zh-CN"region = "华东"
不太建议 key 直接写成长中文句子。
原因是 key 往往还要参与过滤、prompt 模板、甚至不同存储系统的字段映射,英文 snake_case 更稳。这个建议是基于官方 key-value 设计与过滤链路作出的工程实践总结。(GitHub)
7.2 中文业务里优先补这几类字段
对中文企业文档尤其建议补:
file_namesourcedoc_typebiz_unitlangeffective_dateversionownertenant_id
因为这些字段最容易在“跨部门、多版本、同主题不同来源”的中文知识库里发挥作用。官方已经证明file_name、index、userId、主题标签这类字段能直接影响检索和回答质量。(GitHub)
7.3 日期和编号要标准化
中文文档经常会出现:
2025年10月2025/10十月 20252025-10
如果 metadata 要参与过滤,建议统一成一种格式,比如:
- 日期:
yyyy-MM-dd - 月份:
yyyy-MM - 版本:纯整数或统一字符串
- 文号:规范字符串
这属于工程优化建议,但它直接服务于官方支持的 metadata filter 用法。(GitHub)
7.4 中文检索场景把lang显式打上
如果你的知识库里中英混合,建议显式加:
metadata.put("lang","zh-CN");然后在检索器里按语言过滤,避免把英文 FAQ 和中文制度混在一起召回。这个是对官方 filter 机制的直接应用。(GitHub)
8. 一个更贴近中文项目的写法
importdev.langchain4j.data.document.Document;importdev.langchain4j.data.document.Metadata;Metadatametadata=Metadata.from(Map.of("file_name","员工手册-2025版.pdf","doc_type","制度","biz_unit","人力资源","lang","zh-CN","version",2025,"source","hr-policy-repo"));Documentdocument=Document.from(text,metadata);后续切分后,每个 segment 会继承这些字段,并自动带上index。(GitHub)
如果要做过滤:
FilteronlyZhPolicy=metadataKey("lang").isEqualTo("zh-CN");这种用法与官方 metadata filtering 示例是一致的。(GitHub)
如果要把来源一起注入模型:
ContentInjectorinjector=DefaultContentInjector.builder().metadataKeysToInclude(asList("file_name","doc_type","index")).build();这与官方 advanced metadata injection 示例完全同类。(GitHub)
9. 实战建议
如果你把Metadata用在正式项目里,我建议把它当成一份小型 schema 来治理,而不是临时 Map。比较稳的做法是:
- 先定义一组固定 key
- 明确每个 key 的类型
- 约束日期/版本/租户字段格式
- 只把真正有检索价值、注入价值的字段放进去
- 避免“同 key 多种类型”
这套做法和官方 API 设计方向是一致的:Metadata 适合做轻量、稳定、可过滤、可注入的上下文标签。(GitHub)