using System.Collections.Frozen; using AIStudio.Settings.DataModel; namespace AIStudio.Assistants.BeyondFactsLab; /// /// Reads the prompts and the knowledge base of the BeyondFacts Lab, which come with AI Studio. /// /// /// The files are German Markdown below Assistants/BeyondFactsLab, embedded into the app. A development build reads /// them from the disk, so that a changed prompt shows in the next request without building again; see AppResources. /// While the person allows it, files in the folder for prompt overrides replace them, see BflPromptOverrides. /// public static class BflPromptLibrary { /// /// The folder of the lab, relative to the project folder. /// private const string FOLDER = "Assistants/BeyondFactsLab"; /// /// The prompt every chat of the lab starts with: role, principles, and the places of the other parts. /// public const string CORE_PATH = FOLDER + "/Prompts/core.md"; /// /// The guide by Lara Wüster (TUM), which every chat of the lab keeps in its context. /// public const string KNOWLEDGE_PATH = FOLDER + "/Knowledge/beyondfacts-paper.md"; /// /// The prompt of the conversation in which the lab tells whether it fits a task. It stands on its own, without /// the core and the guide. /// public const string ONBOARDING_PATH = FOLDER + "/Prompts/onboarding.md"; /// /// The request which lets the model write the work state of a step, at the end of the conversation of the step. /// public const string WORK_STATE_PATH = FOLDER + "/Prompts/work-state.md"; /// /// The folder of the requests which let the model write a result, one for each kind a model writes. /// private const string RESULTS_FOLDER = FOLDER + "/Prompts/results/"; /// /// The folder of the prompts of the research agents: the prompt they share, and the task of each topic. /// private const string RESEARCH_FOLDER = FOLDER + "/Prompts/research/"; /// /// The prompt every research agent starts with: its role, its rules, and the public details about the event. /// public const string RESEARCH_PATH = RESEARCH_FOLDER + "research.md"; /// /// The prompt every agent of the research on a person starts with: its role, its rules, the person, what earlier /// stages found, and the public details about the event. /// public const string PERSON_RESEARCH_PATH = RESEARCH_FOLDER + "person.md"; /// /// The prompt with which the lab suggests whom of a long list of participants to research. It knows the details of /// the preparation, so its request has no tools. /// public const string PARTICIPANT_SELECTION_PATH = RESEARCH_FOLDER + "participant-selection.md"; /// /// The instruction to address the person formally. /// private const string ADDRESS_FORMAL_PATH = FOLDER + "/Prompts/snippets/address-formal.md"; /// /// The instruction to address the person informally. /// private const string ADDRESS_INFORMAL_PATH = FOLDER + "/Prompts/snippets/address-informal.md"; /// /// How the chats of the steps treat the guidelines of the organization; the core carries it only while there are /// any, see BflOrganizationRules. /// public const string ORGANIZATION_RULES_PATH = FOLDER + "/Prompts/snippets/organization-rules.md"; /// /// What the conversation about whether the lab fits a task knows about the guidelines of the organization; it /// carries it only while there are any. /// public const string ONBOARDING_ORGANIZATION_RULES_PATH = FOLDER + "/Prompts/snippets/organization-rules-onboarding.md"; /// /// The folder of the files which describe how the chat of the step "Test situation" plays people from the research. /// private const string SIMULATION_FOLDER = FOLDER + "/Prompts/simulation/"; /// /// The rules for every simulation of people from the research: their limits, how the chat marks them, and how an /// exercise runs and ends. /// public const string PERSONA_PATH = SIMULATION_FOLDER + "persona.md"; /// /// The placeholders BflPromptComposer fills in the core. /// private static readonly FrozenSet CORE_PLACEHOLDERS = FrozenSet.Create(StringComparer.Ordinal, BflPromptTemplate.RESPONSE_LANGUAGE, BflPromptTemplate.ADDRESS_FORM, BflPromptTemplate.KNOWLEDGE, BflPromptTemplate.STEP, BflPromptTemplate.MATERIALS, BflPromptTemplate.TOOLS, BflPromptTemplate.RESEARCH, BflPromptTemplate.DETAILS, BflPromptTemplate.MISSING_DETAILS, BflPromptTemplate.WORK_STATES, BflPromptTemplate.ORGANIZATION_RULES); /// /// The placeholders BflPromptComposer fills in the prompt of the onboarding. /// private static readonly FrozenSet ONBOARDING_PLACEHOLDERS = FrozenSet.Create(StringComparer.Ordinal, BflPromptTemplate.RESPONSE_LANGUAGE, BflPromptTemplate.ADDRESS_FORM, BflPromptTemplate.TOOLS, BflPromptTemplate.ORGANIZATION_RULES); /// /// The placeholders BflPromptComposer fills in the request for a work state. /// private static readonly FrozenSet WORK_STATE_PLACEHOLDERS = FrozenSet.Create(StringComparer.Ordinal, BflPromptTemplate.SECTIONS, BflPromptTemplate.FIELDS); /// /// The placeholders BflPromptComposer fills in the request for a result. /// private static readonly FrozenSet RESULT_PLACEHOLDERS = FrozenSet.Create(StringComparer.Ordinal, BflPromptTemplate.MISSING_WORK_STATES); /// /// The placeholders BflPromptComposer fills in the prompt of the research agents. /// /// /// Nothing but the public details about the event: no placeholder brings the material, the work states, or any /// other detail of a preparation into a research. /// private static readonly FrozenSet RESEARCH_PLACEHOLDERS = FrozenSet.Create(StringComparer.Ordinal, BflPromptTemplate.RESPONSE_LANGUAGE, BflPromptTemplate.TOPIC, BflPromptTemplate.TOOL_CALLS, BflPromptTemplate.PUBLIC_DETAILS); /// /// The placeholders BflPromptComposer fills in the prompt of the agents of the research on a person. /// /// /// Nothing but public content: the public details about the event, the entry of the person in the list of /// participants, and what earlier stages found on the web. No placeholder brings the material, the work states, or /// any other detail of a preparation into a research. /// private static readonly FrozenSet PERSON_RESEARCH_PLACEHOLDERS = FrozenSet.Create(StringComparer.Ordinal, BflPromptTemplate.RESPONSE_LANGUAGE, BflPromptTemplate.TOPIC, BflPromptTemplate.TOOL_CALLS, BflPromptTemplate.PERSON, BflPromptTemplate.EARLIER_STAGES, BflPromptTemplate.PUBLIC_DETAILS); /// /// The placeholders BflPromptComposer fills in the prompt which suggests whom to research: the details of the /// preparation and the list of participants. /// private static readonly FrozenSet PARTICIPANT_SELECTION_PLACEHOLDERS = FrozenSet.Create(StringComparer.Ordinal, BflPromptTemplate.SELECTION_SIZE, BflPromptTemplate.DETAILS, BflPromptTemplate.PARTICIPANTS); /// /// The placeholders of every other file: none, because BflPromptComposer inserts these files as values. /// private static readonly FrozenSet NO_PLACEHOLDERS = FrozenSet.Empty; /// /// Gets the prompt of a step, named like the folder of the step. /// /// The step. public static string StepPath(BflStep step) => $"{FOLDER}/Prompts/steps/{step.FolderName}.md"; /// /// Gets the request which lets the model write a result. /// /// The kind of result, one a model writes. /// No model writes this kind of result. public static string ResultPath(BflResultKind kind) => kind switch { BflResultKind.POSITION_PAPER => RESULTS_FOLDER + "position-paper.md", BflResultKind.CONVERSATION_GUIDE => RESULTS_FOLDER + "conversation-guide.md", BflResultKind.QUESTIONS_AND_ANSWERS => RESULTS_FOLDER + "questions-and-answers.md", BflResultKind.OPEN_POINTS => RESULTS_FOLDER + "open-points.md", _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "No model writes this result.") }; /// /// Gets the task of the research agent of a topic. /// /// The topic of the research. public static string ResearchTopicPath(BflResearchTopic topic) => $"{RESEARCH_FOLDER}{topic.FileStem}.md"; /// /// Gets the task of the agent of a stage of the research on a person. /// /// The stage of the research. public static string PersonResearchStagePath(BflPersonResearchStage stage) => $"{RESEARCH_FOLDER}person-{stage.FileStem}.md"; /// /// Gets the file which describes the simulation of a mode with people from the research. /// /// The mode, one with people. /// The mode plays generic counterparts, which need no file of their own. public static string SimulationPath(BflSimulationMode mode) => $"{SIMULATION_FOLDER}{mode.FileStem}.md"; /// /// Gets the instruction for a form of address. /// /// The form of address. public static string AddressPath(AddressForm addressForm) => addressForm is AddressForm.INFORMAL ? ADDRESS_INFORMAL_PATH : ADDRESS_FORMAL_PATH; /// /// Gets every file of the lab's prompts, e.g., to check that the app comes with all of them. /// public static IReadOnlyList AllPaths => [ CORE_PATH, KNOWLEDGE_PATH, ONBOARDING_PATH, WORK_STATE_PATH, ..BflResultKind.All.Where(kind => kind.IsWrittenByModel).Select(ResultPath), RESEARCH_PATH, ..BflResearchTopic.All.Select(ResearchTopicPath), PERSON_RESEARCH_PATH, ..BflPersonResearchStage.All.Select(PersonResearchStagePath), PARTICIPANT_SELECTION_PATH, ..Enum.GetValues().Select(AddressPath).Distinct(), ORGANIZATION_RULES_PATH, ONBOARDING_ORGANIZATION_RULES_PATH, ..BflStep.All.Select(StepPath), PERSONA_PATH, ..BflSimulationMode.All.Where(mode => mode.HasPeople).Select(SimulationPath) ]; /// /// Gets a file of the lab's prompts relative to the folder of the lab, e.g., "Prompts/core.md". /// /// The file, one of the paths of this class. /// The file lies outside the folder of the lab. public static string RelativePath(string path) => path.StartsWith(FOLDER + "/", StringComparison.Ordinal) ? path[(FOLDER.Length + 1)..] : throw new ArgumentException("The file lies outside the folder of the BeyondFacts Lab.", nameof(path)); /// /// Gets the placeholders a file of the lab's prompts may contain. /// /// /// BflPromptComposer fills placeholders in the core, in the prompt of the onboarding, in the requests for a work /// state and for a result, in the prompts of the research agents on topics and on people, and in the prompt which /// suggests whom to research only. It inserts every other file as a value, whose placeholders it leaves as they /// are, so that they would reach the model as text. /// /// The file, one of the paths of this class. public static IReadOnlySet PlaceholdersOf(string path) => path switch { CORE_PATH => CORE_PLACEHOLDERS, ONBOARDING_PATH => ONBOARDING_PLACEHOLDERS, WORK_STATE_PATH => WORK_STATE_PLACEHOLDERS, RESEARCH_PATH => RESEARCH_PLACEHOLDERS, PERSON_RESEARCH_PATH => PERSON_RESEARCH_PLACEHOLDERS, PARTICIPANT_SELECTION_PATH => PARTICIPANT_SELECTION_PLACEHOLDERS, _ when path.StartsWith(RESULTS_FOLDER, StringComparison.Ordinal) => RESULT_PLACEHOLDERS, _ => NO_PLACEHOLDERS }; /// /// Reads the texts of the system prompt of a step. /// /// The step. /// How the model addresses the person. /// The folder whose files replace the prompts, or null while the person does not allow it. /// The mode of the simulation in effect, which only the step "Test situation" reads, see BflEffectiveSimulation. /// The cancellation token. /// The texts, which BflPromptComposer puts together. /// The app comes without one of the files. /// A file which replaces a prompt contains a placeholder which the lab does not fill in it. public static async Task LoadPartsAsync(BflStep step, AddressForm addressForm, string? overrideDirectory = null, BflSimulationMode simulation = BflSimulationMode.GENERIC, CancellationToken token = default) => new( await ReadAsync(CORE_PATH, overrideDirectory, token), await ReadAsync(KNOWLEDGE_PATH, overrideDirectory, token), await ReadAsync(StepPath(step), overrideDirectory, token), await ReadAsync(AddressPath(addressForm), overrideDirectory, token), await ReadAsync(ORGANIZATION_RULES_PATH, overrideDirectory, token), await ReadSimulationAsync(step, simulation, overrideDirectory, token)); /// /// Reads how the chat of a step plays people from the research: the rules of every simulation, followed by the /// description of its mode. /// /// The step. /// The mode of the simulation in effect. /// The folder whose files replace the prompts, or null while the person does not allow it. /// The cancellation token. /// The text; empty for every step but "Test situation", and for generic counterparts. private static async Task ReadSimulationAsync(BflStep step, BflSimulationMode simulation, string? overrideDirectory, CancellationToken token) { if (step is not BflStep.TEST_SITUATION || !simulation.HasPeople) return string.Empty; var rules = await ReadAsync(PERSONA_PATH, overrideDirectory, token); var mode = await ReadAsync(SimulationPath(simulation), overrideDirectory, token); return $"{rules.Trim()}\n\n{mode.Trim()}"; } /// /// Reads the texts of the system prompt of the conversation about whether the lab fits a task. /// /// How the model addresses the person. /// The folder whose files replace the prompts, or null while the person does not allow it. /// The cancellation token. /// The texts, which BflPromptComposer puts together. /// The app comes without one of the files. /// A file which replaces a prompt contains a placeholder which the lab does not fill in it. public static async Task LoadOnboardingPartsAsync(AddressForm addressForm, string? overrideDirectory = null, CancellationToken token = default) => new( await ReadAsync(ONBOARDING_PATH, overrideDirectory, token), await ReadAsync(AddressPath(addressForm), overrideDirectory, token), await ReadAsync(ONBOARDING_ORGANIZATION_RULES_PATH, overrideDirectory, token)); /// /// Reads the texts of the system prompt of a research agent. /// /// The topic of the research. /// The folder whose files replace the prompts, or null while the person does not allow it. /// The cancellation token. /// The texts, which BflPromptComposer puts together. /// The app comes without one of the files. /// A file which replaces a prompt contains a placeholder which the lab does not fill in it. public static async Task LoadResearchPartsAsync(BflResearchTopic topic, string? overrideDirectory = null, CancellationToken token = default) => new( await ReadAsync(RESEARCH_PATH, overrideDirectory, token), await ReadAsync(ResearchTopicPath(topic), overrideDirectory, token)); /// /// Reads the texts of the system prompt of an agent of the research on a person. /// /// The stage of the research. /// The folder whose files replace the prompts, or null while the person does not allow it. /// The cancellation token. /// The texts, which BflPromptComposer puts together; the task of the stage is the topic. /// The app comes without one of the files. /// A file which replaces a prompt contains a placeholder which the lab does not fill in it. public static async Task LoadPersonResearchPartsAsync(BflPersonResearchStage stage, string? overrideDirectory = null, CancellationToken token = default) => new( await ReadAsync(PERSON_RESEARCH_PATH, overrideDirectory, token), await ReadAsync(PersonResearchStagePath(stage), overrideDirectory, token)); /// /// Reads a file of the lab's prompts: the file which replaces it, if there is one, or else the file which comes /// with the app. /// /// The file, one of the paths of this class. /// The folder whose files replace the prompts, or null while the person does not allow it. /// The cancellation token. /// The text of the file. /// The app comes without the file, or the file is empty. /// The file which replaces the prompt contains a placeholder which the lab does not fill in it. public static async Task ReadAsync(string path, string? overrideDirectory = null, CancellationToken token = default) { if (overrideDirectory is not null && await BflPromptOverrides.TryReadAsync(overrideDirectory, path, token) is { } replacement) return replacement; return await ReadBuiltInAsync(path, token); } /// /// Reads a file of the lab's prompts as it comes with the app. /// /// The file, one of the paths of this class. /// The cancellation token. /// The text of the file. /// The app comes without the file, or the file is empty. public static async Task ReadBuiltInAsync(string path, CancellationToken token = default) { var text = await AppResources.ReadTextAsync(path, token); if (string.IsNullOrWhiteSpace(text)) throw new FileNotFoundException("The BeyondFacts Lab misses one of its prompts.", path); return text; } }