Skip to content
Merged
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: 7 additions & 0 deletions CSharpToJsonSchema.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.Integrat
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.AotTests", "src\tests\CSharpToJsonSchema.AotTests\CSharpToJsonSchema.AotTests.csproj", "{6167F915-83EB-42F9-929B-AD4719A55811}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.MeaiTests", "src\tests\CSharpToJsonSchema.MeaiTests\CSharpToJsonSchema.MeaiTests.csproj", "{DC07C90E-A58C-44B3-82D2-E2EB8F777B92}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -76,6 +78,10 @@ Global
{6167F915-83EB-42F9-929B-AD4719A55811}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6167F915-83EB-42F9-929B-AD4719A55811}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6167F915-83EB-42F9-929B-AD4719A55811}.Release|Any CPU.Build.0 = Release|Any CPU
{DC07C90E-A58C-44B3-82D2-E2EB8F777B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC07C90E-A58C-44B3-82D2-E2EB8F777B92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC07C90E-A58C-44B3-82D2-E2EB8F777B92}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC07C90E-A58C-44B3-82D2-E2EB8F777B92}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -89,6 +95,7 @@ Global
{8AFCC30C-C59D-498D-BE68-9A328B3E5599} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{247C813A-9072-4DF3-B403-B35E3688DB4B} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{6167F915-83EB-42F9-929B-AD4719A55811} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{DC07C90E-A58C-44B3-82D2-E2EB8F777B92} = {AAA11B78-2764-4520-A97E-46AA7089A588}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CED9A020-DBA5-4BE6-8096-75E528648EC1}
Expand Down
10 changes: 10 additions & 0 deletions src/libs/CSharpToJsonSchema.Generators/Conversion/ToModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public static InterfaceData PrepareData(
strict;
var generateGoogleFunctionTool = attributeData.NamedArguments.FirstOrDefault(x => x.Key == "GoogleFunctionTool").Value.Value is bool googleFunctionTool &&
googleFunctionTool;
var meaiFunctionTool= attributeData.NamedArguments.FirstOrDefault(x => x.Key == "MeaiFunctionTool").Value.Value is bool meaift &&
meaift;
var methods = interfaceSymbol
.GetMembers()
.OfType<IMethodSymbol>()
Expand Down Expand Up @@ -46,6 +48,7 @@ public static InterfaceData PrepareData(
Namespace: interfaceSymbol.ContainingNamespace.ToDisplayString(),
Name: interfaceSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
GoogleFunctionTool:generateGoogleFunctionTool,
MeaiFunctionTool:meaiFunctionTool,
Methods: methods);
}

