Technology

青春Gemini Pro野郎はChatGPTの夢を見ない ~嘘だらけのメディアFiction Timesの技術紹介~

こんにちは。virapture株式会社でCEOしながらラグナロク株式会社でもCKOとして働いている@mogmetです。

先程クソアプリの紹介記事を書きました。

こちらの記事では、技術の力で日常にユーモアを注入するアプリFiction Timesの開発秘話をご紹介します。

Flutter、Firebase、そしてGemini ProというAIまで、最新技術を駆使して構築されたこのプロジェクトは、ただのクソアプリとは一線を画します。
(そんなことはないか)

作ったきっかけ

速攻でGeminiProのアプリを作った方のこんなつぶやきを見受けました。

それなのであればGeminiProちゃんにはハルシネーション(AIが事実に基づかない情報を生成する現象)という名の夢を見てもらおうということで嘘だらけのメディアをGemini Proに作ってもらうことにしました。
(タイトル回収)

構成

このサービスは下記のような構成となっております。

  • Frontend: Flutter web
  • Backend: CloudFunctions
  • Database: firestore
  • Hosting: firebase hosting
  • AI: gemeni pro

これらの構成についてそれぞれ紹介させていただきます。

Frontend: Flutter Web

まずは、ユーザーインターフェースです。
フロントエンドにはFlutter Webを採用しました。

なぜか?

それは、一貫したユーザーエクスペリエンスをモバイルとWebの両方で提供できるからです。
Hot Reloadが支える迅速な開発サイクルで、ユーザーがまさに求めるユーモア溢れるインタラクションを瞬時に形にしました。

ちなみにUIの殆どはChat GPTに考えてもらったのであまり作り込んでないです。

CleanShot 2023-12-25 at 11.03.48.png

Backend: Cloud Functions

バックエンドでは、FirebaseのCloud Functionsを活用しています。
サーバレスアーキテクチャを採用することで、インフラ管理の負担を軽減し、開発者がビジネスロジックに集中できる環境を実現しました。
今回のケースでは、タレコミによるとarticleが生成され、それをトリガーとして、Cloud Functionsが起動してGeminiProにニュースを考えてもらい、articleを更新することで記事を生成しています。

実装をするためには、先にAPI Keyの取得をします。

Gemini Proへの通信はライブラリを使うと簡単に実装することができます。

    const ai = new GoogleGenerativeAI(GEMINI_API_KEY)
    const model = ai.getGenerativeModel({model: 'gemini-pro'})
    // $sourceにはユーザからのタレコミ情報が入る
    const prompt = `あなたは情報提供者から寄せられた情報をもとにユーモラスな記事を書き上げる編集長です。ユーザからの情報は文字数が少ないこともありますが、その情報を元に勝手に想像を膨らませて800文字前後の空想の記事を書いてください。レスポンスはJSON形式で、titleとbodyの要素を元に返却してください。下記が提供情報です。
\`\`\`
${source}
\`\`\`
`
    const response = await model.generateContentStream(prompt)
    let responseText = ''
    for await (const chunk of response.stream) {
      responseText += chunk.text() // JSONで値がはいってくる
    }

ただ、markdownと、改行入りのレスポンスが返ってくるのでJSONになるように整形の必要がありました。

function createJson(responseText: string) {
  const jsonMatch = responseText.match(/{[\s\S]*}/s)
  if (!jsonMatch) {
    logger.error(text)
    throw new Error('Invalid JSON response')
  }
  try {
    // ""内の改行を\nに変換する
    const data = jsonMatch[0].replace(/"([^"]*)"/g, (match) => {
      return match.replace(/\n/g, '\\n')
    })
    return JSON.parse(data)
  } catch (e) {
    logger.error(jsonMatch[0])
    throw e
  }
}

Database: Firestore

データベースにはFirestoreを選択。

リアルタイムでのデータ同期と、強力なクエリ言語により、非同期的なデータフローをスムーズに処理。
Firestoreのスケーラブルな性質は、Fiction Timesが急成長するユーザーベースに対応する上で欠かせません。

