Spring AI + LangGraph4j开发智能文档问答与知识管理智能体

内容分享4小时前发布
0 4 0

该智能体分为:「文档解析→知识提取→智能问答→动态更新」,智能体覆盖企业常见的知识库建设场景(如产品手册、内部制度、技术文档问答),核心亮点是 支持多格式文档、知识结构化存储、上下文记忆问答,同时体现 LangGraph4j 的「条件分支流转」和「状态回溯」能力。

一、项目核心目标

用户上传文档(PDF/Word/Markdown)或输入自然语言问题(如 “产品 X 的 API 调用限制是什么?”),智能体自动完成:

  1. 文档处理:解析上传的多格式文档,提取文本内容(处理表格、图片文字 OCR);
  2. 知识提取:将非结构化文本转化为结构化知识(如 FAQ、实体关系、关键要点);
  3. 知识存储:将结构化知识存入向量数据库(支持类似性检索);
  4. 智能问答:接收用户问题,结合上下文和知识库,生成精准回答(附引用来源);
  5. 知识更新:支持文档增量上传,自动更新知识库,避免重复存储;
  6. 上下文记忆:连续对话中记住历史问题,支持多轮追问(如 “这个限制是否适用于企业版?”)。

二、技术栈选型

组件

作用

版本提议

Spring Boot

项目骨架、Web 接口、依赖管理

3.2.x

Spring AI

大模型调用、工具封装、向量嵌入生成

1.0.0+

LangGraph4j

流程编排、状态管理、多轮对话上下文

0.1.0+

Apache Tika

多格式文档解析(PDF/Word/Markdown)

2.9.x

Tesseract OCR

图片文字提取(处理文档中的截图 / 图片)

5.3.x

Pinecone/Qdrant

向量数据库(存储结构化知识 + 类似性检索)

最新稳定版(云服务 / 本地部署)

Spring Data JPA

存储文档元数据(上传人、时间、标签)

3.2.x

Redis

缓存对话上下文、临时文件路径

7.0+

Hutool

工具类(文件操作、字符串处理)

5.8.x

Swagger/knife4j

接口文档(便于测试上传 / 问答接口)

最新稳定版

Spring AI + LangGraph4j开发智能文档问答与知识管理智能体

三、核心架构设计

基于 LangGraph4j 的「状态机 + 条件分支」模式,拆解为 6 个核心模块,流程分为「文档入库流程」和「问答流程」两大分支:

1. 状态定义(State)

全局状态类需同时支持「文档处理」和「问答」两大流程,存储中间结果和上下文:

import lombok.Data;
import java.util.List;
import java.util.Map;

@Data
public class KnowledgeState {
    // 通用字段
    private String processType; // 流程类型:DOC_UPLOAD(文档上传)/ QA(问答)
    private String status; // 状态:PROCESSING/COMPLETED/FAILED
    private String errorMsg;

    // 文档上传相关字段
    private List<String> filePaths; // 上传文件的临时路径
    private List<String> documentTexts; // 解析后的文档文本(按文件分)
    private List<StructuredKnowledge> structuredKnowledges; // 结构化知识
    private String documentId; // 入库后的文档ID(关联元数据)

    // 问答相关字段
    private String userId; // 用户ID(关联对话上下文)
    private String userQuestion; // 当前用户问题
    private List<String> historyQuestions; // 历史问题(上下文记忆)
    private List<String> historyAnswers; // 历史回答(上下文记忆)
    private String retrievedKnowledge; // 从向量库检索到的相关知识
    private String finalAnswer; // 最终回答(含引用来源)
}

// 结构化知识实体(存储到向量库)
@Data
public class StructuredKnowledge {
    private String id; // 唯一标识
    private String content; // 知识内容(如“API调用限制:单用户日调用1000次”)
    private String source; // 来源(如“产品手册v2.0.pdf第3页”)
    private String type; // 类型(FAQ/ENTITY/KEY_POINT)
    private Map<String, String> metadata; // 元数据(标签、创建时间等)
    private float[] embedding; // 向量嵌入(由Spring AI生成)
}

2. 工具封装(Tools)

基于 Spring AI 的 Tool 接口,封装文档解析、知识提取、向量存储、类似性检索等核心能力:

(1)文档解析工具(多格式 + OCR)

