mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-15 11:28:27 +00:00
Moved the chats of the BeyondFacts Lab to the embedded chat of assistants (#1203)
This commit is contained in:
1 parent
dc97db256e
commit
cb54ad297d
27 files changed
+1023
-743
No files matched your search
@@ -1,7 +1,8 @@
|
||||
namespace AIStudio.Chat.Embedded;
|
||||
|
||||
/// <summary>
|
||||
/// Decides when an embedded chat sends the message it opens with, see EmbeddedChat.OpeningMessage.
|
||||
/// Decides how an embedded chat opens: when it sends the message it opens with, see EmbeddedChat.OpeningMessage, and
|
||||
/// when it offers to let the model begin, see EmbeddedChat.StartRequest.
|
||||
/// </summary>
|
||||
public static class EmbeddedChatOpening
|
||||
{
|
||||
@@ -19,4 +20,17 @@ public static class EmbeddedChatOpening
|
||||
/// <returns>True when the chat sends the message now.</returns>
|
||||
public static bool SendsNow(ContentText? openingMessage, EmbeddedConversation? conversation, bool wasSent) =>
|
||||
openingMessage is not null && !wasSent && conversation is { IsAvailable: true, IsRunning: false, UserMessageCount: 0 };
|
||||
|
||||
/// <summary>
|
||||
/// Tells whether an embedded chat offers to let the model open its conversation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only before anything the user sees: once the model or the user wrote, the conversation has begun. The user may
|
||||
/// always write first instead.
|
||||
/// </remarks>
|
||||
/// <param name="startRequest">The request which lets the model begin, or null without one.</param>
|
||||
/// <param name="conversation">The chat shown, or null while it loads.</param>
|
||||
/// <returns>True when the chat offers to begin.</returns>
|
||||
public static bool OffersStart(string? startRequest, EmbeddedConversation? conversation) =>
|
||||
!string.IsNullOrWhiteSpace(startRequest) && conversation is { IsAvailable: true, IsRunning: false, HasVisibleMessages: false };
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace AIStudio.Chat.Embedded;
|
||||
|
||||
/// <summary>
|
||||
/// What an assistant decided while it readied a request of an embedded chat: whether the request goes out, and what it
|
||||
/// carries beside the stored chat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Both are decided together, since what a request carries may only be known once it was readied, e.g., which images
|
||||
/// of the assistant fit beside the system prompt. A ready preparation which carries more starts from READY:
|
||||
/// <c>EmbeddedChatPreparation.READY with { ThreadForRequest = … }</c>.
|
||||
/// </remarks>
|
||||
public sealed record EmbeddedChatPreparation
|
||||
{
|
||||
/// <summary>
|
||||
/// The request goes out with the chat as it is.
|
||||
/// </summary>
|
||||
public static readonly EmbeddedChatPreparation READY = new() { IsReady = true };
|
||||
|
||||
/// <summary>
|
||||
/// The request does not go out; whoever readied it told the user why.
|
||||
/// </summary>
|
||||
public static readonly EmbeddedChatPreparation REFUSED = new() { IsReady = false };
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the request goes out.
|
||||
/// </summary>
|
||||
public bool IsReady { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chat which the request sends, e.g., with images which the stored chat never holds. The chat itself by
|
||||
/// default.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called once the message and the block of the answer are in the chat, so that the request carries both. Change a
|
||||
/// copy only: the chat is stored as it is.
|
||||
/// </remarks>
|
||||
public Func<ChatThread, ChatThread> ThreadForRequest { get; init; } = thread => thread;
|
||||
}
|
||||
@@ -27,13 +27,8 @@ public sealed record EmbeddedChatRequest
|
||||
|
||||
/// <summary>
|
||||
/// Readies the chat for the request before the message goes in: its system prompt, profile, tools, and data
|
||||
/// sources, which a chat partly does not store. Returns false to send nothing; it tells the user why itself.
|
||||
/// sources, which a chat partly does not store. Returns what the request carries beside the stored chat, or
|
||||
/// EmbeddedChatPreparation.REFUSED to send nothing; it tells the user why itself.
|
||||
/// </summary>
|
||||
public Func<ChatThread, Task<bool>> PrepareAsync { get; init; } = _ => Task.FromResult(true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chat which the request sends, e.g., with images which the stored chat never holds. The chat itself
|
||||
/// by default.
|
||||
/// </summary>
|
||||
public Func<ChatThread, ChatThread> ThreadForRequest { get; init; } = thread => thread;
|
||||
public Func<ChatThread, Task<EmbeddedChatPreparation>> PrepareAsync { get; init; } = _ => Task.FromResult(EmbeddedChatPreparation.READY);
|
||||
}
|
||||
@@ -134,7 +134,8 @@ public sealed class EmbeddedChatService(SettingsManager settingsManager, Assista
|
||||
var status = AssistantSessionStatus.FAILED;
|
||||
try
|
||||
{
|
||||
if (!await request.PrepareAsync(thread) || !await this.CheckProviderAsync(conversation, thread, request.Provider))
|
||||
var preparation = await request.PrepareAsync(thread);
|
||||
if (!preparation.IsReady || !await this.CheckProviderAsync(conversation, thread, request.Provider))
|
||||
return false;
|
||||
|
||||
thread.RuntimeComponent = conversation.Component;
|
||||
@@ -177,7 +178,7 @@ public sealed class EmbeddedChatService(SettingsManager settingsManager, Assista
|
||||
sessionId = (await sessionService.TryBeginAsync(sessionKey, conversation.Component.Name, cancellation, thread, new(StringComparer.Ordinal))).SessionId;
|
||||
await this.NotifyAsync(chatId);
|
||||
|
||||
await this.answerAsync(request, answer, request.ThreadForRequest(thread), cancellation.Token);
|
||||
await this.answerAsync(request, answer, preparation.ThreadForRequest(thread), cancellation.Token);
|
||||
status = cancellation.IsCancellationRequested ? AssistantSessionStatus.CANCELED : AssistantSessionStatus.COMPLETED;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -214,6 +215,40 @@ public sealed class EmbeddedChatService(SettingsManager settingsManager, Assista
|
||||
? sessionService.CancelAsync(SessionKey(conversation))
|
||||
: Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the conversation of a chat, so that it starts over, e.g., because the user asked for it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Works for a chat which this version cannot open as well; it becomes available again. The new conversation keeps
|
||||
/// the ID of the chat, so what the old one received goes now; otherwise, it would wait for the cleanup at the next
|
||||
/// start.
|
||||
/// </remarks>
|
||||
/// <param name="chatId">The ID of a chat which GetAsync loaded before.</param>
|
||||
/// <param name="createThread">Creates the thread of the new conversation; it gets the ID of the chat.</param>
|
||||
/// <returns>False while the model answers in the chat, or when it was not loaded.</returns>
|
||||
/// <exception cref="IOException">The stored chat could not be deleted; the conversation stays as it is.</exception>
|
||||
/// <exception cref="UnauthorizedAccessException">The stored chat could not be deleted; the conversation stays as it is.</exception>
|
||||
public async Task<bool> ResetAsync(Guid chatId, Func<ChatThread> createThread)
|
||||
{
|
||||
if (!this.conversations.TryGetValue(chatId, out var conversation))
|
||||
return false;
|
||||
|
||||
lock (this.runningLock)
|
||||
{
|
||||
if (conversation.IsRunning)
|
||||
return false;
|
||||
}
|
||||
|
||||
await conversation.Store.DeleteThreadAsync(chatId);
|
||||
var thread = createThread();
|
||||
thread.ChatId = chatId;
|
||||
conversation.Thread = thread;
|
||||
|
||||
await ChatProvenance.ForgetChatsAsync([chatId]);
|
||||
await this.NotifyAsync(chatId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the answer of a chat and forgets the chat, e.g., because the user deleted what it belonged to.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace AIStudio.Chat.Embedded;
|
||||
|
||||
/// <summary>
|
||||
/// Decides how much room an embedded chat has left in the context of its model, see EmbeddedChat.ShowsContextWindow.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Meant for assistants whose requests carry long instructions, such as the guide of the BeyondFacts Lab: their
|
||||
/// conversations fill the context faster than an ordinary chat, and a model which loses the beginning of its context
|
||||
/// loses its instructions first. Therefore, the chat stops before.
|
||||
/// </remarks>
|
||||
public static class EmbeddedChatTokenBudget
|
||||
{
|
||||
/// <summary>
|
||||
/// From which share of the context on the chat warns.
|
||||
/// </summary>
|
||||
public const double WARNING_SHARE = 0.8;
|
||||
|
||||
/// <summary>
|
||||
/// How many tokens the chat keeps free for the next message and its answer.
|
||||
/// </summary>
|
||||
public const int RESERVE_TOKENS = 4_096;
|
||||
|
||||
/// <summary>
|
||||
/// Decides how much room a chat has left.
|
||||
/// </summary>
|
||||
/// <param name="tokens">What the conversation costs, with the system prompt, and the context of the model.</param>
|
||||
/// <returns>The state of the budget.</returns>
|
||||
public static EmbeddedChatTokenBudgetState Of(ConversationTokens tokens)
|
||||
{
|
||||
if (!tokens.IsKnown || !tokens.Window.IsKnown)
|
||||
return EmbeddedChatTokenBudgetState.UNKNOWN;
|
||||
|
||||
var window = tokens.Window.DefaultTokens;
|
||||
if (tokens.Tokens > window - RESERVE_TOKENS)
|
||||
return EmbeddedChatTokenBudgetState.FULL;
|
||||
|
||||
return tokens.Tokens >= window * WARNING_SHARE ? EmbeddedChatTokenBudgetState.WARNING : EmbeddedChatTokenBudgetState.OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace AIStudio.Chat.Embedded;
|
||||
|
||||
/// <summary>
|
||||
/// How much room an embedded chat has left in the context of its model.
|
||||
/// </summary>
|
||||
public enum EmbeddedChatTokenBudgetState
|
||||
{
|
||||
/// <summary>
|
||||
/// Nobody knows: the count failed, or the model has no statement about its context.
|
||||
/// </summary>
|
||||
UNKNOWN,
|
||||
|
||||
/// <summary>
|
||||
/// Room enough.
|
||||
/// </summary>
|
||||
OK,
|
||||
|
||||
/// <summary>
|
||||
/// Most of the context is used; the user should think about starting over.
|
||||
/// </summary>
|
||||
WARNING,
|
||||
|
||||
/// <summary>
|
||||
/// The next message and its answer would not fit anymore, so the chat sends no more.
|
||||
/// </summary>
|
||||
FULL,
|
||||
}
|
||||
@@ -25,4 +25,11 @@ public interface IEmbeddedChatStore
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <exception cref="FileNotFoundException">What the chat belonged to was deleted meanwhile; nothing is left to store.</exception>
|
||||
public Task SaveThreadAsync(ChatThread thread, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a chat, so that its conversation starts over, also one which this version cannot open.
|
||||
/// </summary>
|
||||
/// <param name="chatId">The ID of the chat.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
public Task DeleteThreadAsync(Guid chatId, CancellationToken token = default);
|
||||
}
|
||||
Reference in new issue
Block a user