Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,27 @@ internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentRecord a
internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentVersion agentVersion, ChatOptions? chatOptions)
: this(
aiProjectClient,
new AgentReference(Throw.IfNull(agentVersion).Name, agentVersion.Version),
CreateAgentReference(Throw.IfNull(agentVersion)),
(agentVersion.Definition as PromptAgentDefinition)?.Model,
chatOptions)
{
this._agentVersion = agentVersion;
}

/// <summary>
/// Creates an <see cref="AgentReference"/> from an <see cref="AgentVersion"/>.
/// Uses the agent version's version if available, otherwise defaults to "latest".
/// </summary>
/// <param name="agentVersion">The agent version to create a reference from.</param>
/// <returns>An <see cref="AgentReference"/> for the specified agent version.</returns>
private static AgentReference CreateAgentReference(AgentVersion agentVersion)
{
// If the version is null or empty, use "latest" as the default.
// This handles cases where hosted agents (like MCP agents) may not have a version assigned.
var version = string.IsNullOrEmpty(agentVersion.Version) ? "latest" : agentVersion.Version;
Comment thread
rogerbarreto marked this conversation as resolved.
Outdated
return new AgentReference(agentVersion.Name, version);
Comment thread
rogerbarreto marked this conversation as resolved.
}

/// <inheritdoc/>
public override object? GetService(Type serviceType, object? serviceKey = null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,16 @@ private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion
}
}

// Use the agent version's ID if available, otherwise generate one from name and version.
// This handles cases where hosted agents (like MCP agents) may not have an ID assigned.
var version = string.IsNullOrEmpty(agentVersion.Version) ? "latest" : agentVersion.Version;
var agentId = string.IsNullOrEmpty(agentVersion.Id)
Comment thread
rogerbarreto marked this conversation as resolved.
Outdated
? $"{agentVersion.Name}:{version}"
: agentVersion.Id;

Comment thread
rogerbarreto marked this conversation as resolved.
Outdated
var agentOptions = new ChatClientAgentOptions()
{
Id = agentVersion.Id,
Id = agentId,
Name = agentVersion.Name,
Description = agentVersion.Description,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2384,6 +2384,72 @@ public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_PreservesSetting

#endregion

#region Empty Version and ID Handling Tests

/// <summary>
/// Verify that GetAIAgentAsync handles an agent with empty version by using "latest" as fallback.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithEmptyVersion_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions { Instructions = "Test" }
};

// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);

// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
// Verify the agent ID is generated from server-returned name ("agent_abc123") and "latest"
Assert.Equal("agent_abc123:latest", agent.Id);
}

/// <summary>
/// Verify that AsAIAgent with AgentRecord handles empty version by using "latest" as fallback.
/// </summary>
[Fact]
public void AsAIAgent_WithAgentRecordEmptyVersion_CreatesAgentWithGeneratedId()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
AgentRecord agentRecord = this.CreateTestAgentRecordWithEmptyVersion();

// Act
var agent = client.AsAIAgent(agentRecord);

// Assert
Assert.NotNull(agent);
// Verify the agent ID is generated from agent record name ("agent_abc123") and "latest"
Assert.Equal("agent_abc123:latest", agent.Id);
}

/// <summary>
/// Verify that AsAIAgent with AgentVersion handles empty version by using "latest" as fallback.
/// </summary>
[Fact]
public void AsAIAgent_WithAgentVersionEmptyVersion_CreatesAgentWithGeneratedId()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
AgentVersion agentVersion = this.CreateTestAgentVersionWithEmptyVersion();

// Act
var agent = client.AsAIAgent(agentVersion);

// Assert
Assert.NotNull(agent);
// Verify the agent ID is generated from agent version name ("agent_abc123") and "latest"
Assert.Equal("agent_abc123:latest", agent.Id);
}

#endregion

#region ApplyToolsToAgentDefinition Tests

/// <summary>
Expand Down Expand Up @@ -2678,6 +2744,30 @@ private AgentRecord CreateTestAgentRecord(AgentDefinition? agentDefinition = nul
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJson(agentDefinition: agentDefinition)))!;
}

/// <summary>
/// Creates a test AIProjectClient with empty version fields for testing hosted MCP agents.
/// </summary>
private FakeAgentClient CreateTestAgentClientWithEmptyVersion(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
{
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse, useEmptyVersion: true);
}

/// <summary>
/// Creates a test AgentRecord with empty version for testing hosted MCP agents.
/// </summary>
private AgentRecord CreateTestAgentRecordWithEmptyVersion(AgentDefinition? agentDefinition = null)
{
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithEmptyVersion(agentDefinition: agentDefinition)))!;
}

/// <summary>
/// Creates a test AgentVersion with empty version for testing hosted MCP agents.
/// </summary>
private AgentVersion CreateTestAgentVersionWithEmptyVersion()
{
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion()))!;
}