import org.apache.tika.Tika;
import org.apache.tika.metadata.Metadata;
import org.springframework.stereotype.Component;
import java.io.File;

@Component
public class DocumentParserTool implements Tool {
    private final Tika tika = new Tika();
    private final TesseractOCRService ocrService; // 自定义OCR服务(封装Tesseract)

    public DocumentParserTool(TesseractOCRService ocrService) {
        this.ocrService = ocrService;
    }

    @Override
    public String getName() {
        return "document_parser_tool";
    }

    @Override
    public String getDescription() {
        return "解析PDF/Word/Markdown等格式文件,提取文本内容(含图片OCR识别)";
    }

    @Override
    public Object call(Object input) {
        List<String> filePaths = (List<String>) input;
        return filePaths.stream().map(filePath -> {
            try {
                File file = new File(filePath);
                Metadata metadata = new Metadata();
                // 用Tika解析文本(自动识别文件格式)
                String text = tika.parseToString(file, metadata);
                // 若文本为空(可能是纯图片文档),调用OCR提取
                if (text.trim().isEmpty()) {
                    text = ocrService.extractTextFromImageFile(file);
                }
                return text;
            } catch (Exception e) {
                throw new RuntimeException("解析文件失败:" + filePath, e);
            }
        }).toList();
    }
}

(2)知识结构化提取工具(基于大模型)

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.stereotype.Component;

@Component
public class KnowledgeExtractionTool implements Tool {
    private final ChatClient chatClient;
    private final EmbeddingClient embeddingClient; // Spring AI向量嵌入客户端(如OpenAI Embedding)

    public KnowledgeExtractionTool(ChatClient chatClient, EmbeddingClient embeddingClient) {
        this.chatClient = chatClient;
        this.embeddingClient = embeddingClient;
    }

    @Override
    public String getName() {
        return "knowledge_extraction_tool";
    }

    @Override
    public String getDescription() {
        return "将非结构化文本转化为结构化知识(FAQ/实体/关键要点),并生成向量嵌入";
    }

    @Override
    public Object call(Object input) {
        Map<String, Object> params = (Map<String, Object>) input;
        String documentText = (String) params.get("text");
        String source = (String) params.get("source"); // 文档来源(如文件名+页码)

        // 构造提示词,让大模型生成结构化知识
        String prompt = PromptTemplate.create("""
                请从以下文本中提取结构化知识,格式要求:
                1. 类型:FAQ(问题+答案)、KEY_POINT(关键要点)、ENTITY(实体关系)
                2. 每个知识项需包含:content(内容)、type(类型)、source(来源)
                3. 内容简洁准确,避免冗余
                文本:{text}
                来源:{source}
                返回JSON数组,无需其他说明
                """).render(Map.of("text", documentText, "source", source));

        // 调用大模型获取结构化知识JSON
        String jsonResult = chatClient.prompt(prompt).call().content();
        List<StructuredKnowledge> knowledges = new ObjectMapper().readValue(jsonResult, 
                new TypeReference<List<StructuredKnowledge>>() {});

        // 为每个知识项生成向量嵌入(用于后续检索)
        knowledges.forEach(knowledge -> {
            float[] embedding = embeddingClient.embed(knowledge.getContent()).getResult();
            knowledge.setEmbedding(embedding);
        });

        return knowledges;
    }
}

(3)其他核心工具

  • 向量库存储工具:将结构化知识存入 Pinecone/Qdrant,支持增量更新(去重);
  • 类似性检索工具:根据用户问题生成向量,从向量库检索 Top5 相关知识;
  • 问答生成工具:结合历史上下文、检索到的知识,生成带来源引用的回答;
  • 上下文管理工具:从 Redis 读取 / 存储用户对话历史,支持多轮追问。

3. 智能体角色定义(Agents)

每个角色对应一个流程节点,通过 LangGraph4j 的 Node 接口实现:

import dev.langchain4j.langgraph.Node;

// 1. 文档上传入口智能体(判断流程类型,分支流转)
public class EntryAgent implements Node<KnowledgeState> {
    @Override
    public KnowledgeState execute(KnowledgeState state) {
        // 根据processType分支:文档上传→解析工具,问答→检索工具
        if ("DOC_UPLOAD".equals(state.getProcessType())) {
            state.setStatus("DOC_PARSING");
        } else if ("QA".equals(state.getProcessType())) {
            state.setStatus("KNOWLEDGE_RETRIEVING");
        } else {
            state.setStatus("FAILED");
            state.setErrorMsg("未知流程类型");
        }
        return state;
    }
}

