mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [BREAKING]Delay AIContext Materialization until the end of the pipeline is reached. (#3883)
* Delay AIContext Materialization until the end of the pipeline is reached. * Address PR comments. * Address PR comments
This commit is contained in:
committed by
GitHub
Unverified
parent
d71e076d15
commit
72a863f4bf
@@ -119,14 +119,13 @@ namespace SampleApp
|
||||
{
|
||||
AIFunctionFactory.Create((string item) => AddTodoItem(context.Session, item), "AddTodoItem", "Adds an item to the todo list."),
|
||||
AIFunctionFactory.Create((int index) => RemoveTodoItem(context.Session, index), "RemoveTodoItem", "Removes an item from the todo list. Index is zero based.")
|
||||
}).ToList(),
|
||||
}),
|
||||
Messages =
|
||||
(inputContext.Messages ?? [])
|
||||
.Concat(
|
||||
[
|
||||
new MEAI.ChatMessage(ChatRole.User, outputMessageBuilder.ToString()).WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)
|
||||
])
|
||||
.ToList()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -56,41 +56,44 @@ public sealed class AIContext
|
||||
public string? Instructions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of messages to add to the conversation history.
|
||||
/// Gets or sets the sequence of messages to use for the current invocation.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A list of <see cref="ChatMessage"/> instances to be permanently added to the conversation history,
|
||||
/// or <see langword="null"/> if no messages should be added.
|
||||
/// A sequence of <see cref="ChatMessage"/> instances to be used for the current invocation,
|
||||
/// or <see langword="null"/> if no messages should be used.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Unlike <see cref="Instructions"/> and <see cref="Tools"/>, messages added through this property become
|
||||
/// permanent additions to the conversation history. They will persist beyond the current invocation and
|
||||
/// will be available in future interactions within the same conversation thread.
|
||||
/// Unlike <see cref="Instructions"/> and <see cref="Tools"/>, messages added through this property may become
|
||||
/// permanent additions to the conversation history.
|
||||
/// If chat history is managed by the underlying AI service, these messages will become part of chat history.
|
||||
/// If chat history is managed using a <see cref="ChatHistoryProvider"/>, these messages will be passed to the
|
||||
/// <see cref="ChatHistoryProvider.InvokedCoreAsync(ChatHistoryProvider.InvokedContext, System.Threading.CancellationToken)"/> method,
|
||||
/// and the provider can choose which of these messages to permanently add to the conversation history.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This property is useful for:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Injecting relevant historical context or background information</description></item>
|
||||
/// <item><description>Injecting relevant historical context e.g. memories</description></item>
|
||||
/// <item><description>Injecting relevant background information e.g. via Retrieval Augmented Generation</description></item>
|
||||
/// <item><description>Adding system messages that provide ongoing context</description></item>
|
||||
/// <item><description>Including retrieved information that should be part of the conversation record</description></item>
|
||||
/// <item><description>Inserting contextual exchanges that inform the current conversation</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public IList<ChatMessage>? Messages { get; set; }
|
||||
public IEnumerable<ChatMessage>? Messages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of tools or functions to make available to the AI model for the current invocation.
|
||||
/// Gets or sets a sequence of tools or functions to make available to the AI model for the current invocation.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A list of <see cref="AITool"/> instances that will be available to the AI model during the current invocation,
|
||||
/// A sequence of <see cref="AITool"/> instances that will be available to the AI model during the current invocation,
|
||||
/// or <see langword="null"/> if no additional tools should be provided.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// These tools are transient and apply only to the current AI model invocation. They are combined with any
|
||||
/// tools already configured for the agent to provide an expanded set of capabilities for the specific interaction.
|
||||
/// These tools are transient and apply only to the current AI model invocation. Any existing tools
|
||||
/// are provided as input to the <see cref="AIContextProvider"/> instances, so context providers can choose to modify or replace the existing tools
|
||||
/// as needed based on the current context. The resulting set of tools is then passed to the underlying AI model, which may choose to utilize them when generating responses.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Context-specific tools enable:
|
||||
@@ -102,5 +105,5 @@ public sealed class AIContext
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public IList<AITool>? Tools { get; set; }
|
||||
public IEnumerable<AITool>? Tools { get; set; }
|
||||
}
|
||||
|
||||
@@ -170,8 +170,7 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
Instructions = inputContext.Instructions,
|
||||
Messages =
|
||||
(inputContext.Messages ?? [])
|
||||
.Concat(outputMessage is not null ? [outputMessage] : [])
|
||||
.ToList(),
|
||||
.Concat(outputMessage is not null ? [outputMessage] : []),
|
||||
Tools = inputContext.Tools
|
||||
};
|
||||
}
|
||||
|
||||
@@ -677,7 +677,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
throw new InvalidOperationException("Input messages are not allowed when continuing a background response using a continuation token.");
|
||||
}
|
||||
|
||||
List<ChatMessage> inputMessagesForChatClient = [];
|
||||
IEnumerable<ChatMessage> inputMessagesForChatClient = inputMessages;
|
||||
|
||||
// Populate the session messages only if we are not continuing an existing response as it's not allowed
|
||||
if (chatOptions?.ContinuationToken is null)
|
||||
@@ -688,13 +688,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
// The ChatHistoryProvider returns the merged result (history + input messages).
|
||||
if (chatHistoryProvider is not null)
|
||||
{
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(this, typedSession, inputMessages);
|
||||
var providerMessages = await chatHistoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
|
||||
inputMessagesForChatClient.AddRange(providerMessages);
|
||||
}
|
||||
else
|
||||
{
|
||||
inputMessagesForChatClient.AddRange(inputMessages);
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(this, typedSession, inputMessagesForChatClient);
|
||||
inputMessagesForChatClient = await chatHistoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// If we have an AIContextProvider, we should get context from it, and update our
|
||||
@@ -705,8 +700,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
var aiContext = new AIContext
|
||||
{
|
||||
Instructions = chatOptions?.Instructions,
|
||||
Messages = inputMessagesForChatClient.ToList(),
|
||||
Tools = chatOptions?.Tools as List<AITool> ?? chatOptions?.Tools?.ToList()
|
||||
Messages = inputMessagesForChatClient,
|
||||
Tools = chatOptions?.Tools
|
||||
};
|
||||
|
||||
foreach (var aiContextProvider in aiContextProviders)
|
||||
@@ -715,13 +710,14 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
aiContext = await aiContextProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Use the returned messages, tools and instructions directly since the provider accumulated them.
|
||||
inputMessagesForChatClient = aiContext.Messages as List<ChatMessage> ?? aiContext.Messages?.ToList() ?? [];
|
||||
// Materialize the accumulated messages and tools once at the end of the provider pipeline.
|
||||
inputMessagesForChatClient = aiContext.Messages ?? [];
|
||||
|
||||
if (chatOptions?.Tools is { Count: > 0 } || aiContext.Tools is { Count: > 0 })
|
||||
var tools = aiContext.Tools as IList<AITool> ?? aiContext.Tools?.ToList();
|
||||
if (chatOptions?.Tools is { Count: > 0 } || tools is { Count: > 0 })
|
||||
{
|
||||
chatOptions ??= new();
|
||||
chatOptions.Tools = aiContext.Tools;
|
||||
chatOptions.Tools = tools;
|
||||
}
|
||||
|
||||
if (chatOptions?.Instructions is not null || aiContext.Instructions is not null)
|
||||
@@ -750,7 +746,10 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
chatOptions.ConversationId = typedSession.ConversationId;
|
||||
}
|
||||
|
||||
return (typedSession, chatOptions, inputMessagesForChatClient, continuationToken);
|
||||
// Materialize the accumulated messages once at the end of the provider pipeline, reusing the existing list if possible.
|
||||
List<ChatMessage> messagesList = inputMessagesForChatClient as List<ChatMessage> ?? inputMessagesForChatClient.ToList();
|
||||
|
||||
return (typedSession, chatOptions, messagesList, continuationToken);
|
||||
}
|
||||
|
||||
private void UpdateSessionConversationId(ChatClientAgentSession session, string? responseConversationId, CancellationToken cancellationToken)
|
||||
|
||||
@@ -171,7 +171,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
{
|
||||
Instructions = inputContext.Instructions,
|
||||
Messages = inputContext.Messages,
|
||||
Tools = (inputContext.Tools ?? []).Concat(tools).ToList()
|
||||
Tools = (inputContext.Tools ?? []).Concat(tools)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -204,8 +204,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
.Concat(
|
||||
[
|
||||
new ChatMessage(ChatRole.User, contextText).WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)
|
||||
])
|
||||
.ToList(),
|
||||
]),
|
||||
Tools = inputContext.Tools
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
{
|
||||
Instructions = inputContext.Instructions,
|
||||
Messages = inputContext.Messages,
|
||||
Tools = (inputContext.Tools ?? []).Concat(this._tools).ToList()
|
||||
Tools = (inputContext.Tools ?? []).Concat(this._tools)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -161,8 +161,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
.Concat(
|
||||
[
|
||||
new ChatMessage(ChatRole.User, formatted).WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)
|
||||
])
|
||||
.ToList(),
|
||||
]),
|
||||
Tools = inputContext.Tools
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
@@ -33,9 +34,10 @@ public class AIContextTests
|
||||
};
|
||||
|
||||
Assert.NotNull(context.Messages);
|
||||
Assert.Equal(2, context.Messages.Count);
|
||||
Assert.Equal("Hello", context.Messages[0].Text);
|
||||
Assert.Equal("Hi there!", context.Messages[1].Text);
|
||||
var messages = context.Messages.ToList();
|
||||
Assert.Equal(2, messages.Count);
|
||||
Assert.Equal("Hello", messages[0].Text);
|
||||
Assert.Equal("Hi there!", messages[1].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -51,8 +53,9 @@ public class AIContextTests
|
||||
};
|
||||
|
||||
Assert.NotNull(context.Tools);
|
||||
Assert.Equal(2, context.Tools.Count);
|
||||
Assert.Equal("Function1", context.Tools[0].Name);
|
||||
Assert.Equal("Function2", context.Tools[1].Name);
|
||||
var tools = context.Tools.ToList();
|
||||
Assert.Equal(2, tools.Count);
|
||||
Assert.Equal("Function1", tools[0].Name);
|
||||
Assert.Equal("Function2", tools[1].Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
@@ -54,7 +55,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
|
||||
await sut.ClearStoredMemoriesAsync(mockSession);
|
||||
var ctxBefore = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List<ChatMessage> { question } }));
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?[0].Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [input]));
|
||||
@@ -63,8 +64,8 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List<ChatMessage> { question } }));
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Caoimhe", ctxAfterAdding.Messages?[0].Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?[0].Text ?? string.Empty);
|
||||
Assert.Contains("Caoimhe", ctxAfterAdding.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
@@ -79,7 +80,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
|
||||
await sut.ClearStoredMemoriesAsync(mockSession);
|
||||
var ctxBefore = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List<ChatMessage> { question } }));
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?[0].Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [assistantIntro]));
|
||||
@@ -88,8 +89,8 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List<ChatMessage> { question } }));
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Caoimhe", ctxAfterAdding.Messages?[0].Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?[0].Text ?? string.Empty);
|
||||
Assert.Contains("Caoimhe", ctxAfterAdding.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
@@ -110,8 +111,8 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
|
||||
var ctxBefore1 = await sut1.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession1, new AIContext { Messages = new List<ChatMessage> { question } }));
|
||||
var ctxBefore2 = await sut2.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession2, new AIContext { Messages = new List<ChatMessage> { question } }));
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore1.Messages?[0].Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore2.Messages?[0].Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore1.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore2.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
|
||||
// Act
|
||||
await sut1.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession1, [assistantIntro]));
|
||||
@@ -119,8 +120,8 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
var ctxAfterAdding2 = await GetContextWithRetryAsync(sut2, mockSession2, question);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Caoimhe", ctxAfterAdding1.Messages?[0].Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxAfterAdding2.Messages?[0].Text ?? string.Empty);
|
||||
Assert.Contains("Caoimhe", ctxAfterAdding1.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
Assert.DoesNotContain("Caoimhe", ctxAfterAdding2.Messages?.LastOrDefault()?.Text ?? string.Empty);
|
||||
|
||||
// Cleanup
|
||||
await sut1.ClearStoredMemoriesAsync(mockSession1);
|
||||
@@ -133,7 +134,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
for (int i = 0; i < attempts; i++)
|
||||
{
|
||||
ctx = await provider.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, session, new AIContext { Messages = new List<ChatMessage> { question } }), CancellationToken.None);
|
||||
var text = ctx.Messages?[0].Text;
|
||||
var text = ctx.Messages?.LastOrDefault()?.Text;
|
||||
if (!string.IsNullOrEmpty(text) && text.IndexOf("Caoimhe", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -118,9 +118,10 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
Assert.Equal("What is my name?", doc.RootElement.GetProperty("query").GetString());
|
||||
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
Assert.Equal(2, aiContext.Messages.Count);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages[0].GetAgentRequestMessageSourceType());
|
||||
var contextMessage = aiContext.Messages[1];
|
||||
var messages = aiContext.Messages.ToList();
|
||||
Assert.Equal(2, messages.Count);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, messages[0].GetAgentRequestMessageSourceType());
|
||||
var contextMessage = messages[1];
|
||||
Assert.Equal(ChatRole.User, contextMessage.Role);
|
||||
Assert.Contains("Name is Caoimhe", contextMessage.Text);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, contextMessage.GetAgentRequestMessageSourceType());
|
||||
|
||||
@@ -496,9 +496,9 @@ public partial class ChatClientAgentTests
|
||||
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
|
||||
new ValueTask<AIContext>(new AIContext
|
||||
{
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
|
||||
Instructions = ctx.AIContext.Instructions + "\ncontext provider instructions",
|
||||
Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") }).ToList()
|
||||
Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") })
|
||||
}));
|
||||
mockProvider
|
||||
.Protected()
|
||||
@@ -567,7 +567,7 @@ public partial class ChatClientAgentTests
|
||||
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
|
||||
new ValueTask<AIContext>(new AIContext
|
||||
{
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
|
||||
}));
|
||||
mockProvider
|
||||
.Protected()
|
||||
@@ -626,7 +626,7 @@ public partial class ChatClientAgentTests
|
||||
new ValueTask<AIContext>(new AIContext
|
||||
{
|
||||
Instructions = ctx.AIContext.Instructions,
|
||||
Messages = ctx.AIContext.Messages?.ToList(),
|
||||
Messages = ctx.AIContext.Messages,
|
||||
Tools = ctx.AIContext.Tools
|
||||
}));
|
||||
|
||||
@@ -1875,9 +1875,9 @@ public partial class ChatClientAgentTests
|
||||
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
|
||||
new ValueTask<AIContext>(new AIContext
|
||||
{
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
|
||||
Instructions = ctx.AIContext.Instructions + "\ncontext provider instructions",
|
||||
Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") }).ToList()
|
||||
Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") })
|
||||
}));
|
||||
mockProvider
|
||||
.Protected()
|
||||
@@ -1954,7 +1954,7 @@ public partial class ChatClientAgentTests
|
||||
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
|
||||
new ValueTask<AIContext>(new AIContext
|
||||
{
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
|
||||
Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
|
||||
}));
|
||||
mockProvider
|
||||
.Protected()
|
||||
|
||||
@@ -106,12 +106,13 @@ public sealed class TextSearchProviderTests
|
||||
Assert.Equal("Sample user question?\nAdditional part", capturedInput);
|
||||
Assert.Null(aiContext.Instructions); // TextSearchProvider uses a user message for context injection.
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
Assert.Equal(3, aiContext.Messages!.Count); // 2 input messages + 1 search result message
|
||||
Assert.Equal("Sample user question?", aiContext.Messages![0].Text);
|
||||
Assert.Equal("Additional part", aiContext.Messages![1].Text);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages![0].GetAgentRequestMessageSourceType());
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages![1].GetAgentRequestMessageSourceType());
|
||||
var message = aiContext.Messages!.Last();
|
||||
var messages = aiContext.Messages!.ToList();
|
||||
Assert.Equal(3, messages.Count); // 2 input messages + 1 search result message
|
||||
Assert.Equal("Sample user question?", messages[0].Text);
|
||||
Assert.Equal("Additional part", messages[1].Text);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, messages[0].GetAgentRequestMessageSourceType());
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, messages[1].GetAgentRequestMessageSourceType());
|
||||
var message = messages.Last();
|
||||
Assert.Equal(ChatRole.User, message.Role);
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, message.GetAgentRequestMessageSourceType());
|
||||
string text = message.Text!;
|
||||
@@ -181,11 +182,13 @@ public sealed class TextSearchProviderTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages); // Input messages are preserved.
|
||||
Assert.Single(aiContext.Messages!);
|
||||
Assert.Equal("Q?", aiContext.Messages![0].Text);
|
||||
var messages = aiContext.Messages!.ToList();
|
||||
Assert.Single(messages);
|
||||
Assert.Equal("Q?", messages[0].Text);
|
||||
Assert.NotNull(aiContext.Tools);
|
||||
Assert.Single(aiContext.Tools);
|
||||
var tool = aiContext.Tools.Single();
|
||||
var tools = aiContext.Tools!.ToList();
|
||||
Assert.Single(tools);
|
||||
var tool = tools[0];
|
||||
Assert.Equal(expectedName, tool.Name);
|
||||
Assert.Equal(expectedDescription, tool.Description);
|
||||
}
|
||||
@@ -202,8 +205,9 @@ public sealed class TextSearchProviderTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages); // Input messages are preserved on error.
|
||||
Assert.Single(aiContext.Messages!);
|
||||
Assert.Equal("Q?", aiContext.Messages![0].Text);
|
||||
var messages = aiContext.Messages!.ToList();
|
||||
Assert.Single(messages);
|
||||
Assert.Equal("Q?", messages[0].Text);
|
||||
Assert.Null(aiContext.Tools);
|
||||
this._loggerMock.Verify(
|
||||
l => l.Log(
|
||||
@@ -297,9 +301,10 @@ public sealed class TextSearchProviderTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
Assert.Equal(2, aiContext.Messages!.Count); // 1 input message + 1 formatted result message
|
||||
Assert.Equal("Q?", aiContext.Messages![0].Text);
|
||||
Assert.Equal("Custom formatted context with 2 results.", aiContext.Messages![1].Text);
|
||||
var messages = aiContext.Messages!.ToList();
|
||||
Assert.Equal(2, messages.Count); // 1 input message + 1 formatted result message
|
||||
Assert.Equal("Q?", messages[0].Text);
|
||||
Assert.Equal("Custom formatted context with 2 results.", messages[1].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -332,9 +337,10 @@ public sealed class TextSearchProviderTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
Assert.Equal(2, aiContext.Messages!.Count); // 1 input message + 1 formatted result message
|
||||
Assert.Equal("Q?", aiContext.Messages![0].Text);
|
||||
Assert.Equal("R1,R2", aiContext.Messages![1].Text);
|
||||
var messages = aiContext.Messages!.ToList();
|
||||
Assert.Equal(2, messages.Count); // 1 input message + 1 formatted result message
|
||||
Assert.Equal("Q?", messages[0].Text);
|
||||
Assert.Equal("R1,R2", messages[1].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -350,8 +356,9 @@ public sealed class TextSearchProviderTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages); // Input messages are preserved when no results found.
|
||||
Assert.Single(aiContext.Messages!);
|
||||
Assert.Equal("Q?", aiContext.Messages![0].Text);
|
||||
var messages = aiContext.Messages!.ToList();
|
||||
Assert.Single(messages);
|
||||
Assert.Equal("Q?", messages[0].Text);
|
||||
Assert.Null(aiContext.Instructions);
|
||||
Assert.Null(aiContext.Tools);
|
||||
}
|
||||
|
||||
@@ -396,9 +396,10 @@ public class ChatHistoryMemoryProviderTests
|
||||
Times.Once);
|
||||
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
Assert.Equal(2, aiContext.Messages.Count);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages[0].GetAgentRequestMessageSourceType());
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, aiContext.Messages[1].GetAgentRequestMessageSourceType());
|
||||
var messages = aiContext.Messages.ToList();
|
||||
Assert.Equal(2, messages.Count);
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, messages[0].GetAgentRequestMessageSourceType());
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, messages[1].GetAgentRequestMessageSourceType());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user