import '../../data/ai/llm_service.dart'; import '../frame/validate_frame_level.dart'; import '../models/frame_pattern.dart'; import 'few_shot_builder.dart'; import 'frame_candidate.dart'; import 'parse_response.dart'; /// JSON schema (function-calling parameters) for the model output. const Map kFrameCandidatesSchema = { 'type': 'object', 'properties': { 'candidates': { 'type': 'array', 'minItems': 1, 'maxItems': 3, 'items': { 'type': 'object', 'properties': { 'level': {'type': 'string', 'enum': ['L2', 'L3']}, 'framed_text': {'type': 'string', 'maxLength': 120}, 'confidence': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'source_pattern_id': {'type': 'string'}, }, 'required': ['level', 'framed_text'], }, }, }, 'required': ['candidates'], }; const Duration _defaultTimeout = Duration(seconds: 10); const int _maxRawTextLength = 200; /// Main domain function (#215 §A). Pure-ish: depends only on injected /// [llm] and [framePatterns]. Never throws — failure returns an empty list /// so callers (UI) can decide messaging (graceful degradation, AC-9). Future> suggestFrame( SuggestFrameInput input, { required LlmService llm, required List framePatterns, Duration timeout = _defaultTimeout, }) async { final raw = input.rawText.trim(); if (raw.isEmpty || raw.length > _maxRawTextLength) { return const []; } final prompt = buildFewShotPrompt(input, framePatterns); Map json; try { json = await llm .generateStructured(prompt, kFrameCandidatesSchema) .timeout(timeout); } catch (_) { // Timeout / StateError / FormatException / anything else: graceful. return const []; } List candidates; try { candidates = parseFrameCandidates(json); } on FormatException { return const []; } // Drop L0/L1 + avoidance-violating via validateFrameLevel. final validated = []; for (final c in candidates) { final result = validateFrameLevel( FrameInput( level: c.level, framedText: c.framedText, originalText: input.rawText, ), knownPatterns: framePatterns, ); if (result.status == FrameValidationStatus.reject) continue; validated.add(c); if (validated.length >= 3) break; } return validated; }