// 2. 文档解析智能体(调用文档解析工具)
public class DocumentParseAgent implements Node<KnowledgeState> {
    private final DocumentParserTool parserTool;

    public DocumentParseAgent(DocumentParserTool parserTool) {
        this.parserTool = parserTool;
    }

    @Override
    public KnowledgeState execute(KnowledgeState state) {
        if (!"DOC_PARSING".equals(state.getStatus())) return state;
        try {
            List<String> documentTexts = (List<String>) parserTool.call(state.getFilePaths());
            state.setDocumentTexts(documentTexts);
            state.setStatus("KNOWLEDGE_EXTRACTING");
        } catch (Exception e) {
            state.setStatus("FAILED");
            state.setErrorMsg("文档解析失败:" + e.getMessage());
        }
        return state;
    }
}

// 3. 知识提取智能体、4. 向量库存储智能体、5. 知识检索智能体、6. 问答生成智能体(类似实现)

4. 流程编排(LangGraph4j 分支流程图)

通过 LangGraph4j 定义「文档入库」和「问答」两大分支流程,支持条件流转:

import dev.langchain4j.langgraph.Graph;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KnowledgeGraphConfig {

    // 注入所有智能体和工具
    private final EntryAgent entryAgent;
    private final DocumentParseAgent parseAgent;
    private final KnowledgeExtractionAgent extractionAgent;
    private final VectorStoreAgent vectorStoreAgent;
    private final KnowledgeRetrievalAgent retrievalAgent;
    private final QAGenerationAgent qaAgent;

    // 构造函数注入(Spring自动装配)
    public KnowledgeGraphConfig(EntryAgent entryAgent, DocumentParseAgent parseAgent,
                                KnowledgeExtractionAgent extractionAgent, VectorStoreAgent vectorStoreAgent,
                                KnowledgeRetrievalAgent retrievalAgent, QAGenerationAgent qaAgent) {
        this.entryAgent = entryAgent;
        this.parseAgent = parseAgent;
        this.extractionAgent = extractionAgent;
        this.vectorStoreAgent = vectorStoreAgent;
        this.retrievalAgent = retrievalAgent;
        this.qaAgent = qaAgent;
    }

    @Bean
    public Graph<KnowledgeState> knowledgeGraph() {
        // 1. 创建图,起始节点为入口智能体(分支判断)
        Graph<KnowledgeState> graph = Graph.builder(KnowledgeState.class)
                .startWith(entryAgent)
                .build();

        // 2. 入口→文档解析(流程类型为DOC_UPLOAD)
        graph.addEdge(entryAgent, state -> 
                "DOC_PARSING".equals(state.getStatus()) ? parseAgent : retrievalAgent);

        // 3. 文档解析→知识提取→向量库存储→结束(文档入库流程)
        graph.addEdge(parseAgent, state -> 
                "KNOWLEDGE_EXTRACTING".equals(state.getStatus()) ? extractionAgent : Graph.END);
        graph.addEdge(extractionAgent, state -> 
                "VECTOR_STORING".equals(state.getStatus()) ? vectorStoreAgent : Graph.END);
        graph.addEdge(vectorStoreAgent, Graph.END);

        // 4. 入口→知识检索→问答生成→结束(问答流程)
        graph.addEdge(retrievalAgent, state -> 
                "QA_GENERATING".equals(state.getStatus()) ? qaAgent : Graph.END);
        graph.addEdge(qaAgent, Graph.END);

        // 5. 所有节点失败时直接结束
        graph.addEdge(parseAgent, state -> "FAILED".equals(state.getStatus()) ? Graph.END : null);
        graph.addEdge(retrievalAgent, state -> "FAILED".equals(state.getStatus()) ? Graph.END : null);

        return graph;
    }
}

5. 接口暴露(Spring Web)

提供文档上传、智能问答、对话上下文清空等 HTTP 接口:

import dev.langchain4j.langgraph.Graph;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/knowledge")
public class KnowledgeController {

    private final Graph<KnowledgeState> knowledgeGraph;
    private final FileStorageService fileStorageService; // 自定义文件存储服务(临时存储上传文件)
    private final RedisTemplate<String, Object> redisTemplate;

