Skip to content

Commit da99fc0

Browse files
author
Gunpal Jain
committed
fix: Functions without parameters
1 parent 10780c4 commit da99fc0

File tree

7 files changed

+223
-28
lines changed

7 files changed

+223
-28
lines changed

src/libs/CSharpToJsonSchema/SchemaSubsetHelper.cs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ namespace CSharpToJsonSchema;
99

1010
public static class SchemaBuilder
1111
{
12-
13-
public static OpenApiSchema ConvertToSchema(JsonTypeInfo type, string descriptionString)
12+
public static OpenApiSchema? ConvertToSchema(JsonTypeInfo type, string descriptionString)
1413
{
14+
if (type.Properties.Count == 0)
15+
return null;
1516
var typeInfo = type;
1617

1718
var dics = JsonSerializer.Deserialize(descriptionString,
@@ -26,33 +27,35 @@ public static OpenApiSchema ConvertToSchema(JsonTypeInfo type, string descriptio
2627
{
2728
schema["type"] = "string";
2829
}
29-
30+
3031
ExtractDescription(context, schema, dics);
3132
if (context.PropertyInfo == null)
3233
return schema;
33-
34+
3435
return schema;
3536
},
3637
});
37-
38+
3839
var schema = JsonSerializer.Deserialize(x.ToJsonString(), OpenApiSchemaJsonContext.Default.OpenApiSchema);
3940

4041
foreach (var re in schema.Properties)
4142
{
4243
required.Add(re.Key);
4344
}
44-
45+
4546
var mainDescription = schema.Description ?? (dics.TryGetValue("mainFunction_Desc", out var desc) ? desc : "");
46-
return new OpenApiSchema()
47-
{
48-
Description = mainDescription,
49-
Properties = schema.Properties,
50-
Required = required,
51-
Type = "object"
52-
};
47+
return
48+
new OpenApiSchema()
49+
{
50+
Description = mainDescription,
51+
Properties = schema.Properties,
52+
Required = required,
53+
Type = "object"
54+
};
5355
}
54-
55-
private static void ExtractDescription(JsonSchemaExporterContext context, JsonNode schema, IDictionary<string, string> dics)
56+
57+
private static void ExtractDescription(JsonSchemaExporterContext context, JsonNode schema,
58+
IDictionary<string, string> dics)
5659
{
5760
// Determine if a type or property and extract the relevant attribute provider.
5861
ICustomAttributeProvider? attributeProvider = context.PropertyInfo is not null
@@ -76,22 +79,22 @@ private static void ExtractDescription(JsonSchemaExporterContext context, JsonNo
7679
}
7780

7881
FixType(schema);
79-
82+
8083
// Apply description attribute to the generated schema.
8184
if (description is not null)
8285
{
8386
if (schema is not JsonObject jObj)
8487
{
8588
// Handle the case where the schema is a Boolean.
8689
JsonValueKind valueKind = schema.GetValueKind();
87-
90+
8891
schema = jObj = new JsonObject();
8992
if (valueKind is JsonValueKind.False)
9093
{
9194
jObj.Add("not", true);
9295
}
9396
}
94-
97+
9598
jObj.Insert(0, "description", description);
9699
}
97100
}
@@ -100,7 +103,7 @@ private static void FixType(JsonNode schema)
100103
{
101104
// If "type" is an array, remove "null" and collapse if it leaves only one type
102105
var typeValue = schema["type"];
103-
if (typeValue!= null && typeValue is JsonArray array)
106+
if (typeValue != null && typeValue is JsonArray array)
104107
{
105108
if (array.Count == 2)
106109
{
@@ -123,6 +126,7 @@ private static void FixType(JsonNode schema)
123126
}
124127
}
125128
}
129+
126130
public static string ToCamelCase(string str)
127131
{
128132
if (!string.IsNullOrEmpty(str) && str.Length > 1)

src/tests/CSharpToJsonSchema.AotTests/MethodFunctionTools_Tests.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ public async Task Should_SampleFunctionTool_StringAsync()
1010
var value = await tools.CallAsync(nameof(mf.SampleFunctionTool_StringAsync), "{\"input\":\"test\"}");
1111

1212
Assert.Equal(value, "\"Hello world\"");
13-
1413
}
1514

