Skip to content

Commit 9c1fb9e

Browse files
committed
feat: enhance FinishReason enum with lenient JSON converter and additional values
1 parent 9a90bb0 commit 9c1fb9e

4 files changed

Lines changed: 133 additions & 1 deletion

File tree

src/GenerativeAI/Core/ResponseHelper.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ internal static string FormatErrorMessage(FinishReason response)
7373
FinishReason.SPII => "The generation stopped because the content might include Sensitive Personally Identifiable Information (SPII).",
7474
FinishReason.MALFORMED_FUNCTION_CALL => "The generation stopped because a malformed or invalid function call was generated by the model.",
7575
FinishReason.IMAGE_SAFETY => "The generation stopped because the generated images were flagged for containing safety violations.",
76+
FinishReason.MODEL_ARMOR => "The generation stopped due to model armor protection mechanisms.",
77+
FinishReason.IMAGE_PROHIBITED_CONTENT => "Image generation stopped because the generated images contain prohibited content.",
78+
FinishReason.IMAGE_RECITATION => "Image generation stopped because the generated images were flagged for recitation.",
79+
FinishReason.IMAGE_OTHER => "Image generation stopped due to other miscellaneous issues.",
80+
FinishReason.UNEXPECTED_TOOL_CALL => "The generation stopped because the model generated a tool call but no tools were enabled in the request.",
81+
FinishReason.NO_IMAGE => "The generation stopped because the model was expected to generate an image, but none was generated.",
82+
FinishReason.TOO_MANY_TOOL_CALLS => "The generation stopped because the model called too many tools consecutively.",
7683
_ => "The generation stopped for an unexpected or unhandled reason."
7784
};
7885
}

src/GenerativeAI/Types/ContentGeneration/Outputs/FinishReason.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
using System.Text.Json.Serialization;
2+
using GenerativeAI.Types.Converters;
23

34
namespace GenerativeAI.Types;
45

56
/// <summary>
67
/// Defines the reason why the model stopped generating tokens.
78
/// </summary>
9+
/// <remarks>
10+
/// This enum uses a lenient JSON converter that gracefully handles unknown values
11+
/// by falling back to <see cref="OTHER"/> instead of throwing an exception.
12+
/// This prevents crashes when Google adds new FinishReason values to their API.
13+
/// </remarks>
814
/// <seealso href="https://ai.google.dev/api/generate-content#FinishReason">See Official API Documentation</seealso>
9-
[JsonConverter(typeof(JsonStringEnumConverter<FinishReason>))]
15+
[JsonConverter(typeof(LenientFinishReasonConverter))]
1016
public enum FinishReason
1117
{
1218
/// <summary>
@@ -69,4 +75,46 @@ public enum FinishReason
6975
/// Token generation stopped because generated images contain safety violations.
7076
/// </summary>
7177
IMAGE_SAFETY = 11,
78+
79+
/// <summary>
80+
/// Token generation stopped due to model armor protection mechanisms.
81+
/// Documented in Vertex AI (google.cloud.aiplatform.v1).
82+
/// </summary>
83+
MODEL_ARMOR = 12,
84+
85+
/// <summary>
86+
/// Image generation stopped because generated images have prohibited content.
87+
/// Documented in Vertex AI (google.cloud.aiplatform.v1).
88+
/// </summary>
89+
IMAGE_PROHIBITED_CONTENT = 13,
90+
91+
/// <summary>
92+
/// Image generation stopped because generated images were flagged for recitation.
93+
/// Documented in Vertex AI (google.cloud.aiplatform.v1).
94+
/// </summary>
95+
IMAGE_RECITATION = 14,
96+
97+
/// <summary>
98+
/// Image generation stopped because of other miscellaneous issues.
99+
/// Documented in Vertex AI (google.cloud.aiplatform.v1).
100+
/// </summary>
101+
IMAGE_OTHER = 15,
102+
103+
/// <summary>
104+
/// Model generated a tool call but no tools were enabled in the request.
105+
/// Documented in Gemini API and Vertex AI.
106+
/// </summary>
107+
UNEXPECTED_TOOL_CALL = 16,
108+
109+
/// <summary>
110+
/// The model was expected to generate an image, but none was generated.
111+
/// Documented in Vertex AI (google.cloud.aiplatform.v1).
112+
/// </summary>
113+
NO_IMAGE = 17,
114+
115+
/// <summary>
116+
/// Model called too many tools consecutively, thus the system exited execution.
117+
/// Documented in generativelanguagepb (SDK/proto contract).
118+
/// </summary>
119+
TOO_MANY_TOOL_CALLS = 18,
72120
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
4+
namespace GenerativeAI.Types.Converters;
5+
6+
/// <summary>
7+
/// Lenient JSON converter for FinishReason that handles unknown enum values gracefully.
8+
/// When an unknown string value is encountered, it falls back to FinishReason.OTHER
9+
/// instead of throwing an exception. This prevents crashes when Google adds new
10+
/// FinishReason values to their API.
11+
/// </summary>
12+
public class LenientFinishReasonConverter : JsonConverter<FinishReason>
13+
{
14+
/// <summary>
15+
/// Reads and converts the JSON to FinishReason.
16+
/// </summary>
17+
public override FinishReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
18+
{
19+
if (reader.TokenType != JsonTokenType.String)
20+
{
21+
throw new JsonException($"Expected string value for FinishReason, got {reader.TokenType}");
22+
}
23+
24+
var value = reader.GetString();
25+
if (string.IsNullOrEmpty(value))
26+
{
27+
return FinishReason.FINISH_REASON_UNSPECIFIED;
28+
}
29+
30+
if (Enum.TryParse<FinishReason>(value, ignoreCase: true, out var result))
31+
{
32+
return result;
33+
}
34+
35+
return FinishReason.OTHER;
36+
}
37+
38+
/// <summary>
39+
/// Writes the FinishReason value as JSON.
40+
/// </summary>
41+
public override void Write(Utf8JsonWriter writer, FinishReason value, JsonSerializerOptions options)
42+
{
43+
writer.WriteStringValue(value.ToString());
44+
}
45+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Text.Json;
2+
using GenerativeAI.Types;
3+
using Shouldly;
4+
5+
namespace GenerativeAI.Tests.Converters;
6+
7+
public class LenientFinishReasonConverter_Tests
8+
{
9+
[Fact]
10+
public void Read_KnownValues_DeserializeCorrectly()
11+
{
12+
// Old value
13+
JsonSerializer.Deserialize<FinishReason>("\"STOP\"").ShouldBe(FinishReason.STOP);
14+
15+
// New value with lowercase (tests case insensitivity)
16+
JsonSerializer.Deserialize<FinishReason>("\"unexpected_tool_call\"").ShouldBe(FinishReason.UNEXPECTED_TOOL_CALL);
17+
}
18+
19+
[Fact]
20+
public void Read_UnknownValue_FallsBackToOther()
21+
{
22+
var result = JsonSerializer.Deserialize<FinishReason>("\"UNKNOWN_FUTURE_VALUE\"");
23+
result.ShouldBe(FinishReason.OTHER);
24+
}
25+
26+
[Fact]
27+
public void Write_EnumValue_SerializesCorrectly()
28+
{
29+
var json = JsonSerializer.Serialize(FinishReason.UNEXPECTED_TOOL_CALL);
30+
json.ShouldBe("\"UNEXPECTED_TOOL_CALL\"");
31+
}
32+
}

0 commit comments

Comments
 (0)