mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
88ea9d08c7
* Initial plan * Update code for Microsoft.Extensions.AI.Abstractions 10.4.0 breaking changes - Rename FunctionApprovalRequestContent → ToolApprovalRequestContent - Rename FunctionApprovalResponseContent → ToolApprovalResponseContent - Rename UserInputRequestContent → ToolApprovalRequestContent - Rename UserInputResponseContent → ToolApprovalResponseContent - Update .FunctionCall property → .ToolCall with FunctionCallContent casts where needed - Update .Id property → .RequestId on the renamed types - Rename FunctionApprovalRequestEventGenerator → ToolApprovalRequestEventGenerator - Rename FunctionApprovalResponseEventGenerator → ToolApprovalResponseEventGenerator Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update OpenAI 2.9.1, ME.AI 10.4.0, fix breaking API changes Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Fix remaining ME.AI 10.4.0 breaking changes: MCP approval types, .Output→.Outputs Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Use pattern matching with `when` for ToolApprovalRequestContent/FunctionCallContent Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Update Azure.AI.OpenAI to 2.9.0-beta.1 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Fix remaining GetResponsesClient(model) build failures for Azure.AI.OpenAI 2.9.0-beta.1 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Address review feedback: remove redundant type checks in TestRequestAgent.cs and fix error message in AIAgentHostExecutor.cs Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Update Azure.AI.Projects to 2.0.0-beta.2 with namespace migration - Azure.AI.Projects 2.0.0-beta.1 → 2.0.0-beta.2 - Azure.AI.Projects.OpenAI → Azure.AI.Extensions.OpenAI (transitive) - Agent types moved to Azure.AI.Projects.Agents namespace - AgentRecord.Versions.Latest → AgentRecord.GetLatestVersion() - OpenAPIFunctionDefinition → OpenApiFunctionDefinition - BingCustomSearchToolParameters → BingCustomSearchToolOptions - MemorySearchPreviewTool.UpdateDelay → UpdateDelayInSecs - Azure.Identity 1.17.1 → 1.19.0 - Microsoft.Identity.Client.Extensions.Msal 4.78.0 → 4.83.1 Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Fix remaining type renames for Azure.AI.Projects 2.0.0-beta.2 - BrowserAutomationToolParameters → BrowserAutomationToolOptions - MemoryUpdateOptions.UpdateDelay stays as UpdateDelay (not renamed) - WaitForMemoriesUpdateAsync parameter order: pollingInterval before options - AIProjectAgentsOperations → AgentsClient Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Fix format errors and OpenTelemetry test for ME.AI 10.4.0 - Remove unused 'using Azure.AI.Extensions.OpenAI' and fix import ordering in Agent_With_AzureAIProject/Program.cs - Update OpenTelemetryAgentTests: gen_ai.tool.definitions is now always emitted regardless of EnableSensitiveData per ME.AI 10.4.0 change (dotnet/extensions#7346). Tool definitions are not considered sensitive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix GetRepoFolder() to work in git worktrees Use 'workflow-samples' directory as repo root marker instead of '.git', which fails in worktrees (.git is a file) and also matches too early when a '.github' folder exists in subdirectories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix formatting: remove unused usings and fix import ordering dotnet format applied across 59 impacted projects. Primarily removes unnecessary 'using Azure.AI.Projects' where Azure.AI.Projects.Agents provides all needed types, and fixes import ordering per editorconfig. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Disable AzureAIAgentsPersistent integration tests (#4769) Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent which was removed in ME.AI 10.4.0 (renamed to ToolApprovalResponseContent), causing TypeLoadException at runtime. Mark all 6 test classes with IntegrationDisabled trait until Persistent ships a version targeting ME.AI 10.4.0+. Upstream fix: https://github.com/Azure/azure-sdk-for-net/pull/56929 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add README with compatibility note for AzureAI.Persistent (#4769) Documents that Azure.AI.Agents.Persistent 1.2.0-beta.9 is only compatible with ME.AI ≤10.3.0 and OpenAI ≤2.8.0 due to type renames in ME.AI 10.4.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix file encoding: restore UTF-8 BOM on Persistent test files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Mark AzureAI.Persistent as IsPackable=false (#4769) Prevent shipping until Azure.AI.Agents.Persistent targets ME.AI 10.4.0+. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Moving IsPackable after import --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
652 lines
24 KiB
C#
652 lines
24 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.AI;
|
|
using OpenTelemetry.Trace;
|
|
|
|
#pragma warning disable CA1861 // Avoid constant arrays as arguments
|
|
#pragma warning disable RCS1186 // Use Regex instance instead of static method
|
|
|
|
namespace Microsoft.Agents.AI.UnitTests;
|
|
|
|
public class OpenTelemetryAgentTests
|
|
{
|
|
[Fact]
|
|
public void Ctor_InvalidArgs_Throws()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() => new OpenTelemetryAgent(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void Ctor_NullSourceName_Valid()
|
|
{
|
|
using var agent = new OpenTelemetryAgent(new TestAIAgent(), null);
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
[Fact]
|
|
public void Properties_DelegateToInnerAgent()
|
|
{
|
|
TestAIAgent innerAgent = new()
|
|
{
|
|
NameFunc = () => "TestAgent",
|
|
DescriptionFunc = () => "This is a test agent.",
|
|
};
|
|
|
|
using var agent = new OpenTelemetryAgent(innerAgent, "MySource");
|
|
|
|
Assert.Equal("TestAgent", agent.Name);
|
|
Assert.Equal("This is a test agent.", agent.Description);
|
|
Assert.Equal(innerAgent.Id, agent.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void EnableSensitiveData_Roundtrips()
|
|
{
|
|
using var agent = new OpenTelemetryAgent(new TestAIAgent(), "MySource");
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
Assert.False(agent.EnableSensitiveData);
|
|
agent.EnableSensitiveData = true;
|
|
Assert.True(agent.EnableSensitiveData);
|
|
agent.EnableSensitiveData = false;
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false, false)]
|
|
[InlineData(false, true)]
|
|
[InlineData(true, false)]
|
|
[InlineData(true, true)]
|
|
public async Task WithoutChatOptions_ExpectedInformationLogged_Async(bool enableSensitiveData, bool streaming)
|
|
{
|
|
var sourceName = Guid.NewGuid().ToString();
|
|
var activities = new List<Activity>();
|
|
using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
|
|
.AddSource(sourceName)
|
|
.AddInMemoryExporter(activities)
|
|
.Build();
|
|
|
|
var innerAgent = new TestAIAgent
|
|
{
|
|
NameFunc = () => "TestAgent",
|
|
DescriptionFunc = () => "This is a test agent.",
|
|
|
|
RunAsyncFunc = async (messages, session, options, cancellationToken) =>
|
|
{
|
|
await Task.Yield();
|
|
return new AgentResponse(new ChatMessage(ChatRole.Assistant, "The blue whale, I think."))
|
|
{
|
|
ResponseId = "id123",
|
|
Usage = new UsageDetails
|
|
{
|
|
InputTokenCount = 10,
|
|
OutputTokenCount = 20,
|
|
TotalTokenCount = 42,
|
|
},
|
|
AdditionalProperties = new()
|
|
{
|
|
["system_fingerprint"] = "abcdefgh",
|
|
["AndSomethingElse"] = "value2",
|
|
},
|
|
};
|
|
},
|
|
|
|
RunStreamingAsyncFunc = CallbackAsync,
|
|
|
|
GetServiceFunc = (serviceType, serviceKey) =>
|
|
serviceType == typeof(AIAgentMetadata) ? new AIAgentMetadata("TestAgentProviderFromAIAgentMetadata") :
|
|
serviceType == typeof(ChatClientMetadata) ? new ChatClientMetadata("TestAgentProviderFromChatClientMetadata", new Uri("http://localhost:12345/something"), "amazingmodel") :
|
|
null,
|
|
};
|
|
|
|
async static IAsyncEnumerable<AgentResponseUpdate> CallbackAsync(
|
|
IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken)
|
|
{
|
|
await Task.Yield();
|
|
|
|
foreach (string text in new[] { "The ", "blue ", "whale,", " ", "", "I", " think." })
|
|
{
|
|
await Task.Yield();
|
|
yield return new AgentResponseUpdate(ChatRole.Assistant, text)
|
|
{
|
|
ResponseId = "id123",
|
|
};
|
|
}
|
|
|
|
yield return new AgentResponseUpdate
|
|
{
|
|
Contents = [new UsageContent(new()
|
|
{
|
|
InputTokenCount = 10,
|
|
OutputTokenCount = 20,
|
|
TotalTokenCount = 42,
|
|
})],
|
|
AdditionalProperties = new()
|
|
{
|
|
["system_fingerprint"] = "abcdefgh",
|
|
["AndSomethingElse"] = "value2",
|
|
},
|
|
};
|
|
}
|
|
|
|
using var agent = new OpenTelemetryAgent(innerAgent, sourceName) { EnableSensitiveData = enableSensitiveData };
|
|
|
|
List<ChatMessage> messages =
|
|
[
|
|
new(ChatRole.System, "You are a close friend."),
|
|
new(ChatRole.User, "Hey!"),
|
|
new(ChatRole.Assistant, [new FunctionCallContent("12345", "GetPersonName")]),
|
|
new(ChatRole.Tool, [new FunctionResultContent("12345", "John")]),
|
|
new(ChatRole.Assistant, "Hey John, what's up?"),
|
|
new(ChatRole.User, "What's the biggest animal?")
|
|
];
|
|
|
|
if (streaming)
|
|
{
|
|
await foreach (var update in agent.RunStreamingAsync(messages))
|
|
{
|
|
await Task.Yield();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await agent.RunAsync(messages);
|
|
}
|
|
|
|
var activity = Assert.Single(activities);
|
|
|
|
Assert.NotNull(activity.Id);
|
|
Assert.NotEmpty(activity.Id);
|
|
|
|
Assert.Equal("localhost", activity.GetTagItem("server.address"));
|
|
Assert.Equal(12345, (int)activity.GetTagItem("server.port")!);
|
|
|
|
Assert.Equal($"invoke_agent {agent.Name}({agent.Id})", activity.DisplayName);
|
|
Assert.Equal("invoke_agent", activity.GetTagItem("gen_ai.operation.name"));
|
|
Assert.Equal("TestAgentProviderFromAIAgentMetadata", activity.GetTagItem("gen_ai.provider.name"));
|
|
Assert.Equal(innerAgent.Name, activity.GetTagItem("gen_ai.agent.name"));
|
|
Assert.Equal(innerAgent.Id, activity.GetTagItem("gen_ai.agent.id"));
|
|
Assert.Equal(innerAgent.Description, activity.GetTagItem("gen_ai.agent.description"));
|
|
|
|
Assert.Equal("amazingmodel", activity.GetTagItem("gen_ai.request.model"));
|
|
|
|
Assert.Equal("id123", activity.GetTagItem("gen_ai.response.id"));
|
|
Assert.Equal(10, activity.GetTagItem("gen_ai.usage.input_tokens"));
|
|
Assert.Equal(20, activity.GetTagItem("gen_ai.usage.output_tokens"));
|
|
Assert.Equal(enableSensitiveData ? "abcdefgh" : null, activity.GetTagItem("system_fingerprint"));
|
|
Assert.Equal(enableSensitiveData ? "value2" : null, activity.GetTagItem("AndSomethingElse"));
|
|
|
|
Assert.True(activity.Duration.TotalMilliseconds > 0);
|
|
|
|
var tags = activity.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
if (enableSensitiveData)
|
|
{
|
|
Assert.Equal(ReplaceWhitespace("""
|
|
[
|
|
{
|
|
"role": "system",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "You are a close friend."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "user",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "Hey!"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"parts": [
|
|
{
|
|
"type": "tool_call",
|
|
"id": "12345",
|
|
"name": "GetPersonName"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "tool",
|
|
"parts": [
|
|
{
|
|
"type": "tool_call_response",
|
|
"id": "12345",
|
|
"response": "John"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "Hey John, what's up?"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "user",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "What's the biggest animal?"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
"""), ReplaceWhitespace(tags["gen_ai.input.messages"]));
|
|
|
|
Assert.Equal(ReplaceWhitespace("""
|
|
[
|
|
{
|
|
"role": "assistant",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "The blue whale, I think."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
"""), ReplaceWhitespace(tags["gen_ai.output.messages"]));
|
|
}
|
|
else
|
|
{
|
|
Assert.False(tags.ContainsKey("gen_ai.input.messages"));
|
|
Assert.False(tags.ContainsKey("gen_ai.output.messages"));
|
|
}
|
|
|
|
Assert.False(tags.ContainsKey("gen_ai.system_instructions"));
|
|
Assert.False(tags.ContainsKey("gen_ai.tool.definitions"));
|
|
}
|
|
|
|
public static IEnumerable<object[]> WithChatOptions_ExpectedInformationLogged_Async_MemberData() =>
|
|
from enableSensitiveData in new[] { false, true }
|
|
from streaming in new[] { false, true }
|
|
from name in new[] { null, "TestAgent" }
|
|
from description in new[] { null, "This is a test agent." }
|
|
select new object[] { enableSensitiveData, streaming, name, description, true };
|
|
|
|
[Theory]
|
|
[MemberData(nameof(WithChatOptions_ExpectedInformationLogged_Async_MemberData))]
|
|
[InlineData(true, false, "TestAgent", "This is a test agent.", false)]
|
|
[InlineData(true, true, "TestAgent", "This is a test agent.", false)]
|
|
public async Task WithChatOptions_ExpectedInformationLogged_Async(
|
|
bool enableSensitiveData, bool streaming, string name, string description, bool hasListener)
|
|
{
|
|
var sourceName = Guid.NewGuid().ToString();
|
|
var activities = new List<Activity>();
|
|
var builder = OpenTelemetry.Sdk.CreateTracerProviderBuilder();
|
|
if (hasListener)
|
|
{
|
|
builder.AddSource(sourceName);
|
|
}
|
|
using var tracerProvider = builder
|
|
.AddInMemoryExporter(activities)
|
|
.Build();
|
|
|
|
var innerAgent = new TestAIAgent
|
|
{
|
|
NameFunc = () => name,
|
|
DescriptionFunc = () => description,
|
|
|
|
RunAsyncFunc = async (messages, session, options, cancellationToken) =>
|
|
{
|
|
await Task.Yield();
|
|
return new AgentResponse(new ChatMessage(ChatRole.Assistant, "The blue whale, I think."))
|
|
{
|
|
ResponseId = "id123",
|
|
Usage = new UsageDetails
|
|
{
|
|
InputTokenCount = 10,
|
|
OutputTokenCount = 20,
|
|
TotalTokenCount = 42,
|
|
},
|
|
AdditionalProperties = new()
|
|
{
|
|
["system_fingerprint"] = "abcdefgh",
|
|
["AndSomethingElse"] = "value2",
|
|
},
|
|
};
|
|
},
|
|
|
|
RunStreamingAsyncFunc = CallbackAsync,
|
|
|
|
GetServiceFunc = (serviceType, serviceKey) =>
|
|
serviceType == typeof(AIAgentMetadata) ? new AIAgentMetadata("TestAgentProviderFromAIAgentMetadata") :
|
|
serviceType == typeof(ChatClientMetadata) ? new ChatClientMetadata("TestAgentProviderFromChatClientMetadata", new Uri("http://localhost:12345/something"), "amazingmodel") :
|
|
null,
|
|
};
|
|
|
|
async static IAsyncEnumerable<AgentResponseUpdate> CallbackAsync(
|
|
IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken)
|
|
{
|
|
await Task.Yield();
|
|
|
|
foreach (string text in new[] { "The ", "blue ", "whale,", " ", "", "I", " think." })
|
|
{
|
|
await Task.Yield();
|
|
yield return new AgentResponseUpdate(ChatRole.Assistant, text)
|
|
{
|
|
ResponseId = "id123",
|
|
};
|
|
}
|
|
|
|
yield return new AgentResponseUpdate
|
|
{
|
|
Contents = [new UsageContent(new()
|
|
{
|
|
InputTokenCount = 10,
|
|
OutputTokenCount = 20,
|
|
TotalTokenCount = 42,
|
|
})],
|
|
AdditionalProperties = new()
|
|
{
|
|
["system_fingerprint"] = "abcdefgh",
|
|
["AndSomethingElse"] = "value2",
|
|
},
|
|
};
|
|
}
|
|
|
|
using var agent = new OpenTelemetryAgent(innerAgent, sourceName) { EnableSensitiveData = enableSensitiveData };
|
|
|
|
List<ChatMessage> messages =
|
|
[
|
|
new(ChatRole.System, "You are a close friend."),
|
|
new(ChatRole.User, "Hey!"),
|
|
new(ChatRole.Assistant, [new FunctionCallContent("12345", "GetPersonName")]),
|
|
new(ChatRole.Tool, [new FunctionResultContent("12345", "John")]),
|
|
new(ChatRole.Assistant, "Hey John, what's up?"),
|
|
new(ChatRole.User, "What's the biggest animal?")
|
|
];
|
|
|
|
var options = new ChatClientAgentRunOptions()
|
|
{
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
FrequencyPenalty = 3.0f,
|
|
MaxOutputTokens = 123,
|
|
ModelId = "replacementmodel",
|
|
TopP = 4.0f,
|
|
TopK = 7,
|
|
PresencePenalty = 5.0f,
|
|
ResponseFormat = ChatResponseFormat.Json,
|
|
Temperature = 6.0f,
|
|
Seed = 42,
|
|
StopSequences = ["hello", "world"],
|
|
AdditionalProperties = new()
|
|
{
|
|
["service_tier"] = "value1",
|
|
["SomethingElse"] = "value2",
|
|
},
|
|
Instructions = "You are helpful.",
|
|
Tools =
|
|
[
|
|
AIFunctionFactory.Create((string personName) => personName, "GetPersonAge", "Gets the age of a person by name."),
|
|
new HostedWebSearchTool(),
|
|
AIFunctionFactory.Create((string location) => "", "GetCurrentWeather", "Gets the current weather for a location.").AsDeclarationOnly(),
|
|
],
|
|
}
|
|
};
|
|
|
|
if (streaming)
|
|
{
|
|
await foreach (var update in agent.RunStreamingAsync(messages, options: options))
|
|
{
|
|
await Task.Yield();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await agent.RunAsync(messages, options: options);
|
|
}
|
|
|
|
if (!hasListener)
|
|
{
|
|
Assert.Empty(activities);
|
|
return;
|
|
}
|
|
|
|
var activity = Assert.Single(activities);
|
|
var tags = activity.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
|
|
Assert.NotNull(activity.Id);
|
|
Assert.NotEmpty(activity.Id);
|
|
|
|
Assert.Equal("localhost", activity.GetTagItem("server.address"));
|
|
Assert.Equal(12345, (int)activity.GetTagItem("server.port")!);
|
|
|
|
if (string.IsNullOrWhiteSpace(innerAgent.Name))
|
|
{
|
|
Assert.Equal($"invoke_agent {innerAgent.Id}", activity.DisplayName);
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal($"invoke_agent {innerAgent.Name}({innerAgent.Id})", activity.DisplayName);
|
|
}
|
|
|
|
Assert.Equal("invoke_agent", activity.GetTagItem("gen_ai.operation.name"));
|
|
Assert.Equal("TestAgentProviderFromAIAgentMetadata", activity.GetTagItem("gen_ai.provider.name"));
|
|
Assert.Equal(innerAgent.Name, activity.GetTagItem("gen_ai.agent.name"));
|
|
Assert.Equal(innerAgent.Id, activity.GetTagItem("gen_ai.agent.id"));
|
|
if (description is null)
|
|
{
|
|
Assert.False(tags.ContainsKey("gen_ai.agent.description"));
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal(innerAgent.Description, activity.GetTagItem("gen_ai.agent.description"));
|
|
}
|
|
|
|
Assert.Equal("replacementmodel", activity.GetTagItem("gen_ai.request.model"));
|
|
Assert.Equal(3.0f, activity.GetTagItem("gen_ai.request.frequency_penalty"));
|
|
Assert.Equal(4.0f, activity.GetTagItem("gen_ai.request.top_p"));
|
|
Assert.Equal(5.0f, activity.GetTagItem("gen_ai.request.presence_penalty"));
|
|
Assert.Equal(6.0f, activity.GetTagItem("gen_ai.request.temperature"));
|
|
Assert.Equal(7, activity.GetTagItem("gen_ai.request.top_k"));
|
|
Assert.Equal(123, activity.GetTagItem("gen_ai.request.max_tokens"));
|
|
Assert.Equal("""["hello", "world"]""", activity.GetTagItem("gen_ai.request.stop_sequences"));
|
|
Assert.Equal(enableSensitiveData ? "value1" : null, activity.GetTagItem("service_tier"));
|
|
Assert.Equal(enableSensitiveData ? "value2" : null, activity.GetTagItem("SomethingElse"));
|
|
Assert.Equal(42L, activity.GetTagItem("gen_ai.request.seed"));
|
|
|
|
Assert.Equal("id123", activity.GetTagItem("gen_ai.response.id"));
|
|
Assert.Equal(10, activity.GetTagItem("gen_ai.usage.input_tokens"));
|
|
Assert.Equal(20, activity.GetTagItem("gen_ai.usage.output_tokens"));
|
|
Assert.Equal(enableSensitiveData ? "abcdefgh" : null, activity.GetTagItem("system_fingerprint"));
|
|
Assert.Equal(enableSensitiveData ? "value2" : null, activity.GetTagItem("AndSomethingElse"));
|
|
|
|
Assert.True(activity.Duration.TotalMilliseconds > 0);
|
|
|
|
if (enableSensitiveData)
|
|
{
|
|
Assert.Equal(ReplaceWhitespace("""
|
|
[
|
|
{
|
|
"role": "system",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "You are a close friend."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "user",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "Hey!"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"parts": [
|
|
{
|
|
"type": "tool_call",
|
|
"id": "12345",
|
|
"name": "GetPersonName"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "tool",
|
|
"parts": [
|
|
{
|
|
"type": "tool_call_response",
|
|
"id": "12345",
|
|
"response": "John"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "Hey John, what's up?"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "user",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "What's the biggest animal?"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
"""), ReplaceWhitespace(tags["gen_ai.input.messages"]));
|
|
|
|
Assert.Equal(ReplaceWhitespace("""
|
|
[
|
|
{
|
|
"role": "assistant",
|
|
"parts": [
|
|
{
|
|
"type": "text",
|
|
"content": "The blue whale, I think."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
"""), ReplaceWhitespace(tags["gen_ai.output.messages"]));
|
|
|
|
Assert.Equal(ReplaceWhitespace("""
|
|
[
|
|
{
|
|
"type": "text",
|
|
"content": "You are helpful."
|
|
}
|
|
]
|
|
"""), ReplaceWhitespace(tags["gen_ai.system_instructions"]));
|
|
|
|
Assert.Equal(ReplaceWhitespace("""
|
|
[
|
|
{
|
|
"type": "function",
|
|
"name": "GetPersonAge",
|
|
"description": "Gets the age of a person by name.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"personName": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"required": [
|
|
"personName"
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"type": "web_search"
|
|
},
|
|
{
|
|
"type": "function",
|
|
"name": "GetCurrentWeather",
|
|
"description": "Gets the current weather for a location.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"required": [
|
|
"location"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
"""), ReplaceWhitespace(tags["gen_ai.tool.definitions"]));
|
|
}
|
|
else
|
|
{
|
|
Assert.False(tags.ContainsKey("gen_ai.input.messages"));
|
|
Assert.False(tags.ContainsKey("gen_ai.output.messages"));
|
|
Assert.False(tags.ContainsKey("gen_ai.system_instructions"));
|
|
|
|
// gen_ai.tool.definitions is always emitted regardless of EnableSensitiveData (ME.AI 10.4.0+)
|
|
Assert.Equal(ReplaceWhitespace("""
|
|
[
|
|
{
|
|
"type": "function",
|
|
"name": "GetPersonAge",
|
|
"description": "Gets the age of a person by name.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"personName": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"required": [
|
|
"personName"
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"type": "web_search"
|
|
},
|
|
{
|
|
"type": "function",
|
|
"name": "GetCurrentWeather",
|
|
"description": "Gets the current weather for a location.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"required": [
|
|
"location"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
"""), ReplaceWhitespace(tags["gen_ai.tool.definitions"]));
|
|
}
|
|
}
|
|
|
|
private static string ReplaceWhitespace(string? input) => Regex.Replace(input ?? "", @"\s+", "").Trim();
|
|
}
|