diff --git a/src/main/ai/visionPrompt.ts b/src/main/ai/visionPrompt.ts index 7a93989..1eb9b8b 100644 --- a/src/main/ai/visionPrompt.ts +++ b/src/main/ai/visionPrompt.ts @@ -7,16 +7,21 @@ export function buildVisionPrompt( // v0.3.11 — vision model 이 'format:json' constraint 를 부분적으로만 따르는 경우가 // 잦음 (특히 gemma3 vision). title 한국어 + JSON only 를 prompt 에서 명시 강조, // markdown fence 금지 표기로 schema parse 통과율 개선. - return `다음 메모와 첨부 이미지를 종합 분석해 한국어로 요약하세요. + // 본문 비었으면 이미지만 보고 title/summary 채우도록 명시. 그냥 "(이미지만 있음)" 만 + // 던지면 모델이 null 반환 (gemma4:26b 확인). 본문 있으면 본문 우선, 없으면 이미지 묘사. + const bodySection = text + ? `메모 본문:\n${text}\n\n첨부 이미지도 함께 분석해 요약에 반영하세요.` + : `본문이 없으니 첨부 이미지의 내용 (텍스트/사람/장면/문서 등) 을 보고 title 과 summary 를 한국어로 작성하세요. null 반환 금지.`; -메모 본문 (비어 있을 수 있음): -${text || '(이미지만 있음)'} + return `다음 메모를 한국어로 분석해 JSON 으로 정리하세요. -규칙: -- "title" 은 반드시 한국어로 (영어 금지). 60자 이내. -- "summary" 는 3줄. 이미지 시각 정보 (텍스트/사람/장면) 포함. -- "tags" 는 영문 kebab-case (예: meeting-notes), 최대 3개. 한국어 태그 금지. -- "due_date" 는 ISO 형식 YYYY-MM-DD 또는 null. +${bodySection} + +규칙 (위반 시 재시도): +- "title": 한국어 문자열 필수, null 금지. 60자 이내. 영어 단독 금지. +- "summary": 한국어 문자열 필수, null 금지. 3줄. 이미지 시각 정보 (텍스트/사람/장면) 포함. +- "tags": 영문 kebab-case 배열 (예: ["meeting-notes"]), 최대 3개. 한국어 태그 금지. 없으면 []. +- "due_date": ISO YYYY-MM-DD 또는 null. 빈 문자열 금지. 오직 JSON 객체 하나만 출력. markdown 코드 펜스 (\`\`\`) / 설명 prose 금지. 출력 형식: {"title":"...","summary":"...","tags":[],"due_date":null} diff --git a/tests/unit/visionPrompt.test.ts b/tests/unit/visionPrompt.test.ts index 5b42577..4c882e3 100644 --- a/tests/unit/visionPrompt.test.ts +++ b/tests/unit/visionPrompt.test.ts @@ -15,9 +15,16 @@ describe('buildVisionPrompt', () => { expect(result).toContain('work, meeting, project, todo'); }); - it('uses (이미지만 있음) placeholder when text is empty', () => { + it('본문 빈 경우 이미지 묘사 + null 금지 명시 (v0.3.14+)', () => { const result = buildVisionPrompt('', '2026-05-09', [], []); - expect(result).toContain('(이미지만 있음)'); - expect(result).not.toContain('\n\n\n'); // no double-blank from empty text + expect(result).toContain('본문이 없으니'); + expect(result).toContain('null 반환 금지'); + expect(result).not.toContain('메모 본문:\n'); + }); + + it('본문 있는 경우 본문 우선 + 이미지 함께 분석 명시', () => { + const result = buildVisionPrompt('회의 메모', '2026-05-09', [], []); + expect(result).toContain('메모 본문:\n회의 메모'); + expect(result).toContain('첨부 이미지도 함께 분석'); }); });