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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package cn.iocoder.yudao.module.qcreport.service.aiimport.document;
 
import java.util.List;
 
/**
 * 一个文件被适配器抽取后的中间结果。
 * <p>
 * <b>刻意不是树、也不是 HTML</b>:它只是「这份文件能交给模型的东西是什么」这个更小的问题的答案。
 * 树要表达版式,HTML 要表达样式,两者都会把「模型能看见什么」放大到不可控;这里只留两个通道,
 * 迫使下游(提示词构造、调用计划)不必再考虑第三种形态。
 *
 * <h3>为什么是双通道而不是一种</h3>
 * 电子版 PDF / Word / Excel 有真实文本层,抽取成字符串最省 token,模型也读得最准;
 * 扫描件与照片没有文本层,只能把页面渲染成图交给多模态模型。
 * 这两条路的调用方式({@code chat} 与 {@code chatWithImage})、计费口径、失败模式都不同,
 * 所以必须在类型上分开,而不是塞进一个字段里靠猜。
 *
 * @param channel   通道类型,见 {@link Channel}
 * @param text      TEXT 通道的正文,多页时页间以 {@code --- 第 N 页 ---} 分隔;IMAGES 通道恒为空串
 * @param images    IMAGES 通道的图片,每页一张;TEXT 通道恒为空列表
 * @param pageCount 页数,用于超限校验与提示文案
 * @param extractor 实际生效的抽取器标识(如 {@code pdfbox-text}),会写进日志便于排查
 * @param notes     抽取阶段的软提示,直接进响应的 warnings 展示给用户
 */
public record QcReportDocumentExtract(
        Channel channel,
        String text,
        List<ImagePart> images,
        int pageCount,
        String extractor,
        List<String> notes) {
 
    /**
     * 纵向合并续格的占位标记。
     * <p>
     * 它属于「抽取出来的文本长什么样」这条契约,所以和 {@code text} 放在一处,
     * 由生产者({@link OfficeImportAdapter})与解释者({@code QcReportTemplatePromptBuilder})
     * 共用一份,避免两边各写一个字面量后各自漂移。
     * <p>
     * 为什么不能留空:{@code w:vMerge} 的续格在 Word 里表示「这一格与上一行是同一个值」,
     * 但 POI 取出来就是空串。留空的话,模型区分不出「合并续格」与「原件本来就没填」,
     * 于是整张表看起来是一层平铺的检验项,看不出分组结构。
     */
    public static final String VERTICAL_MERGE_MARK = "↑同上";
 
    /**
     * 抽取通道。
     */
    public enum Channel {
        /** 有文本层,整份文件拼成一段文本,一次模型调用 */
        TEXT,
        /** 无文本层(扫描件/照片),逐页转图,每页一次模型调用 */
        IMAGES
    }
 
    /**
     * IMAGES 通道里的一页图。
     *
     * @param bytes    图片字节,执行器负责转成裸 base64
     * @param mimeType 形如 {@code image/png} 的裸类型,不带 {@code data:} 前缀
     * @param label    页标签,用于拼错误与提示文案,例如「扫描件.pdf 第 2 页」
     */
    public record ImagePart(byte[] bytes, String mimeType, String label) {
    }
 
    /**
     * 文本通道的工厂。{@code notes} 允许为空表示没有软提示。
     */
    public static QcReportDocumentExtract ofText(String text, int pageCount, String extractor,
                                                 List<String> notes) {
        return new QcReportDocumentExtract(Channel.TEXT, text, List.of(), pageCount, extractor, notes);
    }
 
    /**
     * 图片通道的工厂。
     */
    public static QcReportDocumentExtract ofImages(List<ImagePart> images, int pageCount,
                                                   String extractor, List<String> notes) {
        return new QcReportDocumentExtract(Channel.IMAGES, "", List.copyOf(images), pageCount,
                extractor, notes);
    }
 
}