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://docs.langchain4j.dev/intro/