using AIStudio.Provider; using AIStudio.Settings; using AIStudio.Settings.DataModel; using AIStudio.Tools.ToolCallingSystem; namespace AIStudio.Tools; /// /// Decides what a selection of tools or data sources offers when several models work with it, e.g., both models of a /// comparison series of the Model Comparison assistant: each of them must be allowed everything the selection offers. /// /// /// With a single model, each rule gives what that model alone gives, so a selection for one model stays as it was. /// public static class SharedSelectionRules { /// /// Gets the confidence which counts for several models: the lowest of them, since a tool which one of them may not /// use must not be offered to any. /// /// The confidence of each model. /// The lowest confidence, or ConfidenceLevel.NONE without any model. public static ConfidenceLevel LowestConfidence(IEnumerable levels) => levels.DefaultIfEmpty(ConfidenceLevel.NONE).Min(); /// /// Tells whether several models can use tools: only when each of them can. /// /// Whether each model can use tools. /// The answer of the first model which cannot, with its reason, or that tools are available. public static ToolCallingAvailability ToolCallingOfAll(IEnumerable availabilities) { foreach (var availability in availabilities) if (!availability.IsAvailable) return availability; return ToolCallingAvailability.Available(); } /// /// Decides how the data sources are searched when several models chat with the same data source options: by /// Semantic Search only when each of them can search itself, so that none gets passages the other has to look for. /// /// How the data sources would be searched for each model on its own. /// /// The mode of the first model which cannot search itself, with its reason; otherwise every message when the user /// prefers it, and Semantic Search when not. Without any model, every message, since nobody could search. /// public static EffectiveRetrievalMode RetrievalModeOfAll(IEnumerable modes) { EffectiveRetrievalMode? result = null; foreach (var mode in modes) { if (mode.SemanticSearchBlockReason is not ToolOfferBlockReason.NONE) return mode; if (result is null || mode.Mode is DataSourceRetrievalMode.EVERY_MESSAGE) result = mode; } return result ?? new(DataSourceRetrievalMode.EVERY_MESSAGE, ToolOfferBlockReason.NONE); } /// /// Gets the data sources which several models may use: those which each of them may use. /// /// /// Every list keeps only the data sources which the checks put into that list for each model, in the order of the /// first model. That holds for those waiting for a re-index or a repair as well: such a data source passed every /// check, and the reason belongs to the data source, so it lands in the same list for each model which may use it. /// /// What the checks found for each model. /// The data sources each model may use, and nothing without any model. public static AllowedSelectedDataSources DataSourcesOfAll(IReadOnlyList results) { if (results.Count == 0) return new([], [], [], [], []); return new( Shared(results, result => result.AllowedDataSources), Shared(results, result => result.SelectedDataSources), Shared(results, result => result.DataSourcesAwaitingReindex), Shared(results, result => result.DataSourcesNeedingRepair), Shared(results, result => result.UnreachableDataSources)); } /// /// Gets the data sources of one list which every result holds in that list, in the order of the first result. /// private static IReadOnlyList Shared(IReadOnlyList results, Func> list) { var otherIds = results.Skip(1).Select(result => list(result).Select(dataSource => dataSource.Id).ToHashSet(StringComparer.Ordinal)).ToList(); return [.. list(results[0]).Where(dataSource => otherIds.All(ids => ids.Contains(dataSource.Id)))]; } }