Skip to content

Structured responses with ZIO Schema

Define a case class deriving a ZIO Schema and the client will instruct the model to return JSON matching that shape, then decode the response into your type. The schema is automatically converted to the OpenAPI schema format expected by the Vertex AI API, so you get type-safe structured output without writing the schema by hand:

scala
import zio.*
import zio.schema.*

import com.anymindgroup.gcp.aiplatform.ExpressModelClient
import com.anymindgroup.gcp.auth.defaultAccessTokenBackend
import com.github.plokhotnyuk.jsoniter_scala as json

object express_vertex_ai_schema extends ZIOAppDefault:
  def run = for
    authedBackend <- defaultAccessTokenBackend()
    express        = ExpressModelClient(
                backend = authedBackend,
                projectsId = "my-gcp-project",
                locationsId = "global",
                publishersId = "google",
                modelsId = "gemini-3.5-flash",
              )
    joke <- express.generate[Joke]("Tell me a joke")
    _     = println("Joke: " + joke.setup + " \u2014 " + joke.punchline)
  yield ()

  case class Joke(setup: String, punchline: String) derives Schema
  given json.core.JsonValueCodec[Joke] = json.macros.JsonCodecMaker.make

Custom requests

For more control over the conversation you can build a GenerateContentRequest yourself — supplying multiple messages, system instructions and a response schema — and pass it to generate:

scala
import zio.*
import zio.schema.*

import com.anymindgroup.gcp.aiplatform.*
import com.anymindgroup.gcp.aiplatform.v1.schemas.*
import com.anymindgroup.gcp.auth.defaultAccessTokenBackend
import com.github.plokhotnyuk.jsoniter_scala as json

object express_vertex_ai_custom_req extends ZIOAppDefault:
  def run = for
    authedBackend <- defaultAccessTokenBackend()
    express        = ExpressModelClient(
                backend = authedBackend,
                projectsId = "my-gcp-project",
                locationsId = "global",
                publishersId = "google",
                modelsId = "gemini-3.5-flash",
              )

    msg = GenerateContentRequest(
            contents = Chunk(
              GoogleCloudAiplatformV1Content(
                parts = Chunk(GoogleCloudAiplatformV1Part(text = Some("hello, my name is John"))),
                role = Some("user"),
              ),
              GoogleCloudAiplatformV1Content(
                parts = Chunk(GoogleCloudAiplatformV1Part(text = Some("I'm 24 years old"))),
                role = Some("user"),
              ),
              GoogleCloudAiplatformV1Content(
                parts = Chunk(GoogleCloudAiplatformV1Part(text = Some("I am from the US"))),
                role = Some("user"),
              ),
            ),
            responseSchema = Schema[Person],
          ).withSystemInstructions("extract person's information from the conversation")

    person <- express.generate[Person](msg)
    _       = println("Name: " + person.name)
    _       = println("Age: " + person.age)
    _       = println("Country: " + person.country)
  yield ()

  case class Person(name: Option[String], age: Option[Int], country: Option[String]) derives Schema
  given json.core.JsonValueCodec[Person] = json.macros.JsonCodecMaker.make