feat: improve AI dialogue evaluation and capability boundaries

This commit is contained in:
shen
2026-09-19 18:58:14 -07:00
parent 335d0304b1
commit 7306dc0f85
23 changed files with 1308 additions and 452 deletions
@@ -0,0 +1,53 @@
part of '../ai_service.dart';
/// Advisory evaluation for open-ended learner answers. Local rules still
/// decide whether an attempt counts as evidence or changes learning state.
class AiAnswerEvaluationCapability {
const AiAnswerEvaluationCapability._(this._service);
final AiService _service;
static const descriptor = AiCapabilityDescriptor(
id: 'answer-evaluation',
promptVersion: 1,
outputContract: 'WritingAiFeedback',
);
Future<WritingAiFeedback?> evaluateWriting({
required AiProviderType provider,
required String endpoint,
required String model,
required String lessonId,
required String taskPrompt,
required String answer,
String level = 'A0',
}) => _service.writingFeedback(
provider: provider,
endpoint: endpoint,
model: model,
lessonId: lessonId,
taskPrompt: taskPrompt,
answer: answer,
level: level,
);
Future<WritingAiFeedback?> evaluateAnswer({
required AiProviderType provider,
required String endpoint,
required String model,
required String answerId,
required String target,
required String taskPrompt,
required String answer,
String level = 'A0',
}) => _service.answerFeedback(
provider: provider,
endpoint: endpoint,
model: model,
answerId: answerId,
target: target,
taskPrompt: taskPrompt,
answer: answer,
level: level,
);
}
@@ -0,0 +1,43 @@
part of '../ai_service.dart';
/// Stable metadata for an AI capability boundary.
///
/// [promptVersion] is bumped whenever prompt semantics or the expected model
/// contract changes. [outputContract] names the validated application model;
/// model output must never flow directly into learning state.
class AiCapabilityDescriptor {
const AiCapabilityDescriptor({
required this.id,
required this.promptVersion,
required this.outputContract,
});
final String id;
final int promptVersion;
final String outputContract;
}
/// Typed entry points for bounded AI features.
///
/// This is deliberately a capability registry, not an autonomous agent. The
/// application remains responsible for sequencing, fallback, completion and
/// mastery decisions.
class AiCapabilities {
AiCapabilities._(this._service);
final AiService _service;
late final speech = AiSpeechTranscriptionCapability._(_service);
late final lexicon = AiLexiconExplanationCapability._(_service);
late final dialogue = AiDialogueCoachCapability._(_service);
late final evaluation = AiAnswerEvaluationCapability._(_service);
late final review = AiReviewGenerationCapability._(_service);
List<AiCapabilityDescriptor> get descriptors => [
AiSpeechTranscriptionCapability.descriptor,
AiLexiconExplanationCapability.descriptor,
AiDialogueCoachCapability.descriptor,
AiAnswerEvaluationCapability.descriptor,
AiReviewGenerationCapability.descriptor,
];
}
@@ -0,0 +1,57 @@
part of '../ai_service.dart';
/// Supplies bounded dialogue suggestions while the application owns the turn
/// state machine and all completion decisions.
class AiDialogueCoachCapability {
const AiDialogueCoachCapability._(this._service);
final AiService _service;
static const descriptor = AiCapabilityDescriptor(
id: 'dialogue-coach',
promptVersion: 2,
outputContract: 'DialogueAiResponse or DialogueAiIntervention',
);
Future<DialogueAiResponse?> reply({
required AiProviderType provider,
required String endpoint,
required String model,
required List<Map<String, String>> history,
required String aiGoal,
required String learnerTask,
String level = 'A0',
List<String> allowedLanguage = const [],
}) => _service.dialogueReply(
provider: provider,
endpoint: endpoint,
model: model,
history: history,
aiGoal: aiGoal,
learnerTask: learnerTask,
level: level,
allowedLanguage: allowedLanguage,
);
Future<DialogueAiIntervention?> evaluateTurn({
required AiProviderType provider,
required String endpoint,
required String model,
required String partnerLine,
required String taskLabel,
required String learnerText,
String? turnId,
String? hint,
String level = 'A0',
}) => _service.checkDialogueIntervention(
provider: provider,
endpoint: endpoint,
model: model,
partnerLine: partnerLine,
taskLabel: taskLabel,
learnerText: learnerText,
turnId: turnId,
hint: hint,
level: level,
);
}
@@ -0,0 +1,39 @@
part of '../ai_service.dart';
/// Read-only language explanations. Results may be cached for display but do
/// not affect review scheduling, evidence or mastery.
class AiLexiconExplanationCapability {
const AiLexiconExplanationCapability._(this._service);
final AiService _service;
static const descriptor = AiCapabilityDescriptor(
id: 'lexicon-explanation',
promptVersion: 1,
outputContract: 'Temporary definition or SentenceAnalysisResult',
);
Future<String?> define({
required AiProviderType provider,
required String endpoint,
required String model,
required String text,
}) => _service.temporaryDefinition(
provider: provider,
endpoint: endpoint,
model: model,
text: text,
);
Future<SentenceAnalysisResult?> analyzeSentence({
required AiProviderType provider,
required String endpoint,
required String model,
required String text,
}) => _service.analyzeSentence(
provider: provider,
endpoint: endpoint,
model: model,
text: text,
);
}
@@ -0,0 +1,55 @@
part of '../ai_service.dart';
/// Generates bounded review material. Generation and audit remain explicit
/// workflow steps so the model cannot autonomously publish a lesson.
class AiReviewGenerationCapability {
const AiReviewGenerationCapability._(this._service);
final AiService _service;
static const descriptor = AiCapabilityDescriptor(
id: 'review-generation',
promptVersion: 1,
outputContract: 'GeneratedReviewVariant or audited GeneratedLesson',
);
Future<GeneratedReviewVariant?> generateVariant({
required AiProviderType provider,
required String endpoint,
required String model,
required String targetItemId,
required String basePrompt,
}) => _service.generateReviewVariant(
provider: provider,
endpoint: endpoint,
model: model,
targetItemId: targetItemId,
basePrompt: basePrompt,
);
Future<GeneratedLesson?> generateAdaptiveLesson({
required AiProviderType provider,
required String endpoint,
required String model,
required String targetItemId,
required String targetLabel,
}) => _service.generateAdaptiveLesson(
provider: provider,
endpoint: endpoint,
model: model,
targetItemId: targetItemId,
targetLabel: targetLabel,
);
Future<bool> auditAdaptiveLesson({
required AiProviderType provider,
required String endpoint,
required String model,
required GeneratedLesson lesson,
}) => _service.auditGeneratedLesson(
provider: provider,
endpoint: endpoint,
model: model,
lesson: lesson,
);
}
@@ -0,0 +1,27 @@
part of '../ai_service.dart';
/// Converts learner audio into a transcript. It does not award speech
/// evidence; transcript confirmation remains an application decision.
class AiSpeechTranscriptionCapability {
const AiSpeechTranscriptionCapability._(this._service);
final AiService _service;
static const descriptor = AiCapabilityDescriptor(
id: 'speech-transcription',
promptVersion: 1,
outputContract: 'String transcript',
);
Future<String?> transcribe({
required String filePath,
required AiProviderType provider,
required String endpoint,
required String model,
}) => _service.transcribeAudio(
filePath: filePath,
provider: provider,
endpoint: endpoint,
model: model,
);
}