https://www.boundaryml.com logo
Image Output
# help
m
Hey, I was wondering, are there any examples of implementing image output? I am trying to implement nano-banana (or
gemini-2.5-flash-image
) and this was my setup:
Copy code
client<llm> AdsGeminiImageEdit {
  provider vertex-ai
  retry_policy Exponential
  options {
    model "gemini-2.5-flash-image"
    location "europe-west1"
    credentials env.GOOGLE_PROJECT_SECRET
    generationConfig {
      maxOutputTokens 32768
      temperature 1
    }
    media_url_handler {
      image "send_url_add_mime_type"
    }
  }
}

function EditAdImage(
  prompt: string,
  referenceImages: image[],
) -> image {
  client AdsGeminiImageEdit
  prompt #"
    You are an expert marketing image editor. ...

    {{ _.role("user") }}
    Prompt:
    {{ prompt }}

    Reference Images:
    {% for image in referenceImages %}
    {{ image }}
    {% endfor %}
  "#
}
FIrst of all, interesting behavior if " {{ ctx.output_format }}" is written at the end of the prompt:
Copy code
thread 'tokio-runtime-worker' panicked at library/alloc/src/fmt.rs:650:14:
a formatting trait implementation returned an error when the underlying stream did not: Error
2025-11-09T01:19:41.316 [BAML ERROR]
But after removing it, I get this instead:
Copy code
...
    ...chGvN9nEAAAAASUVORK5CYII="}}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":1621,"candidatesTokenCount":1290,"totalTokenCount":2911,"trafficType":"ON_DEMAND","promptTokensDetails":[{"modality":"TEXT","tokenCount":1105},{"modality":"IMAGE","tokenCount":516}],"candidatesTokensDetails":[{"modality":"IMAGE","tokenCount":1290}]},"modelVersion":"gemini-2.5-flash-image","createTime":"2025-11-09T00:37:28.452545Z","responseId":"SOIPacHPG6_h_c8Prr-Z8AM"}
    Caused by:
        missing field `text`
I am running
baml-cli 0.213.0
g
Hi! image outputs (media outputs in general) aren’t on the roadmap, unfortunately, since there’s not yet a cross-provider standard for producing them. Multimodal inputs are standard enough that we can provide media, but only as an input type. For Gemini flash specifically, you may be able to capture the image as base64 characters, if you model some of that raw response you saw as a BAML class. The panic/error you saw; noted, we will get that fixed! It should be a regular BAML compiler error telling you that image is not allowed in return position - we just missed that.
Would you like some help doing the Gemini-specific thing? I could walk you through that if you like.
m
Thank you for the quick response! I think I am already handling image inputs correctly (I got it to work with image analysis), but I was primarily interested in image outputs. Is there any workaround that I can do for keeping image outputs from AI APIs (at the end of the day) on BAML side? or will that part be impossible to migrate?
g
Oh, it’s possible! I think that model uses the chat responses API, so you just have to find out how the model encodes the image, then build a BAML class to mirror that structure.
First, change your output type to ‘string’ and print the string to see the output shape (it should look like json, with a large base64 text blob under some field)
m
Oh I see what you mean. The output type to be string and just parse it. Thanks I will try that
Actually I wasn't able to set it up... could you help me out? I get this:
Copy code
Unspecified error code: 2 Failed to parse into a response accepted by baml_runtime::internal::llm_client::primitive::vertex::types::VertexResponse: {"candidates":[{"content":{"role":"model","parts":[{"inlineData":{"mimeType":"image/png","data":"iVBORw0KGgoAA ... I="}}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":608,"candidatesTokenCount":1290,"totalTokenCount":1898,"trafficType":"ON_DEMAND","promptTokensDetails":[{"modality":"IMAGE","tokenCount":516},{"modality":"TEXT","tokenCount":92}],"candidatesTokensDetails":[{"modality":"IMAGE","tokenCount":1290}]},"modelVersion":"gemini-2.5-flash-image","createTime":"2025-11-09T13:10:45.558212Z","responseId":"1ZIQaYSJIuWzmPUPwLHPyQU"}


 Caused by:
    missing field `text`

Request options: {"generationConfig":{"maxOutputTokens":32768,"temperature":1,"responseModalities":["TEXT","IMAGE"]}}
I tried to remove text modality, still same error. I did actually see the text for a split second in the BAML Playground, but after that it just errored. I was trying to figure this out with Codex and got this answer:
Copy code
What you’re seeing now confirms the Vertex provider in the current BAML runtime can’t handle inlineData at all. Even though we force the model to add a text summary, the response still contains a second part with just inlineData. The runtime tries to deserialize each part into a struct that requires a text field, hits the image part, and throws missing field 'text' before your function ever sees the payload.
It recommends me to
Copy code
Bypass BAML’s LLM client and call Vertex directly (a small proxy you can hit from BAML).
but I have looked around and didn't see any support of custom client or something like that.
I tried setting it up like this:
Copy code
client<llm> AdsGeminiImageEdit {
  provider vertex-ai
//   retry_policy Exponential
  options {
    model "gemini-2.5-flash-image"
    location "europe-west1"
    credentials env.GOOGLE_PROJECT_SECRET
    generationConfig {
      maxOutputTokens 32768
      temperature 1
      responseModalities ["TEXT", "IMAGE"]
    }
    media_url_handler {
      image "send_url_add_mime_type"
    }
  }
}

class GeminiInlineData {
  mimeType string?
  data string?
}

class GeminiPart {
  inlineData GeminiInlineData?
  text string?
}

class GeminiContent {
  role string?
  parts GeminiPart[]
}

class GeminiCandidate {
  content GeminiContent?
  finishReason string?
  index int?
}

class GeminiTokensDetail {
  modality string?
  tokenCount int?
}

class GeminiUsageMetadata {
  promptTokenCount int?
  candidatesTokenCount int?
  totalTokenCount int?
  trafficType string?
  promptTokensDetails GeminiTokensDetail[]?
  candidatesTokensDetails GeminiTokensDetail[]?
}

class GeminiImageEditResponse {
  candidates GeminiCandidate[]?
  usageMetadata GeminiUsageMetadata?
  modelVersion string?
  createTime string?
  responseId string?
}

function EditAdImage(
  prompt: string,
  referenceImages: image[],
) -> GeminiImageEditResponse {
  client AdsGeminiImageEdit
g
Ahhh, makes sense. Because Gemini is adding an extra field to the response outside of the chat/completions “band”, our runtime doesn’t recognize the response and throws an error. We’re still not out of ideas for BAMl though! There’s something called the Modular API that will allow you to make your request as you’re already doing, and receive a response in raw json from the provider. I believe this bypasses both of our parsers: the protocol layer JSON parsing that is failing for you, and the LLM layer. From your application code you can dig through the raw response json and retrieve the image data. The trade off is that your function can’t be called from the playground, because the playground runs the full api (not the modular api). Does all that make sense?
m
Hmm I will try it out next week probably. Will this Modular API work for any endpoint? because we are calling various image/video models directly, and also through third-party providers such as Replicate or Fal. Ideally, I'd be able to migrate all AI-logic to BAML
g
https://docs.boundaryml.com/guide/baml-advanced/modular-api You will get an HTTP request with the Modular API, targeting chat/completions. As long as your LLM provider endpoint is serving the completions API, that request should “just work”. And if not, you could fashion whatever custom request is needed out of the rendered prompt that Modular API gives you.
If that sounds a little vague, please let me know, especially if you try it out or get stuck, or if you want to hop on a call and try it together.
m
Thanks! I'll keep this is my backlog for now