mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [BREAKING] Simplify TextSearchProvider construction and improve Mem0Provider scoping. (#1905)
* Simplify TextSearchProvider construction and improve Mem0Provider scoping * Fixing indentation.
This commit is contained in:
committed by
GitHub
Unverified
parent
bb8ef466de
commit
5e38c63455
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -31,10 +32,15 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
private readonly Mem0Client _client;
|
||||
private readonly ILogger<Mem0Provider>? _logger;
|
||||
|
||||
private readonly Mem0ProviderScope _storageScope;
|
||||
private readonly Mem0ProviderScope _searchScope;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Mem0Provider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">Configured <see cref="HttpClient"/> (base address + auth).</param>
|
||||
/// <param name="storageScope">Optional values to scope the memory storage with.</param>
|
||||
/// <param name="searchScope">Optional values to scope the memory search with. Defaults to <paramref name="storageScope"/> if not provided.</param>
|
||||
/// <param name="options">Provider options.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory.</param>
|
||||
/// <remarks>
|
||||
@@ -47,21 +53,35 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
/// new Mem0AIContextProvider(httpClient);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public Mem0Provider(HttpClient httpClient, Mem0ProviderOptions? options = null, ILoggerFactory? loggerFactory = null)
|
||||
public Mem0Provider(HttpClient httpClient, Mem0ProviderScope storageScope, Mem0ProviderScope? searchScope = null, Mem0ProviderOptions? options = null, ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(httpClient.BaseAddress?.AbsoluteUri))
|
||||
{
|
||||
throw new ArgumentException("The HttpClient BaseAddress must be set for Mem0 operations.", nameof(httpClient));
|
||||
}
|
||||
|
||||
this.ApplicationId = options?.ApplicationId;
|
||||
this.AgentId = options?.AgentId;
|
||||
this.ThreadId = options?.ThreadId;
|
||||
this.UserId = options?.UserId;
|
||||
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
||||
|
||||
this._logger = loggerFactory?.CreateLogger<Mem0Provider>();
|
||||
this._client = new Mem0Client(httpClient);
|
||||
|
||||
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
||||
this._storageScope = new Mem0ProviderScope(Throw.IfNull(storageScope));
|
||||
this._searchScope = searchScope ?? storageScope;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this._storageScope.ApplicationId)
|
||||
&& string.IsNullOrWhiteSpace(this._storageScope.AgentId)
|
||||
&& string.IsNullOrWhiteSpace(this._storageScope.ThreadId)
|
||||
&& string.IsNullOrWhiteSpace(this._storageScope.UserId))
|
||||
{
|
||||
throw new ArgumentException("At least one of ApplicationId, AgentId, ThreadId, or UserId must be provided for the storage scope.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this._searchScope.ApplicationId)
|
||||
&& string.IsNullOrWhiteSpace(this._searchScope.AgentId)
|
||||
&& string.IsNullOrWhiteSpace(this._searchScope.ThreadId)
|
||||
&& string.IsNullOrWhiteSpace(this._searchScope.UserId))
|
||||
{
|
||||
throw new ArgumentException("At least one of ApplicationId, AgentId, ThreadId, or UserId must be provided for the search scope.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,6 +90,7 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
/// <param name="httpClient">Configured <see cref="HttpClient"/> (base address + auth).</param>
|
||||
/// <param name="serializedState">A <see cref="JsonElement"/> representing the serialized state of the store.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <param name="options">Provider options.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <remarks>
|
||||
@@ -82,46 +103,30 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
/// new Mem0AIContextProvider(httpClient, state);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public Mem0Provider(HttpClient httpClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, ILoggerFactory? loggerFactory = null)
|
||||
public Mem0Provider(HttpClient httpClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, Mem0ProviderOptions? options = null, ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(httpClient.BaseAddress?.AbsoluteUri))
|
||||
{
|
||||
throw new ArgumentException("The HttpClient BaseAddress must be set for Mem0 operations.", nameof(httpClient));
|
||||
}
|
||||
|
||||
this._logger = loggerFactory?.CreateLogger<Mem0Provider>();
|
||||
this._client = new Mem0Client(httpClient);
|
||||
|
||||
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
||||
|
||||
var jso = jsonSerializerOptions ?? Mem0JsonUtilities.DefaultOptions;
|
||||
var state = serializedState.Deserialize(jso.GetTypeInfo(typeof(Mem0State))) as Mem0State;
|
||||
|
||||
this.ApplicationId = state?.ApplicationId;
|
||||
this.AgentId = state?.AgentId;
|
||||
this.ThreadId = state?.ThreadId;
|
||||
this.UserId = state?.UserId;
|
||||
this._contextPrompt = state?.ContextPrompt ?? DefaultContextPrompt;
|
||||
if (state == null || state.StorageScope == null || state.SearchScope == null)
|
||||
{
|
||||
throw new InvalidOperationException("The Mem0Provider state did not contain the required scope properties.");
|
||||
}
|
||||
|
||||
this._logger = loggerFactory?.CreateLogger<Mem0Provider>();
|
||||
this._client = new Mem0Client(httpClient);
|
||||
this._storageScope = state.StorageScope;
|
||||
this._searchScope = state.SearchScope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the application to scope memories to.
|
||||
/// </summary>
|
||||
public string? ApplicationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the agent to scope memories to.
|
||||
/// </summary>
|
||||
public string? AgentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the thread to scope memories to.
|
||||
/// </summary>
|
||||
public string? ThreadId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the user to scope memories to.
|
||||
/// </summary>
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -134,10 +139,10 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
try
|
||||
{
|
||||
var memories = (await this._client.SearchAsync(
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId,
|
||||
this._searchScope.ApplicationId,
|
||||
this._searchScope.AgentId,
|
||||
this._searchScope.ThreadId,
|
||||
this._searchScope.UserId,
|
||||
queryText,
|
||||
cancellationToken).ConfigureAwait(false)).ToList();
|
||||
|
||||
@@ -150,20 +155,20 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
this._logger.LogInformation(
|
||||
"Mem0AIContextProvider: Retrieved {Count} memories. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
|
||||
memories.Count,
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId);
|
||||
this._searchScope.ApplicationId,
|
||||
this._searchScope.AgentId,
|
||||
this._searchScope.ThreadId,
|
||||
this._searchScope.UserId);
|
||||
if (outputMessageText is not null)
|
||||
{
|
||||
this._logger.LogTrace(
|
||||
"Mem0AIContextProvider: Search Results\nInput:{Input}\nOutput:{MessageText}\nApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
|
||||
queryText,
|
||||
outputMessageText,
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId);
|
||||
this._searchScope.ApplicationId,
|
||||
this._searchScope.AgentId,
|
||||
this._searchScope.ThreadId,
|
||||
this._searchScope.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,10 +186,10 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
this._logger?.LogError(
|
||||
ex,
|
||||
"Mem0AIContextProvider: Failed to search Mem0 for memories due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId);
|
||||
this._searchScope.ApplicationId,
|
||||
this._searchScope.AgentId,
|
||||
this._searchScope.ThreadId,
|
||||
this._searchScope.UserId);
|
||||
return new AIContext();
|
||||
}
|
||||
}
|
||||
@@ -207,10 +212,10 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
this._logger?.LogError(
|
||||
ex,
|
||||
"Mem0AIContextProvider: Failed to send messages to Mem0 due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId);
|
||||
this._storageScope.ApplicationId,
|
||||
this._storageScope.AgentId,
|
||||
this._storageScope.ThreadId,
|
||||
this._storageScope.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,23 +225,16 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public Task ClearStoredMemoriesAsync(CancellationToken cancellationToken = default) =>
|
||||
this._client.ClearMemoryAsync(
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId,
|
||||
this._storageScope.ApplicationId,
|
||||
this._storageScope.AgentId,
|
||||
this._storageScope.ThreadId,
|
||||
this._storageScope.UserId,
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
var state = new Mem0State
|
||||
{
|
||||
ApplicationId = this.ApplicationId,
|
||||
AgentId = this.AgentId,
|
||||
ThreadId = this.ThreadId,
|
||||
UserId = this.UserId,
|
||||
ContextPrompt = this._contextPrompt == DefaultContextPrompt ? null : this._contextPrompt
|
||||
};
|
||||
var state = new Mem0State(this._storageScope, this._searchScope);
|
||||
|
||||
var jso = jsonSerializerOptions ?? Mem0JsonUtilities.DefaultOptions;
|
||||
return JsonSerializer.SerializeToElement(state, jso.GetTypeInfo(typeof(Mem0State)));
|
||||
@@ -262,10 +260,10 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
}
|
||||
|
||||
await this._client.CreateMemoryAsync(
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId,
|
||||
this._storageScope.ApplicationId,
|
||||
this._storageScope.AgentId,
|
||||
this._storageScope.ThreadId,
|
||||
this._storageScope.UserId,
|
||||
message.Text,
|
||||
message.Role.Value,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
@@ -274,10 +272,14 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
|
||||
internal sealed class Mem0State
|
||||
{
|
||||
public string? ApplicationId { get; set; }
|
||||
public string? AgentId { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
public string? ThreadId { get; set; }
|
||||
public string? ContextPrompt { get; set; }
|
||||
[JsonConstructor]
|
||||
public Mem0State(Mem0ProviderScope storageScope, Mem0ProviderScope searchScope)
|
||||
{
|
||||
this.StorageScope = storageScope;
|
||||
this.SearchScope = searchScope;
|
||||
}
|
||||
|
||||
public Mem0ProviderScope StorageScope { get; set; }
|
||||
public Mem0ProviderScope SearchScope { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,35 +5,8 @@ namespace Microsoft.Agents.AI.Mem0;
|
||||
/// <summary>
|
||||
/// Options for configuring the <see cref="Mem0Provider"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Mem0 memories can be scoped by one or more of: application, agent, thread, and user.
|
||||
/// At least one scope must be provided; otherwise Mem0 will reject requests.
|
||||
/// </remarks>
|
||||
public sealed class Mem0ProviderOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the application to scope memories to.
|
||||
/// </summary>
|
||||
/// <remarks>If not set, the scope of the memories will span all applications.</remarks>
|
||||
public string? ApplicationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the agent to scope memories to.
|
||||
/// </summary>
|
||||
/// <remarks>If not set, the scope of the memories will span all agents.</remarks>
|
||||
public string? AgentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the thread to scope memories to.
|
||||
/// </summary>
|
||||
public string? ThreadId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the user to scope memories to.
|
||||
/// </summary>
|
||||
/// <remarks>If not set, the scope of the memories will span all users.</remarks>
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When providing memories to the model, this string is prefixed to the retrieved memories to supply context.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Mem0;
|
||||
|
||||
/// <summary>
|
||||
/// Allows scoping of memories for the <see cref="Mem0Provider"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Mem0 memories can be scoped by one or more of: application, agent, thread, and user.
|
||||
/// At least one scope must be provided; otherwise Mem0 will reject requests.
|
||||
/// </remarks>
|
||||
public sealed class Mem0ProviderScope
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Mem0ProviderScope"/> class.
|
||||
/// </summary>
|
||||
public Mem0ProviderScope() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Mem0ProviderScope"/> class by cloning an existing scope.
|
||||
/// </summary>
|
||||
/// <param name="sourceScope">The scope to clone.</param>
|
||||
public Mem0ProviderScope(Mem0ProviderScope sourceScope)
|
||||
{
|
||||
Throw.IfNull(sourceScope);
|
||||
|
||||
this.ApplicationId = sourceScope.ApplicationId;
|
||||
this.AgentId = sourceScope.AgentId;
|
||||
this.ThreadId = sourceScope.ThreadId;
|
||||
this.UserId = sourceScope.UserId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the application to scope memories to.
|
||||
/// </summary>
|
||||
/// <remarks>If not set, the scope of the memories will span all applications.</remarks>
|
||||
public string? ApplicationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the agent to scope memories to.
|
||||
/// </summary>
|
||||
/// <remarks>If not set, the scope of the memories will span all agents.</remarks>
|
||||
public string? AgentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the thread to scope memories to.
|
||||
/// </summary>
|
||||
public string? ThreadId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional ID for the user to scope memories to.
|
||||
/// </summary>
|
||||
/// <remarks>If not set, the scope of the memories will span all users.</remarks>
|
||||
public string? UserId { get; set; }
|
||||
}
|
||||
@@ -44,82 +44,72 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
private readonly ILogger<TextSearchProvider>? _logger;
|
||||
private readonly AITool[] _tools;
|
||||
private readonly Queue<string> _recentMessagesText;
|
||||
private readonly TextSearchProviderOptions _options;
|
||||
private readonly List<ChatRole> _recentMessageRolesIncluded;
|
||||
private readonly int _recentMessageMemoryLimit;
|
||||
private readonly TextSearchProviderOptions.TextSearchBehavior _searchTime;
|
||||
private readonly string _contextPrompt;
|
||||
private readonly string _citationsPrompt;
|
||||
private readonly Func<IList<TextSearchResult>, string>? _contextFormatter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextSearchProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="searchAsync">Delegate that executes the search logic. Must not be <see langword="null"/>.</param>
|
||||
/// <param name="options">Optional configuration options.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="searchAsync"/> is <see langword="null"/>.</exception>
|
||||
public TextSearchProvider(Func<string, CancellationToken, Task<IEnumerable<TextSearchResult>>> searchAsync, TextSearchProviderOptions? options = null, ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
this._searchAsync = searchAsync ?? throw new ArgumentNullException(nameof(searchAsync));
|
||||
this._options = options ?? new();
|
||||
Throw.IfLessThan(this._options.RecentMessageMemoryLimit, 0);
|
||||
this._logger = loggerFactory?.CreateLogger<TextSearchProvider>();
|
||||
this._recentMessagesText = new();
|
||||
this._recentMessageRolesIncluded = this._options.RecentMessageRolesIncluded ?? [ChatRole.User];
|
||||
|
||||
// Create the on-demand search tool (only used if behavior is OnDemandFunctionCalling)
|
||||
this._tools =
|
||||
[
|
||||
AIFunctionFactory.Create(
|
||||
this.SearchAsync,
|
||||
name: this._options.FunctionToolName ?? DefaultPluginSearchFunctionName,
|
||||
description: this._options.FunctionToolDescription ?? DefaultPluginSearchFunctionDescription)
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextSearchProvider"/> class from previously serialized state.
|
||||
/// </summary>
|
||||
/// <param name="searchAsync">Delegate that executes the search logic. Must not be <see langword="null"/>.</param>
|
||||
/// <param name="serializedState">A <see cref="JsonElement"/> representing the serialized provider state.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional serializer options (unused - source generated context is used).</param>
|
||||
/// <param name="options">Optional configuration options.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="searchAsync"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// Only overridden prompts (function name, function description, context prompt, citations prompt) are restored.
|
||||
/// If a value was not persisted or matches the defaults it will fall back to the built-in defaults.
|
||||
/// Custom <see cref="TextSearchProviderOptions.ContextFormatter"/> delegates are not serialized.
|
||||
/// </remarks>
|
||||
public TextSearchProvider(Func<string, CancellationToken, Task<IEnumerable<TextSearchResult>>> searchAsync, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, TextSearchProviderOptions? options = null, ILoggerFactory? loggerFactory = null)
|
||||
public TextSearchProvider(
|
||||
Func<string, CancellationToken, Task<IEnumerable<TextSearchResult>>> searchAsync,
|
||||
JsonElement serializedState,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null,
|
||||
TextSearchProviderOptions? options = null,
|
||||
ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
this._searchAsync = searchAsync ?? throw new ArgumentNullException(nameof(searchAsync));
|
||||
this._options = options ?? new();
|
||||
Throw.IfLessThan(this._options.RecentMessageMemoryLimit, 0);
|
||||
// Validate and assign parameters
|
||||
this._searchAsync = Throw.IfNull(searchAsync);
|
||||
this._logger = loggerFactory?.CreateLogger<TextSearchProvider>();
|
||||
this._recentMessageRolesIncluded = this._options.RecentMessageRolesIncluded ?? [ChatRole.User];
|
||||
this._recentMessageMemoryLimit = Throw.IfLessThan(options?.RecentMessageMemoryLimit ?? 0, 0);
|
||||
this._recentMessageRolesIncluded = options?.RecentMessageRolesIncluded ?? [ChatRole.User];
|
||||
this._searchTime = options?.SearchTime ?? TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke;
|
||||
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
|
||||
this._citationsPrompt = options?.CitationsPrompt ?? DefaultCitationsPrompt;
|
||||
this._contextFormatter = options?.ContextFormatter;
|
||||
|
||||
// Restore recent messages from serialized state if provided
|
||||
List<string>? restoredMessages = null;
|
||||
|
||||
var state = serializedState.Deserialize(AgentJsonUtilities.DefaultOptions.GetTypeInfo(typeof(TextSearchProviderState))) as TextSearchProviderState;
|
||||
if (state?.RecentMessagesText is { Count: > 0 })
|
||||
if (serializedState.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
|
||||
{
|
||||
restoredMessages = state.RecentMessagesText;
|
||||
this._recentMessagesText = new();
|
||||
}
|
||||
else
|
||||
{
|
||||
var jso = jsonSerializerOptions ?? AgentJsonUtilities.DefaultOptions;
|
||||
var state = serializedState.Deserialize(jso.GetTypeInfo(typeof(TextSearchProviderState))) as TextSearchProviderState;
|
||||
if (state?.RecentMessagesText is { Count: > 0 })
|
||||
{
|
||||
restoredMessages = state.RecentMessagesText;
|
||||
}
|
||||
|
||||
// Restore recent messages respecting the limit (may truncate if limit changed afterwards).
|
||||
this._recentMessagesText = restoredMessages is null ? new() : new(restoredMessages.Take(this._options.RecentMessageMemoryLimit));
|
||||
// Restore recent messages respecting the limit (may truncate if limit changed afterwards).
|
||||
this._recentMessagesText = restoredMessages is null ? new() : new(restoredMessages.Take(this._recentMessageMemoryLimit));
|
||||
}
|
||||
|
||||
// Create the on-demand search tool (only used if behavior is OnDemandFunctionCalling)
|
||||
this._tools =
|
||||
[
|
||||
AIFunctionFactory.Create(
|
||||
this.SearchAsync,
|
||||
name: this._options.FunctionToolName ?? DefaultPluginSearchFunctionName,
|
||||
description: this._options.FunctionToolDescription ?? DefaultPluginSearchFunctionDescription)
|
||||
name: options?.FunctionToolName ?? DefaultPluginSearchFunctionName,
|
||||
description: options?.FunctionToolDescription ?? DefaultPluginSearchFunctionDescription)
|
||||
];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this._options.SearchTime != TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke)
|
||||
if (this._searchTime != TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke)
|
||||
{
|
||||
// Expose the search tool for on-demand invocation.
|
||||
return new AIContext { Tools = this._tools }; // No automatic message injection.
|
||||
@@ -171,7 +161,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
/// <inheritdoc />
|
||||
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int limit = this._options.RecentMessageMemoryLimit;
|
||||
int limit = this._recentMessageMemoryLimit;
|
||||
if (limit <= 0)
|
||||
{
|
||||
return default; // Memory disabled.
|
||||
@@ -220,9 +210,9 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
{
|
||||
// Only persist values that differ from defaults plus recent memory configuration & messages.
|
||||
TextSearchProviderState state = new();
|
||||
if (this._options.RecentMessageMemoryLimit > 0 && this._recentMessagesText.Count > 0)
|
||||
if (this._recentMessageMemoryLimit > 0 && this._recentMessagesText.Count > 0)
|
||||
{
|
||||
state.RecentMessagesText = this._recentMessagesText.Take(this._options.RecentMessageMemoryLimit).ToList();
|
||||
state.RecentMessagesText = this._recentMessagesText.Take(this._recentMessageMemoryLimit).ToList();
|
||||
}
|
||||
|
||||
return JsonSerializer.SerializeToElement(state, AgentJsonUtilities.DefaultOptions.GetTypeInfo(typeof(TextSearchProviderState)));
|
||||
@@ -253,9 +243,9 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
/// <returns>Formatted string (may be empty).</returns>
|
||||
private string FormatResults(IList<TextSearchResult> results)
|
||||
{
|
||||
if (this._options.ContextFormatter is not null)
|
||||
if (this._contextFormatter is not null)
|
||||
{
|
||||
return this._options.ContextFormatter(results) ?? string.Empty;
|
||||
return this._contextFormatter(results) ?? string.Empty;
|
||||
}
|
||||
|
||||
if (results.Count == 0)
|
||||
@@ -264,7 +254,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(this._options.ContextPrompt ?? DefaultContextPrompt);
|
||||
sb.AppendLine(this._contextPrompt);
|
||||
for (int i = 0; i < results.Count; i++)
|
||||
{
|
||||
var result = results[i];
|
||||
@@ -279,7 +269,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
sb.AppendLine($"Contents: {result.Text}");
|
||||
sb.AppendLine("----");
|
||||
}
|
||||
sb.AppendLine(this._options.CitationsPrompt ?? DefaultCitationsPrompt);
|
||||
sb.AppendLine(this._citationsPrompt);
|
||||
sb.AppendLine();
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user