Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
7 changes: 0 additions & 7 deletions agentschema-emitter/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions agentschema-emitter/src/templates/csharp/file.cs.njk
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ public{% if node.isAbstract %} abstract{% endif %} class {{ node.typeName.name }
{
var result = new List<{{ collection.prop.typeName.name }}>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -107,6 +113,11 @@ public{% if node.isAbstract %} abstract{% endif %} class {{ node.typeName.name }
{
result.Add({{ collection.prop.typeName.name }}.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add({{ collection.prop.typeName.name }}.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand Down
39 changes: 39 additions & 0 deletions agentschema-emitter/src/templates/csharp/utils.cs.njk
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,43 @@ internal static class Utils

return dict;
}

/// <summary>
/// Normalizes a YamlDotNet Dictionary&lt;object, object&gt; to Dictionary&lt;string, object?&gt;,
/// recursively normalizing all nested values.
/// </summary>
/// <param name="dict">The YamlDotNet dictionary to normalize.</param>
/// <returns>A normalized Dictionary&lt;string, object?&gt;.</returns>
public static Dictionary<string, object?> NormalizeDictionary(this Dictionary<object, object> dict)
{
var result = new Dictionary<string, object?>();
foreach (var kvp in dict)
{
result[kvp.Key?.ToString() ?? ""] = NormalizeValue(kvp.Value);
}
return result;
}

/// <summary>
/// Recursively normalizes a value from YamlDotNet deserialization,
/// converting Dictionary&lt;object, object&gt; to Dictionary&lt;string, object?&gt;
/// and normalizing items within lists.
/// </summary>
/// <param name="value">The value to normalize.</param>
/// <returns>The normalized value.</returns>
public static object? NormalizeValue(this object? value)
{
if (value is Dictionary<object, object> dictObjObj)
return dictObjObj.NormalizeDictionary();

if (value is IEnumerable<object> enumerable)
{
var list = new List<object?>();
foreach (var item in enumerable)
list.Add(NormalizeValue(item));
return list;
}

return value;
}
}
99 changes: 99 additions & 0 deletions runtime/csharp/AgentSchema.Tests/PromptAgentToolsYamlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft. All rights reserved.
using Xunit;

#pragma warning disable IDE0130
namespace AgentSchema;
#pragma warning restore IDE0130

/// <summary>
/// Regression tests verifying that tools are correctly deserialized from YAML.
/// YamlDotNet 16.x deserializes nested YAML mappings as Dictionary&lt;object, object&gt;
/// rather than Dictionary&lt;string, object?&gt;, which previously caused the Tools
/// collection to always be empty after loading from YAML.
/// </summary>
public class PromptAgentToolsYamlTests
{
[Fact]
public void LoadYaml_ListFormat_ToolsAreDeserializedCorrectly()
{
string yaml = """
kind: prompt
name: my-agent
model: gpt-4o
template: Hello
tools:
- name: search
kind: function
description: Search the web
""";

var agent = AgentDefinition.FromYaml(yaml) as PromptAgent;

Assert.NotNull(agent);
Assert.NotNull(agent.Tools);
Assert.NotEmpty(agent.Tools);
Assert.Single(agent.Tools);
Assert.Equal("search", agent.Tools[0].Name);
Assert.Equal("function", agent.Tools[0].Kind);
Assert.Equal("Search the web", agent.Tools[0].Description);
}

[Fact]
public void LoadYaml_DictionaryFormat_ToolsAreDeserializedCorrectly()
{
string yaml = """
kind: prompt
name: my-agent
model: gpt-4o
template: Hello
tools:
search:
kind: function
description: Search the web
calculate:
kind: function
description: Run a calculation
""";

var agent = AgentDefinition.FromYaml(yaml) as PromptAgent;

Assert.NotNull(agent);
Assert.NotNull(agent.Tools);
Assert.NotEmpty(agent.Tools);
Assert.Equal(2, agent.Tools.Count);

var searchTool = agent.Tools.FirstOrDefault(t => t.Name == "search");
Assert.NotNull(searchTool);
Assert.Equal("function", searchTool.Kind);
Assert.Equal("Search the web", searchTool.Description);

var calcTool = agent.Tools.FirstOrDefault(t => t.Name == "calculate");
Assert.NotNull(calcTool);
Assert.Equal("function", calcTool.Kind);
Assert.Equal("Run a calculation", calcTool.Description);
}

[Fact]
public void LoadYaml_ListFormat_MultipleTool_CountIsCorrect()
{
string yaml = """
kind: prompt
name: my-agent
model: gpt-4o
template: Hello
tools:
- name: search
kind: function
description: Search the web
- name: calculate
kind: function
description: Run a calculation
""";

var agent = AgentDefinition.FromYaml(yaml) as PromptAgent;

Assert.NotNull(agent);
Assert.NotNull(agent.Tools);
Assert.Equal(2, agent.Tools.Count);
}
}
11 changes: 11 additions & 0 deletions runtime/csharp/AgentSchema/AgentManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ public static IList<Resource> LoadResources(object data, LoadContext? context)
{
var result = new List<Resource>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -173,6 +179,11 @@ public static IList<Resource> LoadResources(object data, LoadContext? context)
{
result.Add(Resource.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add(Resource.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions runtime/csharp/AgentSchema/ContainerAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ public static IList<ProtocolVersionRecord> LoadProtocols(object data, LoadContex
{
var result = new List<ProtocolVersionRecord>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -154,6 +160,11 @@ public static IList<ProtocolVersionRecord> LoadProtocols(object data, LoadContex
{
result.Add(ProtocolVersionRecord.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add(ProtocolVersionRecord.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand All @@ -168,6 +179,12 @@ public static IList<EnvironmentVariable> LoadEnvironmentVariables(object data, L
{
var result = new List<EnvironmentVariable>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -199,6 +216,11 @@ public static IList<EnvironmentVariable> LoadEnvironmentVariables(object data, L
{
result.Add(EnvironmentVariable.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add(EnvironmentVariable.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions runtime/csharp/AgentSchema/ObjectProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public static IList<Property> LoadProperties(object data, LoadContext? context)
{
var result = new List<Property>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -113,6 +119,11 @@ public static IList<Property> LoadProperties(object data, LoadContext? context)
{
result.Add(Property.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add(Property.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions runtime/csharp/AgentSchema/PromptAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ public static IList<Tool> LoadTools(object data, LoadContext? context)
{
var result = new List<Tool>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -154,6 +160,11 @@ public static IList<Tool> LoadTools(object data, LoadContext? context)
{
result.Add(Tool.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add(Tool.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions runtime/csharp/AgentSchema/PropertySchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ public static IList<Property> LoadProperties(object data, LoadContext? context)
{
var result = new List<Property>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -123,6 +129,11 @@ public static IList<Property> LoadProperties(object data, LoadContext? context)
{
result.Add(Property.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add(Property.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions runtime/csharp/AgentSchema/Tool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public static IList<Binding> LoadBindings(object data, LoadContext? context)
{
var result = new List<Binding>();

// Normalize YamlDotNet's Dictionary<object, object> to Dictionary<string, object?>
if (data is Dictionary<object, object> rawYamlDict)
{
data = rawYamlDict.NormalizeDictionary();
}

if (data is Dictionary<string, object?> dict)
{
// Convert named dictionary to list
Expand Down Expand Up @@ -132,6 +138,11 @@ public static IList<Binding> LoadBindings(object data, LoadContext? context)
{
result.Add(Binding.Load(itemDict, context));
}
else if (item is Dictionary<object, object> yamlItemDict)
{
// Handle YamlDotNet's Dictionary<object, object> list items
result.Add(Binding.Load(yamlItemDict.NormalizeDictionary(), context));
}
}
}

Expand Down
Loading