1615
[Fact]
@@ -72,7 +71,20 @@ public async Task Should_SampleFunctionTool_Static_Void()
7271
{
7372
var tools = new Tools([MethodFunctionTools.SampleFunctionTool_Static_Void]);
7473
await tools.CallAsync(nameof(MethodFunctionTools.SampleFunctionTool_Static_Void), "{\"input\":\"test\"}");
75-
74+
}
75+
76+
[Fact]
77+
public async Task Should_SampleFunctionTool_Static_No_Parameters()
78+
{
79+
var tools = new Tools([MethodFunctionTools.SampleFunctionTool_Static_No_Parameters]);
80+
await tools.CallAsync(nameof(MethodFunctionTools.SampleFunctionTool_Static_No_Parameters), "{}");
81+
}
82+
83+
[Fact]
84+
public async Task Should_SampleFunctionTool_No_Parameters()
85+
{
86+
var tools = new Tools([MethodFunctionTools.SampleFunctionTool_No_Parameters]);
87+
await tools.CallAsync(nameof(MethodFunctionTools.SampleFunctionTool_No_Parameters), "{}");
7688
}
7789

7890
}

src/tests/CSharpToJsonSchema.AotTests/Services/MethodFunctionTools.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,15 @@ public static string SampleFunctionTool_Static_String(string input)
5252
{
5353
return "Hello world from string return";
5454
}
55+
56+
[FunctionTool]
57+
public static string SampleFunctionTool_Static_No_Parameters()
58+
{
59+
return "Hello world from string return";
60+
}
61+
[FunctionTool]
62+
public static string SampleFunctionTool_No_Parameters()
63+
{
64+
return "Hello world from string return";
65+
}
5566
}

src/tests/CSharpToJsonSchema.IntegrationTests/MethodFunctionTools.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public string SampleFunctionTool_Static_String(string input)
5353
{
5454
return "Hello world from string return";
5555
}
56+
57+
5658

5759

5860

src/tests/CSharpToJsonSchema.MeaiTests/Meai_Tests.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,60 @@ public async Task ShouldInvokeTheFunctions()
3737
Console.WriteLine(response.Text);
3838
}
3939

