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: 2 additions & 5 deletions CSharpToJsonSchema.sln
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.AotTests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.MeaiTests", "src\tests\CSharpToJsonSchema.MeaiTests\CSharpToJsonSchema.MeaiTests.csproj", "{DC07C90E-A58C-44B3-82D2-E2EB8F777B92}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AotConsole", "src\tests\AotConsole\AotConsole.csproj", "{1942E3E5-F151-4C90-BECE-140AAD8C66DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -78,10 +80,6 @@ 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 @@ -95,7 +93,6 @@ 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
36 changes: 18 additions & 18 deletions src/libs/CSharpToJsonSchema.Generators/Sources.Method.Calls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CSharpToJsonSchema.Generators.JsonGen.Helpers;
using CSharpToJsonSchema.Generators.Models;
using H.Generators.Extensions;
using SymbolDisplayFormat = Microsoft.CodeAnalysis.SymbolDisplayFormat;

namespace CSharpToJsonSchema.Generators;

Expand All @@ -13,7 +14,7 @@ public static string GenerateFunctionCalls(InterfaceData @interface)
return string.Empty;
var extensionsClassName = @interface.Name;
var res = @$"#nullable enable
#pragma warning disable CS8602
#pragma warning disable CS8602,CS8600

namespace {@interface.Namespace}
{{
Expand Down Expand Up @@ -77,14 +78,14 @@ public partial class {extensionsClassName}
#if NET6_0_OR_GREATER
if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault)
{{
#pragma disable warning IL2026, IL3050
#pragma warning disable IL2026, IL3050
return global::System.Text.Json.JsonSerializer.Deserialize<{method.Name}Args>(json, new global::System.Text.Json.JsonSerializerOptions
{{
PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase,
Converters = {{{{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}}}
}}) ??
throw new global::System.InvalidOperationException(""Could not deserialize JSON."");
#pragma restore warning IL2026, IL3050
#pragma warning restore IL2026, IL3050
}}
else
{{
Expand All @@ -107,19 +108,19 @@ public partial class {extensionsClassName}
private string Call{method.Name}(string json)
{{
var args = As{method.Name}Args(json);
var func = (dynamic) Delegates[""{method.Name}""];
var jsonResult = func.Invoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken").Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}"))});
var func = Delegates[""{method.Name}""];
var jsonResult = ({method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}) func.DynamicInvoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken").Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}"))});

#if NET6_0_OR_GREATER
if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault)
{{
#pragma disable warning IL2026, IL3050
#pragma warning disable IL2026, IL3050
return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions
{{
PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase,
Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }},
}});
#pragma restore warning IL2026, IL3050
#pragma warning restore IL2026, IL3050
}}
else
{{
Expand All @@ -137,9 +138,9 @@ public partial class {extensionsClassName}
{@interface.Methods.Where(static x => x is { IsAsync: false, IsVoid: true }).Select(method => $@"
private void Call{method.Name}(string json)
{{
var func = (dynamic) Delegates[""{method.Name}""];
var func = Delegates[""{method.Name}""];
var args = As{method.Name}Args(json);
func.Invoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken").Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}"))});
func.DynamicInvoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken").Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}"))});
}}
").Inject()}