Expand All @@ -60,6 +63,7 @@ public static InterfaceData PrepareMethodData(
List<MethodData> methodList = new();
List<string> namespaces = new();
bool generateGoogleFunctionTools = false;
bool meaiFunctionTools = false;
foreach (var l in list)
{
var (interfaceSymbol, attributeData) = l;
Expand All @@ -70,6 +74,11 @@ public static InterfaceData PrepareMethodData(
if(ggft)
generateGoogleFunctionTools = true;

var meai = attributeData.NamedArguments.FirstOrDefault(x => x.Key == "MeaiFunctionTool").Value.Value is bool meaift &&
meaift;
if(meai)
meaiFunctionTools = true;

var x = interfaceSymbol;
var parameters = x.Parameters
//.Where(static x => x.Type.MetadataName != "CancellationToken")
Expand All @@ -94,6 +103,7 @@ public static InterfaceData PrepareMethodData(
Namespace: GetCommonRootNamespace(namespaces)??namespaceName,
Name: className,
GoogleFunctionTool: generateGoogleFunctionTools,
MeaiFunctionTool:meaiFunctionTools,
Methods: methodList.ToArray());
}
public static string? GetCommonRootNamespace(IEnumerable<string> namespaces)
Expand Down
22 changes: 21 additions & 1 deletion src/libs/CSharpToJsonSchema.Generators/JsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ private void ProcessMethods(IncrementalGeneratorInitializationContext context)
attributes
.SelectAndReportExceptions(AsGoogleFunctionToolsForMethods, context, Id)
.AddSource(context);
attributes
.SelectAndReportExceptions(AsMeaiFunctionToolsForMethods, context, Id)
.AddSource(context);

var generator = new JsonSourceGenerator();
generator.InitializeForFunctionTools(context);
Expand All @@ -73,6 +76,9 @@ private void ProcessInterfaces(IncrementalGeneratorInitializationContext context
attributes
.SelectAndReportExceptions(AsGoogleFunctionToolsForInterface, context, Id)
.AddSource(context);
attributes
.SelectAndReportExceptions(AsMeaiFunctionToolsForInterface, context, Id)
.AddSource(context);

var generator = new JsonSourceGenerator();
generator.Initialize2(context);
Expand Down Expand Up @@ -119,14 +125,28 @@ private static FileWithName AsFunctionCalls(InterfaceData @interface)
Name: $"{@interface.Name}.FunctionCalls.generated.cs",
Text: Sources.GenerateFunctionCalls(@interface));
}

private static FileWithName AsGoogleFunctionToolsForMethods(InterfaceData @interface)
{
return new FileWithName(
Name: $"{@interface.Name}.GoogleFunctionTools.generated.cs",
Text: Sources.GenerateGoogleFunctionToolForMethods(@interface));
}

private static FileWithName AsMeaiFunctionToolsForMethods(InterfaceData @interface)
{
return new FileWithName(
Name: $"{@interface.Name}.MeaiTools.generated.cs",
Text: Sources.GenerateMeaiFunctionToolForMethods(@interface));
}

private static FileWithName AsMeaiFunctionToolsForInterface(InterfaceData @interface)
{
return new FileWithName(
Name: $"{@interface.Name}.MeaiToolsExtensions.generated.cs",
Text: Sources.GenerateMeaiFunctionToolForInterface(@interface));
}

private static FileWithName AsGoogleFunctionToolsForInterface(InterfaceData @interface)
{
return new FileWithName(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ public readonly record struct InterfaceData(
string Namespace,
string Name,
bool GoogleFunctionTool,
bool MeaiFunctionTool,
IReadOnlyCollection<MethodData> Methods);
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public static string GenerateFunctionCalls(InterfaceData @interface)

namespace {@interface.Namespace}
{{








{@interface.Methods.Select(static method => $@"
public class {method.Name}Args
{{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ namespace {@interface.Namespace}
{{
public partial class {extensionsClassName}
{{
public static implicit operator global::GenerativeAI.Tools.GenericFunctionTool ({@interface.Namespace}.{extensionsClassName} tools)
public static implicit operator global::GenerativeAI.Core.GoogleFunctionTool ({@interface.Namespace}.{extensionsClassName} tools)
{{
return tools.AsGoogleFunctionTool();
}}

public global::GenerativeAI.Tools.GenericFunctionTool AsGoogleFunctionTool()
public global::GenerativeAI.Core.GoogleFunctionTool AsGoogleFunctionTool()
{{
return new global::GenerativeAI.Tools.GenericFunctionTool(this.AsTools(), this.AsCalls());
}}
Expand All @@ -42,9 +42,9 @@ public static string GenerateGoogleFunctionToolForInterface(InterfaceData @inter

namespace {@interface.Namespace}
{{
public partial class {extensionsClassName}
public static partial class {extensionsClassName}
{{
public global::GenerativeAI.Core.IFunctionTool AsGoogleFunctionTool(this {@interface.Name} service)
public static global::GenerativeAI.Core.IFunctionTool AsGoogleFunctionTool(this {@interface.Name} service)
{{
return new global::GenerativeAI.Tools.GenericFunctionTool(service.AsTools(), service.AsCalls());
}}
Expand Down
69 changes: 69 additions & 0 deletions src/libs/CSharpToJsonSchema.Generators/Sources.Method.MeaiTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using CSharpToJsonSchema.Generators.Models;

namespace CSharpToJsonSchema.Generators;

internal static partial class Sources
{
public static string GenerateMeaiFunctionToolForMethods(InterfaceData @interface)
{
if(@interface.Methods.Count == 0 || [email protected])
return string.Empty;
var extensionsClassName = @interface.Name;

return @$"
#nullable enable

namespace {@interface.Namespace}
{{
public partial class {extensionsClassName}
{{
public static implicit operator global::System.Collections.Generic.List<global::Microsoft.Extensions.AI.AITool>? ({@interface.Namespace}.{extensionsClassName} tools)
{{
return tools.AsMeaiTools();
}}

public global::System.Collections.Generic.List<global::Microsoft.Extensions.AI.AITool> AsMeaiTools()
{{
var lst = new global::System.Collections.Generic.List<global::Microsoft.Extensions.AI.AITool>();
var tools = this.AsTools();
var calls = this.AsCalls();
foreach (var tool in tools)
{{
var call = calls[tool.Name];
lst.Add(new global::CSharpToJsonSchema.MeaiFunction(tool, call));
}}
return lst;
}}
}}
}}";
}

public static string GenerateMeaiFunctionToolForInterface(InterfaceData @interface)
{
if([email protected])
return string.Empty;
var extensionsClassName = @interface.Name.Substring(startIndex: 1) + "Extensions";

return @$"
#nullable enable

namespace {@interface.Namespace}
{{
public partial class {extensionsClassName}
{{
public static global::System.Collections.Generic.List<global::Microsoft.Extensions.AI.AITool> AsMeaiTools(this {@interface.Name} service)
{{
var lst = new global::System.Collections.Generic.List<global::Microsoft.Extensions.AI.AITool>();
var tools = service.AsTools();
var calls = service.AsCalls();
foreach (var tool in tools)
{{
var call = calls[tool.Name];
lst.Add(new global::CSharpToJsonSchema.MeaiFunction(tool, call));
}}
return lst;
}}
}}
}}";
}
}
1 change: 1 addition & 0 deletions src/libs/CSharpToJsonSchema/CSharpToJsonSchema.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="9.3.0-preview.1.25114.11" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>

Expand Down
5 changes: 5 additions & 0 deletions src/libs/CSharpToJsonSchema/FunctionToolAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ public sealed class FunctionToolAttribute : Attribute
/// Generate Google Function Tools extensions for Google_GenerativeAI SDK
/// </summary>
public bool GoogleFunctionTool { get; set; }

/// <summary>
/// Generate Microsoft.Extension.AI compatible function tools
/// </summary>
public bool MeaiFunctionTool { get; set; }
}
5 changes: 5 additions & 0 deletions src/libs/CSharpToJsonSchema/GenerateJsonSchemaAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ public sealed class GenerateJsonSchemaAttribute : Attribute
/// Generate Google Function Tools extensions for Google_GenerativeAI SDK
/// </summary>
public bool GoogleFunctionTool { get; set; }

/// <summary>
/// Generate Microsoft.Extension.AI compatible function tools
/// </summary>
public bool MeaiFunctionTool { get; set; }
}
100 changes: 100 additions & 0 deletions src/libs/CSharpToJsonSchema/MeaiFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System.Collections.ObjectModel;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.AI;

namespace CSharpToJsonSchema;

/// <summary>
/// Represents a function that wraps a tool and provides functionality for executing the tool with specified arguments.
/// </summary>
public partial class MeaiFunction : AIFunction
{
private readonly Tool _tool;
private readonly Func<string, CancellationToken, Task<string>> _call;

private JsonElement _jsonSchema;

/// <summary>
/// Gets the JSON schema representing the parameters of the tool.
/// </summary>
public override JsonElement JsonSchema => _jsonSchema;

/// <summary>
/// Gets the name of the tool.
/// </summary>
public override string Name => _tool.Name;

/// <summary>
/// Gets the description of the tool.
/// </summary>
public override string Description => _tool.Description;

/// <summary>
/// Gets additional properties associated with the tool.
/// </summary>
public override IReadOnlyDictionary<string, object?> AdditionalProperties => new ReadOnlyDictionary<string, object?>(_tool.AdditionalProperties);

/// <summary>
/// Initializes a new instance of the <see cref="MeaiFunction"/> class.
/// </summary>
/// <param name="tool">The tool associated with this function.</param>
/// <param name="call">The function to execute the tool with input arguments.</param>
public MeaiFunction(Tool tool, Func<string, CancellationToken, Task<string>> call)
{
this._tool = tool;
this._call = call;

_jsonSchema = JsonSerializer.Deserialize<JsonElement>(
JsonSerializer.Serialize(tool.Parameters, OpenApiSchemaJsonContext.Default.OpenApiSchema),
OpenApiSchemaJsonContext.Default.JsonElement);

if (tool.Strict == true)
{
tool.AdditionalProperties.Add("Strict", true);
}
}

/// <summary>
/// Invokes the tool with the given arguments asynchronously.
/// </summary>
/// <param name="arguments">The arguments to pass to the tool.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation, if needed.</param>
/// <returns>The result of the tool's execution as a deserialized object.</returns>
protected override async Task<object?> InvokeCoreAsync(IEnumerable<KeyValuePair<string, object?>> arguments,
CancellationToken cancellationToken)
{
var json = GetArgsString(arguments);

var call = await _call(json, cancellationToken);

return JsonSerializer.Deserialize(call, OpenApiSchemaJsonContext.Default.JsonElement);
}

/// <summary>
/// Converts a collection of arguments into a JSON string.
/// </summary>
/// <param name="arguments">The arguments to be converted into a JSON string.</param>
/// <returns>A JSON string representation of the arguments.</returns>
private string GetArgsString(IEnumerable<KeyValuePair<string, object?>> arguments)
{
var jsonObject = new JsonObject();

foreach (var args in arguments)
{
if (args.Value is JsonElement element)
{
if (element.ValueKind == JsonValueKind.Array)
jsonObject[args.Key] = JsonArray.Create(element);
else if (element.ValueKind == JsonValueKind.Object)
jsonObject[args.Key] = JsonObject.Create(element);
}
Comment on lines +85 to +91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle non-array/object JsonElement cases.
If the JsonElement represents a string, number, or boolean, it will be ignored. Consider adding a fallback to capture these value kinds:

if (element.ValueKind == JsonValueKind.Array)
    jsonObject[args.Key] = JsonArray.Create(element);
else if (element.ValueKind == JsonValueKind.Object)
    jsonObject[args.Key] = JsonObject.Create(element);
+else
+    jsonObject[args.Key] = element.GetRawText();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (args.Value is JsonElement element)
{
if (element.ValueKind == JsonValueKind.Array)
jsonObject[args.Key] = JsonArray.Create(element);
else if (element.ValueKind == JsonValueKind.Object)
jsonObject[args.Key] = JsonObject.Create(element);
}
if (args.Value is JsonElement element)
{
if (element.ValueKind == JsonValueKind.Array)
jsonObject[args.Key] = JsonArray.Create(element);
else if (element.ValueKind == JsonValueKind.Object)
jsonObject[args.Key] = JsonObject.Create(element);
else
jsonObject[args.Key] = element.GetRawText();
}

else if (args.Value is JsonNode node)
{
jsonObject[args.Key] = node;
}
}

return jsonObject.ToJsonString();
}
}
4 changes: 4 additions & 0 deletions src/libs/CSharpToJsonSchema/OpenApiSchemaJsonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ namespace CSharpToJsonSchema;
[JsonSerializable(typeof(OpenApiSchema))]
[JsonSerializable(typeof(IDictionary<string, OpenApiSchema>))]
[JsonSerializable(typeof(IDictionary<string, string>))]
[JsonSerializable(typeof(Tool))]
[JsonSerializable(typeof(List<Tool>))]
Comment on lines +8 to +9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing import for Tool class

The code adds JSON serialization for Tool and List<Tool> types, but there's no import statement for the Tool class. This could cause compilation errors.

1
using System.Text.Json.Serialization;
2
+using System.Collections.Generic;
+// Add the appropriate namespace for the Tool class
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[JsonSerializable(typeof(Tool))]
[JsonSerializable(typeof(List<Tool>))]
using System.Text.Json.Serialization;
using System.Collections.Generic;
// Add the appropriate namespace for the Tool class
[JsonSerializable(typeof(Tool))]
[JsonSerializable(typeof(List<Tool>))]

[JsonSerializable(typeof(JsonElement))]
[JsonSourceGenerationOptions(NumberHandling = JsonNumberHandling.Strict, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,WriteIndented = false)]
public partial class OpenApiSchemaJsonContext:JsonSerializerContext
{

Expand Down
Loading
Loading