mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 21:59:48 +00:00
Added agent architecture & first agent
This commit is contained in:
parent
6065728128
commit
9b4a2c1d49
109
app/MindWork AI Studio/Agents/AgentBase.cs
Normal file
109
app/MindWork AI Studio/Agents/AgentBase.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
using AIStudio.Chat;
|
||||||
|
using AIStudio.Provider;
|
||||||
|
using AIStudio.Settings;
|
||||||
|
using AIStudio.Tools;
|
||||||
|
|
||||||
|
// ReSharper disable MemberCanBePrivate.Global
|
||||||
|
|
||||||
|
namespace AIStudio.Agents;
|
||||||
|
|
||||||
|
public abstract class AgentBase(SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent
|
||||||
|
{
|
||||||
|
protected SettingsManager SettingsManager { get; init; } = settingsManager;
|
||||||
|
|
||||||
|
protected IJSRuntime JsRuntime { get; init; } = jsRuntime;
|
||||||
|
|
||||||
|
protected ThreadSafeRandom RNG { get; init; } = rng;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the type or category of this agent.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the agent.
|
||||||
|
/// </summary>
|
||||||
|
public abstract string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The agent's job description. Will be used for the system prompt.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract string JobDescription { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the system prompt provided for the agent.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract string SystemPrompt(string additionalData);
|
||||||
|
|
||||||
|
#region Implementation of IAgent
|
||||||
|
|
||||||
|
public abstract AIStudio.Settings.Provider? ProviderSettings { get; set; }
|
||||||
|
|
||||||
|
public abstract Task<ChatThread> ProcessContext(ChatThread chatThread, IDictionary<string, string> additionalData);
|
||||||
|
|
||||||
|
public abstract Task<ContentBlock> ProcessInput(ContentBlock input, IDictionary<string, string> additionalData);
|
||||||
|
|
||||||
|
public abstract Task<bool> MadeDecision(ContentBlock input);
|
||||||
|
|
||||||
|
public abstract IReadOnlyCollection<ContentBlock> GetContext();
|
||||||
|
|
||||||
|
public abstract IReadOnlyCollection<ContentBlock> GetAnswers();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected ChatThread CreateChatThread(string systemPrompt) => new()
|
||||||
|
{
|
||||||
|
WorkspaceId = Guid.Empty,
|
||||||
|
ChatId = Guid.NewGuid(),
|
||||||
|
Name = string.Empty,
|
||||||
|
Seed = this.RNG.Next(),
|
||||||
|
SystemPrompt = systemPrompt,
|
||||||
|
Blocks = [],
|
||||||
|
};
|
||||||
|
|
||||||
|
protected DateTimeOffset AddUserRequest(ChatThread thread, string request)
|
||||||
|
{
|
||||||
|
var time = DateTimeOffset.Now;
|
||||||
|
thread.Blocks.Add(new ContentBlock
|
||||||
|
{
|
||||||
|
Time = time,
|
||||||
|
ContentType = ContentType.TEXT,
|
||||||
|
Role = ChatRole.USER,
|
||||||
|
Content = new ContentText
|
||||||
|
{
|
||||||
|
Text = request,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task AddAIResponseAsync(ChatThread thread, DateTimeOffset time)
|
||||||
|
{
|
||||||
|
if(this.ProviderSettings is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var providerSettings = this.ProviderSettings.Value;
|
||||||
|
var aiText = new ContentText
|
||||||
|
{
|
||||||
|
// We have to wait for the remote
|
||||||
|
// for the content stream:
|
||||||
|
InitialRemoteWait = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var resultingContentBlock = new ContentBlock
|
||||||
|
{
|
||||||
|
Time = time,
|
||||||
|
ContentType = ContentType.TEXT,
|
||||||
|
Role = ChatRole.AI,
|
||||||
|
Content = aiText,
|
||||||
|
};
|
||||||
|
|
||||||
|
thread.Blocks.Add(resultingContentBlock);
|
||||||
|
|
||||||
|
// Use the selected provider to get the AI response.
|
||||||
|
// By awaiting this line, we wait for the entire
|
||||||
|
// content to be streamed.
|
||||||
|
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, providerSettings.Model, thread);
|
||||||
|
}
|
||||||
|
}
|
94
app/MindWork AI Studio/Agents/AgentTextContentCleaner.cs
Normal file
94
app/MindWork AI Studio/Agents/AgentTextContentCleaner.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using AIStudio.Chat;
|
||||||
|
using AIStudio.Settings;
|
||||||
|
using AIStudio.Tools;
|
||||||
|
|
||||||
|
namespace AIStudio.Agents;
|
||||||
|
|
||||||
|
public sealed class AgentTextContentCleaner(SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : AgentBase(settingsManager, jsRuntime, rng)
|
||||||
|
{
|
||||||
|
private static readonly ContentBlock EMPTY_BLOCK = new()
|
||||||
|
{
|
||||||
|
Content = null,
|
||||||
|
ContentType = ContentType.NONE,
|
||||||
|
Role = ChatRole.AGENT,
|
||||||
|
Time = DateTimeOffset.UtcNow,
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly List<ContentBlock> context = new();
|
||||||
|
private readonly List<ContentBlock> answers = new();
|
||||||
|
|
||||||
|
#region Overrides of AgentBase
|
||||||
|
|
||||||
|
public override Settings.Provider? ProviderSettings { get; set; }
|
||||||
|
|
||||||
|
protected override Type Type => Type.SYSTEM;
|
||||||
|
|
||||||
|
public override string Id => "Text Content Cleaner";
|
||||||
|
|
||||||
|
protected override string JobDescription =>
|
||||||
|
"""
|
||||||
|
You receive a Markdown document as input. Your goal is to identify the main content of the document
|
||||||
|
and return it including Markdown formatting. Remove areas that do not belong to the main part of the
|
||||||
|
document. For a blog article, return only the text of the article with its formatting. For a scientific
|
||||||
|
paper, only the contents of the paper. Delete elements of navigation, advertisements, HTML artifacts,
|
||||||
|
cookie banners, etc. If the content contains images, these images remain. The same applies to links.
|
||||||
|
Ensure that links and images are present as valid Markdown:
|
||||||
|
|
||||||
|
- Syntax of links: [link text](URL)
|
||||||
|
- Syntax of images: 
|
||||||
|
|
||||||
|
If you find relative links or images with relative paths, correct them to absolute paths. For this
|
||||||
|
purpose, here is the source URL:
|
||||||
|
""";
|
||||||
|
|
||||||
|
protected override string SystemPrompt(string additionalData) => $"{this.JobDescription} `{additionalData}`.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override async Task<ChatThread> ProcessContext(ChatThread chatThread, IDictionary<string, string> additionalData)
|
||||||
|
{
|
||||||
|
// We process the last block of the chat thread. Then, we add the result
|
||||||
|
// to the chat thread as the last block:
|
||||||
|
var answer = await this.ProcessInput(chatThread.Blocks[^1], additionalData);
|
||||||
|
chatThread.Blocks.Add(answer);
|
||||||
|
|
||||||
|
this.context.Clear();
|
||||||
|
this.context.AddRange(chatThread.Blocks);
|
||||||
|
|
||||||
|
return chatThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
// <inheritdoc />
|
||||||
|
public override async Task<ContentBlock> ProcessInput(ContentBlock input, IDictionary<string, string> additionalData)
|
||||||
|
{
|
||||||
|
if (input.Content is not ContentText text)
|
||||||
|
return EMPTY_BLOCK;
|
||||||
|
|
||||||
|
if(text.InitialRemoteWait || text.IsStreaming)
|
||||||
|
return EMPTY_BLOCK;
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(text.Text))
|
||||||
|
return EMPTY_BLOCK;
|
||||||
|
|
||||||
|
if(!additionalData.TryGetValue("sourceURL", out var sourceURL) || string.IsNullOrWhiteSpace(sourceURL))
|
||||||
|
return EMPTY_BLOCK;
|
||||||
|
|
||||||
|
var thread = this.CreateChatThread(this.SystemPrompt(sourceURL));
|
||||||
|
var time = this.AddUserRequest(thread, text.Text);
|
||||||
|
await this.AddAIResponseAsync(thread, time);
|
||||||
|
|
||||||
|
var answer = thread.Blocks[^1];
|
||||||
|
this.answers.Add(answer);
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// <inheritdoc />
|
||||||
|
public override Task<bool> MadeDecision(ContentBlock input) => Task.FromResult(true);
|
||||||
|
|
||||||
|
// <inheritdoc />
|
||||||
|
public override IReadOnlyCollection<ContentBlock> GetContext() => this.context;
|
||||||
|
|
||||||
|
// <inheritdoc />
|
||||||
|
public override IReadOnlyCollection<ContentBlock> GetAnswers() => this.answers;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
55
app/MindWork AI Studio/Agents/IAgent.cs
Normal file
55
app/MindWork AI Studio/Agents/IAgent.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using AIStudio.Chat;
|
||||||
|
|
||||||
|
namespace AIStudio.Agents;
|
||||||
|
|
||||||
|
public interface IAgent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the agent.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The provider to use for this agent.
|
||||||
|
/// </summary>
|
||||||
|
public AIStudio.Settings.Provider? ProviderSettings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a chat thread (i.e., context) and returns the updated thread.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chatThread">The chat thread to process. The thread is the context for the agent.</param>
|
||||||
|
/// <param name="additionalData">Additional data to use for processing the chat thread.</param>
|
||||||
|
/// <returns>The updated chat thread. The last content block of the thread is the agent's response.</returns>
|
||||||
|
public Task<ChatThread> ProcessContext(ChatThread chatThread, IDictionary<string, string> additionalData);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the input content block and returns the agent's response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The content block to process. It represents the input.</param>
|
||||||
|
/// <param name="additionalData">Additional data to use for processing the input.</param>
|
||||||
|
/// <returns>The content block representing the agent's response.</returns>
|
||||||
|
public Task<ContentBlock> ProcessInput(ContentBlock input, IDictionary<string, string> additionalData);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The agent makes a decision based on the input.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The content block to process. Should be a question or a request.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if a decision has been made based on the input, false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public Task<bool> MadeDecision(ContentBlock input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the context of the agent.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The collection of content blocks representing the agent's context. This includes the user's and the other agent's messages.</returns>
|
||||||
|
public IReadOnlyCollection<ContentBlock> GetContext();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the answers from the agent's context.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The collection of content blocks representing the answers provided by this agent.
|
||||||
|
/// </returns>
|
||||||
|
public IReadOnlyCollection<ContentBlock> GetAnswers();
|
||||||
|
}
|
27
app/MindWork AI Studio/Agents/Type.cs
Normal file
27
app/MindWork AI Studio/Agents/Type.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace AIStudio.Agents;
|
||||||
|
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an unspecified agent type.
|
||||||
|
/// </summary>
|
||||||
|
UNSPECIFIED = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a conversational agent who produces human-like responses and feedback (depending on the context and its job description).
|
||||||
|
/// For example, an expert agent for a specific domain. Answers might be detailed and informative.
|
||||||
|
/// </summary>
|
||||||
|
CONVERSATIONAL,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a worker agent type who performs tasks and provides information or services (depending on the context and its job description).
|
||||||
|
/// For example, a quality assurance agent who assesses the quality of a product or service. Answers might be short and concise.
|
||||||
|
/// </summary>
|
||||||
|
WORKER,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the system agent type who processes the input and provides a specific response (depending on the context and its job description).
|
||||||
|
/// For example, a HTML content agent who processes the arbitrary HTML content and provides a structured Markdown response. Answers might be structured and formatted.
|
||||||
|
/// </summary>
|
||||||
|
SYSTEM,
|
||||||
|
}
|
@ -11,6 +11,7 @@ public enum ChatRole
|
|||||||
SYSTEM,
|
SYSTEM,
|
||||||
USER,
|
USER,
|
||||||
AI,
|
AI,
|
||||||
|
AGENT,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using AIStudio;
|
using AIStudio;
|
||||||
|
using AIStudio.Agents;
|
||||||
using AIStudio.Components;
|
using AIStudio.Components;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
using AIStudio.Tools;
|
||||||
@ -30,6 +31,7 @@ builder.Services.AddSingleton<Rust>();
|
|||||||
builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>();
|
builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>();
|
||||||
builder.Services.AddSingleton<SettingsManager>();
|
builder.Services.AddSingleton<SettingsManager>();
|
||||||
builder.Services.AddSingleton<ThreadSafeRandom>();
|
builder.Services.AddSingleton<ThreadSafeRandom>();
|
||||||
|
builder.Services.AddTransient<AgentTextContentCleaner>();
|
||||||
builder.Services.AddHostedService<UpdateService>();
|
builder.Services.AddHostedService<UpdateService>();
|
||||||
builder.Services.AddHostedService<TemporaryChatService>();
|
builder.Services.AddHostedService<TemporaryChatService>();
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents()
|
||||||
|
@ -41,6 +41,7 @@ public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.co
|
|||||||
{
|
{
|
||||||
ChatRole.USER => "user",
|
ChatRole.USER => "user",
|
||||||
ChatRole.AI => "assistant",
|
ChatRole.AI => "assistant",
|
||||||
|
ChatRole.AGENT => "assistant",
|
||||||
|
|
||||||
_ => "user",
|
_ => "user",
|
||||||
},
|
},
|
||||||
|
@ -52,6 +52,7 @@ public class ProviderFireworks() : BaseProvider("https://api.fireworks.ai/infere
|
|||||||
{
|
{
|
||||||
ChatRole.USER => "user",
|
ChatRole.USER => "user",
|
||||||
ChatRole.AI => "assistant",
|
ChatRole.AI => "assistant",
|
||||||
|
ChatRole.AGENT => "assistant",
|
||||||
ChatRole.SYSTEM => "system",
|
ChatRole.SYSTEM => "system",
|
||||||
|
|
||||||
_ => "user",
|
_ => "user",
|
||||||
|
@ -51,6 +51,7 @@ public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/
|
|||||||
{
|
{
|
||||||
ChatRole.USER => "user",
|
ChatRole.USER => "user",
|
||||||
ChatRole.AI => "assistant",
|
ChatRole.AI => "assistant",
|
||||||
|
ChatRole.AGENT => "assistant",
|
||||||
ChatRole.SYSTEM => "system",
|
ChatRole.SYSTEM => "system",
|
||||||
|
|
||||||
_ => "user",
|
_ => "user",
|
||||||
|
@ -55,6 +55,7 @@ public sealed class ProviderOpenAI() : BaseProvider("https://api.openai.com/v1/"
|
|||||||
{
|
{
|
||||||
ChatRole.USER => "user",
|
ChatRole.USER => "user",
|
||||||
ChatRole.AI => "assistant",
|
ChatRole.AI => "assistant",
|
||||||
|
ChatRole.AGENT => "assistant",
|
||||||
ChatRole.SYSTEM => "system",
|
ChatRole.SYSTEM => "system",
|
||||||
|
|
||||||
_ => "user",
|
_ => "user",
|
||||||
|
@ -44,6 +44,7 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide
|
|||||||
{
|
{
|
||||||
ChatRole.USER => "user",
|
ChatRole.USER => "user",
|
||||||
ChatRole.AI => "assistant",
|
ChatRole.AI => "assistant",
|
||||||
|
ChatRole.AGENT => "assistant",
|
||||||
ChatRole.SYSTEM => "system",
|
ChatRole.SYSTEM => "system",
|
||||||
|
|
||||||
_ => "user",
|
_ => "user",
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# v0.8.6, build 168
|
# v0.8.6, build 168
|
||||||
- Added possibility to configure a default provider for chats
|
- Added possibility to configure a default provider for chats
|
||||||
|
- Added architecture for future agent usage
|
||||||
|
- Added a first agent to read, analyze and extract text from Markdown data
|
||||||
- Improved the readability of the `settings.json` file by using indentation and enum names instead of numbers
|
- Improved the readability of the `settings.json` file by using indentation and enum names instead of numbers
|
||||||
- Improved assistant overview; assistants will now wrap to the next line if there are too many to fit on the row
|
- Improved assistant overview; assistants will now wrap to the next line if there are too many to fit on the row
|
||||||
- Increased the default value for the live translation delay from 1,000 to 1,500 ms
|
- Increased the default value for the live translation delay from 1,000 to 1,500 ms
|
||||||
|
- Fixed random number generator usage to be thread-safe
|
Loading…
Reference in New Issue
Block a user