Expand All @@ -149,20 +150,20 @@ public partial class {extensionsClassName}
global::System.Threading.CancellationToken cancellationToken = default)
{{
var args = As{method.Name}Args(json);
var func = (dynamic) Delegates[""{method.Name}""];
var jsonResult = await func.Invoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken")
.Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}").Append("cancellationToken"))});
var func = Delegates[""{method.Name}""];
var jsonResult = await (({method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})func.DynamicInvoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken")
.Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}").Append("cancellationToken"))}));

#if NET6_0_OR_GREATER
if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault)
{{
#pragma disable warning IL2026, IL3050
#pragma warning disable IL2026, IL3050
return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions
{{
PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase,
Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }},
}});
#pragma restore warning IL2026, IL3050
#pragma warning restore IL2026, IL3050
}}
else
{{
Expand All @@ -186,9 +187,8 @@ public partial class {extensionsClassName}
global::System.Threading.CancellationToken cancellationToken = default)
{{
var args = As{method.Name}Args(json);
//var func = (global::System.Func<{GetInputsTypes(method)}>) Delegates[""{method.Name}""];
var func = (dynamic) Delegates[""{method.Name}""];
await func.Invoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken").Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}"))}, cancellationToken);
var func = Delegates[""{method.Name}""];
await (({method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})func.DynamicInvoke({string.Join(", ", method.Parameters.Where(s=>s.Type.Name!="CancellationToken").Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}"))}, cancellationToken));

return string.Empty;
}}
Expand All @@ -210,7 +210,7 @@ public partial class {extensionsClassName}JsonSerializerContext: global::System.
{{
}}
}}
#pragma warning restore CS8602
#pragma warning restore CS8602,CS8600
";
return res;
}
Expand Down
23 changes: 17 additions & 6 deletions src/libs/CSharpToJsonSchema.Generators/Sources.Method.Tools.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CSharpToJsonSchema.Generators.Models;
using CSharpToJsonSchema.Generators.Conversion;
using CSharpToJsonSchema.Generators.Models;
using H.Generators.Extensions;
using Microsoft.CodeAnalysis;

Expand Down Expand Up @@ -28,7 +29,7 @@ public static string GenerateFunctionToolClientImplementation(InterfaceData @int
namespace {@interface.Namespace}
{{
public partial class {extensionsClassName}
{{
{{
static {extensionsClassName}()
{{
AddAllTools();
Expand Down Expand Up @@ -66,14 +67,16 @@ public void AddTool(global::System.Delegate tool)
AvailableTools.Add(newTool);
if(Delegates.ContainsKey(name))
throw new global::System.Exception({"$\"Function {name} is already registered\""});
Delegates.Add(name, tool);
Delegates.Add(name, tool);

}}

public Tools(global::System.Delegate[] tools)
{{
foreach (var tool in tools)
for(int i = 0; i < tools.Length; i++)
{{
AddTool(tool);
var x = tools[i];
AddTool(x);
}}
}}

Expand All @@ -86,7 +89,7 @@ public Tools(global::System.Delegate[] tools)
{{
return (global::System.Collections.Generic.List<global::CSharpToJsonSchema.Tool>) (this.AvailableTools??= new global::System.Collections.Generic.List<global::CSharpToJsonSchema.Tool>());
}}
}}
}}
}}";
}

Expand All @@ -97,4 +100,12 @@ private static string GetInputsTypes(MethodData first, bool addReturnType = true
f.Add(first.ReturnType.ToDisplayString());
return string.Join(", ", f);
}