    public KnowledgeController(Graph<KnowledgeState> knowledgeGraph, FileStorageService fileStorageService,
                               RedisTemplate<String, Object> redisTemplate) {
        this.knowledgeGraph = knowledgeGraph;
        this.fileStorageService = fileStorageService;
        this.redisTemplate = redisTemplate;
    }

    // 1. 文档上传接口(支持多文件)
    @PostMapping("/upload-document")
    public ResponseEntity<ApiResponse> uploadDocument(
            @RequestParam("files") MultipartFile[] files,
            @RequestParam("userId") String userId) {
        try {
            // 保存上传文件到临时目录,获取文件路径
            List<String> filePaths = Arrays.stream(files)
                    .map(fileStorageService::saveTempFile)
                    .toList();

            // 初始化状态
            KnowledgeState initialState = new KnowledgeState();
            initialState.setProcessType("DOC_UPLOAD");
            initialState.setFilePaths(filePaths);
            initialState.setStatus("STARTED");

            // 执行文档入库流程
            KnowledgeState finalState = knowledgeGraph.execute(initialState);

            if ("COMPLETED".equals(finalState.getStatus())) {
                return ResponseEntity.ok(new ApiResponse("success", "文档入库成功", finalState.getDocumentId()));
            } else {
                return ResponseEntity.badRequest().body(new ApiResponse("failed", finalState.getErrorMsg(), null));
            }
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body(new ApiResponse("failed", "上传失败:" + e.getMessage(), null));
        }
    }

    // 2. 智能问答接口(支持多轮对话)
    @PostMapping("/qa")
    public ResponseEntity<QAAnswerResponse> qa(
            @RequestBody QAQuestionRequest request) {
        String userId = request.getUserId();
        String userQuestion = request.getUserQuestion();

        // 从Redis获取用户历史对话上下文
        List<String> historyQuestions = getHistory(userId, "questions");
        List<String> historyAnswers = getHistory(userId, "answers");

        // 初始化状态
        KnowledgeState initialState = new KnowledgeState();
        initialState.setProcessType("QA");
        initialState.setUserId(userId);
        initialState.setUserQuestion(userQuestion);
        initialState.setHistoryQuestions(historyQuestions);
        initialState.setHistoryAnswers(historyAnswers);
        initialState.setStatus("STARTED");

        // 执行问答流程
        KnowledgeState finalState = knowledgeGraph.execute(initialState);

        if ("COMPLETED".equals(finalState.getStatus())) {
            // 更新Redis中的对话历史
            updateHistory(userId, "questions", userQuestion);
            updateHistory(userId, "answers", finalState.getFinalAnswer());

            return ResponseEntity.ok(new QAAnswerResponse(
                    "success",
                    finalState.getFinalAnswer(),
                    finalState.getRetrievedKnowledge() // 返回引用来源
            ));
        } else {
            return ResponseEntity.badRequest().body(new QAAnswerResponse(
                    "failed",
                    finalState.getErrorMsg(),
                    null
            ));
        }
    }

    // 辅助方法:获取/更新Redis对话历史
    private List<String> getHistory(String userId, String key) {
        String redisKey = "knowledge:history:" + userId + ":" + key;
        return Optional.ofNullable(redisTemplate.opsForList().range(redisKey, 0, -1))
                .map(list -> list.stream().map(String::valueOf).toList())
                .orElse(List.of());
    }

    private void updateHistory(String userId, String key, String content) {
        String redisKey = "knowledge:history:" + userId + ":" + key;
        redisTemplate.opsForList().rightPush(redisKey, content);
        redisTemplate.expire(redisKey, Duration.ofDays(7)); // 保留7天历史
    }

    // 请求/响应DTO
    @Data
    static class QAQuestionRequest {
        private String userId;
        private String userQuestion;
    }

    @Data
    static class QAAnswerResponse {
        private String code;
        private String answer;
        private String source; // 引用来源(如“产品手册v2.0.pdf第3页”)
    }
}

四、关键特性与扩展点

1. 核心特性

  • 多格式文档支持:通过 Apache Tika + OCR 覆盖 PDF/Word/Markdown/ 图片文档;
  • 结构化知识存储:将非结构化文本转化为 FAQ / 实体 / 要点,向量库存储支持高效检索;
  • 上下文记忆:Redis 缓存对话历史,支持多轮追问(如 “这个 API 限制是否可申请提升?”);
  • 分支流程编排:LangGraph4j 实现 “文档入库” 和 “问答” 两大流程分支,逻辑清晰;
  • 来源可追溯:回答中附带知识来源,提升可信度(企业场景关键需求)。

