使用 Angular、Firebase AI Logic 和 Gemini 3 构建 AI 驱动的 Alt 文本生成器

发布: (2025年12月11日 GMT+8 22:46)
6 min read
原文: Dev.to

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;
}

配置参数

参数描述默认值
geminiModelNameGemini 模型标识符gemini-3-pro-preview
vertexAILocationVertex AI 所在地区(例如 globalglobal
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;
  }
}

该服务:

  1. 将上传的图像转换为 Vertex AI 可接受的格式。
  2. 发送多步骤提示,指示 Gemini 3 生成 alt 文本、标签、推荐以及事实。
  3. 解析 JSON 响应,提取“思考”摘要,并返回 token 使用元数据。
Back to Blog

相关文章

阅读更多 »