Skip to content

Commit 8b4aa1e

Browse files
authored
.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.
1 parent ada5b83 commit 8b4aa1e

File tree

5 files changed

+214
-75
lines changed

5 files changed

+214
-75
lines changed

dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -131,31 +131,62 @@ public override async ValueTask<AIContext> InvokingAsync(InvokingContext context
131131
Environment.NewLine,
132132
context.RequestMessages.Where(m => !string.IsNullOrWhiteSpace(m.Text)).Select(m => m.Text));
133133

134-
var memories = (await this._client.SearchAsync(
135-
this.ApplicationId,
136-
this.AgentId,
137-
this.ThreadId,
138-
this.UserId,
139-
queryText,
140-
cancellationToken).ConfigureAwait(false)).ToList();
134+
try
135+
{
136+
var memories = (await this._client.SearchAsync(
137+
this.ApplicationId,
138+
this.AgentId,
139+
this.ThreadId,
140+
this.UserId,
141+
queryText,
142+
cancellationToken).ConfigureAwait(false)).ToList();
141143

142-
var contextInstructions = memories.Count == 0
143-
? null
144-
: $"{this._contextPrompt}\n{string.Join(Environment.NewLine, memories)}";
144+
var outputMessageText = memories.Count == 0
145+
? null
146+
: $"{this._contextPrompt}\n{string.Join(Environment.NewLine, memories)}";
145147

146-
if (this._logger is not null)
147-
{
148-
this._logger.LogInformation("Mem0AIContextProvider retrieved {Count} memories.", memories.Count);
149-
if (contextInstructions is not null)
148+
if (this._logger is not null)
150149
{
151-
this._logger.LogTrace("Mem0AIContextProvider instructions: {Instructions}", contextInstructions);
150+
this._logger.LogInformation(
151+
"Mem0AIContextProvider: Retrieved {Count} memories. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
152+
memories.Count,
153+
this.ApplicationId,
154+
this.AgentId,
155+
this.ThreadId,
156+
this.UserId);
157+
if (outputMessageText is not null)
158+
{
159+
this._logger.LogTrace(
160+
"Mem0AIContextProvider: Search Results\nInput:{Input}\nOutput:{MessageText}\nApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
161+
queryText,
162+
outputMessageText,
163+
this.ApplicationId,
164+
this.AgentId,
165+
this.ThreadId,
166+
this.UserId);
167+
}
152168
}
153-
}
154169

155-
return new AIContext
170+
return new AIContext
171+
{
172+
Messages = [new ChatMessage(ChatRole.User, outputMessageText)]
173+
};
174+
}
175+
catch (ArgumentException)
156176
{
157-
Messages = [new ChatMessage(ChatRole.User, contextInstructions)]
158-
};
177+
throw;
178+
}
179+
catch (Exception ex)
180+
{
181+
this._logger?.LogError(
182+
ex,
183+
"Mem0AIContextProvider: Failed to search Mem0 for memories due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
184+
this.ApplicationId,
185+
this.AgentId,
186+
this.ThreadId,
187+
this.UserId);
188+
return new AIContext();
189+
}
159190
}
160191

161192
/// <inheritdoc />
@@ -166,12 +197,20 @@ public override async ValueTask InvokedAsync(InvokedContext context, Cancellatio
166197
return; // Do not update memory on failed invocations.
167198
}
168199

169-
// Persist request and response messages after invocation.
170-
await this.PersistMessagesAsync(context.RequestMessages, cancellationToken).ConfigureAwait(false);
171-
172-
if (context.ResponseMessages is not null)
200+
try
201+
{
202+
// Persist request and response messages after invocation.
203+
await this.PersistMessagesAsync(context.RequestMessages.Concat(context.ResponseMessages ?? []), cancellationToken).ConfigureAwait(false);
204+
}
205+
catch (Exception ex)
173206
{
174-
await this.PersistMessagesAsync(context.ResponseMessages, cancellationToken).ConfigureAwait(false);
207+
this._logger?.LogError(
208+
ex,
209+
"Mem0AIContextProvider: Failed to send messages to Mem0 due to error. ApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'",
210+
this.ApplicationId,
211+
this.AgentId,
212+
this.ThreadId,
213+
this.UserId);
175214
}
176215
}
177216

