fix(ai): 再解析を専用プロンプト+temperature=0.3に変更(東魁hallucination対策)
- reanalyzeSakeLabel() を新設: 前回のname/brandを渡し「本当に正しいか?」と問い直す - 通常解析(temperature=0)と再解析(temperature=0.3)を分離 → 同じ画像で毎回同じ誤答を返す問題を解消 - _callDirectApi に temperature パラメータを追加
This commit is contained in:
parent
902128a3ff
commit
582553ccfa
|
|
@ -345,7 +345,13 @@ class _SakeDetailScreenState extends ConsumerState<SakeDetailScreen> {
|
||||||
showDialog(context: context, barrierDismissible: false, builder: (context) => const AnalyzingDialog());
|
showDialog(context: context, barrierDismissible: false, builder: (context) => const AnalyzingDialog());
|
||||||
|
|
||||||
final geminiService = ref.read(geminiServiceProvider);
|
final geminiService = ref.read(geminiServiceProvider);
|
||||||
final result = await geminiService.analyzeSakeLabel(existingPaths, forceRefresh: true);
|
// 再解析専用メソッド: 前回の name/brand を渡してモデルに再考させる
|
||||||
|
// temperature=0.3 で非決定論的にすることで hallucination の繰り返しを防ぐ
|
||||||
|
final result = await geminiService.reanalyzeSakeLabel(
|
||||||
|
existingPaths,
|
||||||
|
previousName: _sake.displayData.displayName,
|
||||||
|
previousBrand: _sake.displayData.displayBrewery,
|
||||||
|
);
|
||||||
|
|
||||||
final newItem = _sake.copyWith(
|
final newItem = _sake.copyWith(
|
||||||
name: result.name ?? _sake.displayData.displayName,
|
name: result.name ?? _sake.displayData.displayName,
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,74 @@ name・brand を出力する直前に以下を確認してください:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 再解析専用メソッド: 前回の結果を「疑わしい」として渡し、モデルに再考させる
|
||||||
|
///
|
||||||
|
/// 通常の analyzeSakeLabel と異なる点:
|
||||||
|
/// - 前回の name/brand を明示的に伝え「本当にこれが正しいか?」と問い直す
|
||||||
|
/// - temperature=0.3 で確定論的でなくする(同じ入力でも違う出力の余地を作る)
|
||||||
|
/// - これにより 東魁→東魁盛 のような hallucination が再解析でも繰り返されにくくなる
|
||||||
|
Future<SakeAnalysisResult> reanalyzeSakeLabel(
|
||||||
|
List<String> imagePaths, {
|
||||||
|
String? previousName,
|
||||||
|
String? previousBrand,
|
||||||
|
}) async {
|
||||||
|
final prevNameStr = previousName != null ? '「$previousName」' : '不明';
|
||||||
|
final prevBrandStr = previousBrand != null ? '「$previousBrand」' : '不明';
|
||||||
|
|
||||||
|
final challengePrompt = '''
|
||||||
|
【再解析モード — 前回の回答を検証してください】
|
||||||
|
|
||||||
|
前回の解析では以下の結果が返されました:
|
||||||
|
- name(銘柄名): $prevNameStr
|
||||||
|
- brand(蔵元名): $prevBrandStr
|
||||||
|
|
||||||
|
この回答をユーザーが確認し、誤りの可能性があると指摘しました。
|
||||||
|
添付画像を最初から丁寧に見直してください。
|
||||||
|
|
||||||
|
## 【必須確認ステップ】
|
||||||
|
1. ラベル内の文字を1文字ずつ目で追ってください
|
||||||
|
2. 前回の name=$prevNameStr の各漢字がラベルに実際に存在するか確認してください
|
||||||
|
3. 存在しない文字が含まれていれば、ラベルに見えている文字のみに修正してください
|
||||||
|
4. 「ラベルに N 文字しか見えないなら N 文字のみ返す」を厳守してください
|
||||||
|
|
||||||
|
## 【絶対ルール】name・brand・prefectureの読み取り(OCR厳守)
|
||||||
|
- ラベルに印刷されている文字だけを一字一句そのまま出力してください
|
||||||
|
- あなたが知っている「正式名称」への変換・補完は禁止
|
||||||
|
- 【禁止例】「東魁」→「東魁盛」禁止 / 「男山」→「男山本醸造」禁止
|
||||||
|
- ラベルに都道府県名がなければ prefecture は null(推測禁止)
|
||||||
|
|
||||||
|
## その他のフィールド(推定可)
|
||||||
|
ラベル情報+日本酒の一般知識を使って推定してください。
|
||||||
|
|
||||||
|
## 出力形式
|
||||||
|
以下のJSONのみ返す(説明文不要):
|
||||||
|
{
|
||||||
|
"name": "ラベルに写っている銘柄名(補完禁止)",
|
||||||
|
"brand": "ラベルに写っている蔵元名(補完禁止)",
|
||||||
|
"prefecture": "ラベルに書かれた都道府県名(なければnull)",
|
||||||
|
"type": "特定名称(なければnull)",
|
||||||
|
"description": "説明文(100文字程度)",
|
||||||
|
"catchCopy": "20文字以内のキャッチコピー",
|
||||||
|
"confidenceScore": 80,
|
||||||
|
"flavorTags": ["フルーティー", "辛口"],
|
||||||
|
"tasteStats": {"aroma":3,"sweetness":3,"acidity":3,"bitterness":3,"body":3},
|
||||||
|
"alcoholContent": 15.0,
|
||||||
|
"polishingRatio": 50,
|
||||||
|
"sakeMeterValue": 3.0,
|
||||||
|
"riceVariety": null,
|
||||||
|
"yeast": null,
|
||||||
|
"manufacturingYearMonth": null
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
return _callDirectApi(
|
||||||
|
imagePaths,
|
||||||
|
challengePrompt,
|
||||||
|
forceRefresh: true,
|
||||||
|
temperature: 0.3,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 共通実装: ProxyへのAPIコール
|
/// 共通実装: ProxyへのAPIコール
|
||||||
Future<SakeAnalysisResult> _callProxyApi({
|
Future<SakeAnalysisResult> _callProxyApi({
|
||||||
required List<String> imagePaths,
|
required List<String> imagePaths,
|
||||||
|
|
@ -215,7 +283,7 @@ name・brand を出力する直前に以下を確認してください:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Direct Cloud API Implementation (No Proxy)
|
/// Direct Cloud API Implementation (No Proxy)
|
||||||
Future<SakeAnalysisResult> _callDirectApi(List<String> imagePaths, String? customPrompt, {bool forceRefresh = false}) async {
|
Future<SakeAnalysisResult> _callDirectApi(List<String> imagePaths, String? customPrompt, {bool forceRefresh = false, double temperature = 0}) async {
|
||||||
// 1. キャッシュチェック(同じ画像なら即座に返す)
|
// 1. キャッシュチェック(同じ画像なら即座に返す)
|
||||||
// forceRefresh=trueの場合はキャッシュをスキップ
|
// forceRefresh=trueの場合はキャッシュをスキップ
|
||||||
if (!forceRefresh && imagePaths.isNotEmpty) {
|
if (!forceRefresh && imagePaths.isNotEmpty) {
|
||||||
|
|
@ -316,7 +384,7 @@ name・brand を出力する直前に以下を確認してください:
|
||||||
),
|
),
|
||||||
generationConfig: GenerationConfig(
|
generationConfig: GenerationConfig(
|
||||||
responseMimeType: 'application/json',
|
responseMimeType: 'application/json',
|
||||||
temperature: 0,
|
temperature: temperature,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.46+53
|
version: 1.0.47+54
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.1
|
sdk: ^3.10.1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue