better ai engine error handling

This commit is contained in:
Fabian Freund
2025-10-15 06:46:08 +02:00
parent 31c52ca47c
commit 3e65418597
@@ -20,6 +20,7 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:exceptions/exceptions.dart';
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:flutter_mozilla_components/ml_utils.dart'; import 'package:flutter_mozilla_components/ml_utils.dart';
@@ -27,6 +28,7 @@ import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:synchronized/synchronized.dart'; import 'package:synchronized/synchronized.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
@@ -62,36 +64,44 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
} }
} }
Future<String?> predictDocumentTopic(Set<String> titles) async { Future<Result<String?>> predictDocumentTopic(Set<String> titles) async {
if (!ref.read( if (!ref.read(
generalSettingsWithDefaultsProvider.select( generalSettingsWithDefaultsProvider.select(
(settings) => settings.enableLocalAiFeatures, (settings) => settings.enableLocalAiFeatures,
), ),
)) { )) {
return null; return Result.success(null);
} }
if (titles.isNotEmpty) { if (titles.isNotEmpty) {
if (_topicCache.get(titles) case final String title) { if (_topicCache.get(titles) case final String title) {
return title; return Result.success(title);
} }
try { try {
final title = await _engineLock.synchronized(() async { final title = await _engineLock.synchronized(() async {
await _initialLoadComplete.future; await _initialLoadComplete.future;
final title = await _service.predictDocumentTopic(titles); final title = await Result.fromAsync(
() async => await _service.predictDocumentTopic(titles),
);
title.onSuccess((title) => _topicCache.set(titles, title));
return _topicCache.set(titles, title); return title;
}, timeout: const Duration(seconds: 120)); }, timeout: const Duration(seconds: 120));
return title; return title;
} on TimeoutException { } on TimeoutException {
return null; return Result.failure(
const ErrorMessage(
source: 'Document Title Prediction',
message: 'Timeout',
),
);
} }
} }
return null; return Result.success(null);
} }
Future<List<String>?> suggestDocuments({ Future<List<String>?> suggestDocuments({
@@ -126,15 +136,30 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
...assignedDocumentsProcessed, ...assignedDocumentsProcessed,
]); ]);
final neighbors = embeddings.mapNotNull( return embeddings.fold(
(embeddings) => findNearestNeighborsRecursive( (embeddings) {
embeddings: embeddings, final neighbors = embeddings.mapNotNull(
assignedDocuments: assignedDocumentsProcessed, (embeddings) =>
unassignedDocuments: unassignedDocumentsProcessed, findNearestNeighborsRecursive(
).map((neighbor) => processedDocuments[neighbor] ?? neighbor).toList(), embeddings: embeddings,
); assignedDocuments: assignedDocumentsProcessed,
unassignedDocuments: unassignedDocumentsProcessed,
)
.map((neighbor) => processedDocuments[neighbor] ?? neighbor)
.toList(),
);
return neighbors; return neighbors;
},
onFailure: (errorMessage) {
logger.e(
errorMessage.message,
error: errorMessage.details,
stackTrace: errorMessage.stackTrace,
);
return null;
},
);
} }
Future<List<SuggestedContainer>?> suggestClusters({ Future<List<SuggestedContainer>?> suggestClusters({
@@ -164,42 +189,66 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
unassignedDocumentsProcessed, unassignedDocumentsProcessed,
); );
final clusters = embeddings.mapNotNull( return embeddings.fold(
(embeddings) => clusterEmbeddings(embeddings: embeddings.values.toList()) (embeddings) async {
.map( final clusters = embeddings.mapNotNull(
(cluster) => (embeddings) =>
cluster.map((i) => embeddings.keys.elementAt(i)).toList(), clusterEmbeddings(embeddings: embeddings.values.toList())
) .map(
.toList(), (cluster) => cluster
.map((i) => embeddings.keys.elementAt(i))
.toList(),
)
.toList(),
);
final clusterResult = await clusters.mapNotNull(
(cluster) => Future.wait(
cluster.map((clusterTitles) async {
final originalTitles = clusterTitles
.map((title) => processedDocuments[title] ?? title)
.toSet();
final topic = await predictDocumentTopic(originalTitles);
return (
topic: topic.fold(
(topic) => topic,
onFailure: (errorMessage) {
logger.e(
errorMessage.message,
error: errorMessage.details,
stackTrace: errorMessage.stackTrace,
);
return null;
},
),
tabIds: originalTitles
.map(
(title) => unassignedDocumentsInput.entries
.firstWhere((entry) => entry.value == title)
.key,
)
.toList(),
);
}),
),
);
return clusterResult;
},
onFailure: (errorMessage) {
logger.e(
errorMessage.message,
error: errorMessage.details,
stackTrace: errorMessage.stackTrace,
);
return null;
},
); );
final clusterResult = await clusters.mapNotNull(
(cluster) => Future.wait(
cluster.map((clusterTitles) async {
final originalTitles = clusterTitles
.map((title) => processedDocuments[title] ?? title)
.toSet();
final topic = await predictDocumentTopic(originalTitles);
return (
topic: topic,
tabIds: originalTitles
.map(
(title) => unassignedDocumentsInput.entries
.firstWhere((entry) => entry.value == title)
.key,
)
.toList(),
);
}),
),
);
return clusterResult;
} }
Future<Map<String, List<double>>?> generateDocumentEmbeddings( Future<Result<Map<String, List<double>>?>> generateDocumentEmbeddings(
List<String> documents, List<String> documents,
) async { ) async {
try { try {
@@ -216,24 +265,30 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
final generatedEmbeddings = await _engineLock.synchronized(() async { final generatedEmbeddings = await _engineLock.synchronized(() async {
await _initialLoadComplete.future; await _initialLoadComplete.future;
final embeddings = await _service.generateDocumentEmbeddings( final embeddings = await Result.fromAsync(
documents, () async => await _service.generateDocumentEmbeddings(documents),
); );
return embeddings; return embeddings;
}, timeout: const Duration(seconds: 120)); }, timeout: const Duration(seconds: 120));
if (!generatedEmbeddings.isSuccess) {
return Result.failure(generatedEmbeddings.error!);
}
for (var i = 0; i < embeddingsToGenerate.length; i++) { for (var i = 0; i < embeddingsToGenerate.length; i++) {
embeddings[embeddingsToGenerate[i]] = generatedEmbeddings[i]; embeddings[embeddingsToGenerate[i]] = generatedEmbeddings.value[i];
} }
} }
return { return Result.success({
for (final MapEntry(:key, :value) in embeddings.entries) for (final MapEntry(:key, :value) in embeddings.entries)
if (value != null) key: value, if (value != null) key: value,
}; });
} on TimeoutException { } on TimeoutException {
return null; return Result.failure(
const ErrorMessage(source: 'Document Embeddings', message: 'Timeout'),
);
} }
} }
@@ -283,11 +338,23 @@ Future<String?> topicSuggestion(
.read(geckoInferenceRepositoryProvider.notifier) .read(geckoInferenceRepositoryProvider.notifier)
.predictDocumentTopic(titles.value); .predictDocumentTopic(titles.value);
if (ref.mounted && topic.isNotEmpty) { return topic.fold(
ref.keepAlive(); (topic) {
} if (ref.mounted && topic.isNotEmpty) {
ref.keepAlive();
}
return topic; return topic;
},
onFailure: (errorMessage) {
logger.e(
errorMessage.message,
error: errorMessage.details,
stackTrace: errorMessage.stackTrace,
);
return null;
},
);
} }
@Riverpod() @Riverpod()
@@ -359,6 +426,19 @@ Future<List<String>?> containerTabSuggestions(
.read(geckoInferenceRepositoryProvider.notifier) .read(geckoInferenceRepositoryProvider.notifier)
.predictDocumentTopic( .predictDocumentTopic(
assignedTitles.value.map((tab) => tab.$2).toSet(), assignedTitles.value.map((tab) => tab.$2).toSet(),
)
.then(
(result) => result.fold(
(value) => value,
onFailure: (errorMessage) {
logger.e(
errorMessage.message,
error: errorMessage.details,
stackTrace: errorMessage.stackTrace,
);
return null;
},
),
); );
if (ref.mounted && topic != null) { if (ref.mounted && topic != null) {