Repository: firebase/firebase-unity-sdk
Pull request #1203: VertexAI - Add Schemas and Function Calls
Author: login: a-maurice
Created: 2025-03-06T00:29:25Z | Merged: 2025-03-07T23:43:53Z

### Description
> Provide details of the change, and generalize the change in the PR title above.

Add support and tests for defining Schemas and Function Calls in VertexAI.
***
### Testing
> Describe how you've tested these changes.

Running the tests locally.
***

### Type of Change
Place an `x` the applicable box:
- [ ] Bug fix. Add the issue # below if applicable.
- [x] New feature. A non-breaking change which adds functionality.
- [ ] Other, such as a build process or documentation change.
***




Diff:
diff --git a/vertexai/src/FunctionCalling.cs b/vertexai/src/FunctionCalling.cs
index 5e7caeab9..35d364502 100644
--- a/vertexai/src/FunctionCalling.cs
+++ b/vertexai/src/FunctionCalling.cs
@@ -16,54 +16,193 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
+using Firebase.VertexAI.Internal;
 
 namespace Firebase.VertexAI {
 
+/// <summary>
+/// Structured representation of a function declaration.
+///
+/// This `FunctionDeclaration` is a representation of a block of code that can be used
+/// as a `Tool` by the model and executed by the client.
+/// 
+/// Function calling can be used to provide data to the model that was not known at the time it
+/// was trained (for example, the current date or weather conditions) or to allow it to interact
+/// with external systems (for example, making an API request or querying/updating a database).
+/// For more details and use cases, see [Introduction to function
+/// calling](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling).
+/// </summary>
 public readonly struct FunctionDeclaration {
   // No public properties, on purpose since it is meant for user input only
 
+  private string Name { get; }
+  private string Description { get; }
+  private Schema Parameters { get; }
+
+  /// <summary>
+  /// Constructs a new `FunctionDeclaration`.
+  /// </summary>
+  /// <param name="name">The name of the function; must be a-z, A-Z, 0-9, or contain
+  ///   underscores and dashes, with a maximum length of 63.</param>
+  /// <param name="description">A brief description of the function.</param>
+  /// <param name="parameters">Describes the parameters to this function.</param>
+  /// <param name="optionalParameters">The names of parameters that may be omitted by the model
+  ///   in function calls; by default, all parameters are considered required.</param>
   public FunctionDeclaration(string name, string description,
       IDictionary<string, Schema> parameters,
       IEnumerable<string> optionalParameters = null) {
-    throw new NotImplementedException();
+    Name = name;
+    Description = description;
+    Parameters = Schema.Object(parameters, optionalParameters);
+  }
+
+  /// <summary>
+  /// Intended for internal use only.
+  /// This method is used for serializing the object to JSON for the API request.
+  /// </summary>
+  internal Dictionary<string, object> ToJson() {
+    return new() {
+      { "name", Name },
+      { "description", Description },
+      { "parameters", Parameters.ToJson() },
+    };
   }
 }
 
+/// <summary>
+/// A helper tool that the model may use when generating responses.
+///
+/// A `Tool` is a piece of code that enables the system to interact with external systems to
+/// perform an action, or set of actions, outside of knowledge and scope of the model.
+/// </summary>
 public readonly struct Tool {
   // No public properties, on purpose since it is meant for user input only
 
+  private List<FunctionDeclaration> Functions { get; }
+
+  /// <summary>
+  /// Creates a tool that allows the model to perform function calling.
+  /// </summary>
+  /// <param name="functionDeclarations">A list of `FunctionDeclarations` available to the model
+  ///   that can be used for function calling.</param>
   public Tool(params FunctionDeclaration[] functionDeclarations) {
-    throw new NotImplementedException();
+    Functions = new List<FunctionDeclaration>(functionDeclarations);
   }
+  /// <summary>
+  /// Creates a tool that allows the model to perform function calling.
+  /// </summary>
+  /// <param name="functionDeclarations">A list of `FunctionDeclarations` available to the model
+  ///   that can be used for function calling.</param>
   public Tool(IEnumerable<FunctionDeclaration> functionDeclarations) {
-    throw new NotImplementedException();
+    Functions = new List<FunctionDeclaration>(functionDeclarations);
+  }
+
+  /// <summary>
+  /// Intended for internal use only.
+  /// This method is used for serializing the object to JSON for the API request.
+  /// </summary>
+  internal Dictionary<string, object> ToJson() {
+    return new() {
+      { "functionDeclarations", Functions.Select(f => f.ToJson()).ToList() }
+    };
   }
 }
 
+/// <summary>
+/// Tool configuration for any `Tool` specified in the request.
+/// </summary>
 public readonly struct ToolConfig {
   // No public properties, on purpose since it is meant for user input only
 
+  private FunctionCallingConfig? Config { get; }
+
+  /// <summary>
+  /// Constructs a new `ToolConfig`.
+  /// </summary>
+  /// <param name="functionCallingConfig">Configures how the model should use the
+  ///   provided functions.</param>
   public ToolConfig(FunctionCallingConfig? functionCallingConfig = null) {
-    throw new NotImplementedException();
+    Config = functionCallingConfig;
+  }
+
+  /// <summary>
+  /// Intended for internal use only.
+  /// This method is used for serializing the object to JSON for the API request.
+  /// </summary>
+  internal Dictionary<string, object> ToJson() {
+    var json = new Dictionary<string, object>();
+    if (Config.HasValue) {
+      json["functionCallingConfig"] = Config?.ToJson();
+    }
+    return json;
   }
 }
 
+/// <summary>
+/// Configuration for specifying function calling behavior.
+/// </summary>
 public readonly struct FunctionCallingConfig {
   // No public properties, on purpose since it is meant for user input only
 
+  private string Mode { get; }
+  private List<string> AllowedFunctionNames { get; }
+
+  private FunctionCallingConfig(string mode, IEnumerable<string> allowedFunctionNames = null) {
+    Mode = mode;
+    if (allowedFunctionNames != null) {
+      AllowedFunctionNames = new List<string>(allowedFunctionNames);
+    } else {
+      AllowedFunctionNames = null;
+    }
+  }
+
+  /// <summary>
+  /// Creates a function calling config where the model calls functions at its discretion.
+  ///
+  /// > Note: This is the default behavior.
+  /// </summary>
   public static FunctionCallingConfig Auto() {
-    throw new NotImplementedException();
+    return new FunctionCallingConfig("AUTO");
   }
 
+  /// <summary>
+  /// Creates a function calling config where the model will always call a provided function.
+  /// </summary>
+  /// <param name="allowedFunctionNames">A set of function names that, when provided, limits the
+  ///   function that the model will call.</param>
   public static FunctionCallingConfig Any(params string[] allowedFunctionNames) {
-    throw new NotImplementedException();
+    return new FunctionCallingConfig("ANY", allowedFunctionNames);
   }
+  /// <summary>
+  /// Creates a function calling config where the model will always call a provided function.
+  /// </summary>
+  /// <param name="allowedFunctionNames">A set of function names that, when provided, limits the
+  ///   function that the model will call.</param>
   public static FunctionCallingConfig Any(IEnumerable<string> allowedFunctionNames) {
-    throw new NotImplementedException();
+    return new FunctionCallingConfig("ANY", allowedFunctionNames);
   }
 
+  /// Creates a function calling config where the model will never call a function.
+  ///
+  /// > Note: This can also be achieved by not passing any `FunctionDeclaration` tools when
+  ///     instantiating the model.
   public static FunctionCallingConfig None() {
-    throw new NotImplementedException();
+    return new FunctionCallingConfig("NONE");
+  }
+
+  /// <summary>
+  /// Intended for internal use only.
+  /// This method is used for serializing the object to JSON for the API request.
+  /// </summary>
+  internal Dictionary<string, object> ToJson() {
+    var json = new Dictionary<string, object>() {
+      { "mode", Mode }
+    };
+    if (AllowedFunctionNames != null) {
+      json["allowedFunctionNames"] = AllowedFunctionNames;
+    }
+    return json;
   }
 }
 
diff --git a/vertexai/src/GenerationConfig.cs b/vertexai/src/GenerationConfig.cs
index ea3ca3def..540dc52aa 100644
--- a/vertexai/src/GenerationConfig.cs
+++ b/vertexai/src/GenerationConfig.cs
@@ -128,7 +128,7 @@ public readonly struct GenerationConfig {
   /// `responseSchema`.</param>
   /// 
   /// <param name="responseSchema">Output schema of the generated candidate text. If set, a compatible
-  /// `responseMIMEType` must also be set.
+  /// `responseMimeType` must also be set.
   ///
   /// Compatible MIME types:
   /// - `application/json`: Schema for JSON response.
@@ -174,8 +174,7 @@ internal Dictionary<string, object> ToJson() {
     if (_frequencyPenalty.HasValue) jsonDict["frequencyPenalty"] = _frequencyPenalty.Value;
     if (_stopSequences != null && _stopSequences.Length > 0) jsonDict["stopSequences"] = _stopSequences;
     if (!string.IsNullOrWhiteSpace(_responseMimeType)) jsonDict["responseMimeType"] = _responseMimeType;
-    // TODO: Add the ResponseSchema
-    // if (_responseSchema != null) jsonDict["responseSchema"] = _responseSchema.ToJson();
+    if (_responseSchema != null) jsonDict["responseSchema"] = _responseSchema.ToJson();
 
     return jsonDict;
   }
diff --git a/vertexai/src/GenerativeModel.cs b/vertexai/src/GenerativeModel.cs
index a94846c3d..8efd9355d 100644
--- a/vertexai/src/GenerativeModel.cs
+++ b/vertexai/src/GenerativeModel.cs
@@ -171,6 +171,10 @@ private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
     string bodyJson = ModelContentsToJson(content);
     request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
 
+#if FIREBASE_LOG_REST_CALLS
+    UnityEngine.Debug.Log("Request:\n" + bodyJson);
+#endif
+
     HttpResponseMessage response = await _httpClient.SendAsync(request);
     // TODO: Convert any timeout exception into a VertexAI equivalent
     // TODO: Convert any HttpRequestExceptions, see:
@@ -179,6 +183,11 @@ private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
     response.EnsureSuccessStatusCode();
 
     string result = await response.Content.ReadAsStringAsync();
+
+#if FIREBASE_LOG_REST_CALLS
+    UnityEngine.Debug.Log("Response:\n" + result);
+#endif
+
     return GenerateContentResponse.FromJson(result);
   }
 
@@ -193,6 +202,10 @@ private async IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsy
     string bodyJson = ModelContentsToJson(content);
     request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
 
+#if FIREBASE_LOG_REST_CALLS
+    UnityEngine.Debug.Log("Request:\n" + bodyJson);
+#endif
+
     HttpResponseMessage response =
         await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
     // TODO: Convert any timeout exception into a VertexAI equivalent
@@ -209,6 +222,10 @@ private async IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsy
     while ((line = await reader.ReadLineAsync()) != null) {
       // Only pass along strings that begin with the expected prefix.
       if (line.StartsWith(StreamPrefix)) {
+#if FIREBASE_LOG_REST_CALLS
+        UnityEngine.Debug.Log("Streaming Response:\n" + line);
+#endif
+
         yield return GenerateContentResponse.FromJson(line[StreamPrefix.Length..]);
       }
     }
@@ -245,7 +262,12 @@ private string ModelContentsToJson(IEnumerable<ModelContent> contents) {
     if (_safetySettings != null && _safetySettings.Length > 0) {
       jsonDict["safetySettings"] = _safetySettings.Select(s => s.ToJson()).ToList();
     }
-    // TODO: Tool and ToolConfig (Part of FunctionCalling)
+    if (_tools != null && _tools.Length > 0) {
+      jsonDict["tools"] = _tools.Select(t => t.ToJson()).ToList();
+    }
+    if (_toolConfig.HasValue) {
+      jsonDict["toolConfig"] = _toolConfig?.ToJson();
+    }
     if (_systemInstruction.HasValue) {
       jsonDict["systemInstruction"] = _systemInstruction?.ToJson();
     }
diff --git a/vertexai/src/Internal/InternalHelpers.cs b/vertexai/src/Internal/InternalHelpers.cs
index b59cfe9f9..5de291178 100644
--- a/vertexai/src/Internal/InternalHelpers.cs
+++ b/vertexai/src/Internal/InternalHelpers.cs
@@ -45,9 +45,14 @@ public static bool TryParseValue<T>(this JsonDict jsonDict, string key,
       if (obj is T tObj) {
         value = tObj;
         return true;
-      } else if (typeof(T) == typeof(int) && obj is long asLong) {
+      } else if (obj is long asLong &&
+          (typeof(T) == typeof(int) ||
+           typeof(T) == typeof(float) ||
+           typeof(T) == typeof(double))) {
         // MiniJson puts all ints as longs, so special case it
-        value = (T)(object)(int)asLong;
+        // Also, if the number didn't have a decimal point, we still want to
+        // allow it to be a float or double.
+        value = (T)Convert.ChangeType(asLong, typeof(T));
         return true;
       } else if (typeof(T) == typeof(float) && obj is double asDouble) {
         // Similarly, floats are stored as doubles.
diff --git a/vertexai/src/Schema.cs b/vertexai/src/Schema.cs
index ae07da85e..3b17306cb 100644
--- a/vertexai/src/Schema.cs
+++ b/vertexai/src/Schema.cs
@@ -16,11 +16,22 @@
 
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
 
 namespace Firebase.VertexAI {
 
+/// <summary>
+/// A `Schema` object allows the definition of input and output data types.
+///
+/// These types can be objects, but also primitives and arrays. Represents a select subset of an
+/// [OpenAPI 3.0 schema object](https://spec.openapis.org/oas/v3.0.3#schema).
+/// </summary>
 public class Schema {
 
+  /// <summary>
+  /// The value type of a `Schema`.
+  /// </summary>
   public enum SchemaType {
     String,
     Number,
@@ -30,16 +41,73 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
@@ -47,48 +115,252 @@ public Schema(
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable,
+        format: "float"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a string.
+  /// </summary>
+  /// <param name="description">An optional description of what the string should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
+  /// <param name="format">An optional pattern that values need to adhere to.</param>
   public static Schema String(
       string description = null,
-      bool? nullable = null,
-      string format = null) { throw new NotImplementedException(); }
+      bool nullable = false,
+      StringFormat? format = null) {
+    return new Schema(SchemaType.String,
+        description: description,
+        nullable: nullable,
+        format: format?.Format
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing an object.
+  ///
+  /// This schema instructs the model to produce data of type "Object", which has keys of type
+  /// "String" and values of any other data type (including nested "Objects"s).
+  ///
+  /// **Example:** A `City` could be represented with the following object `Schema`.
+  /// ```
+  /// Schema.Object(properties: new Dictionary() {
+  ///   { "name", Schema.String() },
+  ///   { "population", Schema.Integer() }
+  /// })
+  /// ```
+  /// </summary>
+  /// <param name="properties">The map of the object's property names to their `Schema`s.</param>
+  /// <param name="optionalProperties">The list of optional properties. They must correspond to the keys
+  ///   provided in the `properties` map. By default it's empty, signaling the model that all
+  ///   properties are to be included.</param>
+  /// <param name="description">An optional description of what the object represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Object(
       IDictionary<string, Schema> properties,
-      IEnumerable<string> requiredProperties = null,
+      IEnumerable<string> optionalProperties = null,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    if (properties == null) {
+      throw new ArgumentNullException(nameof(properties));
+    }
+
+    if (optionalProperties != null) {
+      // Check for invalid optional properties
+      var invalidOptionalProperties = optionalProperties.Except(properties.Keys).ToList();
+      if (invalidOptionalProperties.Any()) {
+        throw new ArgumentException(
+            "The following optional properties are not present in the properties dictionary: " +
+              $"{string.Join(", ", invalidOptionalProperties)}",
+            nameof(optionalProperties));
+      }
+    }
+
+    // Determine required properties
+    var required =
+        properties.Keys.Except(optionalProperties ?? Enumerable.Empty<string>()).ToList();
+
+    foreach (string key in properties.Keys) {
+      if (optionalProperties == null || !optionalProperties.Contains(key)) {
+        required.Add(key);
+      }
+    }
+
+    return new Schema(SchemaType.Object,
+        description: description,
+        nullable: nullable,
+        properties: properties,
+        requiredProperties: required
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for an array.
+  /// </summary>
+  /// <param name="items">The `Schema` of the elements stored in the array.</param>
+  /// <param name="description">An optional description of what the array represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Array(
       Schema items,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Array,
+        description: description,
+        nullable: nullable,
+        items: items
+      );
+  }
 
+  /// <summary>
+  /// Returns a [Schema] for an enumeration.
+  ///
+  /// For example, the cardinal directions can be represented as:
+  /// ```
+  /// Schema.Enum(new string[]{ "North", "East", "South", "West" }, "Cardinal directions")
+  /// ```
+  /// </summary>
+  /// <param name="values">The list of valid values for this enumeration.</param>
+  /// <param name="description">An optional description of what the enum represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Enum(
       IEnumerable<string> values,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.String,
+        description: description,
+        nullable: nullable,
+        enumValues: values,
+        format: "enum"
+      );
+  }
+
+  /// <summary>
+  /// Intended for internal use only.
+  /// This method is used for serializing the object to JSON for the API request.
+  /// </summary>
+  internal Dictionary<string, object> ToJson() {
+    Dictionary<string, object> json = new() {
+      { "type", TypeAsString }
+    };
+    if (!string.IsNullOrWhiteSpace(Description)) {
+      json["description"] = Description;
+    }
+    if (Nullable.HasValue) {
+      json["nullable"] = Nullable.Value;
+    }
+    if (EnumValues != null && EnumValues.Any()) {
+      json["enum"] = EnumValues.ToList();
+    }
+    if (Items != null) {
+      json["items"] = Items.ToJson();
+    }
+    if (Properties != null && Properties.Any()) {
+      Dictionary<string, object> propertiesJson = new();
+      foreach (var kvp in Properties) {
+        propertiesJson[kvp.Key] = kvp.Value.ToJson();
+      }
+      json["properties"] = propertiesJson;
+      
+    }
+    if (RequiredProperties != null && RequiredProperties.Any()) {
+      json["required"] = RequiredProperties.ToList();
+    }
+
+    return json;
+  }
 }
 
 }
diff --git a/vertexai/testapp/Assets/Firebase/Sample/VertexAI/UIHandlerAutomated.cs b/vertexai/testapp/Assets/Firebase/Sample/VertexAI/UIHandlerAutomated.cs
index 32df3688f..0a6e5c6be 100644
--- a/vertexai/testapp/Assets/Firebase/Sample/VertexAI/UIHandlerAutomated.cs
+++ b/vertexai/testapp/Assets/Firebase/Sample/VertexAI/UIHandlerAutomated.cs
@@ -39,12 +39,16 @@ protected override void Start() {
         TestModelOptions,
         TestMultipleCandidates,
         TestBasicTextStream,
+        TestFunctionCallingAny,
+        TestFunctionCallingNone,
+        TestEnumSchemaResponse,
         // Internal tests for Json parsing, requires using a source library.
         InternalTestBasicReplyShort,
         InternalTestCitations,
         InternalTestBlockedSafetyWithMessage,
         InternalTestFinishReasonSafetyNoContent,
         InternalTestUnknownEnumSafetyRatings,
+        InternalTestFunctionCallWithArguments,
       };
 
       testRunner = AutomatedTestRunner.CreateTestRunner(
@@ -86,6 +90,15 @@ private void AssertFloatEq(string message, float value1, float value2) {
       }
     }
 
+    private void AssertType<T>(string message, object obj, out T output) {
+      if (obj is T parsed) {
+        output = parsed;
+      } else {
+        throw new Exception(
+          $"Assertion failed ({testRunner.CurrentTestDescription}): {obj.GetType()} is wrong type ({message})");
+      }
+    }
+
     // Returns true if the given value is between 0 and 1 (inclusive).
     private bool ValidProbability(float value) {
       return value >= 0.0f && value <= 1.0f;
@@ -212,6 +225,7 @@ async Task TestModelOptions() {
       }
     }
 
+    // Test if requesting multiple candidates works.
     async Task TestMultipleCandidates() {
       var genConfig = new GenerationConfig(candidateCount: 2);
 
@@ -226,6 +240,7 @@ async Task TestMultipleCandidates() {
       AssertEq("Incorrect number of Candidates", response.Candidates.Count(), 2);
     }
 
+    // Test if generating a stream of text works.
     async Task TestBasicTextStream() {
       var model = CreateGenerativeModel();
 
@@ -263,6 +278,91 @@ async Task TestBasicTextStream() {
       }
     }
 
+    private readonly string basicFunctionName = "MyBasicTestFunction";
+    private readonly string basicParameterEnumName = "basicTestEnumParameter";
+    private readonly string basicParameterEnumValue = "MyBasicTestEnum";
+    private readonly string basicParameterIntName = "basicTestIntParameter";
+    private readonly string basicParameterObjectName = "basicTestObjectParameter";
+    private readonly string basicParameterObjectBoolean = "BasicTestObjectBoolean";
+    private readonly string basicParameterObjectFloat = "BasicTestObjectFloat";
+
+    // Create a GenerativeModel using the parameters above to test Function Calling.
+    private GenerativeModel CreateGenerativeModelWithBasicFunctionCall(
+      ToolConfig? toolConfig = null) {
+      var tool = new Tool(new FunctionDeclaration(
+        basicFunctionName, "A function used to test Function Calling.",
+        new Dictionary<string, Schema>() {
+          { basicParameterEnumName, Schema.Enum(new string[] { basicParameterEnumValue }) },
+          { basicParameterIntName, Schema.Int("An integer value") },
+          { basicParameterObjectName, Schema.Object(new Dictionary<string, Schema>() {
+              { basicParameterObjectBoolean, Schema.Boolean("Is the float you are including negative?") },
+              { basicParameterObjectFloat, Schema.Float(nullable: true) }
+            }) }
+        }));
+
+      return VertexAI.DefaultInstance.GetGenerativeModel(ModelName,
+        tools: new Tool[] { tool },
+        toolConfig: toolConfig
+      );
+    }
+
+    // Test if FunctionCalling works, using Any to force it.
+    async Task TestFunctionCallingAny() {
+      // Setting this to Any should force my function call.
+      var model = CreateGenerativeModelWithBasicFunctionCall(new ToolConfig(FunctionCallingConfig.Any()));
+
+      GenerateContentResponse response = await model.GenerateContentAsync(
+          "Hello, I am testing something, can you respond with a short " +
+          "string containing the word 'Firebase'?");
+
+      Assert("Response missing candidates.", response.Candidates.Any());
+      var functionCalls = response.FunctionCalls;
+      AssertEq("Wrong number of Function Calls", functionCalls.Count(), 1);
+      var functionCall = functionCalls.First();
+      AssertEq("Wrong FunctionCall name", functionCall.Name, basicFunctionName);
+      AssertEq("Wrong number of Args", functionCall.Args.Count, 3);
+      Assert($"Missing parameter {basicParameterEnumName}", functionCall.Args.ContainsKey(basicParameterEnumName));
+      Assert($"Missing parameter {basicParameterIntName}", functionCall.Args.ContainsKey(basicParameterIntName));
+      Assert($"Missing parameter {basicParameterObjectName}", functionCall.Args.ContainsKey(basicParameterObjectName));
+      AssertEq("Wrong parameter enum value", functionCall.Args[basicParameterEnumName], basicParameterEnumValue);
+      // Ints are returned as longs
+      AssertType("ParameterInt", functionCall.Args[basicParameterIntName], out long _);
+      AssertType("ParameterObject", functionCall.Args[basicParameterObjectName], out Dictionary<string, object> parameterObject);
+      Assert($"Missing object field {basicParameterObjectBoolean}", parameterObject.ContainsKey(basicParameterObjectBoolean));
+      AssertType("ObjectBool", parameterObject[basicParameterObjectBoolean], out bool _);
+      Assert($"Missing object field {basicParameterObjectFloat}", parameterObject.ContainsKey(basicParameterObjectFloat));
+      // The float should be a double, but could be a null, or a long (if the response didn't include a decimal).
+      var objectFloat = parameterObject[basicParameterObjectFloat];
+      Assert($"Object float is the wrong type {objectFloat?.GetType()}", objectFloat == null || objectFloat is double || objectFloat is long);
+    }
+
+    // Test if setting None will prevent Function Calling.
+    async Task TestFunctionCallingNone() {
+      // Setting this to None should block my function call.
+      var model = CreateGenerativeModelWithBasicFunctionCall(new ToolConfig(FunctionCallingConfig.None()));
+
+      GenerateContentResponse response = await model.GenerateContentAsync(
+          "Hello, I am testing something, can you call my function?");
+
+      Assert("Response missing candidates.", response.Candidates.Any());
+      var functionCalls = response.FunctionCalls;
+      AssertEq("Wrong number of Function Calls", functionCalls.Count(), 0);
+    }
+
+    // Test if setting a response schema with an enum works.
+    async Task TestEnumSchemaResponse() {
+      string enumValue = "MyTestEnum";
+      var model = VertexAI.DefaultInstance.GetGenerativeModel(ModelName,
+        generationConfig: new GenerationConfig(
+          responseMimeType: "text/x.enum",
+          responseSchema: Schema.Enum(new string[] { enumValue })));
+      
+      var response = await model.GenerateContentAsync(
+        "Hello, I am testing setting the response schema to an enum.");
+
+      AssertEq("Should only be returning the single enum given", response.Text, enumValue);
+    }
+
     // The url prefix to use when fetching test data to use from the separate GitHub repo.
     readonly string testDataUrl =
         "https://raw.githubusercontent.com/FirebaseExtended/vertexai-sdk-test-data/refs/heads/main/mock-responses/";
@@ -295,7 +395,7 @@ private void ValidateTextPart(GenerateContentResponse response, string expectedT
       int partsCount = parts.Count();
       AssertEq("Parts count", partsCount, 1);
       var part = parts.First();
-      Assert($"Part is the wrong type {part.GetType()}", part is ModelContent.TextPart);
+      AssertType("TextPart", part, out ModelContent.TextPart _);
       string text = ((ModelContent.TextPart)part).Text;
       AssertEq($"Text part", text, expectedText);
 
@@ -327,14 +427,15 @@ private void ValidateSafetyRating(SafetyRating safetyRating,
     }
 
     // Helper function to validate UsageMetadata.
-    private void ValidateUsageMetadata(UsageMetadata usageMetadata, int promptTokenCount,
+    private void ValidateUsageMetadata(UsageMetadata? usageMetadata, int promptTokenCount,
         int candidatesTokenCount, int totalTokenCount) {
+      Assert("UsageMetadata", usageMetadata.HasValue);
       AssertEq("Wrong PromptTokenCount",
-          usageMetadata.PromptTokenCount, promptTokenCount);
+          usageMetadata?.PromptTokenCount, promptTokenCount);
       AssertEq("Wrong CandidatesTokenCount",
-          usageMetadata.CandidatesTokenCount, candidatesTokenCount);
+          usageMetadata?.CandidatesTokenCount, candidatesTokenCount);
       AssertEq("Wrong TotalTokenCount",
-          usageMetadata.TotalTokenCount, totalTokenCount);
+          usageMetadata?.TotalTokenCount, totalTokenCount);
     }
 
     // Helper function to validate Citations.
@@ -377,8 +478,7 @@ async Task InternalTestBasicReplyShort() {
 
       AssertEq("CitationMetadata", candidate.CitationMetadata, null);
 
-      Assert("UsageMetadata", response.UsageMetadata.HasValue);
-      ValidateUsageMetadata(response.UsageMetadata.Value, 6, 7, 13);
+      ValidateUsageMetadata(response.UsageMetadata, 6, 7, 13);
     }
 
     // Test that parsing a response including Citations works.
@@ -476,8 +576,7 @@ async Task InternalTestFinishReasonSafetyNoContent() {
         severity: SafetyRating.HarmSeverity.Negligible,
         severityScore: 0.12109375f);
 
-      Assert("UsageMetadata", response.UsageMetadata.HasValue);
-      ValidateUsageMetadata(response.UsageMetadata.Value, 8, 0, 8);
+      ValidateUsageMetadata(response.UsageMetadata, 8, 0, 8);
     }
 
     // Test that parsing a response with unknown safety enums works.
@@ -513,5 +612,24 @@ async Task InternalTestUnknownEnumSafetyRatings() {
       ValidateSafetyRating(safetyRatings[2], HarmCategory.Unknown,
           probability: SafetyRating.HarmProbability.High);
     }
+
+    // Test that parsing a response with a FunctionCall part works.
+    async Task InternalTestFunctionCallWithArguments() {
+      Dictionary<string, object> json = await GetJsonTestData("unary-success-function-call-with-arguments.json");
+      GenerateContentResponse response = GenerateContentResponse.FromJson(json);
+
+      AssertEq("Candidate count", response.Candidates.Count(), 1);
+      var candidate = response.Candidates.First();
+
+      AssertEq("Role", candidate.Content.Role, "model");
+      AssertEq("Candidate.Parts count", candidate.Content.Parts.Count(), 1);
+      AssertType("FunctionCallPart", candidate.Content.Parts.First(),
+          out ModelContent.FunctionCallPart fcPart);
+      AssertEq("FunctionCall name", fcPart.Name, "sum");
+      AssertEq("FunctionCall args wrong length", fcPart.Args.Count, 2);
+      // The Args are passed along as longs.
+      AssertEq("FunctionCall args[y] wrong value", fcPart.Args["y"], 5L);
+      AssertEq("FunctionCall args[x] wrong value", fcPart.Args["x"], 4L);
+    }
   }
 }


Reviews:
user: login: gemini-code-assist[bot]
body: ## Code Review
This pull request introduces support for defining Schemas and Function Calls in VertexAI, enhancing the model's ability to interact with external systems. The code includes new structures for FunctionDeclaration, Tool, ToolConfig, and FunctionCallingConfig, along with corresponding tests. Overall, the changes seem well-structured and add significant functionality.

### Summary of Findings
* **Missing Nullable Default Value**: In the `Schema` class, the `nullable` parameter in the static methods (e.g., `Boolean`, `Int`, `Long`) should explicitly default to `false` to ensure consistent behavior and avoid unexpected null values. While the implementation does set the default value to false, it is better to be explicit.
* **Potential Overflow in Schema.Int and Schema.Float**: The documentation for `Schema.Int` and `Schema.Float` mentions potential overflow issues when decoding the values. Consider adding additional validation or error handling to prevent unexpected behavior when the decoded values exceed the expected range.
* **Inconsistent UsageMetadata Handling**: In `UIHandlerAutomated.cs`, the `ValidateUsageMetadata` method now accepts a nullable `UsageMetadata` but still asserts that it has a value. This seems contradictory and could lead to unexpected exceptions if `UsageMetadata` is null. Consider handling the null case more gracefully.
* **Logging Macro Usage**: The logging macro `FIREBASE_LOG_REST_CALLS` is used consistently throughout `GenerativeModel.cs` to log requests and responses. Ensure that this macro is properly defined and controlled to prevent excessive logging in production environments.

### Merge Readiness
The pull request introduces significant new functionality and includes corresponding tests, which is commendable. However, there are a few issues that should be addressed before merging, particularly the nullable default value and potential overflow in Schema.Int and Schema.Float. I am unable to directly approve this pull request, and recommend that others review and approve this code before merging. At a minimum, the high severity issues should be addressed before merging.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T00:56:22Z
user: login: a-maurice
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T01:06:03Z
user: login: a-maurice
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T01:06:11Z
user: login: a-maurice
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T01:08:47Z
user: login: a-maurice
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T01:08:54Z
user: login: a-maurice
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T01:09:00Z
user: login: a-maurice
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T01:09:10Z
user: login: a-maurice
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T01:10:34Z
user: login: jonsimantov
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T23:31:25Z

Review comments:
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![high](https://img.shields.io/badge/priority-high-orange)

The documentation mentions potential overflow issues. Consider adding validation or error handling to prevent unexpected behavior.
created_at: 2025-03-06T00:56:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![high](https://img.shields.io/badge/priority-high-orange)

The documentation mentions potential overflow issues. Consider adding validation or error handling to prevent unexpected behavior.
created_at: 2025-03-06T00:56:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable,
+        format: "float"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a string.
+  /// </summary>
+  /// <param name="description">An optional description of what the string should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
+  /// <param name="format">An optional pattern that values need to adhere to.</param>
   public static Schema String(
       string description = null,
-      bool? nullable = null,
-      string format = null) { throw new NotImplementedException(); }
+      bool nullable = false,
+      StringFormat? format = null) {
+    return new Schema(SchemaType.String,
+        description: description,
+        nullable: nullable,
+        format: format?.Format
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing an object.
+  ///
+  /// This schema instructs the model to produce data of type "Object", which has keys of type
+  /// "String" and values of any other data type (including nested "Objects"s).
+  ///
+  /// **Example:** A `City` could be represented with the following object `Schema`.
+  /// ```
+  /// Schema.Object(properties: new Dictionary() {
+  ///   { "name", Schema.String() },
+  ///   { "population", Schema.Integer() }
+  /// })
+  /// ```
+  /// </summary>
+  /// <param name="properties">The map of the object's property names to their `Schema`s.</param>
+  /// <param name="optionalProperties">The list of optional properties. They must correspond to the keys
+  ///   provided in the `properties` map. By default it's empty, signaling the model that all
+  ///   properties are to be included.</param>
+  /// <param name="description">An optional description of what the object represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Object(
       IDictionary<string, Schema> properties,
-      IEnumerable<string> requiredProperties = null,
+      IEnumerable<string> optionalProperties = null,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
      IEnumerable<string> optionalProperties = null,
      string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable,
+        format: "float"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a string.
+  /// </summary>
+  /// <param name="description">An optional description of what the string should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
+  /// <param name="format">An optional pattern that values need to adhere to.</param>
   public static Schema String(
       string description = null,
-      bool? nullable = null,
-      string format = null) { throw new NotImplementedException(); }
+      bool nullable = false,
+      StringFormat? format = null) {
+    return new Schema(SchemaType.String,
+        description: description,
+        nullable: nullable,
+        format: format?.Format
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing an object.
+  ///
+  /// This schema instructs the model to produce data of type "Object", which has keys of type
+  /// "String" and values of any other data type (including nested "Objects"s).
+  ///
+  /// **Example:** A `City` could be represented with the following object `Schema`.
+  /// ```
+  /// Schema.Object(properties: new Dictionary() {
+  ///   { "name", Schema.String() },
+  ///   { "population", Schema.Integer() }
+  /// })
+  /// ```
+  /// </summary>
+  /// <param name="properties">The map of the object's property names to their `Schema`s.</param>
+  /// <param name="optionalProperties">The list of optional properties. They must correspond to the keys
+  ///   provided in the `properties` map. By default it's empty, signaling the model that all
+  ///   properties are to be included.</param>
+  /// <param name="description">An optional description of what the object represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Object(
       IDictionary<string, Schema> properties,
-      IEnumerable<string> requiredProperties = null,
+      IEnumerable<string> optionalProperties = null,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    if (properties == null) {
+      throw new ArgumentNullException(nameof(properties));
+    }
+
+    if (optionalProperties != null) {
+      // Check for invalid optional properties
+      var invalidOptionalProperties = optionalProperties.Except(properties.Keys).ToList();
+      if (invalidOptionalProperties.Any()) {
+        throw new ArgumentException(
+            "The following optional properties are not present in the properties dictionary: " +
+              $"{string.Join(", ", invalidOptionalProperties)}",
+            nameof(optionalProperties));
+      }
+    }
+
+    // Determine required properties
+    var required =
+        properties.Keys.Except(optionalProperties ?? Enumerable.Empty<string>()).ToList();
+
+    foreach (string key in properties.Keys) {
+      if (optionalProperties == null || !optionalProperties.Contains(key)) {
+        required.Add(key);
+      }
+    }
+
+    return new Schema(SchemaType.Object,
+        description: description,
+        nullable: nullable,
+        properties: properties,
+        requiredProperties: required
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for an array.
+  /// </summary>
+  /// <param name="items">The `Schema` of the elements stored in the array.</param>
+  /// <param name="description">An optional description of what the array represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Array(
       Schema items,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
      Schema items,
      string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
    string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
    string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
    string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable,
+        format: "float"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a string.
+  /// </summary>
+  /// <param name="description">An optional description of what the string should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
+  /// <param name="format">An optional pattern that values need to adhere to.</param>
   public static Schema String(
       string description = null,
-      bool? nullable = null,
-      string format = null) { throw new NotImplementedException(); }
+      bool nullable = false,
+      StringFormat? format = null) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
    string description = null,
      bool nullable = false,
      StringFormat? format = null) {
```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
    string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
    string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable,
+        format: "float"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a string.
+  /// </summary>
+  /// <param name="description">An optional description of what the string should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
+  /// <param name="format">An optional pattern that values need to adhere to.</param>
   public static Schema String(
       string description = null,
-      bool? nullable = null,
-      string format = null) { throw new NotImplementedException(); }
+      bool nullable = false,
+      StringFormat? format = null) {
+    return new Schema(SchemaType.String,
+        description: description,
+        nullable: nullable,
+        format: format?.Format
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing an object.
+  ///
+  /// This schema instructs the model to produce data of type "Object", which has keys of type
+  /// "String" and values of any other data type (including nested "Objects"s).
+  ///
+  /// **Example:** A `City` could be represented with the following object `Schema`.
+  /// ```
+  /// Schema.Object(properties: new Dictionary() {
+  ///   { "name", Schema.String() },
+  ///   { "population", Schema.Integer() }
+  /// })
+  /// ```
+  /// </summary>
+  /// <param name="properties">The map of the object's property names to their `Schema`s.</param>
+  /// <param name="optionalProperties">The list of optional properties. They must correspond to the keys
+  ///   provided in the `properties` map. By default it's empty, signaling the model that all
+  ///   properties are to be included.</param>
+  /// <param name="description">An optional description of what the object represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Object(
       IDictionary<string, Schema> properties,
-      IEnumerable<string> requiredProperties = null,
+      IEnumerable<string> optionalProperties = null,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    if (properties == null) {
+      throw new ArgumentNullException(nameof(properties));
+    }
+
+    if (optionalProperties != null) {
+      // Check for invalid optional properties
+      var invalidOptionalProperties = optionalProperties.Except(properties.Keys).ToList();
+      if (invalidOptionalProperties.Any()) {
+        throw new ArgumentException(
+            "The following optional properties are not present in the properties dictionary: " +
+              $"{string.Join(", ", invalidOptionalProperties)}",
+            nameof(optionalProperties));
+      }
+    }
+
+    // Determine required properties
+    var required =
+        properties.Keys.Except(optionalProperties ?? Enumerable.Empty<string>()).ToList();
+
+    foreach (string key in properties.Keys) {
+      if (optionalProperties == null || !optionalProperties.Contains(key)) {
+        required.Add(key);
+      }
+    }
+
+    return new Schema(SchemaType.Object,
+        description: description,
+        nullable: nullable,
+        properties: properties,
+        requiredProperties: required
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for an array.
+  /// </summary>
+  /// <param name="items">The `Schema` of the elements stored in the array.</param>
+  /// <param name="description">An optional description of what the array represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Array(
       Schema items,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Array,
+        description: description,
+        nullable: nullable,
+        items: items
+      );
+  }
 
+  /// <summary>
+  /// Returns a [Schema] for an enumeration.
+  ///
+  /// For example, the cardinal directions can be represented as:
+  /// ```
+  /// Schema.Enum(new string[]{ "North", "East", "South", "West" }, "Cardinal directions")
+  /// ```
+  /// </summary>
+  /// <param name="values">The list of valid values for this enumeration.</param>
+  /// <param name="description">An optional description of what the enum represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Enum(
       IEnumerable<string> values,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider explicitly defaulting `nullable` to `false` for clarity and consistency.

```c#
      IEnumerable<string> values,
      string description = null,
      bool nullable = false) {
```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -171,6 +171,10 @@ private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
     string bodyJson = ModelContentsToJson(content);
     request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
 
+#if FIREBASE_LOG_REST_CALLS
+    UnityEngine.Debug.Log("Request:\n" + bodyJson);
+#endif
path: vertexai/src/GenerativeModel.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Ensure that the `FIREBASE_LOG_REST_CALLS` macro is properly defined and controlled to prevent excessive logging in production environments.
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,6 +183,11 @@ private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
     response.EnsureSuccessStatusCode();
 
     string result = await response.Content.ReadAsStringAsync();
+
+#if FIREBASE_LOG_REST_CALLS
+    UnityEngine.Debug.Log("Response:\n" + result);
+#endif
path: vertexai/src/GenerativeModel.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Ensure that the `FIREBASE_LOG_REST_CALLS` macro is properly defined and controlled to prevent excessive logging in production environments.
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -193,6 +202,10 @@ private async IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsy
     string bodyJson = ModelContentsToJson(content);
     request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
 
+#if FIREBASE_LOG_REST_CALLS
+    UnityEngine.Debug.Log("Request:\n" + bodyJson);
+#endif
path: vertexai/src/GenerativeModel.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Ensure that the `FIREBASE_LOG_REST_CALLS` macro is properly defined and controlled to prevent excessive logging in production environments.
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -209,6 +222,10 @@ private async IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsy
     while ((line = await reader.ReadLineAsync()) != null) {
       // Only pass along strings that begin with the expected prefix.
       if (line.StartsWith(StreamPrefix)) {
+#if FIREBASE_LOG_REST_CALLS
+        UnityEngine.Debug.Log("Streaming Response:\n" + line);
+#endif
path: vertexai/src/GenerativeModel.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Ensure that the `FIREBASE_LOG_REST_CALLS` macro is properly defined and controlled to prevent excessive logging in production environments.
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -327,14 +427,15 @@ private void ValidateSafetyRating(SafetyRating safetyRating,
     }
 
     // Helper function to validate UsageMetadata.
-    private void ValidateUsageMetadata(UsageMetadata usageMetadata, int promptTokenCount,
+    private void ValidateUsageMetadata(UsageMetadata? usageMetadata, int promptTokenCount,
         int candidatesTokenCount, int totalTokenCount) {
+      Assert("UsageMetadata", usageMetadata.HasValue);
path: vertexai/testapp/Assets/Firebase/Sample/VertexAI/UIHandlerAutomated.cs
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

The `UsageMetadata` is asserted to have a value, but it's nullable. This seems contradictory and could lead to exceptions if `UsageMetadata` is null. Consider handling the null case more gracefully, perhaps by skipping the validation if it's null.

```c#
      Assert("UsageMetadata", usageMetadata.HasValue);

```
created_at: 2025-03-06T00:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
path: vertexai/src/Schema.cs
user: login: a-maurice
body: It is up to the user to do this, since they have to decode it themselves.
created_at: 2025-03-06T01:06:03Z
author_association: COLLABORATOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
path: vertexai/src/Schema.cs
user: login: a-maurice
body: It is up to the user to do this, since they have to decode it themselves.
created_at: 2025-03-06T01:06:11Z
author_association: COLLABORATOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable,
+        format: "float"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a string.
+  /// </summary>
+  /// <param name="description">An optional description of what the string should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
+  /// <param name="format">An optional pattern that values need to adhere to.</param>
   public static Schema String(
       string description = null,
-      bool? nullable = null,
-      string format = null) { throw new NotImplementedException(); }
+      bool nullable = false,
+      StringFormat? format = null) {
+    return new Schema(SchemaType.String,
+        description: description,
+        nullable: nullable,
+        format: format?.Format
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing an object.
+  ///
+  /// This schema instructs the model to produce data of type "Object", which has keys of type
+  /// "String" and values of any other data type (including nested "Objects"s).
+  ///
+  /// **Example:** A `City` could be represented with the following object `Schema`.
+  /// ```
+  /// Schema.Object(properties: new Dictionary() {
+  ///   { "name", Schema.String() },
+  ///   { "population", Schema.Integer() }
+  /// })
+  /// ```
+  /// </summary>
+  /// <param name="properties">The map of the object's property names to their `Schema`s.</param>
+  /// <param name="optionalProperties">The list of optional properties. They must correspond to the keys
+  ///   provided in the `properties` map. By default it's empty, signaling the model that all
+  ///   properties are to be included.</param>
+  /// <param name="description">An optional description of what the object represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Object(
       IDictionary<string, Schema> properties,
-      IEnumerable<string> requiredProperties = null,
+      IEnumerable<string> optionalProperties = null,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: a-maurice
body: It already defaults to false
created_at: 2025-03-06T01:08:47Z
author_association: COLLABORATOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable,
+        format: "float"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a string.
+  /// </summary>
+  /// <param name="description">An optional description of what the string should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
+  /// <param name="format">An optional pattern that values need to adhere to.</param>
   public static Schema String(
       string description = null,
-      bool? nullable = null,
-      string format = null) { throw new NotImplementedException(); }
+      bool nullable = false,
+      StringFormat? format = null) {
+    return new Schema(SchemaType.String,
+        description: description,
+        nullable: nullable,
+        format: format?.Format
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing an object.
+  ///
+  /// This schema instructs the model to produce data of type "Object", which has keys of type
+  /// "String" and values of any other data type (including nested "Objects"s).
+  ///
+  /// **Example:** A `City` could be represented with the following object `Schema`.
+  /// ```
+  /// Schema.Object(properties: new Dictionary() {
+  ///   { "name", Schema.String() },
+  ///   { "population", Schema.Integer() }
+  /// })
+  /// ```
+  /// </summary>
+  /// <param name="properties">The map of the object's property names to their `Schema`s.</param>
+  /// <param name="optionalProperties">The list of optional properties. They must correspond to the keys
+  ///   provided in the `properties` map. By default it's empty, signaling the model that all
+  ///   properties are to be included.</param>
+  /// <param name="description">An optional description of what the object represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Object(
       IDictionary<string, Schema> properties,
-      IEnumerable<string> requiredProperties = null,
+      IEnumerable<string> optionalProperties = null,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    if (properties == null) {
+      throw new ArgumentNullException(nameof(properties));
+    }
+
+    if (optionalProperties != null) {
+      // Check for invalid optional properties
+      var invalidOptionalProperties = optionalProperties.Except(properties.Keys).ToList();
+      if (invalidOptionalProperties.Any()) {
+        throw new ArgumentException(
+            "The following optional properties are not present in the properties dictionary: " +
+              $"{string.Join(", ", invalidOptionalProperties)}",
+            nameof(optionalProperties));
+      }
+    }
+
+    // Determine required properties
+    var required =
+        properties.Keys.Except(optionalProperties ?? Enumerable.Empty<string>()).ToList();
+
+    foreach (string key in properties.Keys) {
+      if (optionalProperties == null || !optionalProperties.Contains(key)) {
+        required.Add(key);
+      }
+    }
+
+    return new Schema(SchemaType.Object,
+        description: description,
+        nullable: nullable,
+        properties: properties,
+        requiredProperties: required
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for an array.
+  /// </summary>
+  /// <param name="items">The `Schema` of the elements stored in the array.</param>
+  /// <param name="description">An optional description of what the array represents.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Array(
       Schema items,
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: a-maurice
body: It already defaults to false
created_at: 2025-03-06T01:08:54Z
author_association: COLLABORATOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: a-maurice
body: It already defaults to false
created_at: 2025-03-06T01:09:00Z
author_association: COLLABORATOR
diff_hunk: @@ -30,65 +41,326 @@ public enum SchemaType {
     Object,
   }
 
+  private string TypeAsString =>
+    Type switch {
+      SchemaType.String => "STRING",
+      SchemaType.Number => "NUMBER",
+      SchemaType.Integer => "INTEGER",
+      SchemaType.Boolean => "BOOLEAN",
+      SchemaType.Array => "ARRAY",
+      SchemaType.Object => "OBJECT",
+      _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, "Invalid SchemaType value")
+    };
+
+  /// <summary>
+  /// Modifiers describing the expected format of a string `Schema`.
+  /// </summary>
+  public readonly struct StringFormat {
+    internal string Format { get; }
+
+    private StringFormat(string format) {
+      Format = format;
+    }
+
+    /// <summary>
+    /// A custom string format.
+    /// </summary>
+    public static StringFormat Custom(string format) {
+      return new StringFormat(format);
+    }
+  }
+
+  /// <summary>
+  /// The data type.
+  /// </summary>
   public SchemaType Type { get; }
+  /// <summary>
+  /// A brief description of the parameter.
+  /// </summary>
   public string Description { get; }
+  /// <summary>
+  /// Indicates if the value may be null.
+  /// </summary>
   public bool? Nullable { get; }
+  /// <summary>
+  /// The format of the data.
+  /// </summary>
   public string Format { get; }
-  public IEnumerable<string> EnumValues { get; }
+
+  private readonly ReadOnlyCollection<string> _enumValues;
+  /// <summary>
+  /// Possible values of the element of type "String" with "enum" format.
+  /// </summary>
+  public IEnumerable<string> EnumValues => _enumValues;
+  /// <summary>
+  /// Schema of the elements of type "Array".
+  /// </summary>
   public Schema Items { get; }
-  public IDictionary<string, Schema> Properties { get; }
-  public IEnumerable<string> RequiredProperties { get; }
 
-  public Schema(
+  /// <summary>
+  /// Properties of type "Object".
+  /// </summary>
+  public IReadOnlyDictionary<string, Schema> Properties { get; }
+  private readonly ReadOnlyCollection<string> _requiredProperties;
+  /// <summary>
+  /// Required properties of type "Object".
+  /// </summary>
+  public IEnumerable<string> RequiredProperties => _requiredProperties;
+
+  private Schema(
       SchemaType type,
       string description = null,
       bool? nullable = null,
       string format = null,
       IEnumerable<string> enumValues = null,
       Schema items = null,
       IDictionary<string, Schema> properties = null,
-      IEnumerable<string> requiredProperties = null) { throw new NotImplementedException(); }
+      IEnumerable<string> requiredProperties = null) {
+    Type = type;
+    Description = description;
+    Nullable = nullable;
+    Format = format;
+    _enumValues = new ReadOnlyCollection<string>(
+        enumValues?.ToList() ?? new List<string>());
+    Items = items;
+    if (properties == null) {
+      Properties = new Dictionary<string, Schema>();
+    } else {
+      Properties = new Dictionary<string, Schema>(properties);
+    }
+    _requiredProperties = new ReadOnlyCollection<string>(
+        requiredProperties?.ToList() ?? new List<string>());
+  }
 
+  /// <summary>
+  /// Returns a `Schema` representing a boolean value.
+  /// </summary>
+  /// <param name="description">An optional description of what the boolean should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Boolean(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Boolean,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 32-bit signed integer number.
+  /// 
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a 32-bit
+  ///   integer, but only guarantees that the value will be an integer. Therefore it's *possible*
+  ///   that decoding it as an `int` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the integer should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Int(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable,
+        format: "int32"
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a 64-bit signed integer number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Long(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Integer,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a double-precision floating-point number.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Double(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
+    return new Schema(SchemaType.Number,
+        description: description,
+        nullable: nullable
+      );
+  }
 
+  /// <summary>
+  /// Returns a `Schema` for a single-precision floating-point number.
+  ///
+  /// **Important:** This `Schema` provides a hint to the model that it should generate a
+  ///   single-precision floating-point number, but only guarantees that the value will be a number.
+  ///   Therefore it's *possible* that decoding it as a `float` could overflow.
+  /// </summary>
+  /// <param name="description">An optional description of what the number should contain or represent.</param>
+  /// <param name="nullable">Indicates whether the value can be `null`. Defaults to `false`.</param>
   public static Schema Float(
       string description = null,
-      bool? nullable = null) { throw new NotImplementedException(); }
+      bool nullable = false) {
path: vertexai/src/Schema.cs
user: login: a-maurice
body: It already defaults to false
created_at: 2025-03-06T01:09:10Z
author_association: COLLABORATOR
diff_hunk: @@ -327,14 +427,15 @@ private void ValidateSafetyRating(SafetyRating safetyRating,
     }
 
     // Helper function to validate UsageMetadata.
-    private void ValidateUsageMetadata(UsageMetadata usageMetadata, int promptTokenCount,
+    private void ValidateUsageMetadata(UsageMetadata? usageMetadata, int promptTokenCount,
         int candidatesTokenCount, int totalTokenCount) {
+      Assert("UsageMetadata", usageMetadata.HasValue);
path: vertexai/testapp/Assets/Firebase/Sample/VertexAI/UIHandlerAutomated.cs
user: login: a-maurice
body: Checking HasValue is a way to check if it is null in C#
created_at: 2025-03-06T01:10:34Z
author_association: COLLABORATOR

PR comments:
user: login: a-maurice
created_at: 2025-03-06T00:52:02Z
body: /gemini summary
/gemini review
author_association: COLLABORATOR
user: login: gemini-code-assist[bot]
created_at: 2025-03-06T00:52:09Z
body: The pull request introduces schemas and function calls to VertexAI, which is a great addition! I've performed a review and have a few suggestions.

*   **Correctness**: The implementation of schemas and function calling seems well-structured, with considerations for different data types and configurations.
*   **Maintainability**: The code is generally readable, with clear naming conventions and documentation. The use of readonly structs is a good choice for immutable data structures.
*   **Testing**: New tests have been added to verify the function calling feature.

Overall, the code quality is good, and the changes seem well-integrated. Here's a summary of the style guide elements referenced in the review:

*   The code adheres to common C# conventions, such as using PascalCase for class and struct names, and camelCase for method parameters.
*   `readonly struct` is used for immutable data structures, which is a good practice for data integrity.
*   Comments are used to document the purpose and usage of different types and methods.

author_association: CONTRIBUTOR