40+
[TestMethod]
41+
public async Task ShouldInvokeTheFunctions_NoParameters()
42+
{
43+
var key = Environment.GetEnvironmentVariable("OPEN_AI_APIKEY",EnvironmentVariableTarget.User);
44+
if (string.IsNullOrWhiteSpace(key))
45+
return;
46+
47+
var client = new OpenAIClient(new ApiKeyCredential(key));
48+
49+
Microsoft.Extensions.AI.OpenAIChatClient openAiClient = new OpenAIChatClient(client.GetChatClient("gpt-4o-mini"));
50+
51+
var chatClient = new Microsoft.Extensions.AI.FunctionInvokingChatClient(openAiClient);
52+
var chatOptions = new ChatOptions();
53+
54+
var service = new StudentRecordService();
55+
var tools = new Tools([service.GetStudentsListAsync]);
56+
chatOptions.Tools = tools.AsMeaiTools();
57+
58+
var message = new ChatMessage(ChatRole.User, "Get all students records");
59+
var response = await chatClient.GetResponseAsync(message,options:chatOptions).ConfigureAwait(false);
60+
61+
response.Text.Contains("John", StringComparison.InvariantCultureIgnoreCase).Should()
62+
.Be(true);
63+
64+
Console.WriteLine(response.Text);
65+
}
66+
67+
[TestMethod]
68+
public async Task ShouldInvokeTheFunctions_NoParameters2()
69+
{
70+
var key = Environment.GetEnvironmentVariable("OPEN_AI_APIKEY",EnvironmentVariableTarget.User);
71+
if (string.IsNullOrWhiteSpace(key))
72+
return;
73+
74+
var client = new OpenAIClient(new ApiKeyCredential(key));
75+
76+
Microsoft.Extensions.AI.OpenAIChatClient openAiClient = new OpenAIChatClient(client.GetChatClient("gpt-4o-mini"));
77+
78+
var chatClient = new Microsoft.Extensions.AI.FunctionInvokingChatClient(openAiClient);
79+
var chatOptions = new ChatOptions();
80+
81+
var service = new StudentRecordService();
82+
var tools = new Tools([service.GetStudentsList2]);
83+
chatOptions.Tools = tools.AsMeaiTools();
84+
85+
var message = new ChatMessage(ChatRole.User, "Get all students records");
86+
var response = await chatClient.GetResponseAsync(message,options:chatOptions).ConfigureAwait(false);
87+
88+
response.Text.Contains("John", StringComparison.InvariantCultureIgnoreCase).Should()
89+
.Be(true);
90+
91+
Console.WriteLine(response.Text);
92+
}
93+
4094
//[TestMethod]
4195
public async Task ShouldInvokeTheBookService()
4296
{
@@ -65,4 +119,32 @@ public async Task ShouldInvokeTheBookService()
65119

66120
Console.WriteLine(response.Text);
67121
}
122+
[TestMethod]
123+
public async Task ShouldInvokeTheBookService_NoParameters()
124+
{
125+
var key = Environment.GetEnvironmentVariable("OPEN_AI_APIKEY",EnvironmentVariableTarget.User);
126+
if (string.IsNullOrWhiteSpace(key))
127+
return;
128+
var prompt = "Get list of available books";
129+
130+
var chatClient = new OpenAIClient(new ApiKeyCredential(key)).AsChatClient("gpt-4o-mini").AsBuilder().UseFunctionInvocation().Build();
131+
132+
//Microsoft.Extensions.AI.OpenAIChatClient openAiClient = new OpenAIChatClient(client.GetChatClient("gpt-4o-mini"));
133+
134+
//var chatClient = new GenerativeAIChatClient(Environment.GetEnvironmentVariable("GOOGLE_API_KEY",EnvironmentVariableTarget.User));
135+
//var chatClient = new Microsoft.Extensions.AI.FunctionInvokingChatClient(openAiClient);
136+
var chatOptions = new ChatOptions();
137+
138+
var service = new BookStoreService();
139+
140+
chatOptions.Tools = service.AsMeaiTools();
141+
142+
var message = new ChatMessage(ChatRole.User, prompt);
143+
var response = await chatClient.GetResponseAsync(message,options:chatOptions).ConfigureAwait(false);
144+
145+
response.Text.Contains("Five point someone", StringComparison.InvariantCultureIgnoreCase).Should()
146+
.Be(true);
147+
148+
Console.WriteLine(response.Text);
149+
}
68150
}

src/tests/CSharpToJsonSchema.MeaiTests/Services/BookService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public interface IBookStoreService
1717

1818
[Description("Get book page content")]
1919
public Task<string> GetBookPageContentAsync([Description("Book Name")] string bookName, [Description("Book Page Number")] int bookPageNumber, CancellationToken cancellationToken = default);
20+
[Description("Get List of Books")]
21+
public Task<List<GetAuthorBook>> GetBooksAsync(CancellationToken cancellationToken = default);
2022

2123
}
2224
public class BookStoreService : IBookStoreService
@@ -35,4 +37,9 @@ public Task<string> GetBookPageContentAsync(string bookName, int bookPageNumber,
3537
{
3638
return Task.FromResult("this is a cool weather out there, and I am stuck at home.");
3739
}
40+
41+
public Task<List<GetAuthorBook>> GetBooksAsync(CancellationToken cancellationToken = default)
42+
{
43+
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"}});
44+
}
3845
}

src/tests/CSharpToJsonSchema.MeaiTests/Services/StudentRecordService.cs

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
namespace CSharpToJsonSchema.MeaiTests.Services;
2+
23
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;
34