Firestoreの大きな特徴の一つであるListenerの機能はCloud Functionsによるarticleの上書きを待機する為に使用しています。
これにより、ネタ投稿後、記事更新を待機して記事詳細ページに遷移するような処理を実現しています。article_repository.dart

  Future<Article> createArticle({
    required String reporter,
    required String source,
  }) async {
    final article = Article(
      reporter: reporter,
      source: source,
      body: '',
      title: '',
      createdAt: const FirestoreTimestamp.serverTimestamp(),
      updatedAt: const FirestoreTimestamp.serverTimestamp(),
    );
    final reference = getCollectionReference().doc();
    await reference.set(article);
    final completer = Completer<Article>();
    StreamSubscription<DocumentSnapshot>? subscription;
    subscription = reference.snapshots().listen(
      (snapshot) {
        final updatedArticle = snapshot.data();
        if (updatedArticle == null || snapshot.metadata.hasPendingWrites) {
          return;
        }
        if ((updatedArticle.error.isNotEmpty) ||
            (updatedArticle.body.isNotEmpty &&
                updatedArticle.title.isNotEmpty)) {
          // エラーがあるか、updateされたら処理を終える
          completer.complete(updatedArticle);
          subscription?.cancel();
        }
      },
      onError: completer.completeError,
    );
    // Return the future that completes when the article is updated
    return completer.future;
  }

Hosting: Firebase Hosting

静的ファイルと動的コンテンツのホスティングにはFirebase Hostingを使用。
SSL暗号化によるセキュアな配信はもちろんのこと、CDN経由での高速なローディングを実現しています。
開発からデプロイメントまで、Firebaseのエコシステム内で完結することが、生産性を大きく向上させました。

flutter build webを実施するとbuild/webディレクトリが生成されるのでそれをhostingにデプロイするように設定しています。firebase.json

...
  "hosting": {
    "public": "apps/app/build/web",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
...

AI: Gemini Pro

そして、Fiction Timesの核となるのがAI技術、ここではGemini Proを採用しています。
このAIは、与えられたプロンプトから創造的でユーモアあふれるコンテンツ(という名の夢)を生成します。
ユーザーからの入力を受けて、即座に架空のニュース記事を作り出す能力は、このアプリの最大の売りです。
ChatGPTとは違い、きっとたくさんの夢(ハルシネーション)を見てくれることでしょう。

実装はCloud Functionsの章で紹介しましたが、API経由でレスポンスを取得することも可能です。

curl \
  -H 'Content-Type: application/json' \
  -d '{"contents":[{"parts":[{"text":"Write a story about a magic backpack"}]}]}' \
  -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY

まとめ

技術は日々進化していますが、それを使って何を生み出すかは、我々開発者次第です。Fiction Timesでは、これらの先端技術を組み合わせることで、日常に新たな笑いと発見を提供しています。
技術の真価は、それを通じて人々の生活にどれだけプラスの影響を与えられるかにあります。
Fiction Timesの場合、それはユーザーの日常にユーモアの一瞬をもたらすことでした。

技術を通じて笑顔を作り出す、そんな未来を、皆さんも一緒に創り上げませんか?

今回のソースについては下記にアップしているのでよかったらご参考ください。

https://github.com/virapture/fiction_times

最後に、ワンナイト人狼オンラインというゲームやスノボ向けのcotsumeを作ってます!よかったら遊んでね!
他にもCameconOffchaや問い合わせ対応が簡単にできるCSmartといったといったサービスも作ってるのでよかったら使ってね!

また、チームビルディングや技術顧問、Firebaseの設計やアドバイスといったお話も受け付けてますので御用の方は弊社までお問い合わせください。

https://virapture.com/

https://menta.work/user/20565

ラグナロクでもエンジニアやデザイナーのメンバーを募集しています!!楽しくぶち上げたい人はぜひお話ししましょう!!

https://ragna-rock.com