langchain4j + tika + torna 接口文档解析并导入接口平台,生成代码

场景:

  torna是一个开源的接口平台,可以进行文档管理、使用smart-doc无侵入完成Java源代码和注释提取生成API文档等。因为公司项目原因接触到这个,感觉其局限性比较差,大部分接口文档还是以word、PDF等文档的形式给过来的。于是我想基于torna,利用tika进行文件内容解析,利用langchain4结合大模型进行文档的识别,将非标准的文档转换为标准的json格式后,直接进行落库记录。再加上之前给这个torna加了java代码生成的功能,那么想的是直接导入文档,可以生成java代码,可以管理文档。

解决:

  先给出demo的代码:

package cn.torna.service.ai;

import cn.torna.service.ai.dto.APIDocDTO;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

interface DocInfoExtractor {
    @SystemMessage("你是一个项目经理,会仔细阅读文档内容,了解其结构、字段和数据格式,确定文档中提到的接口信息," +
            "包括:接口名称,接口说明,接口路径(url),请求方式,请求参数,响应参数等。" +
            "参数信息包括:参数名称,参数类型,是否必填,最大长度,示例值,参数描述,子参数信息等" +
            "请忽略其他冗余信息,比如文档目录、页码、附录等信息。" +
            "最后将文档中提到的信息映射为制定的APIDocDTO类,并以JSON格式输出出来。")
    @UserMessage("请根据下文给的接口文档内容,理解其中的接口信息,并映射并输出格式化的JSON数据.现在文档信息的内容为: {{text}}")
    APIDocDTO extractPersonFrom(@V("text")String text);
}

  这里主要利用序列化输出的部分:

package cn.torna.service.ai;


import cn.torna.service.ai.dto.APIDocDTO;
import cn.torna.service.ai.dto.DocInfoDTO;
import cn.torna.service.ai.dto.ModelQueryDTO;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.request.ResponseFormat;
import dev.langchain4j.model.chat.request.ResponseFormatType;
import dev.langchain4j.model.chat.request.json.JsonArraySchema;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.model.chat.request.json.JsonSchema;
import dev.langchain4j.model.chat.request.json.JsonSchemaElement;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.tool.ToolExecution;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import static dev.langchain4j.model.chat.Capability.RESPONSE_FORMAT_JSON_SCHEMA;

/**
 * @author chenchongpo
 */
@Service
@Slf4j
public class AIService {

    @Autowired
    private ChatModel ollamaChatModel;


    /**
     * 解析文件
     *
     * @param file
     */
    public String parseFile(MultipartFile file) {
        try (InputStream stream = file.getInputStream()) {
            // 创建解析器
            Parser parser = new AutoDetectParser();
            ContentHandler contentHandler = new BodyContentHandler();
            Metadata metadata = new Metadata();
            ParseContext parseContext = new ParseContext();

            // 解析文件
            parser.parse(stream, contentHandler, metadata, parseContext);

            // 获取文件内容和元数据
            String content = contentHandler.toString();
            System.out.println("文件内容: " + content);
            content = content.replaceAll("\n{2,}", "\n");
            return content.trim();
        } catch (IOException | SAXException | TikaException e) {
            // 如果解析失败,返回错误信息
            return "文件解析失败: " + e.getMessage();
        }
    }


    public APIDocDTO modelByDocInfo(String message) {
        ChatModel chatModel = OllamaChatModel.builder()
                .baseUrl("http://localhost:11434")
                .modelName("deepseek-r1:7b")
                .supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA)
                .logRequests(true)
                .logResponses(true)
                .build();
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
        DocInfoExtractor docInfoExtractor = AiServices.builder(DocInfoExtractor.class)
                .chatMemory(chatMemory)
                .chatModel(chatModel)
                .build();

        APIDocDTO apiDocDTO = docInfoExtractor.extractPersonFrom(message);
        System.out.println(apiDocDTO);

        return apiDocDTO;
    }

    public APIDocDTO modelByDocInfoFile(MultipartFile file) {

        // 先解析文件
        String message = parseFile(file);
        // 进行模型转换
        ChatModel chatModel = OllamaChatModel.builder()
                .baseUrl("http://localhost:11434")
                .modelName("qwen3:14b")
                .supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA)
                .logRequests(true)
                .logResponses(true)
                .build();

        DocInfoExtractor docInfoExtractor = AiServices.builder(DocInfoExtractor.class)
                .chatModel(chatModel)
                .build();
        // 输出分割后的段落
        List<DocInfoDTO> docInfoDTOs = new ArrayList<>();
        APIDocDTO apiDocDTO = new APIDocDTO();
        apiDocDTO = docInfoExtractor.extractPersonFrom(message);
        System.out.println("结构化结果为:" + JSON.toJSONString(docInfoDTOs));

        return apiDocDTO;
    }
}

  讲一下思路,前端接口上传的文件解析后,传到后端接口。后端接口利用SystemMessage固定大模型的角色,制定对文档内容进行接口文档的提取,并进行结构化输出。然后输出的json其实可以给前端用户确认后,直接落库的。但实际尝试了几个模型:deepseek、qwen3以及谷歌的gemini-1.5-flash。结果如下:

  1、简单的文档deepseek和qwen3都可以解析,但是内容复杂的文档,就解析不了;

  2、而且有的时候会出现幻觉,就是把一些其他信息识别为接口信息。比如把摘要识别成了接口对象。

  3、谷歌的gemini无论怎么样的文档都能解析,无敌!唯一的缺点就是太贵了。每次测试的时候都有一种大材小用的感觉。(PS:不过谷歌能给300美元的免费额度,真香)。

  4、长文本输入输出、模型token上限的限制也会影响解析效果。

  最后这个demo并没有上传到开源项目中,因为没有找到合适的模型,而且有些问题还解决不了。先记录一下,看看后面还有没有进展。

参考:

  https://www.torna.cn/dev/

  https://docs.langchain4j.dev/intro/

  

posted @ 2025-06-23 21:10  陈子白  阅读(172)  评论(0)    收藏  举报