45
public class StudentRecordService
56
{
67
[System.ComponentModel.Description("Get student record for the year")]
78
[FunctionTool(MeaiFunctionTool = true)]
8-
9-
public async Task<StudentRecord> GetStudentRecordAsync(QueryStudentRecordRequest query, CancellationToken cancellationToken = default)
9+
public async Task<StudentRecord> GetStudentRecordAsync(QueryStudentRecordRequest query,
10+
CancellationToken cancellationToken = default)
1011
{
1112
return new StudentRecord
1213
{
@@ -24,6 +25,82 @@ public async Task<StudentRecord> GetStudentRecordAsync(QueryStudentRecordRequest
2425
IsActive = true
2526
};
2627
}
28+
29+
[System.ComponentModel.Description("Get all student Records")]
30+
[FunctionTool(MeaiFunctionTool = true)]
31+
public async Task<List<StudentRecord>> GetStudentsListAsync(CancellationToken cancellationToken = default)
32+
{
33+
return new List<StudentRecord>
34+
{
35+
new StudentRecord
36+
{
37+
StudentId = "12345",
38+
FullName = "John Doe",
39+
Level = StudentRecord.GradeLevel.Senior,
40+
EnrolledCourses = new List<string> { "Math 101", "Physics 202", "History 303" },
41+
Grades = new Dictionary<string, double>
42+
{
43+
{ "Math 101", 3.5 },
44+
{ "Physics 202", 3.8 },
45+
{ "History 303", 3.9 }
46+
},
47+
EnrollmentDate = new DateTime(2020, 9, 1),
48+
IsActive = true
49+
},
50+
new StudentRecord
51+
{
52+
StudentId = "67890",
53+
FullName = "Jane Smith",
54+
Level = StudentRecord.GradeLevel.Junior,
55+
EnrolledCourses = new List<string> { "Biology 101", "Chemistry 202" },
56+
Grades = new Dictionary<string, double>
57+
{
58+
{ "Biology 101", 4.0 },
59+
{ "Chemistry 202", 3.7 }
60+
},
61+
EnrollmentDate = new DateTime(2019, 9, 1),
62+
IsActive = false
63+
}
64+
};
65+
}
66+
67+
[System.ComponentModel.Description("Get all student Records")]
68+
[FunctionTool(MeaiFunctionTool = true)]
69+
public async Task<List<StudentRecord>> GetStudentsList2(CancellationToken cancellationToken = default)
70+
{
71+
return new List<StudentRecord>
72+
{
73+
new StudentRecord
74+
{
75+
StudentId = "12345",
76+
FullName = "John Doe",
77+
Level = StudentRecord.GradeLevel.Senior,
78+
EnrolledCourses = new List<string> { "Math 101", "Physics 202", "History 303" },
79+
Grades = new Dictionary<string, double>
80+
{
81+
{ "Math 101", 3.5 },
82+
{ "Physics 202", 3.8 },
83+
{ "History 303", 3.9 }
84+
},
85+
EnrollmentDate = new DateTime(2020, 9, 1),
86+
IsActive = true
87+
},
88+
new StudentRecord
89+
{
90+
StudentId = "67890",
91+
FullName = "Jane Smith",
92+
Level = StudentRecord.GradeLevel.Junior,
93+
EnrolledCourses = new List<string> { "Biology 101", "Chemistry 202" },
94+
Grades = new Dictionary<string, double>
95+
{
96+
{ "Biology 101", 4.0 },
97+
{ "Chemistry 202", 3.7 }
98+
},
99+
EnrollmentDate = new DateTime(2019, 9, 1),
100+
IsActive = false
101+
}
102+
};
103+
}
27104
}
28105

29106
public class StudentRecord
@@ -57,16 +134,16 @@ public class QueryStudentRecordRequest
57134
{
58135
[Description("The student's full name.")]
59136
public string FullName { get; set; } = string.Empty;
60-
137+
61138
[Description("Grade filters for querying specific grades, e.g., Freshman or Senior.")]
62139
public List<StudentRecord.GradeLevel> GradeFilters { get; set; } = new();
63-
140+
64141
[Description("The start date for the enrollment date range. ISO 8601 standard date")]
65142
public DateTime EnrollmentStartDate { get; set; }
66-
143+
67144
[Description("The end date for the enrollment date range. ISO 8601 standard date")]
68145
public DateTime EnrollmentEndDate { get; set; }
69-
146+
70147
[Description("The flag indicating whether to include only active students.")]
71148
public bool? IsActive { get; set; } = true;
72-
}
149+
}

0 commit comments

Comments
 (0)