本示例基于 Angular框架 + fmode-ng SDK,调用多模态大模型(fmode-1.6-cn
)的「视觉识别+文本理解」能力,实现道德与法治题目图片的全流程处理(图片OCR识别→结构化提取→答案解析→追问互动),核心是通过「视觉模态」突破纯文本输入限制,实现图片内容的语义化解析。
类别 | 具体内容 |
---|---|
框架 | Angular(依赖注入@Injectable ,服务级复用) |
核心SDK | fmode-ng (提供多模态调用completionJSON 、聊天流式输出FmodeChatCompletion ) |
数据存储 | Parse(SurveyItem 存储题目/解析,SurveyLog 存储搜题日志) |
多模态模型 | fmode-1.6-cn (支持视觉+文本双模态,核心用于图片题目识别) |
// 1. 多模态任务模板(关联后端模板逻辑)
export const DaofaQuestionRecognitionTplCode = 'daofa-question-recognition-tpl'; // 题目识别模板
export const DaofaQATplCode = 'daofa-qa-tpl'; // 问答/追问模板
// 2. 多模态模型标识(必须指定支持视觉的模型)
export const DaofaModel = 'fmode-1.6-cn';
通过大模型「视觉能力」解析上传的题目图片(如试卷、练习册拍照),将图片中的文字(题干、选项、材料)转化为结构化JSON数据(题型、关键词、选项等),是整个流程的「多模态入口」。
/**
* 多模态核心:识别题目图片(视觉+文本)
* @param options.images 图片数组(需为Base64格式,大模型视觉输入标准格式)
* @param options.onProgressChange 进度回调(如“正在识别题目内容...”)
* @returns 结构化存储的题目对象(SurveyItem)
*/
async recognizeQuestion(options: {
images: string[]; // 关键:图片输入(Base64字符串数组,支持多图)
onProgressChange?: (progress: string) => void;
loading?: any;
}): Promise<FmodeObject> {
return new Promise(async (resolve, reject) => {
try {
// 1. 多模态提示词设计(规则1:结构化提示,强制输出JSON格式)
const prompt = `请识别图片中的道德与法治题目,并按以下JSON格式输出:
{
"questionType": "题型(single-choice/multi-choice/judge/short-answer/material-analysis)",
"title": "题目标题或简述",
"content": "完整的题目内容",
"options": [{"label": "A", "value": "选项内容"}],
"material": "材料内容(如有)",
"keywords": ["关键词1", "关键词2"] // 如“宪法、权利、义务”
}
要求:
1. 准确识别题干、选项、材料的文字(含标点);
2. 严格匹配题型枚举值,不可自定义;
3. 关键词需贴合道德与法治学科核心概念。`;
// 2. 输出格式约束(规则2:提供JSON示例,降低大模型输出偏差)
const output = `{
"questionType": "single-choice",
"title": "宪法基本权利题",
"content": "下列属于公民基本权利的是?",
"options": [{"label": "A", "value": "依法纳税"}],
"material": "",
"keywords": ["公民基本权利", "宪法"]
}`;
// 3. 调用多模态能力(规则3:视觉识别必须配置vision=true + 传入images)
const questionData = await completionJSON(
prompt, // 文本提示词
output, // 输出格式示例
(content) => { // 增量回调(可选,用于进度更新)
options.loading?.message = `正在识别题目...${content?.length || 0}字`;
},
2, // 重试次数
{
model: DaofaModel, // 指定多模态模型
vision: true, // 启用视觉能力(核心开关)
images: options.images // 传入图片数组(Base64)
}
);
// 4. 结果校验(规则4:多模态输出必须先判空,避免后续流程异常)
if (!questionData || !questionData.content) {
throw new Error('题目识别结果为空(图片无有效文字或识别失败)');
}
// 5. 数据持久化(规则5:识别结果映射到标准存储对象SurveyItem)
const surveyItem = await this.saveSurveyItem({
type: 'daofa', // 学科标识
title: questionData.title || '未命名道德与法治题目',
content: questionData.content, // 识别后的题干
images: options.images, // 关联原始图片
keywords: questionData.keywords || [],
options: questionData.options || [],
createOptions: {
tpl: DaofaQuestionRecognitionTplCode,
params: {
questionType: questionData.questionType,
recognitionMode: 'image-ocr', // 标记为“图片OCR识别”来源
material: questionData.material
}
}
});
resolve(surveyItem);
} catch (err: any) {
reject(new Error(`图片识别失败:${err.message || '未知错误'}`));
}
});
}
将图片识别后的结构化题目(题干、选项、题型)传入大模型,生成符合「道德与法治学科规范」的解析(含标准答案、知识点、易错点等),支持流式输出(实时更新内容)。
async generateAnswer(options: {
surveyItem: FmodeObject; // 图片识别后的题目对象
onContentChange?: (content: string) => void; // 流式内容回调
}): Promise<FmodeObject> {
return new Promise(async (resolve, reject) => {
try {
// 1. 从图片识别结果中提取关键信息(规则:复用多模态输出的结构化数据)
const { content: questionContent, options: questionOptions } = options.surveyItem.attributes;
const questionType = options.surveyItem.get('createOptions').params.questionType;
// 2. 结构化提示词(规则:按学科要求分模块,确保解析专业度)
const prompt = `作为道德与法治教师,解析以下题目:
题目类型:${questionType}
题目内容:${questionContent}
${questionOptions.length ? `选项:\n${questionOptions.map(opt => `${opt.label}. ${opt.value}`).join('\n')}` : ''}
要求按以下格式输出:
【标准答案】(选择题需标注选项,如“A”;简答题需分点)
【知识点】(关联教材章节/法律条文,如“《宪法》第33条 公民在法律面前一律平等”)
【解题思路】(初中生易懂,结合生活实例)
【易错点】(如“混淆‘权利’与‘义务’,依法纳税是义务而非权利”)
【知识拓展】(关联时政热点,如“2024年法治宣传周主题”)`;
// 3. 流式调用(规则:使用FmodeChatCompletion实现实时内容更新)
const completion = new (await import('fmode-ng/lib/core/agent')).FmodeChatCompletion(
[{ role: 'user', content: prompt }], // 上下文
{ model: DaofaModel }
);
const subscription = completion.sendCompletion({ isDirect: true }).subscribe({
next: (message: any) => {
const content = message.content || '';
options.onContentChange?.(content); // 实时更新前端解析内容
// 当输出完成时,保存解析结果并标记选择题正确答案
if (message.complete && content) {
options.surveyItem.set('answer', content);
// 提取正确答案(如从“【标准答案】A”中匹配选项)
if (questionType.includes('choice')) {
const correctLabel = this.extractCorrectAnswer(content);
if (correctLabel) {
const updatedOptions = questionOptions.map(opt => ({
...opt,
check: opt.label === correctLabel // 标记正确选项
}));
options.surveyItem.set('options', updatedOptions);
}
}
options.surveyItem.save().then(resolve);
}
},
error: (err) => reject(new Error(`解析生成失败:${err.message}`))
});
} catch (err: any) {
reject(new Error(`解析生成失败:${err.message}`));
}
});
}
基于已识别的题目和解析,处理用户的后续疑问(如“为什么这个选项不对?”),需传入历史上下文确保回答连贯性。
async handleQuestion(options: {
parentQuestion: FmodeObject; // 原始题目(图片识别结果)
userQuestion: string; // 用户追问内容
}): Promise<FmodeObject> {
return new Promise(async (resolve, reject) => {
try {
// 1. 拼接上下文(规则:必须包含原始题目+已有解析,避免回答脱离场景)
const parentContent = options.parentQuestion.get('content');
const parentAnswer = options.parentQuestion.get('answer');
const prompt = `基于以下题目和解析,回答学生的追问:
题目内容:${parentContent}
已有解析:${parentAnswer}
学生追问:${options.userQuestion}
要求:
1. 针对追问精准回应,不偏离原始题目;
2. 用“启发式”语言(如“你可以先回顾‘权利与义务的区别’,再看选项是否符合权利的定义”);
3. 引用教材/法律条文时标注出处。`;
// 2. 调用大模型并保存追问记录
const completion = new (await import('fmode-ng/lib/core/agent')).FmodeChatCompletion(
[{ role: 'user', content: prompt }],
{ model: DaofaModel }
);
completion.sendCompletion({ isDirect: true }).subscribe({
next: async (message: any) => {
if (message.complete && message.content) {
// 保存追问记录到SurveyItem(类型为daofa-qa)
const qaItem = await this.saveQuestionAnswer({
parent: options.parentQuestion,
question: options.userQuestion,
answer: message.content
});
resolve(qaItem);
}
},
error: (err) => reject(new Error(`追问回答失败:${err.message}`))
});
} catch (err: any) {
reject(new Error(`追问处理失败:${err.message}`));
}
});
}
completionJSON
实现图片识别时,必须在参数中配置 { vision: true, images: 图片数组(Base64格式), model: 多模态模型(如fmode-1.6-cn) }
,缺一不可;images
参数需为「Base64字符串数组」(前端上传图片需先转Base64,不可直接传URL);output
参数的JSON模板),降低大模型输出偏差;parentContent
+parentAnswer
),避免回答脱离场景。recognizeQuestion
)是前置流程,后续的解析生成(generateAnswer
)、追问(handleQuestion
)必须依赖其输出的SurveyItem
对象;questionData
)需先校验「非空+关键字段存在」(如content
、questionType
),再进入存储/后续流程;FmodeChatCompletion
时,需通过next
事件处理增量内容(实时更新UI),complete
事件触发结果保存,避免数据丢失。SurveyItem
用于存储「题目/解析/追问」,需通过type
字段区分(daofa
=原始题目,daofa-qa
=追问记录);SurveyLog
需记录「搜题模式(图片/OCR)、上传图片、识别耗时」,用于后续统计与问题排查;daofa-qa
类型SurveyItem
)需通过parent
字段关联原始题目,确保上下文链路可追溯。graph TD
A[用户上传道德与法治题目图片] --> B[图片转Base64格式]
B --> C[调用recognizeQuestion(多模态视觉识别)]
C --> D{识别结果校验}
D -- 失败 --> E[抛出“识别失败”错误]
D -- 成功 --> F[保存为SurveyItem(type=daofa)]
F --> G[调用generateAnswer(文本解析生成)]
G --> H[流式输出解析内容,标记选择题正确答案]
H --> I[用户发起追问(如“为什么B选项错误?”)]
I --> J[调用handleQuestion(上下文互动)]
J --> K[保存为SurveyItem(type=daofa-qa,关联parent题目)]
K --> L[更新SurveyLog(记录追问次数、耗时)]