Skip to content

Commit 0181fed

Browse files
author
PHAN Xuan Quang
committed
update json mode
1 parent b6b5dc4 commit 0181fed

File tree

7 files changed

+215
-12
lines changed

7 files changed

+215
-12
lines changed

Example APIs/Controllers/GroundingController.cs

+2-6
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,17 @@ public class GroundingController : ControllerBase
1111
[HttpPost("GenerationWithGrounding")]
1212
public async Task<IActionResult> GenerateContentWithGrounding(string apiKey, string prompt)
1313
{
14-
var generatorWithApiKey = new Generator(apiKey)
15-
.IncludesGroundingDetailInResponse()
16-
.IncludesSearchEntryPointInResponse();
14+
var generatorWithApiKey = new Generator(apiKey);
1715

1816
var apiRequest = new ApiRequestBuilder()
1917
.WithPrompt(prompt)
20-
.EnableGrounding()
21-
.WithChatHistory(new List<ChatMessage>())
2218
.WithDefaultGenerationConfig()
2319
.DisableAllSafetySettings()
2420
.Build();
2521

2622
try
2723
{
28-
var response = await generatorWithApiKey.GenerateContentAsync(apiRequest, Generator.GetLatestStableModelVersion());
24+
var response = await generatorWithApiKey.GenerateContentAsync<ChatMessage>(apiRequest, Generator.GetLatestStableModelVersion());
2925
return Ok(response);
3026
}
3127
catch (Exception ex)

Gemini.NET/API Models/API Request/GenerationConfig.cs

+3
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,8 @@ public class GenerationConfig
5151
/// </summary>
5252
[JsonProperty("responseMimeType")]
5353
public string ResponseMimeType { get; set; } = EnumHelper.GetDescription(Enums.ResponseMimeType.PlainText);
54+
55+
[JsonProperty("responseSchema")]
56+
public ResponseSchema? ResponseSchema { get; set; }
5457
}
5558
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Models.Request
4+
{
5+
public class ResponseSchema(string type)
6+
{
7+
[JsonProperty("type")]
8+
public string Type { get; set; } = type;
9+
10+
[JsonProperty("properties")]
11+
public Dictionary<string, ResponseSchema>? Properties { get; set; }
12+
13+
[JsonProperty("required")]
14+
public List<string>? Required { get; set; }
15+
16+
[JsonProperty("items")]
17+
public ResponseSchema? Items { get; set; }
18+
19+
[JsonProperty("enum")]
20+
public List<string>? Enum { get; set; }
21+
22+
[JsonProperty("default")]
23+
public object? Default { get; set; }
24+
25+
public ResponseSchema() : this("object")
26+
{
27+
}
28+
}
29+
}

