使用 Angular、Firebase AI Logic 和 Gemini 3 构建 AI 驱动的 Alt 文本生成器
Source: Dev.to
概览
在本项目中,我将 Gemini 2.5 升级至 Gemini 3(Pro Preview)并在 Vertex AI 中构建了一个智能的图像 Alt‑Text 生成器。该应用不仅能够生成描述性的替代文字,还会生成标签、推荐以及使用内置 Google Search 工具获取的惊人或冷门事实。推理(“思考”)过程对用户可见,帮助他们了解模型是如何得出答案的。
技术栈
| 组件 | 技术 |
|---|---|
| 前端 | Angular v21 |
| 后端 | Firebase AI Logic(Vertex AI) |
| 模型 | Gemini 3.0 Pro Preview |
| 云服务 | Firebase App Check、Firebase Remote Config |
| 功能 | JSON 结构化输出、思考模式、Google Search 依据 |
结构化输出
模型返回严格格式化的 JSON 对象,这使得在 UI 中渲染数据变得非常直接。
思考模式
Gemini 3.0 默认启用“思考”。可以跟踪思考、输入和输出的 token 使用情况,以了解模型推理的成本。
工具调用
应用使用内置的 Google Search 工具检索依据片段、建议和网络搜索查询。这些信息会包含在模型返回的依据元数据中。
远程配置设置
// src/app/firebase/remote-config.ts
import { initializeApp, FirebaseApp } from 'firebase/app';
import { getRemoteConfig, RemoteConfig } from 'firebase/remote-config';
function createRemoteConfig(firebaseApp: FirebaseApp): RemoteConfig {
const remoteConfig = getRemoteConfig(firebaseApp);
// Default values for Remote Config parameters.
remoteConfig.defaultConfig = {
geminiModelName: 'gemini-3-pro-preview',
vertexAILocation: 'global',
includeThoughts: true,
thinkingBudget: 512,
};
return remoteConfig;
}
配置参数
| 参数 | 描述 | 默认值 |
|---|---|---|
geminiModelName | Gemini 模型标识符 | gemini-3-pro-preview |
vertexAILocation | Vertex AI 所在地区(例如 global) | global |
includeThoughts | 模型是否生成思考过程 | true |
thinkingBudget | 思考阶段的 token 预算 | 512 |
如果应用无法获取远程值,则会回退到这些默认值。
Firebase 启动
// src/app/firebase/bootstrap.ts
import { inject } from '@angular/core';
import { initializeApp } from 'firebase/app';
import { initializeAppCheck, ReCaptchaEnterpriseProvider } from 'firebase/app-check';
import { fetchAndActivate } from 'firebase/remote-config';
import { ConfigService } from './config.service';
import { firebaseConfig } from '../environments/environment';
import { createRemoteConfig } from './remote-config';
export async function bootstrapFirebase(): Promise {
try {
const configService = inject(ConfigService);
const firebaseApp = initializeApp(firebaseConfig.app);
// Protect the AI endpoint from abuse.
initializeAppCheck(firebaseApp, {
provider: new ReCaptchaEnterpriseProvider(firebaseConfig.recaptchaEnterpriseSiteKey),
isTokenAutoRefreshEnabled: true,
});
const remoteConfig = createRemoteConfig(firebaseApp);
await fetchAndActivate(remoteConfig);
configService.loadConfig(firebaseApp, remoteConfig);
} catch (err) {
console.error('Remote Config fetch failed', err);
throw err;
}
}
应用初始化
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAppInitializer } from '@angular/core';
import { AppComponent } from './app/app.component';
import { ApplicationConfig } from '@angular/core';
import { bootstrapFirebase } from './app/firebase/bootstrap';
const appConfig: ApplicationConfig = {
providers: [
provideAppInitializer(() => bootstrapFirebase()),
],
};
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));
图像分析模式
// src/app/models/image-analysis.schema.ts
import { Schema } from '@angular/fire/ai';
export const ImageAnalysisSchema = Schema.object({
properties: {
tags: Schema.array({ items: Schema.string() }),
alternativeText: Schema.string(),
recommendations: Schema.array({
items: Schema.object({
properties: {
id: Schema.integer(),
text: Schema.string(),
reason: Schema.string(),
},
}),
}),
fact: Schema.string(),
},
});
生成式 AI 模型设置
// src/app/firebase/ai-model.ts
import { inject } from '@angular/core';
import { getAI, getGenerativeModel, VertexAIBackend } from '@angular/fire/ai';
import { RemoteConfig, getValue } from 'firebase/remote-config';
import { FirebaseApp } from 'firebase/app';
import { ImageAnalysisSchema } from '../models/image-analysis.schema';
import { AI_MODEL } from '../tokens';
function getGenerativeAIModel(firebaseApp: FirebaseApp, remoteConfig: RemoteConfig) {
const model = getValue(remoteConfig, 'geminiModelName').asString();
const vertexAILocation = getValue(remoteConfig, 'vertexAILocation').asString();
const includeThoughts = getValue(remoteConfig, 'includeThoughts').asBoolean();
const thinkingBudget = getValue(remoteConfig, 'thinkingBudget').asNumber();
const ai = getAI(firebaseApp, { backend: new VertexAIBackend(vertexAILocation) });
return getGenerativeModel(ai, {
model,
generationConfig: {
responseMimeType: 'application/json',
responseSchema: ImageAnalysisSchema,
thinkingConfig: {
includeThoughts,
thinkingBudget,
},
},
tools: [{ googleSearch: {} }],
});
}
依赖注入提供者
// src/app/firebase/ai-provider.ts
import { makeEnvironmentProviders, inject } from '@angular/core';
import { ConfigService } from './config.service';
import { AI_MODEL } from '../tokens';
import { getGenerativeAIModel } from './ai-model';
export function provideFirebase() {
return makeEnvironmentProviders([
{
provide: AI_MODEL,
useFactory: () => {
const configService = inject(ConfigService);
if (!configService.remoteConfig) {
throw new Error('Remote config does not exist.');
}
if (!configService.firebaseApp) {
throw new Error('Firebase App does not exist');
}
return getGenerativeAIModel(configService.firebaseApp, configService.remoteConfig);
},
},
]);
}
将提供者添加到应用配置中:
// src/main.ts (excerpt)
import { provideFirebase } from './app/firebase/ai-provider';
const appConfig: ApplicationConfig = {
providers: [
provideAppInitializer(() => bootstrapFirebase()),
provideFirebase(),
],
};
Angular 服务用于 Alt‑Text 生成
// src/app/services/firebase.service.ts
import { Injectable, inject } from '@angular/core';
import { AI_MODEL } from '../tokens';
import { fileToGenerativePart } from '@angular/fire/ai';
import { ImageAnalysisResponse } from '../models/image-analysis.model';
@Injectable({ providedIn: 'root' })
export class FirebaseService {
private aiModel = inject(AI_MODEL);
async generateAltText(image: File): Promise {
const imagePart = await fileToGenerativePart(image);
const prompt = `
You are asked to perform four tasks:
1. Generate an alternative text for the image (max 125 characters).
2. Generate at least 3 tags describing the image.
3. Based on the alternative text and tags, suggest ways to make the image more interesting.
4. Search for a surprising or obscure fact that interconnects the tags. If no direct link exists, find a conceptual link.
`;
const result = await this.aiModel.generateContent({
contents: [{ role: 'user', parts: [{ text: prompt }, imagePart] }],
});
if (result?.response) {
const response = result.response;
const thought = response.thoughtSummary?.() ?? '';
const rawText = response.text?.() ?? '';
// Strip possible markdown fences.
const cleaned = rawText.replace(/```json\s*|```/g, '').trim();
const parsed = JSON.parse(cleaned) as ImageAnalysisResponse;
const tokenUsage = this.getTokenUsage(response.usageMetadata);
return {
...parsed,
thought,
tokenUsage,
};
}
throw new Error('No response from Gemini model');
}
private getTokenUsage(usage: any) {
// Implementation omitted for brevity – extracts input/output token counts.
return usage;
}
}
该服务:
- 将上传的图像转换为 Vertex AI 可接受的格式。
- 发送多步骤提示,指示 Gemini 3 生成 alt 文本、标签、推荐以及事实。
- 解析 JSON 响应,提取“思考”摘要,并返回 token 使用元数据。