private static string GetDelegateInputsTypes(MethodData first, bool addReturnType = true)
{
var f = first.Parameters.Select(s => $"{s.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {s.Name}"+(s.HasExplicitDefaultValue? $" = " + (s.Type.Name == "CancellationToken"?"default":s.ExplicitDefaultValue?.ToString()) :"")).ToList();
if(addReturnType)
f.Add(first.ReturnType.ToDisplayString());
return string.Join(", ", f);
}
}
1 change: 0 additions & 1 deletion src/libs/CSharpToJsonSchema/MeaiFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ private string GetArgsString(IEnumerable<KeyValuePair<string, object?>> argument
}
else if (args.Value is JsonNode node)
{
jsonObject[args.Key] = node;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/libs/CSharpToJsonSchema/TypeToSchemaHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static OpenApiSchema AsJsonSchema(
JsonSerializerOptions? options = null)
{
type = type ?? throw new ArgumentNullException(nameof(type));

#pragma warning disable IL2026
var node = new JsonSerializerOptions
{
TypeInfoResolver = jsonTypeInfoResolver ?? new DefaultJsonTypeInfoResolver(),
Expand All @@ -61,7 +61,7 @@ public static OpenApiSchema AsJsonSchema(
TransformSchemaNode = (context, node) => node,
TreatNullObliviousAsNonNullable = true,
});
#pragma warning restore IL2026
var schema = Create(type, strict);
if (schema.Type == "object")
{
Expand Down
23 changes: 23 additions & 0 deletions src/tests/AotConsole/AotConsole.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\libs\CSharpToJsonSchema.Generators\CSharpToJsonSchema.Generators.csproj" OutputItemType="analyzer" />
<ProjectReference Include="..\..\libs\CSharpToJsonSchema\CSharpToJsonSchema.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI" Version="9.3.0-preview.1.25114.11" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.3.0-preview.1.25114.11" />
<PackageReference Include="OpenAI" Version="2.2.0-beta.1" />
</ItemGroup>

</Project>
27 changes: 27 additions & 0 deletions src/tests/AotConsole/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// See https://aka.ms/new-console-template for more information

using System.ClientModel;
using CSharpToJsonSchema.MeaiTests.Services;
using Microsoft.Extensions.AI;
using OpenAI;

var key = Environment.GetEnvironmentVariable("OPEN_AI_APIKEY",EnvironmentVariableTarget.User);
if (string.IsNullOrWhiteSpace(key))
return;
var prompt = "how does student john doe in senior grade is doing this year, enrollment start 01-01-2024 to 01-01-2025?";

var client = new OpenAIClient(new ApiKeyCredential(key));

Microsoft.Extensions.AI.OpenAIChatClient openAiClient = new OpenAIChatClient(client.GetChatClient("gpt-4o-mini"));

var chatClient = new FunctionInvokingChatClient(openAiClient);
var chatOptions = new ChatOptions();

var service = new StudentRecordService();

var tools = new Tools([service.GetStudentRecordAsync]);
chatOptions.Tools = tools.AsMeaiTools();
var message = new ChatMessage(ChatRole.User, prompt);
var response = await chatClient.GetResponseAsync(message,options:chatOptions).ConfigureAwait(false);

Comment on lines +23 to +26
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for API calls.

The API call lacks try-catch error handling. Network requests can fail for various reasons, and the application should gracefully handle these failures.

-var message = new ChatMessage(ChatRole.User, prompt);
-var response = await chatClient.GetResponseAsync(message,options:chatOptions).ConfigureAwait(false);
+var message = new ChatMessage(ChatRole.User, prompt);
+try
+{
+    var response = await chatClient.GetResponseAsync(message, options: chatOptions).ConfigureAwait(false);
+    Console.WriteLine(response.Choices.LastOrDefault()?.Text ?? "No response received.");
+}
+catch (Exception ex)
+{
+    Console.WriteLine($"Error occurred while communicating with OpenAI: {ex.Message}");
+}
📝 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
chatOptions.Tools = tools.AsMeaiTools();
var message = new ChatMessage(ChatRole.User, prompt);
var response = await chatClient.GetResponseAsync(message,options:chatOptions).ConfigureAwait(false);
chatOptions.Tools = tools.AsMeaiTools();
var message = new ChatMessage(ChatRole.User, prompt);
try
{
var response = await chatClient.GetResponseAsync(message, options: chatOptions).ConfigureAwait(false);
Console.WriteLine(response.Choices.LastOrDefault()?.Text ?? "No response received.");
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred while communicating with OpenAI: {ex.Message}");
}

Console.WriteLine(response.Choices.LastOrDefault().Text);
38 changes: 38 additions & 0 deletions src/tests/AotConsole/Services/BookService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;


namespace CSharpToJsonSchema.MeaiTests.Services;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Namespace mismatch with file location

The namespace CSharpToJsonSchema.MeaiTests.Services doesn't match the file location in the AotConsole project. This can lead to confusion about where the code belongs.

-namespace CSharpToJsonSchema.MeaiTests.Services;
+namespace CSharpToJsonSchema.AotConsole.Services;
📝 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
namespace CSharpToJsonSchema.MeaiTests.Services;
namespace CSharpToJsonSchema.AotConsole.Services;


public class GetAuthorBook
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}

[GenerateJsonSchema(MeaiFunctionTool = true)]
public interface IBookStoreService
{
[Description("Get books written by some author")]
public Task<List<GetAuthorBook>> GetAuthorBooksAsync([Description("Author name")] string authorName, CancellationToken cancellationToken = default);

[Description("Get book page content")]
public Task<string> GetBookPageContentAsync([Description("Book Name")] string bookName, [Description("Book Page Number")] int bookPageNumber, CancellationToken cancellationToken = default);

}
public class BookStoreService : IBookStoreService
{
public Task<List<GetAuthorBook>> GetAuthorBooksAsync(string authorName, CancellationToken cancellationToken = default)
{
return Task.FromResult(new List<GetAuthorBook>([
new GetAuthorBook
{ Title = "Five point someone", Description = "This book is about 3 college friends" },
new GetAuthorBook
{ Title = "Two States", Description = "This book is about intercast marriage in India" }
]));
}

public Task<string> GetBookPageContentAsync(string bookName, int bookPageNumber, CancellationToken cancellationToken = default)
{
return Task.FromResult("this is a cool weather out there, and I am stuck at home.");
}
}
72 changes: 72 additions & 0 deletions src/tests/AotConsole/Services/StudentRecordService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace CSharpToJsonSchema.MeaiTests.Services;
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;

public class StudentRecordService
{
[System.ComponentModel.Description("Get student record for the year")]
[FunctionTool(MeaiFunctionTool = true)]

Comment on lines +7 to +8
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 using statement for FunctionTool attribute.

The code uses the FunctionTool attribute but doesn't include a using directive for the namespace that contains it. Add the appropriate using statement.

 namespace CSharpToJsonSchema.MeaiTests.Services;
 using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;
+using CSharpToJsonSchema; // Add this for FunctionTool attribute
📝 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
[FunctionTool(MeaiFunctionTool = true)]
namespace CSharpToJsonSchema.MeaiTests.Services;
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;
using CSharpToJsonSchema; // Add this for FunctionTool attribute
[FunctionTool(MeaiFunctionTool = true)]

public async Task<StudentRecord> GetStudentRecordAsync(QueryStudentRecordRequest query, CancellationToken cancellationToken = default)
{
return new StudentRecord
{
StudentId = "12345",
FullName = query.FullName,
Level = StudentRecord.GradeLevel.Senior,
EnrolledCourses = new List<string> { "Math 101", "Physics 202", "History 303" },
Grades = new Dictionary<string, double>
{
{ "Math 101", 3.5 },
{ "Physics 202", 3.8 },
{ "History 303", 3.9 }
},
EnrollmentDate = new DateTime(2020, 9, 1),
IsActive = true
};
}
}

public class StudentRecord
{
public enum GradeLevel
{
Freshman,
Sophomore,
Junior,
Senior,
Graduate
}

public string StudentId { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public GradeLevel Level { get; set; } = GradeLevel.Freshman;
public List<string> EnrolledCourses { get; set; } = new List<string>();
public Dictionary<string, double> Grades { get; set; } = new Dictionary<string, double>();
public DateTime EnrollmentDate { get; set; } = DateTime.Now;
public bool IsActive { get; set; } = true;

public double CalculateGPA()
{
if (Grades.Count == 0) return 0.0;
return Grades.Values.Average();
}
}

[Description("Request class containing filters for querying student records.")]
public class QueryStudentRecordRequest
{
[Description("The student's full name.")]
public string FullName { get; set; } = string.Empty;

[Description("Grade filters for querying specific grades, e.g., Freshman or Senior.")]
public List<StudentRecord.GradeLevel> GradeFilters { get; set; } = new();

[Description("The start date for the enrollment date range. ISO 8601 standard date")]
public DateTime EnrollmentStartDate { get; set; }

[Description("The end date for the enrollment date range. ISO 8601 standard date")]
public DateTime EnrollmentEndDate { get; set; }

[Description("The flag indicating whether to include only active students.")]
public bool? IsActive { get; set; } = true;
}
Loading
Loading