☰
【必学收藏】Java + Spring AI构建多模态智能交互:从文档解析到知识图谱实战指南(TaoToken统一Key接入版)
2026/10/2 12:00:39 网站建设 项目流程

1. 为什么 Java 开发者需要一条多模态文档解析到知识图谱的链路

很多 Java 团队手里已经有一堆 Spring Boot 服务,业务数据散落在 PDF 合同、扫描件、Excel 报表和图片里。传统做法是写正则、写 POI、写 OCR 后处理,规则一多就维护不动。现在更省事的思路是:把文档解析、实体关系抽取、知识图谱写入、多模态问答串成一条链路,让模型负责“理解”,Java 负责“编排”。

这条链路能做什么?简单说三件事。第一,把 PDF、图片、表格里的文字和结构读出来;第二,从文本里抽人名、公司、金额、时间、条款这类实体和关系,写进图数据库;第三,用户用自然语言提问时,先检索图谱和向量库,再让模型生成回答。适合谁?适合已经会 Spring Boot、想快速把 AI 能力接进现有系统的 Java 后端,也适合做企业知识库、合同审查、报表问答的团队。

我试过用纯手写规则做合同抽取,字段一改就崩。换成 Spring AI 加统一模型通道后,解析和抽取的代码量下降明显,重点变成调提示词和校验图谱结构。下面按“环境准备 → 文档解析 → 图谱写入 → 模型接入 → 验证 → 排障”的顺序走一遍,代码都可以直接复制改。

核心检索词先明确:Java、Spring AI、多模态、文档解析、知识图谱。这几个词会贯穿全文,你按标题场景一步步跟做即可。

2. TaoToken 统一 Key 接入 Spring AI 的前置准备与依赖配置

Spring AI 的好处是模型调用被抽象成 ChatModel、EmbeddingModel 这些接口,换模型不用改业务代码。但多模态场景往往要调不同能力的模型:解析图片要视觉模型,抽实体要文本模型,做向量要 embedding 模型。如果每个模型都单独申请 Key、单独配地址,配置会非常散。

统一 Key 通道的价值就在这里:一个 Base URL、一个 API Key,通过 model 参数切换不同模型。TaoToken 提供的就是这种 OpenAI 兼容风格的入口,Spring AI 的 OpenAI starter 可以直接对接。你需要准备三样东西:Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api,API Key 在控制台创建,Model ID 按你实际要用的模型填。

先建一个 Spring Boot 3.x 项目,JDK 17 以上。pom.xml 里加这些依赖,版本按你项目实际调整:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0-M6</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-pdf-document-reader</artifactId> <version>1.0.0-M6</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>5.26.0</version> </dependency> </dependencies>

注意 Spring AI 的版本迭代较快,M6 之后包名和配置项可能有变化,遇到类找不到先核对官方迁移说明。依赖装好后,配置文件里把统一通道写进去。application.yml 示例:

spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.2 embedding: options: model: text-embedding-3-small

这里把 Key 放到环境变量里,别硬编码进仓库。启动前设置export TAOTOKEN_API_KEY=你的Key。Model ID 按你控制台里可用的模型填,文本、视觉、embedding 各选一个。这样 Spring AI 会自动注入 OpenAiChatModel 和 OpenAiEmbeddingModel,后面解析、抽取、问答都用同一套通道。

如果你更习惯用 coding plan 做长期编码任务,也可以在控制台里看对应套餐,但本文聚焦 API 接入,先把 Key 和 Base URL 跑通。

3. 可复制的文档解析与知识图谱写入配置片段

这一节是重点,给你能直接落地的配置和代码。文档解析分两类:文本型 PDF 用 PdfDocumentReader 直接读;扫描件和图片要先走视觉模型做 OCR 或描述,再进文本链路。表格单独处理,因为 PDF 表格抽出来容易串行。

先看 PDF 解析。Spring AI 的 DocumentReader 把每页读成 Document 对象,带 metadata:

@Configuration public class DocumentConfig { @Bean public PdfDocumentReader pdfDocumentReader() { return new PdfDocumentReader( "classpath:/docs/sample-contract.pdf", new ParagraphPdfDocumentReaderConfig() ); } }

实际项目里路径是动态的,建议封装成服务方法,传入 InputStream。解析出来的 Document 列表,每页文本送进实体抽取。抽取用 ChatClient 加结构化输出,提示词要求返回 JSON:

@Service public class EntityExtractionService { private final ChatClient chatClient; public EntityExtractionService(ChatClient.Builder builder) { this.chatClient = builder.build(); } public GraphPayload extract(String text) { String prompt = """ 从下面文本中抽取实体和关系,只返回 JSON,不要解释。 格式:{"entities":[{"name":"","type":""}], "relations":[{"source":"","target":"","type":""}]} 文本: """ + text; return chatClient.prompt() .user(prompt) .call() .entity(GraphPayload.class); } }

GraphPayload 用 record 或普通类定义,字段和 JSON 对齐。拿到实体关系后写 Neo4j。配置片段:

neo4j: uri: bolt://localhost:7687 username: neo4j password: ${NEO4J_PASSWORD}

写入代码用 MERGE 避免重复节点:

public void save(GraphPayload payload) { try (Session session = driver.session()) { for (Entity e : payload.entities()) { session.run( "MERGE (n:Entity {name: $name}) SET n.type = $type", Map.of("name", e.name(), "type", e.type()) ); } for (Relation r : payload.relations()) { session.run(""" MATCH (a:Entity {name: $source}) MATCH (b:Entity {name: $target}) MERGE (a)-[rel:REL {type: $type}]->(b) """, Map.of("source", r.source(), "target", r.target(), "type", r.type()) ); } } }