Gemini.NET/ApiRequestBuilder.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,25 @@ public ApiRequestBuilder DisableAllSafetySettings()
113113
/// Sets the default generation configuration for the API request.
114114
/// </summary>
115115
/// <param name="temperature">The sampling temperature to set.</param>
116-
/// <param name="responseMimeType">The response MIME type to set.</param>
116+
/// <param name="maxOutputTokens">The maximum number of tokens in the generated output.</param>
117117
/// <returns>The current instance of <see cref="ApiRequestBuilder"/>.</returns>
118118
/// <exception cref="ArgumentOutOfRangeException">Thrown when the temperature is out of the valid range (0.0 to 2.0).</exception>
119-
public ApiRequestBuilder WithDefaultGenerationConfig(float temperature = 1, ResponseMimeType responseMimeType = ResponseMimeType.PlainText)
119+
public ApiRequestBuilder WithDefaultGenerationConfig(float temperature = 1, int maxOutputTokens = 8192)
120120
{
121121
if (temperature < 0.0F || temperature > 2.0F)
122122
{
123123
throw new ArgumentOutOfRangeException(nameof(temperature), "Temperature must be between 0.0 and 2.0.");
124124
}
125125

126+
if (maxOutputTokens < 1)
127+
{
128+
throw new ArgumentOutOfRangeException(nameof(maxOutputTokens), "Max output tokens must be greater than 0.");
129+
}
130+
126131
_config = new GenerationConfig
127132
{
128133
Temperature = temperature,
129-
ResponseMimeType = EnumHelper.GetDescription(responseMimeType),
134+
MaxOutputTokens = maxOutputTokens
130135
};
131136

132137
return this;

Gemini.NET/Generator.cs

+47-2
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,6 @@ public async Task<ModelResponse> GenerateContentAsync(ApiRequest request, ModelV
192192
var dto = JsonHelper.AsObject<Models.Response.Success.ApiResponse>(responseData);
193193
var groudingMetadata = dto.Candidates[0].GroundingMetadata;
194194

195-
Console.WriteLine(responseData);
196-
197195
return new ModelResponse
198196
{
199197
Result = dto.Candidates[0].Content != null
@@ -233,6 +231,53 @@ public async Task<ModelResponse> GenerateContentAsync(ApiRequest request, ModelV
233231
}
234232
}
235233

234+
/// <summary>
235+
/// Generates content based on the provided API request and returns the result as the specified type.
236+
/// </summary>
237+
/// <typeparam name="T">Type of the generated object </typeparam>
238+
/// <param name="request"></param>
239+
/// <param name="modelVersion"></param>
240+
/// <returns></returns>
241+
/// <exception cref="ArgumentNullException"></exception>
242+
/// <exception cref="InvalidOperationException"></exception>
243+
public async Task<T?> GenerateContentAsync<T>(ApiRequest request, ModelVersion modelVersion = ModelVersion.Gemini_15_Flash)
244+
{
245+
if (!Validator.SupportsJsonOutput(modelVersion))
246+
{
247+
throw new ArgumentNullException(nameof(request), "JSON output is not supported for this model version.");
248+
}
249+
250+
if (request.Tools != null && request.Tools.Count > 0)
251+
{
252+
throw new InvalidOperationException("JSON output is not supported for Grounding.");
253+
}
254+
255+
try
256+
{
257+
if (request.GenerationConfig == null)
258+
{
259+
request.GenerationConfig = new GenerationConfig
260+
{
261+
ResponseMimeType = EnumHelper.GetDescription(ResponseMimeType.Json),
262+
ResponseSchema = OpenApiSchemaGenerator.AsOpenApiSchema<T>(),
263+
};
264+
}
265+
else
266+
{
267+
request.GenerationConfig.ResponseMimeType = EnumHelper.GetDescription(ResponseMimeType.Json);
268+
request.GenerationConfig.ResponseSchema = OpenApiSchemaGenerator.AsOpenApiSchema<T>();
269+
}
270+
271+
var response = await GenerateContentAsync(request, modelVersion);
272+
273+
return JsonHelper.AsObject<T>(response.Result);
274+
}
275+
catch (Exception ex)
276+
{
277+
throw new InvalidOperationException($"Failed to generate content: {ex.Message}", ex);
278+
}
279+
}
280+
236281

237282
/// <summary>
238283
/// Gets the latest stable model version of Gemini.

Gemini.NET/Helpers/JsonHelper.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ public static class JsonHelper
1515
/// <returns>A JSON string representation of the object.</returns>
1616
public static string AsString<T>(T obj)
1717
{
18-
return JsonConvert.SerializeObject(obj, Formatting.Indented);
18+
return JsonConvert.SerializeObject(obj, Formatting.Indented, new JsonSerializerSettings
19+
{
20+
NullValueHandling = NullValueHandling.Ignore
21+
});
1922
}
2023

2124
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using Models.Request;
2+
using System.Reflection;
3+
4+
namespace Gemini.NET.Helpers
5+
{
6+
public static class OpenApiSchemaGenerator
7+
{
8+
public static ResponseSchema AsOpenApiSchema<T>()
9+
{
10+
return ConvertToSchema(typeof(T), new HashSet<Type>());
11+
}
12+
13+
private static ResponseSchema ConvertToSchema(Type type, HashSet<Type> processedTypes)
14+
{
15+
if (processedTypes.Contains(type))
16+
{
17+
return new ResponseSchema("object");
18+
}
19+
processedTypes.Add(type);
20+
21+
var schema = new ResponseSchema("object")
22+
{
23+
Properties = [],
24+
Required = []
25+
};
26+
27+
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
28+
foreach (var prop in properties)
29+
{
30+
string propName = prop.Name;
31+
Type propType = prop.PropertyType;
32+
ResponseSchema propSchema;
33+
34+
// Xử lý đặc biệt cho SearchSuggestions
35+
if (propName == "SearchSuggestions")
36+
{
37+
propSchema = new ResponseSchema("string");
38+
}
39+
else
40+
{
41+
// Lấy kiểu của property theo định nghĩa OpenAPI
42+
var (openApiType, itemsSchema, enumValues) = GetOpenApiType(propType, processedTypes);
43+
propSchema = new ResponseSchema(openApiType);
44+
45+
if (enumValues != null)
46+
{
47+
propSchema.Enum = enumValues;
48+
}
49+
if (openApiType == "array" && itemsSchema != null)
50+
{
51+
propSchema.Items = itemsSchema;
52+
}
53+
// Nếu kiểu là object (không phải primitive hay string) thì tiến hành convert nested object
54+
if (openApiType == "object" && !propType.IsPrimitive && propType != typeof(string))
55+
{
56+
propSchema = ConvertToSchema(propType, processedTypes);
57+
}
58+
}
59+
60+
// Nếu property được đánh dấu là required (ví dụ dùng RequiredMemberAttribute)
61+
if (Attribute.IsDefined(prop, typeof(System.Runtime.CompilerServices.RequiredMemberAttribute)))
62+
{
63+
schema.Required.Add(propName);
64+
}
65+
schema.Properties[propName] = propSchema;
66+
}
67+
68+
if (schema.Required.Count == 0)
69+
{
70+
schema.Required = null;
71+
}
72+
73+
if (schema.Properties.Count == 0)
74+
{
75+
schema.Properties = null;
76+
}
77+
78+
return schema;
79+
}
80+
81+
// Hàm trả về kiểu openapi, schema của items (nếu array) và danh sách enum nếu có
82+
private static (string type, ResponseSchema? itemsSchema, List<string>? enumValues) GetOpenApiType(Type type, HashSet<Type> processedTypes)
83+
{
84+
List<string>? enumValues = null;
85+
ResponseSchema? itemsSchema = null;
86+
87+
if (type.IsEnum)
88+
{
89+
enumValues = Enum.GetNames(type).ToList();
90+
return ("string", null, enumValues);
91+
}
92+
93+
if (type == typeof(string))
94+
return ("string", null, null);
95+
if (type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(sbyte))
96+
return ("integer", null, null);
97+
if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
98+
return ("number", null, null);
99+
if (type == typeof(bool))
100+
return ("boolean", null, null);
101+
102+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
103+
{
104+
var itemType = type.GetGenericArguments()[0];
105+
var (itemOpenApiType, innerItemsSchema, innerEnumValues) = GetOpenApiType(itemType, processedTypes);
106+
itemsSchema = new ResponseSchema(itemOpenApiType);
107+
if (innerEnumValues != null)
108+
{
109+
itemsSchema.Enum = innerEnumValues;
110+
}
111+
// Nếu item là object (và không phải primitive hay string) thì chuyển đổi nested object
112+
if (itemOpenApiType == "object" && !itemType.IsPrimitive && itemType != typeof(string))
113+
{
114+
itemsSchema = ConvertToSchema(itemType, processedTypes);
115+
}
116+
return ("array", itemsSchema, null);
117+
}
118+
119+
return ("object", null, null);
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)