5 小时以前 9bad721754fe8bbe2e5f459d0706e0fefac569f3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package cn.iocoder.yudao.module.qcreport.service.aiimport.llm;
 
import cn.iocoder.yudao.module.qcreport.service.aiimport.document.QcReportDocumentExtract;
 
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
 
/**
 * 把抽取结果翻译成「该发几次模型调用、每次发什么」。
 * <p>
 * 纯函数:没有 IO、没有 Spring、没有 {@code AiChatApi}。多页 / 多文件 / 超预算这些控制流
 * 因此能在单测里被完整覆盖,而不必 mock 掉真正的外部依赖。
 */
public class QcReportLlmCallPlanner {
 
    private QcReportLlmCallPlanner() {
    }
 
    /**
     * 为一个文件排调用计划。
     * <p>
     * TEXT 通道整份文件一次调用;IMAGES 通道逐页一次——多模态接口一次只收一张图,
     * 这是接口约束而不是取舍。
     * <p>
     * 这里**不做页数上限判断**:超限由适配器在抽取阶段就抛错,因为静默截断会让用户
     * 以为整份文件都识别过了,比直接报错危险得多。
     *
     * @param extract     抽取结果
     * @param sourceLabel 来源标签,通常是原始文件名,会拼进每页的标签里
     * @return 按执行顺序排列的调用列表,至少一项
     */
    public static List<QcReportLlmCall> plan(QcReportDocumentExtract extract, String sourceLabel) {
        if (extract.channel() == QcReportDocumentExtract.Channel.TEXT) {
            return List.of(QcReportLlmCall.ofText(sourceLabel, extract.text()));
        }
        List<QcReportLlmCall> calls = new ArrayList<>(extract.images().size());
        for (QcReportDocumentExtract.ImagePart image : extract.images()) {
            calls.add(QcReportLlmCall.ofImage(
                    sourceLabel + " " + image.label(),
                    Base64.getEncoder().encodeToString(image.bytes()),
                    image.mimeType()));
        }
        return calls;
    }
 
}