generate-images-with-firebase-ai
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGenerating images with Firebase AI Logic
使用Firebase AI Logic生成图像
Gemini image models return interleaved text and image parts from one call. The
response is a sequence to walk, not a string to read.
A request that comes back empty is usually a configuration problem rather than
a bug in your code, so section 1 covers the three settings that cause it.
Gemini图像模型单次调用会返回交错的文本和图像片段。响应是需要遍历的序列,而非可直接读取的字符串。
返回空结果的请求通常是配置问题而非代码bug,因此第1节会介绍导致该问题的三个配置项。
1. Three things that block the very first call
1. 阻碍首次调用的三个常见问题
A first call that returns an error, an empty response, or a 403 is almost
always one of these rather than your Dart. Rule them out before you debug code.
Billing. Image generation has no free tier. On a Spark-plan project the
image models return for , so
the first request fails on quota having made zero requests. Text models do work
on Spark, which means "my other Gemini call works" proves nothing. Upgrade to
Blaze, then verify the current limits rather than trusting this note:
limit: 0generate_content_free_tier_requestsbash
gcloud services quota list --service=generativelanguage.googleapis.com --consumer=projects/YOUR_PROJECT_IDApp Check. Firebase AI Logic enforces App Check when the project has it
turned on. Otherwise the endpoint is open to anyone who extracts your config
from the shipped client, and that config is public by design. Anything
reachable from a device you do not control needs App Check. Debug builds
attest with a debug provider, release builds with a real one. Web has a
specific trap that costs an afternoon, described in .
references/setup.mdresponseModalities首次调用返回错误、空响应或403状态码,几乎都是由以下问题导致,而非你的Dart代码有问题。在调试代码前请先排查这些因素。
计费问题。 图像生成没有免费额度。在Spark方案的项目中,图像模型的会返回,因此即使从未发起过请求,首次调用也会因配额不足失败。文本模型在Spark方案下可以正常使用,因此“我的其他Gemini调用正常”并不能说明问题。请升级到Blaze方案,然后验证当前配额限制,不要仅依赖本文说明:
generate_content_free_tier_requestslimit: 0bash
gcloud services quota list --service=generativelanguage.googleapis.com --consumer=projects/YOUR_PROJECT_IDApp Check问题。 如果项目开启了App Check,Firebase AI Logic会强制校验。否则,任何从发布的客户端中提取到配置的人都可以访问该接口,而该配置本身就是设计为公开的。所有从不受你控制的设备访问的服务都需要启用App Check。调试构建版本使用调试提供方进行认证,正式构建版本使用正式提供方。Web端有一个容易耗费一下午时间的典型坑,在中有说明。
references/setup.mdresponseModalities2. The minimal call that works
2. 最小可用调用示例
dart
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-3.1-flash-image',
generationConfig: GenerationConfig(
responseModalities: [
ResponseModalities.text,
ResponseModalities.image,
],
imageConfig: ImageConfig(
aspectRatio: ImageAspectRatio.landscape16x9,
imageSize: ImageSize.size2K,
),
),
);
final response = await model.generateContent([
Content.multi([
TextPart(prompt),
InlineDataPart('image/jpeg', selfieBytes), // omit for text-to-image
]),
]);FirebaseAI.googleAI()FirebaseAI.vertexAI()Do not pass or to . Both parameters are
deprecated in current ; the instance resolves them from the
on its own.
appCheck:auth:googleAI()firebase_aiFirebaseAppFor model IDs, aspect-ratio and size enums, and when Imagen beats Gemini, read
.
references/models.mddart
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-3.1-flash-image',
generationConfig: GenerationConfig(
responseModalities: [
ResponseModalities.text,
ResponseModalities.image,
],
imageConfig: ImageConfig(
aspectRatio: ImageAspectRatio.landscape16x9,
imageSize: ImageSize.size2K,
),
),
);
final response = await model.generateContent([
Content.multi([
TextPart(prompt),
InlineDataPart('image/jpeg', selfieBytes), // omit for text-to-image
]),
]);FirebaseAI.googleAI()FirebaseAI.vertexAI()不要向传递或参数。这两个参数在当前版本的中已被弃用;实例会自动从中解析这些配置。
googleAI()appCheck:auth:firebase_aiFirebaseApp关于模型ID、宽高比和尺寸枚举,以及Imagen优于Gemini的场景,请阅读。
references/models.md3. Reading the response
3. 处理响应内容
The convenience accessor does not capture the full sequence, so walk
the parts directly. Pattern-match on the part type, which keeps the switch
correct when the SDK adds new part types:
.textdart
Uint8List? image;
final buffer = StringBuffer();
for (final part in candidate.content.parts) {
switch (part) {
case InlineDataPart(:final bytes):
image ??= bytes; // first image wins
case TextPart(:final text):
buffer.write(text);
default:
break;
}
}Keep the interpretation of a response in its own pure function taking a
. , , and are all
publicly constructible, so that function is testable with real SDK types, no
Firebase and no test doubles. It is the one seam in this stack that unit tests
genuinely reach.
CandidateCandidateContentTextPartInlineDataPart便捷的访问器无法获取完整的响应序列,因此请直接遍历所有片段。对片段类型进行模式匹配,这样当SDK新增片段类型时,switch语句依然能正确处理:
.textdart
Uint8List? image;
final buffer = StringBuffer();
for (final part in candidate.content.parts) {
switch (part) {
case InlineDataPart(:final bytes):
image ??= bytes; // first image wins
case TextPart(:final text):
buffer.write(text);
default:
break;
}
}请将响应解析逻辑封装在单独的纯函数中,接收一个类型的参数。、、和都支持公开构造,因此该函数可以使用真实的SDK类型进行测试,不需要依赖Firebase,也不需要测试替身。这是整个技术栈中单元测试真正能够覆盖的边界。
CandidateCandidateContentTextPartInlineDataPartWhen no image comes back
未返回图像的情况
An empty result surfaces as a blank error and reads like a client bug, which
sends people debugging the wrong half of the system. The response carries the
reason, so report it. In order:
| Check | Where | Means |
|---|---|---|
| before candidates | Your input was rejected. Read |
| n/a | Nothing generated at all. |
| on the candidate | The model stopped: safety, recitation, or a token limit. |
| No image but text present | after walking parts | It answered in prose instead of drawing. Usually a prompt problem. |
| Everything empty, no reason | n/a | Say so plainly and let the user retry. This happens intermittently. |
Image-only responses, with no text at all, also happen intermittently on
prompts that reliably return both. If you ask for text alongside the image,
treat its absence as normal and degrade instead of throwing.
空结果通常表现为无明确信息的错误,看起来像是客户端bug,会导致开发者去排查错误的方向。响应中包含了失败原因,请按以下顺序排查:
| 检查项 | 位置 | 含义 |
|---|---|---|
| candidates之前 | 你的输入被拒绝。请同时查看 |
| 无 | 完全没有生成任何内容。 |
| candidate对象上 | 模型停止生成:原因可能是安全策略、复述限制或token上限。 |
| 无图像但有文本 | 遍历片段后 | 模型用文字回答而非生成图像,通常是提示词问题。 |
| 全部为空且无原因 | 无 | 直接告知用户该情况并让其重试,这种问题会间歇性出现。 |
对于原本会同时返回文本和图像的提示词,偶尔也会出现仅返回图像、完全没有文本的情况。如果你要求同时返回文本和图像,请将文本缺失视为正常情况,做降级处理而非抛出异常。
4. Sending a user photo
4. 传入用户照片
Downscale before you send. base64-encodes the bytes
synchronously on the main isolate, so a full-size phone photo freezes the UI
for seconds while the request is built. Gemini downsamples large images anyway,
so you pay for detail that is then discarded. Scale at pick time rather than
after:
InlineDataPart.toJson()dart
final file = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 1280,
maxHeight: 1280,
imageQuality: 85,
);Size the cap to your subject rather than to a habit. 1280px on the long edge
holds a face or a single figure comfortably, while fine texture, legible text
in the source, or a wide scene the model has to read across will want more.
Match to what the picker actually returned.
mimeType发送前先缩小图片尺寸。会在主隔离(main isolate)中同步对字节进行base64编码,因此全尺寸的手机照片会在构建请求时导致UI卡顿数秒。况且Gemini本身会对大图片进行下采样,因此你为不会被用到的细节付出了不必要的代价。请在选择图片时就调整尺寸,而非之后再处理:
InlineDataPart.toJson()dart
final file = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 1280,
maxHeight: 1280,
imageQuality: 85,
);请根据你的拍摄对象设置尺寸上限,而非凭习惯固定值。长边1280px足以清晰呈现人脸或单个人物,但如果需要识别精细纹理、源图中清晰的文字,或是模型需要解读的宽幅场景,则需要更高的分辨率。请将设置为与选择器实际返回的类型一致。
mimeType5. Sizing the result
5. 调整输出结果尺寸
Use when your ratio is one of the supported enum values, because
it is a real constraint. Asking for a ratio in the prompt text is a suggestion
the model frequently ignores. If you need a ratio the enum does not offer, 2:1
for instance, measure what came back and lay out from the measurement:
ImageConfigdart
final descriptor = await ui.ImageDescriptor.encoded(
await ui.ImmutableBuffer.fromUint8List(bytes),
);
final size = ui.Size(descriptor.width.toDouble(), descriptor.height.toDouble());
descriptor.dispose();Wrap it so a failure returns null instead of throwing. An image you cannot
measure is still an image worth showing.
如果你需要的宽高比在支持的枚举值范围内,请使用,因为这是硬性约束。在提示词文本中要求宽高比只是建议,模型经常会忽略。如果你需要枚举不支持的比例(比如2:1),请先测量返回图像的尺寸,再根据测量结果进行布局:
ImageConfigdart
final descriptor = await ui.ImageDescriptor.encoded(
await ui.ImmutableBuffer.fromUint8List(bytes),
);
final size = ui.Size(descriptor.width.toDouble(), descriptor.height.toDouble());
descriptor.dispose();请对这段逻辑做异常封装,失败时返回null而非抛出异常。即使无法测量尺寸的图像依然是值得展示的。
6. Getting text and image from one call
6. 单次调用同时获取文本和图像
You often want machine-readable data about the image the model just drew, such
as a caption or the names it invented. Once that data is pixels your app cannot
read it, so ask for it as text in the same call and reconcile the two halves in
the prompt: "the title painted into the image must match the JSON character for
character."
Parse that text defensively. Asked for a fenced ```json block, the model will
across one session return fenced JSON, bare JSON, prose-wrapped JSON, and
nothing at all. Try the fence, fall back to the first balanced , and
return null instead of throwing.
{...}你通常需要获取模型生成图像的机器可读数据,比如标题或它生成的名称。一旦这些数据变成像素,你的应用就无法读取了,因此请在同一次调用中要求以文本形式返回这些数据,并在提示词中明确两者的一致性:“绘制在图像中的标题必须与JSON中的内容逐字符完全一致”。
请以防御性的方式解析该文本。即使要求模型返回用```包裹的json块,在同一会话中,模型也可能返回带包裹的JSON、裸JSON、被散文包裹的JSON,甚至什么都不返回。请先尝试解析包裹的JSON,失败则回退到查找第一个配对的,解析失败时返回null而非抛出异常。
{...}7. Prompting
7. 提示词编写
The failures here are not code failures, and no unit test reaches them. The
recurring ones have specific fixes worth knowing before you write the first
prompt: placeholder words painted literally into the artwork, the source
photo's clothing surviving an outfit change, text spelled differently in two
places, and panels that do not share a background. Read
.
references/prompting.md这里的失败不是代码问题,单元测试也无法覆盖。在编写第一个提示词之前,有必要了解一些常见问题的特定解决方法:占位词被直接绘制到图像中、源照片的衣服在换装后依然保留、两处文本拼写不一致、多面板图像背景不统一等。请阅读。
references/prompting.md8. Deciding what to test
8. 确定测试范围
The seam is at your boundary. Unit tests protect your interpretation of a
response and nothing past it, so a green suite says nothing about whether the
app produces a good image. covers the layers and what
each one cannot reach.
references/testing.md测试边界在你的代码边界处。单元测试只能覆盖你对响应的解析逻辑,无法覆盖更外层的内容,因此测试全绿并不能保证应用生成的图像质量达标。介绍了不同的测试层级以及各自无法覆盖的范围。
references/testing.mdExternal documentation
外部文档
Everything here is a summary that will drift. When a detail matters, confirm it
at the source.
- Firebase AI Logic docs, including get started and generate images with Gemini
- , the Dart SDK. Its source is the fastest way to settle an API question:
firebase_ai~/.pub-cache/hosted/pub.dev/firebase_ai-*/lib/src/ - , which supplies the
image_pickercall in section 4. A third-party choice you can swap.pickImage - Patrol, a third-party E2E framework by
LeanCode, discussed in
references/testing.md
本文所有内容都是总结,可能会随时间过时。如果涉及关键细节,请以官方源文档为准。
- Firebase AI Logic 文档,包括入门指南和使用Gemini生成图像
- :Dart SDK。查看其源码是解决API问题最快的方式:
firebase_ai~/.pub-cache/hosted/pub.dev/firebase_ai-*/lib/src/ - :第4节中
image_picker调用的来源,是可替换的第三方库。pickImage - Patrol:LeanCode开发的第三方E2E测试框架,在中有讨论。
references/testing.md
Reference files
参考文件
- : Firebase console path, provider choice, billing, and the web App Check debug-token trap
references/setup.md - : model IDs, aspect-ratio and size enums, Gemini vs Imagen
references/models.md - : the mistakes image prompts actually make, and the phrasings that fix them
references/prompting.md - : unit, golden, e2e and eval layers, and what each cannot reach
references/testing.md
- :Firebase控制台路径、提供方选择、计费,以及Web端App Check调试令牌的坑
references/setup.md - :模型ID、宽高比和尺寸枚举、Gemini与Imagen的对比
references/models.md - :图像提示词的常见错误及修正措辞
references/prompting.md - :单元测试、快照测试、E2E测试和评估层级,以及各自的覆盖局限
references/testing.md