using AIStudio.Chat; using AIStudio.Settings.DataModel; namespace AIStudio.Assistants.BeyondFactsLab; /// /// Hands the guidelines of an organization for public appearances, see DataBeyondFactsLab.OrganizationRules, to the /// chats of the steps of the BeyondFacts Lab. /// /// /// The guidelines inform; they are no instructions for the model. They therefore stay out of the system prompt, where /// they could outweigh the rules of the lab, and go along with the first message of a chat instead, marked as coming /// from the organization; the core prompt tells the model how to treat them. Only the request carries them, as it /// carries the images of the material: the stored chat keeps the first message as it was, and every request carries /// the guidelines as they are now. A message of their own in front of the first one would not do: two messages of the /// user in a row break the chat templates of some self-hosted models, and the request has to keep as many messages as /// the chat, see ContentText.CreateFromProviderAsync. /// /// The conversation about whether the lab fits a task prepares no appearance, and the research must learn nothing but /// the public details of the event, so neither of them gets the guidelines. The open points and next steps end with /// them, see AddTo. /// public static class BflOrganizationRules { /// /// Gets the guidelines which the requests of a chat carry. /// /// Where the chat belongs. /// The settings of the lab. /// The guidelines for a chat of a step, and nothing for every other chat. public static string For(BflThreadLocation location, DataBeyondFactsLab settings) => location is BflStepThreadLocation ? settings.OrganizationRules : string.Empty; /// /// Gets the text which the first message of a request carries in front, e.g., to count it beforehand. /// /// /// German and tagged, like the sections of the prompts. /// /// The guidelines of the organization; empty without any. /// The guidelines in their tags, followed by an empty line; nothing without guidelines. public static string Frame(string rules) => string.IsNullOrWhiteSpace(rules) ? string.Empty : $"\n{rules}\n\n\n"; /// /// Gives a request the guidelines in front of the first message, without storing them in the chat. /// /// /// The request gets a copy of that message only; every other message, the answer which streams in above all, /// stays the same object, and the chat keeps as many messages, see BflConversationService.WithImages. /// /// The chat. /// The guidelines of the organization; empty without any. /// The chat for the request: the chat itself without guidelines or without a message yet. public static ChatThread WithRules(ChatThread thread, string rules) { if (string.IsNullOrWhiteSpace(rules)) return thread; var index = thread.Blocks.FindIndex(block => block is { Role: ChatRole.USER, Content: ContentText }); if (index < 0) return thread; var firstMessage = thread.Blocks[index].DeepClone(); var content = (ContentText)firstMessage.Content!; content.Text = Frame(rules) + content.Text; List blocks = [.. thread.Blocks]; blocks[index] = firstMessage; return thread with { Blocks = blocks }; } /// /// Adds the guidelines to a result where they belong: the open points and next steps end with them, word for word, /// under a heading of their own. /// /// /// Added whenever the result is shown or exported, never stored and never written by the model: so they stay word /// for word and as they are now, and sharing a session with the research team, which takes the stored texts, leaves /// them out, since they would reveal the organization. /// /// The kind of result. /// The text of the result, as stored. /// The guidelines of the organization; empty without any. /// The heading of the guidelines, in the language of the app. /// The text to show or export. public static string AddTo(BflResultKind kind, string text, string rules, string heading) => kind is BflResultKind.OPEN_POINTS && !string.IsNullOrWhiteSpace(rules) ? $"{text.TrimEnd()}\n\n## {heading}\n\n{rules}" : text; }