项目实训第二阶段:大模型接入、多模态分析和视频检测的链路升级
目录
一、项目背景
在第一阶段中,我完成了Spring Boot后端骨架的搭建、文件上传接口实现等(参考“项目实训第二阶段”博文)。进入第二阶段,项目的核心升级到“让后端具备智能分析能力,能完成文本诈骗分析、多模态综合分析、模拟诈骗对话等功能”。
随着项目推进,视频检测模块的需求发生了变化。原本后端的视频处理逻辑主要是接收上传文件并直接转发给视频检测服务,但托某王姓同志的福(视频集申请不下来,只能申请视频集剪切的图片集),视频侧需要改为“先切帧、再提取人脸、最后按图片调用模型服务”的方式。因此,第二阶段中我不仅需要完成大模型模块接入,还要对视频检测链路进行升级。
本文主要记录我在第二阶段的开发内容、关键实现以及过程中遇到的问题。
二、第二阶段目标
本阶段我的任务可以概括为以下几个方面:
- 接入大模型API,完成统一调用封装
- 实现文本诈骗分析接口
- 实现多模态综合分析接口
- 实现模拟诈骗对话接口
- 完善音频/视频检测服务接入逻辑
- 升级视频检测流程,实现:
· 视频抽帧
· 人脸检测与裁剪
· 单图调用视频Flask服务
· 结果聚合返回
三、技术选型
在第一阶段的基础上,第二阶段主要补充以下技术:
| 技术 | 版本/说明 |
| 大模型调用 | OpenAI Chat Completions兼容接口 |
| HTTP调用 | RestTemplate |
| Prompt管理 | 本地模板文件+占位符替换 |
| 视频抽帧 | JavaCV / FFmpegFrameGrabber |
| 人脸检测 | OpenCV CascadeClassifier |
| 配置管理 | @ConfigurationProperties |
比之第一阶段,最大新增点在:
· 大模型模块正式接入
· 视频处理链路从整段上传升级为图片级推理流程
四、项目结构与关键代码
1.大模型配置与调用封装
为方便后端统一管理大模型相关参数,我新增大模型配置类LLMConfig,用于读取application.yml中的API 地址、模型名称、温度参数、最大输出token数等配置。
同时,我新增LLMService用于封装大模型调用逻辑。整体思路是:
· 读取本地prompt模板
· 将文本、音频结果、视频结果等变量填入模板
· 通过RestTemplate调用大模型API
· 提取返回结果并交给接口层返回给前端
这样可以使大模型调用逻辑与具体业务接口解耦,如果后面需要更换提示词或调整请求参数,不需要改动太多代码。
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(llmConfig.getApiKey());
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", llmConfig.getModel());
requestBody.put("messages", List.of(Map.of("role", "user", "content", prompt)));
requestBody.put("temperature", llmConfig.getTemperature());
requestBody.put("max_tokens", llmConfig.getMaxTokens());
2.Prompt模板管理
在第一阶段中,我预留了部分基础结构;第二阶段里,可以把大模型相关提示词拆分到resources/prompts/目录下,形成独立模板文件,主要包括:1.文本诈骗分析模板 2.多模态综合分析模板 3.模拟诈骗对话模板
为加载这些模板,我实现了PromptLoader工具类。用于从resources/prompts/下读取模板内容,它可以:将模板中的占位符替换为实际变量并返回最终构造好的prompt文本。
public String loadPrompt(String templateName, Map<String, Object> variables) {
String template = readTemplate(templateName);
if (template == null) {
return "提示词模板加载失败:" + templateName;
}
for (Map.Entry<String, Object> entry : variables.entrySet()) {
String placeholder = "{" + entry.getKey() + "}";
String value = entry.getValue() != null ? entry.getValue().toString() : "";
template = template.replace(placeholder, value);
}
return template;
}
3.文本分析、多模态分析与模拟诈骗接口
在控制层中,我新增了LLMController,并提供了三个核心接口:
(1)文本诈骗分析接口
接口路径:POST /api/llm/analyze
功能:接收用户输入的文本,调用大模型分析其中是否存在诈骗风险,并返回结果。
主要用于对可疑话术进行快速识别。
(2)多模态综合分析接口
接口路径:POST /api/llm/analyze/multi
功能:接收用户补充描述、音频检测结果、视频检测结果,统一交给大模型进行综合分析,生成可读的风险评估报告。
在生活中,用户面对的诈骗场景可能不仅是单一的文本信息,还伴随语音、视频等伪造内容,因此单独分析某一模态的结果显然不够。多模态分析接口的作用,就是将各部分检测结果整合起来,让大模型给出更完整的判断。
(3)模拟诈骗对话接口
接口路径:POST /api/llm/scam/chat
功能:接收当前消息和历史对话,让大模型按照预设诈骗场景继续生成回复,帮助反诈训练和场景模拟。
为支持这部分功能,我设计了ScamScenario枚举,用于定义不同诈骗场景,如冒充熟人、冒充公检法、刷单诈骗等。后续若想扩展更多诈骗剧本,会更加方便一点。
@PostMapping("/analyze")
public Result<String> analyze(@RequestBody Map<String, String> request) {
String text = request.get("text");
if (text == null || text.trim().isEmpty()) {
return Result.error("文本不能为空");
}
String result = llmService.analyzeText(text);
return Result.success(result);
}
@PostMapping("/analyze/multi")
public Result<String> analyzeMulti(@RequestBody MultiModalRequest request) {
String result = llmService.analyzeMultiModal(
request.getText(),
request.getAudioResult(),
request.getVideoResult()
);
return Result.success(result);
}
4.多模态请求结构设计
为了让多模态分析接口输入结构更加明确和前端统一传值,我设计MultiModalRequest作为统一请求体,其中包含:
· 用户补充文本
· 音频检测结果AudioDetectionResult
· 视频检测结果VideoDetectionResult
在后端处理时,我会将:音频伪造概率,视频伪造概率,用户补充描述等信息统一组织到prompt 中,然后再交给大模型生成更通俗易懂的分析报告。
5.音频/视频检测模块完善
在第一阶段中,我完成了DetectionService的基础封装,用于通过RestTemplate调用音频和视频检测Flask服务。本阶段,我沿用这一思路,并对视频检测逻辑进行了升级。
音频部分仍然保持原有结构:(1)上传音频文件 (2)通过 multipart/form-data 调用音频检测服务(3)解析返回结果为 AudioDetectionResult
视频部分则不再局限于“整段视频直接上传”,而是根据配置决定走哪种模式:
· 旧模式:整段视频直接传给视频服务
· 新模式:切帧+人脸裁剪+单图推理+结果聚合
这种设计保证了原有逻辑不会被推翻,也为新需求留出了兼容空间。
public VideoDetectionResult detectVideo(String filePath) {
if (videoProperties.getPreprocess() != null && videoProperties.getPreprocess().isEnabled()) {
return videoImagePipelineService.detectFromVideoFile(filePath);
}
return callDetection(filePath, videoServiceUrl, VideoDetectionResult.class);
}
6.视频检测链路升级
(1)为什么要改造视频链路
原本的思路比较直接:用户上传视频后,后端把整段视频文件转发给视频检测服务即可。
但随着项目推进,视频侧的模型服务输入形式发生了变化,更适合接收从视频中抽取出来的图片,而不是直接接收整段视频。因此,后端需要把视频处理为图片,再交给视频模型服务。
这样一来,视频检测流程就从原来的“整段视频上传”升级为:
上传视频----->切帧----->人脸裁剪----->单图推理----->结果聚合
(2)视频配置类设计
为了让新老逻辑可以灵活切换,我新增了VideoProperties配置类,并在配置文件中增加了如下参数:
· 是否启用视频预处理链路
· 抽帧间隔
· 最大帧数
· 人脸裁剪外扩比例
· 单图推理接口地址
· multipart 字段名
通过这种方式,后续若调整抽帧策略或回退到旧模式,不需要频繁改代码,只修改配置即可。
(3)视频抽帧实现
我新增了VideoFrameExtractorService,用于从本地视频文件中按时间间隔抽取图片帧。
其基本思路是:使用 FFmpegFrameGrabber 打开视频,按设定的时间间隔定位到相应时间点,抽取图像帧并保存为 PNG 文件,返回本次任务抽出的图片列表。
通过该方式,后续人脸检测和单图推理都可以直接使用这些帧图,而不用再重复处理视频文件本身。
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoFile)) {
grabber.start();
for (int i = 0; i < maxFrames; i++) {
grabber.setTimestamp(i * intervalMicros);
Frame frame = grabber.grabImage();
if (frame == null || frame.image == null) {
break;
}
BufferedImage bi = converter.getBufferedImage(frame);
if (bi == null) {
continue;
}
File png = new File(workDir, "frame_" + i + "_" + UUID.randomUUID() + ".png");
ImageIO.write(bi, "png", png);
out.add(png);
}
}
(4)人脸检测与裁剪
为了让传给视频模型服务的图片更加聚焦于有效区域,我新增了FaceCropService。
该模块主要完成:
· 读取抽出的 PNG 帧图
· 调用 OpenCV 进行人脸检测
· 选取面积最大的人脸区域
· 按一定比例进行外扩裁剪
· 将裁剪结果保存为新图片
考虑到实际视频中并不一定每一帧都能成功检测到人脸,因此我还增加了兜底逻辑:
如果检测不到人脸,或者裁剪过程出现异常,则直接使用整帧图片送检
这样可以避免因为部分帧检测失败而导致整条链路中断。
(5)单图推理与结果聚合
在完成视频抽帧和人脸裁剪后,我新增了 VideoImagePipelineService,用于串联整条视频处理流程。
它的主要工作包括:
· 创建临时目录
· 调用抽帧服务获取多张帧图
· 对每一帧尝试做人脸裁剪
· 将裁剪图或整帧图逐张发送给视频模型 Flask 服务
· 接收每一张图片的推理结果
· 将多张结果聚合为统一的VideoDetectionResult
在聚合时,我目前采用了较简单但直观的策略:视频整体伪造概率取各帧中的最大值,每一帧的检测结果保存在frameAnalysis中,fakeType取首个有效类型,confidence使用平均概率做简单映射。
VideoDetectionResult merged = new VideoDetectionResult();
merged.setType("video");
merged.setFakeProbability(maxProb);
merged.setConfidence(avgConfidence(sumProb, counted));
merged.setFakeType(dominantFakeType != null ? dominantFakeType : "face_frame_aggregated");
merged.setFrameAnalysis(analyses);
我认为这一实现方式还需要继续优化。
7. 原有上传接口如何接入新链路
为了尽量减少对前端的影响,我没有新增一套全新的视频上传接口,而是继续沿用原来的:
POST /api/upload/detect
当用户上传type=video的文件时,后端内部仍然调用DetectionService.detectVideo(...)。只不过现在detectVideo()会根据配置决定是走旧逻辑还是走新链路。
这样可以保持前端调用方式基本不变,兼容原有接口并使新需求可以平滑接入现有系统
我期望能尽量在不破坏原有结构的前提下扩展新功能。
五、遇到的问题
我在开发中遇到了几种问题,列在下面,具体解决方案参考上一模块
1. Prompt 内容不适合直接写死在代码中
第二阶段涉及多个大模型能力,如果把所有prompt都直接写在Java类中,不仅代码臃肿,还不便于后期调整
2. 视频新需求会不会破坏原有逻辑
如果直接把视频检测流程全部重写,原有“整段视频上传”的方式就无法保留,一旦新链路还不稳定,可能会影响已有功能。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)