Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22f2d29054 | |||
| e87e42494c |
+22
@@ -42,6 +42,28 @@ internal static class JsonDocumentExtensions
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a successfully-parsed JSON document's root element to a CLR value by its <see cref="JsonValueKind"/>.
|
||||
/// Mirrors the value-kind handling shared by the agent/tool/HTTP executors: objects become records,
|
||||
/// arrays become lists, and scalars become their primitive value.
|
||||
/// </summary>
|
||||
/// <param name="jsonDocument">The parsed JSON document.</param>
|
||||
/// <param name="rawJson">The original JSON text, returned as a fallback when the root kind is undefined.</param>
|
||||
/// <returns>The parsed CLR value.</returns>
|
||||
public static object? ParseJsonValue(this JsonDocument jsonDocument, string rawJson) =>
|
||||
jsonDocument.RootElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Object => jsonDocument.ParseRecord(VariableType.RecordType),
|
||||
JsonValueKind.Array when jsonDocument.RootElement.GetArrayLength() == 0 => new List<object?>(),
|
||||
JsonValueKind.Array => jsonDocument.ParseList(jsonDocument.RootElement.GetListTypeFromJson()),
|
||||
JsonValueKind.String => jsonDocument.RootElement.GetString(),
|
||||
JsonValueKind.Number => jsonDocument.RootElement.GetDouble(),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Null => null,
|
||||
_ => rawJson,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a VariableType.List with schema inferred from the first object element in the array.
|
||||
/// </summary>
|
||||
|
||||
+1
-14
@@ -7,7 +7,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Agents.ObjectModel;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -177,19 +176,7 @@ internal sealed class HttpRequestExecutor(
|
||||
{
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(responseBody);
|
||||
|
||||
object? parsedValue = jsonDocument.RootElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Object => jsonDocument.ParseRecord(VariableType.RecordType),
|
||||
JsonValueKind.Array => jsonDocument.ParseList(jsonDocument.RootElement.GetListTypeFromJson()),
|
||||
JsonValueKind.String => jsonDocument.RootElement.GetString(),
|
||||
JsonValueKind.Number => jsonDocument.RootElement.TryGetInt64(out long l)
|
||||
? l
|
||||
: jsonDocument.RootElement.GetDouble(),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Null => null,
|
||||
_ => responseBody,
|
||||
};
|
||||
object? parsedValue = jsonDocument.ParseJsonValue(responseBody);
|
||||
|
||||
return parsedValue.ToFormula();
|
||||
}
|
||||
|
||||
+16
-4
@@ -13,6 +13,7 @@ using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Agents.ObjectModel;
|
||||
using Microsoft.Agents.ObjectModel.Abstractions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
|
||||
@@ -89,18 +90,29 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, ResponseA
|
||||
await this.AssignAsync(this.AgentOutput?.Messages?.Path, agentResponse.Messages.ToTable(), context).ConfigureAwait(false);
|
||||
|
||||
// Attempt to parse the last message as JSON and assign to the response object variable.
|
||||
PropertyPath? responseObjectPath = this.AgentOutput?.ResponseObject?.Path;
|
||||
string? lastMessageText = agentResponse.Messages.LastOrDefault()?.Text;
|
||||
if (!string.IsNullOrEmpty(lastMessageText))
|
||||
if (responseObjectPath is not null && !string.IsNullOrEmpty(lastMessageText))
|
||||
{
|
||||
FormulaValue? responseObjectValue = null;
|
||||
try
|
||||
{
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(lastMessageText);
|
||||
Dictionary<string, object?> objectProperties = jsonDocument.ParseRecord(VariableType.RecordType);
|
||||
await this.AssignAsync(this.AgentOutput?.ResponseObject?.Path, objectProperties.ToFormula(), context).ConfigureAwait(false);
|
||||
responseObjectValue = jsonDocument.ParseJsonValue(lastMessageText).ToFormula();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Not valid json, skip assignment.
|
||||
// Not valid JSON — skip assignment.
|
||||
}
|
||||
catch (DeclarativeWorkflowException)
|
||||
{
|
||||
// Valid JSON, but not convertible to a workflow value (e.g. a mixed-type or nested array).
|
||||
// Output parsing is best-effort — skip assignment rather than fail the action.
|
||||
}
|
||||
|
||||
if (responseObjectValue is not null)
|
||||
{
|
||||
await this.AssignAsync(responseObjectPath, responseObjectValue, context).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-11
@@ -357,17 +357,7 @@ internal sealed class InvokeFunctionToolExecutor(
|
||||
{
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(jsonString);
|
||||
// Handle different JSON value kinds
|
||||
object? parsedValue = jsonDocument.RootElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Object => jsonDocument.ParseRecord(VariableType.RecordType),
|
||||
JsonValueKind.Array => jsonDocument.ParseList(jsonDocument.RootElement.GetListTypeFromJson()),
|
||||
JsonValueKind.String => jsonDocument.RootElement.GetString(),
|
||||
JsonValueKind.Number => jsonDocument.RootElement.TryGetInt64(out long l) ? l : jsonDocument.RootElement.GetDouble(),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Null => null,
|
||||
_ => jsonString,
|
||||
};
|
||||
object? parsedValue = jsonDocument.ParseJsonValue(jsonString);
|
||||
await this.AssignAsync(this.Model.Output.Result?.Path, parsedValue.ToFormula(), context).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-11
@@ -302,17 +302,7 @@ internal sealed class InvokeMcpToolExecutor(
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(jsonString);
|
||||
|
||||
// Handle different JSON value kinds
|
||||
object? parsedValue = jsonDocument.RootElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Object => jsonDocument.ParseRecord(VariableType.RecordType),
|
||||
JsonValueKind.Array => jsonDocument.ParseList(jsonDocument.RootElement.GetListTypeFromJson()),
|
||||
JsonValueKind.String => jsonDocument.RootElement.GetString(),
|
||||
JsonValueKind.Number => jsonDocument.RootElement.TryGetInt64(out long l) ? l : jsonDocument.RootElement.GetDouble(),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Null => null,
|
||||
_ => jsonString,
|
||||
};
|
||||
object? parsedValue = jsonDocument.ParseJsonValue(jsonString);
|
||||
|
||||
parsedResults.Add(parsedValue);
|
||||
continue;
|
||||
|
||||
+98
@@ -663,4 +663,102 @@ public sealed class JsonDocumentExtensionsTests
|
||||
Assert.Equal(VariableType.ListType, result.Schema["list"].Type);
|
||||
Assert.Equal(typeof(string), result.Schema["empty"].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJsonValue_Object_ReturnsRecord()
|
||||
{
|
||||
// Arrange
|
||||
JsonDocument document = JsonDocument.Parse("""{ "a": "alpha", "b": "beta" }""");
|
||||
|
||||
// Act
|
||||
object? result = document.ParseJsonValue("""{ "a": "alpha", "b": "beta" }""");
|
||||
|
||||
// Assert
|
||||
Dictionary<string, object?> record = Assert.IsType<Dictionary<string, object?>>(result);
|
||||
Assert.Equal("alpha", record["a"]);
|
||||
Assert.Equal("beta", record["b"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJsonValue_Array_ReturnsList()
|
||||
{
|
||||
// Arrange
|
||||
const string Json = """["alpha","beta"]""";
|
||||
JsonDocument document = JsonDocument.Parse(Json);
|
||||
|
||||
// Act
|
||||
object? result = document.ParseJsonValue(Json);
|
||||
|
||||
// Assert
|
||||
List<object?> list = Assert.IsType<List<object?>>(result);
|
||||
Assert.Equal(new object?[] { "alpha", "beta" }, list);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJsonValue_EmptyArray_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
JsonDocument document = JsonDocument.Parse("[]");
|
||||
|
||||
// Act
|
||||
object? result = document.ParseJsonValue("[]");
|
||||
|
||||
// Assert
|
||||
List<object?> list = Assert.IsType<List<object?>>(result);
|
||||
Assert.Empty(list);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJsonValue_String_ReturnsString()
|
||||
{
|
||||
// Arrange
|
||||
JsonDocument document = JsonDocument.Parse("\"hello\"");
|
||||
|
||||
// Act
|
||||
object? result = document.ParseJsonValue("\"hello\"");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("hello", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJsonValue_Number_ReturnsNumericValue()
|
||||
{
|
||||
// Arrange
|
||||
JsonDocument document = JsonDocument.Parse("42");
|
||||
|
||||
// Act
|
||||
object? result = document.ParseJsonValue("42");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(42d, Assert.IsType<double>(result));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("true", true)]
|
||||
[InlineData("false", false)]
|
||||
public void ParseJsonValue_Boolean_ReturnsBoolean(string json, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
JsonDocument document = JsonDocument.Parse(json);
|
||||
|
||||
// Act
|
||||
object? result = document.ParseJsonValue(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJsonValue_Null_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
JsonDocument document = JsonDocument.Parse("null");
|
||||
|
||||
// Act
|
||||
object? result = document.ParseJsonValue("null");
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
}
|
||||
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Agents.ObjectModel;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="InvokeAzureAgentExecutor"/>.
|
||||
/// </summary>
|
||||
public sealed class InvokeAzureAgentExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public void InvokeAzureAgentThrowsWhenModelInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
Assert.Throws<DeclarativeModelException>(() => new InvokeAzureAgentExecutor(new InvokeAzureAgent(), new CapturingAgentProvider("text"), this.State));
|
||||
|
||||
#region Input argument binding
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleNamedArgumentsAreAllBoundAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("acknowledged");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(MultipleNamedArgumentsAreAllBoundAsync),
|
||||
agentName: "BrainCombine",
|
||||
arguments:
|
||||
[
|
||||
("a", ValueExpression.Literal(new StringDataValue("alpha"))),
|
||||
("b", ValueExpression.Literal(new StringDataValue("beta"))),
|
||||
]);
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(provider.CapturedArguments);
|
||||
Assert.Equal("alpha", provider.CapturedArguments!["a"]);
|
||||
Assert.Equal("beta", provider.CapturedArguments!["b"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordValuedArgumentIsBoundAsRecordAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
this.State.Set(
|
||||
"R",
|
||||
FormulaValue.NewRecordFromFields(
|
||||
new NamedValue("a", FormulaValue.New("alpha")),
|
||||
new NamedValue("b", FormulaValue.New("beta"))));
|
||||
CapturingAgentProvider provider = new("acknowledged");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(RecordValuedArgumentIsBoundAsRecordAsync),
|
||||
agentName: "BrainTest",
|
||||
arguments:
|
||||
[
|
||||
("input", ValueExpression.Variable(PropertyPath.TopicVariable("R"))),
|
||||
]);
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(provider.CapturedArguments);
|
||||
IDictionary<string, object?> record = Assert.IsAssignableFrom<IDictionary<string, object?>>(provider.CapturedArguments!["input"]);
|
||||
Assert.Equal("alpha", record["a"]);
|
||||
Assert.Equal("beta", record["b"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InlineRecordExpressionArgumentIsBoundAsRecordAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("acknowledged");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(InlineRecordExpressionArgumentIsBoundAsRecordAsync),
|
||||
agentName: "BrainInlineRecord",
|
||||
arguments:
|
||||
[
|
||||
("input", ValueExpression.Expression("""{ a: "alpha", b: "beta" }""")),
|
||||
]);
|
||||
|
||||
// Act (reporter repro (b): a single argument whose value is an inline record literal)
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(provider.CapturedArguments);
|
||||
IDictionary<string, object?> record = Assert.IsAssignableFrom<IDictionary<string, object?>>(provider.CapturedArguments!["input"]);
|
||||
Assert.Equal("alpha", record["a"]);
|
||||
Assert.Equal("beta", record["b"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Response object parsing
|
||||
|
||||
[Fact]
|
||||
public async Task JsonObjectOutputAssignsRecordAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("""{ "a": "alpha", "b": "beta" }""");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(JsonObjectOutputAssignsRecordAsync),
|
||||
agentName: "BrainObject",
|
||||
responseObjectVariable: "Result");
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
RecordValue record = Assert.IsAssignableFrom<RecordValue>(this.State.Get("Result"));
|
||||
Assert.Equal("alpha", ((StringValue)record.GetField("a")).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JsonArrayOutputAssignsListAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("""["alpha","beta"]""");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(JsonArrayOutputAssignsListAsync),
|
||||
agentName: "BrainArray",
|
||||
responseObjectVariable: "Result");
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
TableValue table = Assert.IsAssignableFrom<TableValue>(this.State.Get("Result"));
|
||||
Assert.Equal(2, table.Rows.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EmptyJsonArrayOutputAssignsEmptyListAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("[]");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(EmptyJsonArrayOutputAssignsEmptyListAsync),
|
||||
agentName: "BrainEmptyArray",
|
||||
responseObjectVariable: "Result");
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
TableValue table = Assert.IsAssignableFrom<TableValue>(this.State.Get("Result"));
|
||||
Assert.Empty(table.Rows);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JsonScalarOutputAssignsScalarAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("42");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(JsonScalarOutputAssignsScalarAsync),
|
||||
agentName: "BrainScalar",
|
||||
responseObjectVariable: "Result");
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
NumberValue number = Assert.IsAssignableFrom<NumberValue>(this.State.Get("Result"));
|
||||
Assert.Equal(42d, number.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MixedJsonArrayOutputSkipsAssignmentAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("""["alpha",1]""");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(MixedJsonArrayOutputSkipsAssignmentAsync),
|
||||
agentName: "BrainMixedArray",
|
||||
responseObjectVariable: "Result");
|
||||
|
||||
// Act (must not throw despite non-convertible JSON)
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
this.VerifyUndefined("Result");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NestedJsonArrayOutputSkipsAssignmentAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("[[1,2],[3,4]]");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(NestedJsonArrayOutputSkipsAssignmentAsync),
|
||||
agentName: "BrainNestedArray",
|
||||
responseObjectVariable: "Result");
|
||||
|
||||
// Act (a nested array parses but is not convertible to a workflow value — must not throw)
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
this.VerifyUndefined("Result");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlainTextOutputSkipsAssignmentAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("hello world");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(PlainTextOutputSkipsAssignmentAsync),
|
||||
agentName: "BrainText",
|
||||
responseObjectVariable: "Result");
|
||||
|
||||
// Act
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
|
||||
// Assert
|
||||
this.VerifyUndefined("Result");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NonObjectJsonOutputWithoutResponseObjectDoesNotThrowAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.InitializeSystem();
|
||||
CapturingAgentProvider provider = new("""["alpha","beta"]""");
|
||||
InvokeAzureAgent model =
|
||||
this.CreateModel(
|
||||
displayName: nameof(NonObjectJsonOutputWithoutResponseObjectDoesNotThrowAsync),
|
||||
agentName: "BrainNoOutput");
|
||||
|
||||
// Act & Assert (reporter repro shape — no output block; must not throw)
|
||||
await this.ExecuteAsync(new InvokeAzureAgentExecutor(model, provider, this.State), isDiscrete: false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private InvokeAzureAgent CreateModel(
|
||||
string displayName,
|
||||
string agentName,
|
||||
IReadOnlyList<(string Key, ValueExpression Value)>? arguments = null,
|
||||
string? responseObjectVariable = null)
|
||||
{
|
||||
InvokeAzureAgent.Builder builder =
|
||||
new()
|
||||
{
|
||||
Id = this.CreateActionId(),
|
||||
DisplayName = this.FormatDisplayName(displayName),
|
||||
Agent =
|
||||
new AzureAgentUsage.Builder
|
||||
{
|
||||
Name = new StringExpression.Builder(StringExpression.Literal(agentName)),
|
||||
},
|
||||
};
|
||||
|
||||
if (arguments is not null)
|
||||
{
|
||||
AzureAgentInput.Builder inputBuilder = new();
|
||||
foreach ((string key, ValueExpression value) in arguments)
|
||||
{
|
||||
inputBuilder.Arguments.Add(key, value);
|
||||
}
|
||||
builder.Input = inputBuilder;
|
||||
}
|
||||
|
||||
if (responseObjectVariable is not null)
|
||||
{
|
||||
builder.Output =
|
||||
new AzureAgentOutput.Builder
|
||||
{
|
||||
AutoSend = new BoolExpression.Builder(BoolExpression.Literal(false)),
|
||||
ResponseObject = new InitializablePropertyPath(PropertyPath.TopicVariable(responseObjectVariable), isInitializer: false),
|
||||
};
|
||||
}
|
||||
|
||||
return AssignParent<InvokeAzureAgent>(builder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimal <see cref="ResponseAgentProvider"/> that returns a single configured text response and
|
||||
/// captures the input arguments supplied to <see cref="InvokeAgentAsync"/>.
|
||||
/// </summary>
|
||||
private sealed class CapturingAgentProvider(string responseText) : ResponseAgentProvider
|
||||
{
|
||||
public IDictionary<string, object?>? CapturedArguments { get; private set; }
|
||||
|
||||
public override IAsyncEnumerable<AgentResponseUpdate> InvokeAgentAsync(
|
||||
string agentId,
|
||||
string? agentVersion,
|
||||
string? conversationId,
|
||||
IEnumerable<ChatMessage>? messages,
|
||||
IDictionary<string, object?>? inputArguments,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.CapturedArguments = inputArguments;
|
||||
return YieldAsync(responseText);
|
||||
}
|
||||
|
||||
public override Task<string> CreateConversationAsync(CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(Guid.NewGuid().ToString("N"));
|
||||
|
||||
public override Task<ChatMessage> CreateMessageAsync(string conversationId, ChatMessage conversationMessage, CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(conversationMessage);
|
||||
|
||||
public override Task<ChatMessage> GetMessageAsync(string conversationId, string messageId, CancellationToken cancellationToken = default) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override async IAsyncEnumerable<ChatMessage> GetMessagesAsync(
|
||||
string conversationId,
|
||||
int? limit = null,
|
||||
string? after = null,
|
||||
string? before = null,
|
||||
bool newestFirst = false,
|
||||
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
private static async IAsyncEnumerable<AgentResponseUpdate> YieldAsync(string text)
|
||||
{
|
||||
yield return new AgentResponseUpdate(ChatRole.Assistant, text);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user