Java 接入降重降AI接口

Java 调用降重降AI API 完整示例:Java 11+ HttpClient 基础调用、Jackson 解析响应、Apache POI 提取脚注占位符、Maven 依赖与并发建议。

Java 接入降重降AI接口

Java 11+ 内置的 java.net.http.HttpClient 足够完成调用,无需额外 HTTP 依赖;JSON 处理推荐 Jackson。接口一次改写同时完成降重和降AI,单次请求处理一个正文自然段(不超过1000字),详细参数说明见接口文档

Maven 依赖

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.0</version>
    </dependency>
    <!-- 如需处理 Word 文档脚注 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

基础调用

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;

public class ReduceClient {

    private static final String API_URL = "https://api.llmapi.fit/completion/v2/reduce";
    private static final String API_KEY = System.getenv().getOrDefault("LLMAPI_KEY", "YOUR_API_KEY");

    private final HttpClient http = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();
    private final ObjectMapper mapper = new ObjectMapper();

    /** 改写单个正文段落 */
    public String reduceParagraph(String text) throws Exception {
        String body = mapper.writeValueAsString(Map.of("text", text));

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(API_URL))
                .header("Authorization", "Bearer " + API_KEY)
                .header("Content-Type", "application/json")
                .timeout(Duration.ofSeconds(180))   // 高峰期智能排队最长约2分钟
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();

        HttpResponse<String> resp = http.send(request, HttpResponse.BodyHandlers.ofString());

        JsonNode data = mapper.readTree(resp.body());
        if (resp.statusCode() == 200 && "success".equals(data.path("code").asText())) {
            return data.path("output_text").asText();
        }
        throw new RuntimeException("HTTP " + resp.statusCode()
                + " " + data.path("code").asText()
                + ": " + data.path("message").asText());
    }
}

逐段落处理全文

接口按段落级别改写,循环处理论文的正文自然段,速率控制在 10 次/秒以内:

List<String> paragraphs = List.of(
        "第一个正文自然段……",
        "第二个正文自然段……"
);

List<String> rewritten = new ArrayList<>();
for (String p : paragraphs) {
    rewritten.add(client.reduceParagraph(p));
    Thread.sleep(500);
}

重试策略

429(请求过于频繁)与 503(GPU服务繁忙)是可重试错误,建议等待 3-5 秒后重试;400(参数/余额)与 401(密钥)不可重试:

public String reduceWithRetry(String text, int maxRetries) throws Exception {
    for (int i = 1; i <= maxRetries; i++) {
        try {
            return reduceParagraph(text);
        } catch (RuntimeException e) {
            if (e.getMessage().contains("HTTP 429") || e.getMessage().contains("HTTP 503")) {
                Thread.sleep(4000);
                continue;
            }
            throw e;
        }
    }
    throw new IllegalStateException("重试次数已用尽");
}

处理 Word 文档脚注(Apache POI)

论文段落常带脚注引用,直接发送会丢失。正确做法是把脚注引用替换为 [[FNn]] 占位符,改写后原样保留,写回时恢复(完整规范见接口文档第8节)。

POI(XWPF)提取段落文本与脚注占位符:

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;

try (XWPFDocument doc = new XWPFDocument(new FileInputStream("论文.docx"))) {
    for (XWPFParagraph paragraph : doc.getParagraphs()) {
        StringBuilder sb = new StringBuilder();
        int fnIndex = 0;

        // 遍历段落内的 run,脚注引用处落占位符
        paragraph.getRuns().forEach(run -> {
            // 注意:脚注引用在 CTP 的子元素中,需按 w:footnoteReference 判断,
            // 普通文本取 run.text(),占位符按段内顺序编号 [[FN0]]、[[FN1]]……
        });

        // 简化写法:遍历 CTP 子元素
        org.apache.xmlbeans.XmlObject[] children = paragraph.getCTP().selectPath(
                "declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' ./*");
        for (org.apache.xmlbeans.XmlObject child : children) {
            org.w3c.dom.Node node = child.getDomNode();
            String localName = node.getLocalName();
            if ("r".equals(localName)) {
                for (int i = 0; i < node.getChildNodes().getLength(); i++) {
                    org.w3c.dom.Node inner = node.getChildNodes().item(i);
                    if ("t".equals(inner.getLocalName()) && inner.getTextContent() != null) {
                        sb.append(inner.getTextContent());
                    } else if ("footnoteReference".equals(inner.getLocalName())) {
                        sb.append(String.format("[[FN%d]]", fnIndex++));
                    }
                }
            }
        }
        String textWithPlaceholders = sb.toString();
        // 调用接口改写后,按占位符切分写回,原脚注引用(原 id)插回原位
    }
}

写回时段落对象保持不动,只替换文字内容,在 [[FNn]] 处插入原 CTRFtnMark/脚注引用节点;word/footnotes.xml 全程不需要修改。

并发建议

  • 批量任务可用 ThreadPoolExecutor(固定 5 线程以内)或 CompletableFuture,请求速率控制在 10 次/秒以内
  • HttpClient 实例全局复用一个即可,Java 11+ 版本内部自带连接池

常见问题

现象原因处理
401 unauthorizedAPI Key 错误检查请求头 Authorization: Bearer YOUR_API_KEY
400 textTooLong单段超过1000字按语义拆分成多个自然段分别调用
HttpTimeoutException高峰期排队请求超时设 180 秒,重试 2-3 次
中文乱码响应未按 UTF-8 解析HttpResponse.BodyHandlers.ofString(body, StandardCharsets.UTF_8)

下一步