namespace AIStudio.Assistants.BeyondFactsLab; public sealed partial class BflResearchRunner { /// /// The request which lets the model suggest whom to research. German, like the prompts. /// public const string SELECTION_REQUEST = "Wähle jetzt die Personen aus, wie es der Abschnitt „Vorgehen“ beschreibt, und antworte in der Form des Abschnitts „Form“."; /// /// The preparations for which the model suggests whom to research right now. Guarded by the lock of the research /// on the topics. /// private readonly HashSet suggesting = []; /// /// Happens when the lab starts or ends suggesting whom of the list of participants of a preparation to research. /// /// /// Called on the thread of the request. A page renders through InvokeAsync. /// public event Func? SelectionChanged; /// /// Tells whether the lab suggests whom of the list of participants of a preparation to research right now. /// /// The ID of the preparation. public bool IsSuggesting(Guid projectId) { lock (this.runningLock) return this.suggesting.Contains(projectId); } /// /// Lets the model suggest whom of a long list of participants to research in depth, and stores the suggestion. /// /// /// /// Unlike the research, this request knows the details of the preparation, see /// BflPromptComposer.ComposeParticipantSelection. It therefore has no tools, and the lab accepts its answer only /// when every name stands on the list, see BflParticipantSelectionCheck; the model gets one chance to correct it. /// The person checks and changes the suggestion before any person is researched. /// /// /// A list of no more than BflParticipantSelection.MAX_SUGGESTED_PEOPLE people needs no suggestion. What goes wrong /// is reported through the message bus; the method itself does not fail. /// /// /// The ID of the preparation. /// The provider of the preparation. /// Stops the request. public async Task SuggestSelectionAsync(Guid projectId, AIStudio.Settings.Provider provider, CancellationToken token = default) { var hidden = BflThreadFactory.CreateHidden("participant-selection"); if (!await conversations.CheckProviderAsync(new BflStepThreadLocation(projectId, BflStep.STRATEGY), hidden, provider)) return; lock (this.runningLock) { if (!this.suggesting.Add(projectId)) return; } try { await this.NotifyAsync(this.SelectionChanged, projectId); if (await store.LoadResearchAsync(projectId, BflResearchTopic.PARTICIPANTS, token) is not { } list || list.Participants.Count <= BflParticipantSelection.MAX_SUGGESTED_PEOPLE) return; var forms = await store.LoadFormsAsync(projectId); var template = await BflPromptLibrary.ReadAsync(BflPromptLibrary.PARTICIPANT_SELECTION_PATH, conversations.PromptOverridesDirectory, token); // No tools: the request knows the preparation, and nothing of it may reach the web. BflThreadFactory.PrepareForRequest(hidden, provider, BflPromptComposer.ComposeParticipantSelection(template, forms, list.Participants)); var check = BflParticipantSelectionCheck.Read(await BflHiddenRequest.AskAsync(hidden, provider, SELECTION_REQUEST, [], string.Empty, token), list.Participants); if (check.Problems.Count > 0) check = BflParticipantSelectionCheck.Read(await BflHiddenRequest.AskAsync(hidden, provider, BflPromptComposer.ComposeParticipantSelectionRepairRequest(check.Problems), [], string.Empty, token), list.Participants); if (check.Problems.Count > 0) { logger.LogWarning("The model suggested whom to research for the preparation '{ProjectId}' of the BeyondFacts Lab in a way the lab does not accept: {Problems}", projectId, string.Join(" | ", check.Problems)); await messageBus.SendError(new(AppIcons.ERROR, TA("The lab could not suggest whom to research in depth. Please choose the people yourself."))); return; } await store.SaveParticipantSelectionAsync(projectId, new BflParticipantSelection { Names = [.. check.Names], Source = BflParticipantSelectionSource.SUGGESTION, ListCreatedAtUtc = list.CreatedAtUtc, }, token); } catch (OperationCanceledException) { // The research which asked for the suggestion was stopped. } catch (Exception exception) when (IsReportedFailure(exception)) { await this.ReportFailureAsync(exception, $"{projectId:D}/research/participant-selection"); } finally { lock (this.runningLock) this.suggesting.Remove(projectId); await this.NotifyAsync(this.SelectionChanged, projectId); } } }