はじめに

はじめまして、CyberAgentの26卒内定者としてABEMAの広告チームで働いているmasaです。

本記事では、内定者バイト期間中に取り組んだAIエージェントの開発についてご紹介します。

背景

ABEMAでは、広告の入稿や広告費用の試算(プランニング)を行うために、広告代理店向けにビジネスマネージャと呼ばれる管理画面を提供しています。 ただ、ビジネスマネージャはユーザーが自由に選択できる箇所が多く、広告に関する詳しい知識がないとビジネスマネージャを十分に使いこなせないという課題感がありました。

例えば広告のターゲットを選ぶ画面では、以下のように複数の項目や区分が存在し、適切に選択するのが難しいです。 image

そこで、自然言語で直感的にビジネスマネージャを操作できるAIエージェントの導入が検討されていました。

自分は内定者バイトとして、AIエージェント基盤を作成し、初期段階として広告のプランニングに機能を絞って実装を行いました。

使用技術

function calling

今回実装したAIエージェントの核となる技術がfunction callingです。

簡単のために外部APIから天気を取得するケースを例に挙げて概要を説明します。

simple weather

ユーザーが明日の天気を質問した際に、サーバーではLLMにプロンプトとして質問内容や、使用できる関数宣言を渡します。

これをもとに、LLMは天気を取得する関数を呼び出すように判断し、サーバーでは実際に関数を呼び出すという流れになります。

これにより、LLMと外部APIの連携を安全に行うことができます。

zodスキーマによる型安全性

function callingではTypeScriptのライブラリであるzodを使用しています。

zodを導入するメリットは以下が挙げられます。

  • LLMのStructured Outputを行うことができ、型安全に開発が可能
  • .describe()で各Fieldのドキュメント化が可能で、AIに柔軟に渡すことができる
  • z.infer()により、スキーマを単一情報源としてDTO/型を共有可能

ここでは天気のAPIを例に、2つのスキーマを定義します

LLMに与える関数定義

特定の日の天気を取得する関数を定義してLLMに与えます。

const Functions = {
    getWeather: {
        name: 'get_weather',
        schema: z.object({
            location: z.string().describe('場所'),
            date: z.string().describe('日付'),
        })
    }
}

LLMのアウトプット

OutputSchemaをLLMへのリクエストに入れることで、不安定な出力を安全に処理することができます。

const OutputSchema = z.object({
    is_function_call: z.boolean().describe('関数呼び出しを行うかどうか'),
    function_call: z.object({
        function_name: z.string().describe('関数名'),
        arguments: z.object({
            location: z.string(),
            date: z.string(),
        }),
    }).optional(),
    question: z.string().optional().describe('情報が不足している場合の質問'),
});

genkitのVertexAIプラグインを用いることで、outputのスキーマを指定することができます。

const ai = genkit({
    plugins: [vertexAI({
        location: config.location,
        projectId: config.projectId
    })]
});

const res = await ai.generate({
    model: vertexAI.model(config.modelName),
    prompt: prompt,
    output: { schema: OutputSchema }
});

zodを使用した場合、以下のように構造化された出力を扱うことができます。

weather

この仕組みにより、ユーザーは自然言語で質問するだけで、システムが自動的に適切なAPIを呼び出して結果を返してくれます。

処理の流れ

ビジネスマネージャは複数のマイクロサービスで構成されており、gRPC通信でやり取りを行います。

実際にはこの他にも多数のサービスがありますが、今回使用したのは以下の2つです。

  • Product Master: 広告のターゲティング情報(年代・ジャンルなど)を管理
  • Planner: 指定した条件でプランニングを実行する

AIエージェントの処理フローは以下のようになっています:

sequence diagram

function callingの使用箇所

今回の実装では、以下の2つのパターンでfunction callingを使用しています:

  1. 情報が十分な場合: is_function_call: trueを返し、save_planning関数を呼び出してプランニングを保存
  2. 情報が不足している場合: is_function_call: falseを返し、questionフィールドでユーザーに質問

この構成のメリット

この構成の大きなメリットとして、他サービスの変更による影響が少ないことが挙げられます。

例えば、Product Masterで管理するデータが変わった際にも、出力のスキーマが変わらなければ、中身をLLMに渡すだけなので、LLM側で適切にfunction calligを行ってくれます。

まとめ

本記事ではABEMAの広告チームにおけるAIエージェントの開発についてご紹介しました。

今回はプランニングに絞ってAIエージェントサーバーを構築しましたが、LLMに与える関数を増やすことで、さまざまな処理を行うことができます。

この記事が参考になれば幸いです。