private const string OpenAPISpec = """
{
"openapi": "3.0.3",
Expand Down Expand Up @@ -2721,9 +2811,9 @@ private AgentVersion CreateTestAgentVersion()
/// </summary>
private sealed class FakeAgentClient : AIProjectClient
{
public FakeAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
public FakeAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, bool useEmptyVersion = false)
{
this.Agents = new FakeAIProjectAgentsOperations(agentName, instructions, description, agentDefinitionResponse);
this.Agents = new FakeAIProjectAgentsOperations(agentName, instructions, description, agentDefinitionResponse, useEmptyVersion);
}

public override ClientConnection GetConnection(string connectionId)
Expand All @@ -2739,60 +2829,76 @@ private sealed class FakeAIProjectAgentsOperations : AIProjectAgentsOperations
private readonly string? _instructions;
private readonly string? _description;
private readonly AgentDefinition? _agentDefinition;
private readonly bool _useEmptyVersion;

public FakeAIProjectAgentsOperations(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
public FakeAIProjectAgentsOperations(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, bool useEmptyVersion = false)
{
this._agentName = agentName;
this._instructions = instructions;
this._description = description;
this._agentDefinition = agentDefinitionResponse;
this._useEmptyVersion = useEmptyVersion;
}

private string GetAgentResponseJson()
{
return this._useEmptyVersion
? TestDataUtil.GetAgentResponseJsonWithEmptyVersion(this._agentName, this._agentDefinition, this._instructions, this._description)
: TestDataUtil.GetAgentResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
}

private string GetAgentVersionResponseJson()
{
return this._useEmptyVersion
? TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion(this._agentName, this._agentDefinition, this._instructions, this._description)
: TestDataUtil.GetAgentVersionResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
}

public override ClientResult GetAgent(string agentName, RequestOptions options)
{
var responseJson = TestDataUtil.GetAgentResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentResponseJson();
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
}

public override ClientResult<AgentRecord> GetAgent(string agentName, CancellationToken cancellationToken = default)
{
var responseJson = TestDataUtil.GetAgentResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentResponseJson();
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
}

public override Task<ClientResult> GetAgentAsync(string agentName, RequestOptions options)
{
var responseJson = TestDataUtil.GetAgentResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentResponseJson();
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
}

public override Task<ClientResult<AgentRecord>> GetAgentAsync(string agentName, CancellationToken cancellationToken = default)
{
var responseJson = TestDataUtil.GetAgentResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentResponseJson();
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
}

public override ClientResult CreateAgentVersion(string agentName, BinaryContent content, RequestOptions? options = null)
{
var responseJson = TestDataUtil.GetAgentVersionResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentVersionResponseJson();
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
}

public override ClientResult<AgentVersion> CreateAgentVersion(string agentName, AgentVersionCreationOptions? options = null, CancellationToken cancellationToken = default)
{
var responseJson = TestDataUtil.GetAgentVersionResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentVersionResponseJson();
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
}

public override Task<ClientResult> CreateAgentVersionAsync(string agentName, BinaryContent content, RequestOptions? options = null)
{
var responseJson = TestDataUtil.GetAgentVersionResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentVersionResponseJson();
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
}

public override Task<ClientResult<AgentVersion>> CreateAgentVersionAsync(string agentName, AgentVersionCreationOptions? options = null, CancellationToken cancellationToken = default)
{
var responseJson = TestDataUtil.GetAgentVersionResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description);
var responseJson = this.GetAgentVersionResponseJson();
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
}
}
Expand Down
32 changes: 32 additions & 0 deletions dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/TestDataUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,38 @@ public static string GetAgentVersionResponseJson(string? agentName = null, Agent
return json;
}

/// <summary>
/// Gets the agent version response JSON with empty version and ID fields for testing hosted agents like MCP agents.
/// </summary>
public static string GetAgentVersionResponseJsonWithEmptyVersion(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
{
var json = s_agentVersionResponseJson;
json = ApplyAgentName(json, agentName);
json = ApplyAgentDefinition(json, agentDefinition);
json = ApplyInstructions(json, instructions);
json = ApplyDescription(json, description);
// Remove the version and id fields to simulate hosted agents without version
json = json.Replace("\"version\": \"1\",", "\"version\": \"\",");
json = json.Replace("\"id\": \"agent_abc123:1\",", "\"id\": \"\",");
return json;
Comment thread
rogerbarreto marked this conversation as resolved.
}

/// <summary>
/// Gets the agent response JSON with empty version and ID fields in the latest version for testing hosted agents like MCP agents.
/// </summary>
public static string GetAgentResponseJsonWithEmptyVersion(string? agentName = null, AgentDefinition? agentDefinition = null, string? instructions = null, string? description = null)
{
var json = s_agentResponseJson;
json = ApplyAgentName(json, agentName);
json = ApplyAgentDefinition(json, agentDefinition);
json = ApplyInstructions(json, instructions);
json = ApplyDescription(json, description);
// Remove the version and id fields to simulate hosted agents without version
json = json.Replace("\"version\": \"1\",", "\"version\": \"\",");
json = json.Replace("\"id\": \"agent_abc123:1\",", "\"id\": \"\",");
return json;
Comment thread
rogerbarreto marked this conversation as resolved.
}

/// <summary>
/// Gets the OpenAI default response JSON with optional placeholder replacements applied.
/// </summary>
Expand Down
Loading