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);
}
}
}