mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Add additional error handling to existing providers. (#1837)
* Add additional error handling to existing providers. * Add additional information when logging mem0 messages. * Fix formatting.
This commit is contained in:
committed by
GitHub
Unverified
parent
ada5b83c80
commit
8b4aa1ebb5
@@ -131,31 +131,62 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
Environment.NewLine,
|
||||
context.RequestMessages.Where(m => !string.IsNullOrWhiteSpace(m.Text)).Select(m => m.Text));
|
||||
|
||||
var memories = (await this._client.SearchAsync(
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId,
|
||||
queryText,
|
||||
cancellationToken).ConfigureAwait(false)).ToList();
|
||||
|
||||
var contextInstructions = memories.Count == 0
|
||||
? null
|
||||
: $"{this._contextPrompt}\n{string.Join(Environment.NewLine, memories)}";
|
||||
|
||||
if (this._logger is not null)
|
||||
try
|
||||
{
|
||||
this._logger.LogInformation("Mem0AIContextProvider retrieved {Count} memories.", memories.Count);
|
||||
if (contextInstructions is not null)
|
||||
var memories = (await this._client.SearchAsync(
|
||||
this.ApplicationId,
|
||||
this.AgentId,
|
||||
this.ThreadId,
|
||||
this.UserId,
|
||||
queryText,
|
||||
cancellationToken).ConfigureAwait(false)).ToList();
|
||||
|
||||
var outputMessageText = memories.Count == 0
|
||||
? null
|
||||
: $"{this._contextPrompt}\n{string.Join(Environment.NewLine, memories)}";
|
||||
|
||||
if (this._logger is not null)
|
||||
{
|
||||
this._logger.LogTrace("Mem0AIContextProvider instructions: {Instructions}", contextInstructions);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new AIContext
|
||||
return new AIContext
|
||||
{
|
||||
Messages = [new ChatMessage(ChatRole.User, outputMessageText)]
|
||||
};
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Messages = [new ChatMessage(ChatRole.User, contextInstructions)]
|
||||
};
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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);
|
||||
return new AIContext();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -166,12 +197,20 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
return; // Do not update memory on failed invocations.
|
||||
}
|
||||
|
||||
// Persist request and response messages after invocation.
|
||||
await this.PersistMessagesAsync(context.RequestMessages, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (context.ResponseMessages is not null)
|
||||
try
|
||||
{
|
||||
await this.PersistMessagesAsync(context.ResponseMessages, cancellationToken).ConfigureAwait(false);
|
||||
// Persist request and response messages after invocation.
|
||||
await this.PersistMessagesAsync(context.RequestMessages.Concat(context.ResponseMessages ?? []), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Microsoft.Agents.AI.Data;
|
||||
/// <para>
|
||||
/// The provider supports two behaviors controlled via <see cref="TextSearchProviderOptions.SearchTime"/>:
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke"/> – Automatically performs a search prior to every AI invocation and injects results as additional instructions.</description></item>
|
||||
/// <item><description><see cref="TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke"/> – Automatically performs a search prior to every AI invocation and injects results as additional messages.</description></item>
|
||||
/// <item><description><see cref="TextSearchProviderOptions.TextSearchBehavior.OnDemandFunctionCalling"/> – Exposes a function tool that the model may invoke to retrieve contextual information when needed.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
@@ -122,12 +122,13 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
if (this._options.SearchTime != TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke)
|
||||
{
|
||||
// Expose the search tool for on-demand invocation.
|
||||
return new AIContext { Tools = this._tools }; // No automatic instructions injection.
|
||||
return new AIContext { Tools = this._tools }; // No automatic message injection.
|
||||
}
|
||||
|
||||
// Aggregate text from memory + current request messages.
|
||||
var sbInput = new StringBuilder();
|
||||
foreach (var messageText in this._recentMessagesText)
|
||||
var requestMessagesText = context.RequestMessages.Where(x => !string.IsNullOrWhiteSpace(x?.Text)).Select(x => x.Text);
|
||||
foreach (var messageText in this._recentMessagesText.Concat(requestMessagesText))
|
||||
{
|
||||
if (sbInput.Length > 0)
|
||||
{
|
||||
@@ -136,38 +137,35 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
sbInput.Append(messageText);
|
||||
}
|
||||
|
||||
foreach (var message in context.RequestMessages)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(message?.Text))
|
||||
{
|
||||
if (sbInput.Length > 0)
|
||||
{
|
||||
sbInput.Append('\n');
|
||||
}
|
||||
sbInput.Append(message.Text);
|
||||
}
|
||||
}
|
||||
string input = sbInput.ToString();
|
||||
|
||||
// Search
|
||||
var results = await this._searchAsync(input, cancellationToken).ConfigureAwait(false);
|
||||
IList<TextSearchResult> materialized = results as IList<TextSearchResult> ?? results.ToList();
|
||||
if (materialized.Count == 0)
|
||||
try
|
||||
{
|
||||
this._logger?.LogWarning("TextSearchProvider: No search results found.");
|
||||
// Search
|
||||
var results = await this._searchAsync(input, cancellationToken).ConfigureAwait(false);
|
||||
IList<TextSearchResult> materialized = results as IList<TextSearchResult> ?? results.ToList();
|
||||
this._logger?.LogInformation("TextSearchProvider: Retrieved {Count} search results.", materialized.Count);
|
||||
|
||||
if (materialized.Count == 0)
|
||||
{
|
||||
return new AIContext();
|
||||
}
|
||||
|
||||
// Format search results
|
||||
string formatted = this.FormatResults(materialized);
|
||||
|
||||
this._logger?.LogTrace("TextSearchProvider: Search Results\nInput:{Input}\nOutput:{MessageText}", input, formatted);
|
||||
|
||||
return new AIContext
|
||||
{
|
||||
Messages = [new ChatMessage(ChatRole.User, formatted) { AdditionalProperties = new AdditionalPropertiesDictionary() { ["IsTextSearchProviderOutput"] = true } }]
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger?.LogError(ex, "TextSearchProvider: Failed to search for data due to error");
|
||||
return new AIContext();
|
||||
}
|
||||
|
||||
// Format search results
|
||||
string formatted = this.FormatResults(materialized);
|
||||
|
||||
this._logger?.LogInformation("TextSearchProvider: Retrieved {Count} search results.", materialized.Count);
|
||||
this._logger?.LogTrace("TextSearchProvider Input:{Input}\nContext Instructions:{Instructions}", input, formatted);
|
||||
|
||||
return new AIContext
|
||||
{
|
||||
Messages = [new ChatMessage(ChatRole.User, formatted) { AdditionalProperties = new AdditionalPropertiesDictionary() { ["IsTextSearchProviderOutput"] = true } }]
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -240,16 +238,16 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
{
|
||||
var results = await this._searchAsync(userQuestion, cancellationToken).ConfigureAwait(false);
|
||||
IList<TextSearchResult> materialized = results as IList<TextSearchResult> ?? results.ToList();
|
||||
string formatted = this.FormatResults(materialized);
|
||||
string outputText = this.FormatResults(materialized);
|
||||
|
||||
this._logger?.LogInformation("TextSearchProvider: Retrieved {Count} search results.", materialized.Count);
|
||||
this._logger?.LogTrace("TextSearchProvider Input:{UserQuestion}\nContext Instructions:{Instructions}", userQuestion, formatted);
|
||||
this._logger?.LogTrace("TextSearchProvider Input:{UserQuestion}\nOutput:{MessageText}", userQuestion, outputText);
|
||||
|
||||
return formatted;
|
||||
return outputText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats search results into an instructions string for model consumption.
|
||||
/// Formats search results into an output string for model consumption.
|
||||
/// </summary>
|
||||
/// <param name="results">The results.</param>
|
||||
/// <returns>Formatted string (may be empty).</returns>
|
||||
|
||||
@@ -30,12 +30,12 @@ public sealed class TextSearchProviderOptions
|
||||
public string? FunctionToolDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the context prompt prefixed to automatically injected results.
|
||||
/// Gets or sets the context prompt prefixed to results.
|
||||
/// </summary>
|
||||
public string? ContextPrompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the instruction appended after automatically injected results to request citations.
|
||||
/// Gets or sets the instruction appended after results to request citations.
|
||||
/// </summary>
|
||||
public string? CitationsPrompt { get; set; }
|
||||
|
||||
@@ -85,7 +85,7 @@ public sealed class TextSearchProviderOptions
|
||||
public enum TextSearchBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute search prior to each invocation and inject results as instructions.
|
||||
/// Execute search prior to each invocation and inject results as a message.
|
||||
/// </summary>
|
||||
BeforeAIInvoke,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user