2. 扩展方向

  • 知识去重与更新:新增文档时,先检索向量库,避免重复存储一样知识;
  • 权限控制:按用户角色限制文档访问权限(如普通员工看不到核心技术文档);
  • 批量导入导出:支持知识库批量导出(Excel/PDF)、批量导入(文件夹上传);
  • 多语言支持:添加翻译工具,支持中英文文档混合问答;
  • 知识可视化:集成 Neo4j 存储实体关系,前端展示知识图谱(如 “产品 X→API→调用限制”);
  • 异步处理:大文件(如几百页 PDF)解析改为异步任务,通过 WebSocket 推送进度。

五、环境配置要点

  1. Spring AI 配置(application.yml):
spring:
  ai:
    openai:
      api-key: sk-xxx
      base-url: https://api.openai.com/v1
    chat:
      model: gpt-4o-mini
    embedding:
      model: text-embedding-3-small # 向量嵌入模型
  servlet:
    multipart:
      max-file-size: 100MB # 支持大文件上传
      max-request-size: 100MB
  1. 向量库配置(以 Pinecone 为例):
pinecone:
  api-key: pcsk-xxx
  environment: us-west1-gcp
  index-name: knowledge-base-index
  1. OCR 配置
tesseract:
  path: /usr/bin/tesseract # Tesseract安装路径(Windows为exe路径)
  data-path: /usr/share/tesseract-ocr/5/tessdata # 语言包路径

Spring AI + LangGraph4j开发智能文档问答与知识管理智能体

六、运行流程演示

流程 1:文档入库

  1. 用户上传文件:POST /api/knowledge/upload-document,参数为 2 个文件(产品手册v2.0.pdf、API文档.md);
  2. 流程执行:入口智能体→文档解析(提取文本 + OCR)→知识提取(生成结构化 FAQ)→向量库存储;
  3. 响应结果:返回文档 ID,知识库新增 10 条结构化知识(如 “API 调用限制:单用户日调用 1000 次”)。

流程 2:智能问答

  1. 用户提问:POST /api/knowledge/qa,Body 为{“userId”:”user123″,”userQuestion”:”产品X的API日调用限制是多少?”};
  2. 流程执行:入口智能体→知识检索(向量库匹配相关知识)→问答生成(结合上下文生成回答);
  3. 响应结果:
{
  "code": "success",
  "answer": "产品X的API单用户日调用限制为1000次",
  "source": "产品手册v2.0.pdf第3页、API文档.md第5节"
}
  1. 多轮追问:用户再提问{“userId”:”user123″,”userQuestion”:”企业版是否有更高限制?”},智能体结合历史问题,返回 “企业版用户 API 日调用限制为 10000 次,需联系客户经理开通”。

七、案例优势

  1. 业务价值明确:直接解决企业 “文档多、查询难” 的痛点,可落地为内部知识库、客户协助中心等产品;
  2. 技术覆盖全面:涵盖文档解析、OCR、向量嵌入、向量检索、多轮对话等 AI 应用核心技术;
  3. LangGraph4j 深度应用:体现「条件分支流转」(文档 / 问答双流程)、「状态存储」(上下文 + 中间结果)、「流程可扩展」(新增分支如 “知识导出”);
  4. 企业级适配:支持大文件、权限控制、来源追溯,符合企业生产环境需求。

该案例适合作为 Spring AI + LangGraph4j 的进阶实战,既覆盖技术难点,又贴近实际业务,可在此基础上快速迭代为商用知识库产品。

© 版权声明

相关文章

4 条评论

  • 头像
    孙鹏 读者

    收藏了,感谢分享

    无记录
    回复
  • 头像
    -此日无事- 投稿者

    创作不易,您的关注点赞,是我坚持的动力。

    无记录
    回复
  • 头像
    娱闻女孩钟无艳 投稿者

    创作不易,您的关注+点赞,是我坚持创作的动力

    无记录
    回复
  • 头像
    小狼狠乖你懂的 读者

    Spring AI + LangGraph4j开发智能文档问答与知识管理智能体

    无记录
    回复