feat(medications): 增强AI药品识别质量控制和多图片支持

- 新增图片可读性预检查机制,识别前先判断图片质量
- 设置置信度阈值为60%,低于阈值自动识别失败
- 支持多图片上传(正面、侧面、辅助图片)提高识别准确度
- 完善识别失败场景的错误分类和用户指导提示
- 新增药品有效期字段支持
- 优化AI提示词,强调安全优先原则
- 更新模型版本为 glm-4.5v 和 glm-4.5-air

数据库变更:
- Medication表新增 sideImageUrl, auxiliaryImageUrl, expiryDate 字段
- DTO层同步支持新增字段的传递和更新

质量控制策略:
- 图片模糊或不可读时直接返回失败
- 无法识别药品名称时主动失败
- 置信度<60%时拒绝识别,建议重新拍摄
- 宁可识别失败也不提供不准确的药品信息
This commit is contained in:
richarjiang
2025-11-21 16:59:36 +08:00
parent a17fe0b965
commit f8fcc81438
7 changed files with 254 additions and 42 deletions

View File

@@ -10,7 +10,6 @@ import {
RecognitionStatusEnum,
RECOGNITION_STATUS_DESCRIPTIONS,
} from '../enums/recognition-status.enum';
import { MedicationFormEnum } from '../enums/medication-form.enum';
/**
* 药物AI识别服务
@@ -39,9 +38,9 @@ export class MedicationRecognitionService {
});
this.visionModel =
this.configService.get<string>('GLM_VISION_MODEL') || 'glm-4v-plus';
this.configService.get<string>('GLM_VISION_MODEL') || 'glm-4.5v';
this.textModel =
this.configService.get<string>('GLM_MODEL') || 'glm-4-flash';
this.configService.get<string>('GLM_MODEL') || 'glm-4.5-air';
}
/**
@@ -167,12 +166,16 @@ export class MedicationRecognitionService {
);
const effectsInfo = await this.analyzeEffects(productInfo);
// 合并所有结果
// 合并所有结果透传所有原始图片URL避免被AI模型修改
const finalResult = {
...productInfo,
...suitabilityInfo,
...ingredientsInfo,
...effectsInfo,
// 强制使用任务记录中存储的原始图片URL覆盖AI可能返回的不正确链接
photoUrl: task.frontImageUrl,
sideImageUrl: task.sideImageUrl,
auxiliaryImageUrl: task.auxiliaryImageUrl,
} as RecognitionResultDto;
// 完成识别
@@ -224,7 +227,31 @@ export class MedicationRecognitionService {
}
const parsed = this.parseJsonResponse(content);
this.logger.log(`药品基本信息识别完成: ${parsed.name}, 置信度: ${parsed.confidence}`);
// 严格的置信度和可读性验证
const confidence = parsed.confidence || 0;
const isReadable = parsed.isReadable !== false; // 默认为 true 保持向后兼容
this.logger.log(
`药品识别结果: ${parsed.name}, 置信度: ${confidence}, 可读性: ${isReadable}`,
);
// 如果模型明确表示看不清或置信度过低,则失败
if (!isReadable) {
throw new Error('图片模糊或光线不足,无法清晰识别药品信息。请重新拍摄更清晰的照片。');
}
if (parsed.name === '无法识别' || parsed.name === '未知' || !parsed.name) {
throw new Error('无法从图片中识别出药品名称。请确保药品名称清晰可见,或选择手动输入。');
}
// 置信度阈值检查:低于 0.6 视为不可靠
if (confidence < 0.6) {
throw new Error(
`识别置信度过低 (${(confidence * 100).toFixed(0)}%)。建议重新拍摄更清晰的照片,确保药品名称、规格等信息清晰可见。`,
);
}
return parsed;
}
@@ -324,20 +351,29 @@ export class MedicationRecognitionService {
private buildProductRecognitionPrompt(): string {
return `你是一位拥有20年从业经验的资深药剂师请根据提供的药品图片包括正面、侧面和可能的辅助面进行详细分析。
**分析要求**
**重要前提条件 - 图片可读性判断**
⚠️ 在进行任何识别之前,你必须首先判断图片是否足够清晰可读:
1. 检查图片是否模糊、过曝、欠曝或有严重反光
2. 检查药品名称、规格等关键信息是否清晰可见
3. 检查文字是否完整、无遮挡
4. 如果图片质量不佳,无法清晰辨认关键信息,必须设置 isReadable 为 false
**只有在图片清晰可读的情况下才能继续分析**
1. 仔细观察药品包装、说明书上的所有信息
2. 识别药品的完整名称(通用名和商品名)
3. 确定药物剂型(片剂/胶囊/注射剂等)
4. 提取规格剂量信息
5. 推荐合理的服用次数和时间
**置信度评估标准**
**置信度评估标准(仅在图片可读时评估)**
- 如果图片清晰且信息完整,置信度应 >= 0.8
- 如果部分信息不清晰但可推断,置信度 0.5-0.8
- 如果无法准确识别,置信度 < 0.5name返回"无法识别"
- 如果部分信息不清晰但大部分可推断,置信度 0.6-0.8
- 如果关键信息缺失或模糊不清,置信度 < 0.6name返回"无法识别"
- 置信度评估必须严格基于实际可见信息,不能猜测或臆断
**返回严格的JSON格式**不要包含任何markdown标记
{
"isReadable": true或false图片是否足够清晰可读,
"name": "药品完整名称",
"photoUrl": "使用正面图片URL",
"form": "剂型(tablet/capsule/injection/drops/syrup/ointment/powder/granules)",
@@ -348,12 +384,17 @@ export class MedicationRecognitionService {
"confidence": 识别置信度(0-1的小数)
}
**重要**
- dosageValue 和 timesPerDay 必须是数字类型,不要加引号
- confidence 必须是 0-1 之间的小数
- medicationTimes 必须是 HH:mm 格式的时间数组
- form 必须是枚举值之一
- 如果无法识别name返回"无法识别"其他字段返回合理的默认值`;
**关键规则(必须遵守)**
1. isReadable 是最重要的字段,如果为 false其他识别结果将被忽略
2. 当图片模糊、反光、文字不清晰时,必须设置 isReadable 为 false
3. 只有在确实能看清并理解图片内容时,才能设置 isReadable 为 true
4. confidence 必须反映真实的识别把握程度,不能虚高
5. 如果 isReadable 为 falsename 必须返回"无法识别"confidence 设为 0
6. dosageValue 和 timesPerDay 必须是数字类型,不要加引号
7. medicationTimes 必须是 HH:mm 格式的时间数组
8. form 必须是枚举值之一
**宁可识别失败,也不要提供不准确的药品信息。用药安全高于一切!**`;
}
/**