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; /** * 把抽取结果翻译成「该发几次模型调用、每次发什么」。 *

* 纯函数:没有 IO、没有 Spring、没有 {@code AiChatApi}。多页 / 多文件 / 超预算这些控制流 * 因此能在单测里被完整覆盖,而不必 mock 掉真正的外部依赖。 */ public class QcReportLlmCallPlanner { private QcReportLlmCallPlanner() { } /** * 为一个文件排调用计划。 *

* TEXT 通道整份文件一次调用;IMAGES 通道逐页一次——多模态接口一次只收一张图, * 这是接口约束而不是取舍。 *

* 这里**不做页数上限判断**:超限由适配器在抽取阶段就抛错,因为静默截断会让用户 * 以为整份文件都识别过了,比直接报错危险得多。 * * @param extract 抽取结果 * @param sourceLabel 来源标签,通常是原始文件名,会拼进每页的标签里 * @return 按执行顺序排列的调用列表,至少一项 */ public static List plan(QcReportDocumentExtract extract, String sourceLabel) { if (extract.channel() == QcReportDocumentExtract.Channel.TEXT) { return List.of(QcReportLlmCall.ofText(sourceLabel, extract.text())); } List 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; } }