图片和表格怎么接?图片走视觉模型,把图片转 base64 或传 URL,提示词要求“描述图片内容并抽取其中文字”。表格建议先用 PDFBox 的表格提取或 Tabula 转成 CSV,再按行拼成文本送抽取,比直接让模型读整页 PDF 稳。多模态的关键是:不同模态先归一化成文本或结构化数据,再进同一条图谱链路。

配置里还有几个参数值得调:temperature 设 0.1 到 0.2,抽取任务要稳定;max-tokens 按文档长度设,太长会截断;embedding 维度要和向量库一致。这些都在 application.yml 的 chat.options 和 embedding.options 下。

4. 验证多模态问答链路:从请求到成功结果

配置写完要验证,别等全写完再跑。分三步验证:先验证模型通道通不通,再验证解析抽取,最后验证图谱问答。

第一步,写一个最小 Controller 测模型:

@RestController public class PingController { private final ChatModel chatModel; public PingController(ChatModel chatModel) { this.chatModel = chatModel; } @GetMapping("/ping") public String ping() { return chatModel.call("用一句话说明什么是知识图谱"); } }

启动后访问http://localhost:8080/ping,能返回中文句子就说明 Base URL、Key、Model ID 三件套正确。如果返回 401,先查 Key;如果连接超时,查 Base URL 是否写成了带路径的错误形式。

第二步,验证解析和抽取。放一个测试 PDF 到 resources/docs,调抽取服务,打印 GraphPayload。成功结果是 entities 里出现公司名、金额、日期,relations 里出现“签署”“属于”这类关系。如果 entities 为空,多半是提示词太宽松或文本太长被截断,把文本按页切分再抽。

第三步,验证图谱问答。用户问“这份合同里甲方是谁”,链路是:问题先做 embedding,去向量库或图谱检索相关实体,再把检索结果拼进提示词让模型回答。检索部分可以用 Neo4j 全文索引,也可以把实体描述做向量存起来。问答接口示例:

@PostMapping("/ask") public String ask(@RequestBody String question) { List<String> context = graphRetriever.retrieve(question); String prompt = "根据以下资料回答,不知道就说不知道:\n" + String.join("\n", context) + "\n问题:" + question; return chatModel.call(prompt); }

成功结果是回答里引用了图谱中的实体名,而不是泛泛而谈。如果模型开始编造,把 temperature 降到 0,并在提示词里强调“只依据资料”。

验证时建议用 curl 或 Postman 固定请求体,方便复现:

curl -X POST http://localhost:8080/ask \ -H "Content-Type: application/json" \ -d '{"question":"合同金额是多少"}'

返回 JSON 里能看到答案和命中的实体。到这一步,多模态问答链路就算跑通了。

5. 本篇常见报错排查:401、local proxy failed 与 reading choices

接入过程里报错集中在几个地方,逐个说。

401 Unauthorized。最常见原因是 Key 没设进环境变量,或者 application.yml 里写了占位符没替换。检查echo $TAOTOKEN_API_KEY是否有值,再确认 base-url 是https://taotoken.net/api,不要多加/v1或结尾斜杠。Spring AI 的 OpenAI starter 会自己拼路径,多写反而 404 或 401。

local proxy failed。这个报错通常出现在本机网络环境有额外代理设置时,Java 进程读到了系统代理变量。检查HTTP_PROXY、HTTPS_PROXY是否被设置,必要时在启动参数里排除,或确认本机网络能直连目标地址。注意不要用任何非正规网络工具,企业环境走正规出口即可。

reading choices 相关报错,比如Cannot deserialize value of type ... from Object value (token 'JsonToken.START_OBJECT')或reading choices失败。这多半是模型返回的 JSON 结构和你的实体类不匹配。比如模型返回了 markdown 代码块包裹的 JSON,或者字段名对不上。解决办法:提示词里明确“只返回 JSON,不要 markdown”,实体类字段用@JsonProperty对齐,必要时加容错解析,先取choices[0].message.content再二次解析。

OAuth 或 token 过期类报错。如果你用的是需要刷新 token 的通道,检查 Key 是否过期,重新在控制台生成。Spring AI 默认用静态 Key,不处理刷新,过期就换。

还有一类是 Neo4j 连接失败,报Unable to connect to bolt://localhost:7687。确认 Neo4j 已启动,密码对,端口没被占。图谱写入失败时,先单独跑一条 MERGE 语句验证连接。

排查顺序建议:先 ping 模型,再测解析,再测图谱,最后测问答。哪一步断就在哪一步查,别一次改多处。

6. 把链路接进现有 Spring Boot 服务的下一步

链路跑通后,下一步是工程化。把解析、抽取、写入做成异步任务,用消息队列解耦,避免大文件阻塞请求线程。图谱查询加缓存,热点实体不用每次查库。提示词抽到配置中心,方便按业务调,不用重新发版。

模型通道方面,统一 Key 的好处是换模型只改配置。文本模型、视觉模型、embedding 模型各留一个 Model ID 配置项,按场景切换。长期做编码和 Agent 任务的话,可以在控制台看 coding plan 是否合适;只是验证模型效果,用模型对话页面快速试提示词更省事。

接入文档和 API Key 管理都在控制台里,建议把 Key 按环境分开,测试和生产的 Key 不要混用。文档解析的边界情况很多,扫描件质量差、表格跨页、字体嵌入异常都会影响抽取,实际项目里要留人工校验入口,别全自动写库。

最后给一个实用技巧:抽取结果先落一张中间表,人工确认后再写图谱。这样即使模型抽错,也不会污染图谱。等准确率稳定了,再逐步放开自动写入。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询