Every developer who uses BlazorMemory writes the same code after every chat message:
var reply = await CallLlmAsync(systemPrompt, userMessage);
await memory.ExtractAsync($"User: {userMessage}\nAssistant: {reply}", userId);
Query memories, build a prompt, call the LLM, extract new facts. Four steps, every time, in every app. v0.7.0 wraps all of that into one call.
MemoryEnabledChat
The new MemoryEnabledChat service takes a delegate for the LLM call and handles everything else:
// Register in Program.cs
builder.Services
.AddBlazorMemory()
.UseIndexedDbStorage()
.UseOpenAiEmbeddings(apiKey)
.UseOpenAiExtractor(apiKey)
.UseMemoryEnabledChat(); // new
// Use it in your chat service
public class ChatService(MemoryEnabledChat chat)
{
public Task<string> SendAsync(string message, string userId)
=> chat.ChatAsync(
message,
userId,
(systemPrompt, msg) => CallOpenAiAsync(systemPrompt, msg));
}
The delegate receives a system prompt that already contains the relevant memories. You call your LLM with it and return the reply. MemoryEnabledChat handles the rest.
What it does internally
// 1. Query relevant memories
var memories = await _memory.QueryAsync(userMessage, userId, QueryOptions);
// 2. Build system prompt with memory context
var systemPrompt = BuildSystemPrompt(memories, BaseSystemPrompt);
// 3. Call your LLM via the delegate
var reply = await llmCall(systemPrompt, userMessage);
// 4. Extract new facts in background
// Errors are swallowed so extraction failures never crash the chat
_ = ExtractSafelyAsync(userMessage, reply, userId, namespace);
return reply;
Extraction errors are swallowed intentionally. A failed extraction is a degraded experience, not a crash. The user still gets their reply.
Customising behaviour
Two properties control how it works:
chat.QueryOptions = new QueryOptions { Limit = 8, Threshold = 0.60f };
chat.BaseSystemPrompt = "You are a helpful assistant for a software company.";
QueryOptions controls how many memories are retrieved and the similarity threshold. BaseSystemPrompt is prepended before the memory context block.
Single-service approach
If you do not want to inject MemoryEnabledChat separately, the same behaviour is available directly on IMemoryService:
var reply = await memory.ChatWithMemoryAsync(
userMessage,
userId,
(systemPrompt, msg) => CallOpenAiAsync(systemPrompt, msg));
Same delegate pattern, same behaviour, fewer injections.
Before and after
Before v0.7.0 a typical chat service looked like this:
public async Task<string> SendAsync(string message, string userId)
{
var memories = await _memory.QueryAsync(message, userId,
new QueryOptions { Limit = 5, Threshold = 0.65f });
var context = string.Join("\n", memories.Select(m => $"- {m.Content}"));
var prompt = $"You are a helpful assistant.\n\nWhat you know:\n{context}";
var reply = await CallOpenAiAsync(prompt, message);
await _memory.ExtractAsync($"User: {message}\nAssistant: {reply}", userId);
return reply;
}
After:
public Task<string> SendAsync(string message, string userId)
=> _chat.ChatAsync(message, userId,
(prompt, msg) => CallOpenAiAsync(prompt, msg));
The logic is the same. The boilerplate is gone.
Getting v0.7.0
dotnet add package BlazorMemory
dotnet add package BlazorMemory.Components
dotnet add package BlazorMemory.Storage.IndexedDb
dotnet add package BlazorMemory.Embeddings.OpenAi
dotnet add package BlazorMemory.Extractor.OpenAi
84 tests passing across 9 packages.
Top comments (0)