dotnet/src/Microsoft.Agents.AI/Data/TextSearchProvider.cs

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace Microsoft.Agents.AI.Data;
2222
/// <para>
2323
/// The provider supports two behaviors controlled via <see cref="TextSearchProviderOptions.SearchTime"/>:
2424
/// <list type="bullet">
25-
/// <item><description><see cref="TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke"/> – Automatically performs a search prior to every AI invocation and injects results as additional instructions.</description></item>
25+
/// <item><description><see cref="TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke"/> – Automatically performs a search prior to every AI invocation and injects results as additional messages.</description></item>
2626
/// <item><description><see cref="TextSearchProviderOptions.TextSearchBehavior.OnDemandFunctionCalling"/> – Exposes a function tool that the model may invoke to retrieve contextual information when needed.</description></item>
2727
/// </list>
2828
/// </para>
@@ -122,12 +122,13 @@ public override async ValueTask<AIContext> InvokingAsync(InvokingContext context
122122
if (this._options.SearchTime != TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke)
123123
{
124124
// Expose the search tool for on-demand invocation.
125-
return new AIContext { Tools = this._tools }; // No automatic instructions injection.
125+
return new AIContext { Tools = this._tools }; // No automatic message injection.
126126
}
127127

128128
// Aggregate text from memory + current request messages.
129129
var sbInput = new StringBuilder();
130-
foreach (var messageText in this._recentMessagesText)
130+
var requestMessagesText = context.RequestMessages.Where(x => !string.IsNullOrWhiteSpace(x?.Text)).Select(x => x.Text);
131+
foreach (var messageText in this._recentMessagesText.Concat(requestMessagesText))
131132
{
132133
if (sbInput.Length > 0)
133134
{
@@ -136,38 +137,35 @@ public override async ValueTask<AIContext> InvokingAsync(InvokingContext context
136137
sbInput.Append(messageText);
137138
}
138139

139-
foreach (var message in context.RequestMessages)
140-
{
141-
if (!string.IsNullOrWhiteSpace(message?.Text))
142-
{
143-
if (sbInput.Length > 0)
144-
{
145-
sbInput.Append('\n');
146-
}
147-
sbInput.Append(message.Text);
148-
}
149-
}
150140
string input = sbInput.ToString();
151141

152-
// Search
153-
var results = await this._searchAsync(input, cancellationToken).ConfigureAwait(false);
154-
IList<TextSearchResult> materialized = results as IList<TextSearchResult> ?? results.ToList();
155-
if (materialized.Count == 0)
142+
try
156143
{
157-
this._logger?.LogWarning("TextSearchProvider: No search results found.");
158-
return new AIContext();
159-
}
144+
// Search
145+
var results = await this._searchAsync(input, cancellationToken).ConfigureAwait(false);
146+
IList<TextSearchResult> materialized = results as IList<TextSearchResult> ?? results.ToList();
147+
this._logger?.LogInformation("TextSearchProvider: Retrieved {Count} search results.", materialized.Count);
160148

161-
// Format search results
162-
string formatted = this.FormatResults(materialized);
149+
if (materialized.Count == 0)
150+
{
151+
return new AIContext();
152+
}
163153

164-
this._logger?.LogInformation("TextSearchProvider: Retrieved {Count} search results.", materialized.Count);
165-
this._logger?.LogTrace("TextSearchProvider Input:{Input}\nContext Instructions:{Instructions}", input, formatted);
154+
// Format search results
155+
string formatted = this.FormatResults(materialized);
166156

167-
return new AIContext
157+
this._logger?.LogTrace("TextSearchProvider: Search Results\nInput:{Input}\nOutput:{MessageText}", input, formatted);
158+
159+
return new AIContext
160+
{
161+
Messages = [new ChatMessage(ChatRole.User, formatted) { AdditionalProperties = new AdditionalPropertiesDictionary() { ["IsTextSearchProviderOutput"] = true } }]
162+
};
163+
}
164+
catch (Exception ex)
168165
{
169-
Messages = [new ChatMessage(ChatRole.User, formatted) { AdditionalProperties = new AdditionalPropertiesDictionary() { ["IsTextSearchProviderOutput"] = true } }]
170-
};
166+
this._logger?.LogError(ex, "TextSearchProvider: Failed to search for data due to error");
167+
return new AIContext();
168+
}
171169
}
172170

173171
/// <inheritdoc />
@@ -240,16 +238,16 @@ internal async Task<string> SearchAsync(string userQuestion, CancellationToken c
240238
{
241239
var results = await this._searchAsync(userQuestion, cancellationToken).ConfigureAwait(false);
242240
IList<TextSearchResult> materialized = results as IList<TextSearchResult> ?? results.ToList();
243-
string formatted = this.FormatResults(materialized);
241+
string outputText = this.FormatResults(materialized);
244242

245243
this._logger?.LogInformation("TextSearchProvider: Retrieved {Count} search results.", materialized.Count);
246-
this._logger?.LogTrace("TextSearchProvider Input:{UserQuestion}\nContext Instructions:{Instructions}", userQuestion, formatted);
244+
this._logger?.LogTrace("TextSearchProvider Input:{UserQuestion}\nOutput:{MessageText}", userQuestion, outputText);
247245

248-
return formatted;
246+
return outputText;
249247
}
250248

251249
/// <summary>
252-
/// Formats search results into an instructions string for model consumption.
250+
/// Formats search results into an output string for model consumption.
253251
/// </summary>
254252
/// <param name="results">The results.</param>
255253
/// <returns>Formatted string (may be empty).</returns>

dotnet/src/Microsoft.Agents.AI/Data/TextSearchProviderOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ public sealed class TextSearchProviderOptions
3030
public string? FunctionToolDescription { get; set; }
3131

3232
/// <summary>
33-
/// Gets or sets the context prompt prefixed to automatically injected results.
33+
/// Gets or sets the context prompt prefixed to results.
3434
/// </summary>
3535
public string? ContextPrompt { get; set; }
3636

3737
/// <summary>
38-
/// Gets or sets the instruction appended after automatically injected results to request citations.
38+
/// Gets or sets the instruction appended after results to request citations.
3939
/// </summary>
4040
public string? CitationsPrompt { get; set; }
4141

@@ -85,7 +85,7 @@ public sealed class TextSearchProviderOptions
8585
public enum TextSearchBehavior
8686
{
8787
/// <summary>
88-
/// Execute search prior to each invocation and inject results as instructions.
88+
/// Execute search prior to each invocation and inject results as a message.
8989
/// </summary>
9090
BeforeAIInvoke,
9191

0 commit comments

Comments
 (0)