🍝

第3回 生成AIのモデルと外部データを連携可能にする

2024/12/06に公開

本エントリはUbie 生成AI Advent Calendar 2024の5日目、「社内用生成AI Webアプリケーションをどのように作っているか」の第3回です。

前回は、プロンプトを再利用する、プリセットストアと共有機能について簡単に説明しました。今回は、生成AIのモデルを外部データと連携可能にする方法についてです。

生成AIのモデルはWebにつながっていない

生成AIのモデル(LLM:Large Language Model=大規模言語モデル)は巨大なアルゴリズムのファイルです。そのため、モデル自身は外部と通信する能力を持っていません。ChatGPTPerplexityなどのサービスは、LLMとは別に外部と通信する機能を持ち、LLMと協調して機能を提供しています。


雰囲気、多分もっと複雑な機構だとは思う。

例に出したChatGPTやPerplexityを提供するOpenAIAnthropicはそれぞれLLMのAPIを第三者に提供していますが、飽くまでLLMの利用が可能なだけで、Webなどの外部にアクセスする能力は提供してくれません。

LLMが外部の情報と連携するには

Webを含む外部情報とLLMの連携を行うのはハードルが高そうに思えます。しかし、LLMが外部情報を必要とする状況を具体的に整理すると、必ずしも自動的に連携する必要はなく、ユーザーとの対話を通じて必要な情報を取得できる可能性が見えてきます。


LLMとの対話部分だけに着目すると、LLMはドラクエ3の情報を使って質問に答えているだけ

とりあえず外部情報を持つプロンプトを渡すところから

まずは、外部情報を含むプロンプトを生成AIに渡すことから考えます。例えばWebページのテキストをコピーして、プロンプトとして入力してみます。恐らく要約等の用途で誰もが自然と行っているのではないかと思います。

第1回 社内用生成AI Webアプリケーションをどのように作っているか 連載予告編の内容を手動でコピーして要約してみます。


第1回の内容を手動でコピー

コピーした内容を貼り付けて、要約をしてもらいます。


Ubie社内で作っているDev Geniusというアプリケーションの画面です

結果は以下の通りです。

このブログ記事は、Ubie社内で開発・運用されている生成AI Webアプリケーション「Dev Genius」の紹介と、今後の連載予告編です。

Dev Geniusは、当初ソフトウェアエンジニアの業務効率化を目的として開発が始まりました。GPT-3.5 TurboのAPI公開を機にプロトタイプを作成し、費用対効果や社内情報の取り扱いなどを検討した結果、全社員向けのアプリケーションとして開発していくことになりました。

現在Dev Geniusは、AI対話、音声処理、UI実装支援、外部連携など、多岐にわたる機能を提供しており、月間で社員の約85%が利用しています。

今後の連載では、Dev Geniusの各機能について、開発背景や機能概要、実装方法、実際の活用事例などを詳しく解説していく予定です。具体的には、プロンプトの再利用、外部データとの連携、Slackからの呼び出し、検索機能との連携、音声書き起こし、ワークフロー、UI生成などを取り上げる予定です。

概ね良さそうです。ただ、Webページの内容を手動でコピーして貼り付けるのはやや面倒ですね。この手順を簡単にするだけでも価値がありそうです。

汎用的なフェッチャーを作る

LLMが必要とする情報を自動で補完するような構造を考える前に、必要な情報を単純に取得する仕組みを考えます。以下はフェッチャーのインタフェースの例です。

export interface Fetcher {
  getContent: (url: string) => Promise<string>;
}

urlを受け取って、stringを返すgetContent関数を持つだけのシンプルなインタフェースです。Webの情報を取得するWebFetcherは以下のような実装になるかもしれません。

// ※このままではまともには使えないので注意
export class WebFetcher implements Fetcher {
  async getContent(url: string): Promise<string> {
    const response = await fetch(url);
    return await response.text();
  }
}

試しに使ってみると...

const fetcher = new WebFetcher();
// 第1回 社内用生成AI Webアプリケーションをどのように作っているか 連載予告編のURL
const url = "https://zenn.dev/ubie_dev/articles/ee95c03794f47f";
const content = await fetcher.getContent(url);

次のような結果が得られました。

<!DOCTYPE html><html lang="ja"><head><meta charSet="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><title>第1回 社内用生成AI Webアプリケーションをどのように作っているか 連載予告編</title>
--省略--

HTMLを丸ごと取り出しているので、かなりの文字数になります。おおよそ7万文字ほどありました。現在のLLMであれば大抵は処理できる長さですが、手動でコピペしたものは3000文字程度だったので、ちょっとノイズが多いですね。ただ、最適化をしていけば十分使えるようになりそうです。Webのコンテンツをどのように取り出すかはWebスクレイピングの領域になるのでここでは割愛します。

まずは手動で使えるようにする

さて、まだLLMは出てきません。とりあえずフェッチャーを画面上で使えるようにしてみます。


UI的にはあんまり良くなくて社内では知る人ぞ知る機能になっていそう

Webページから頑張ってコンテンツをコピペするよりはちょっとマシに思えますね。

いろいろな情報を入力可能にする

汎用的なフェッチャーの良い点は、URLをベースにした取得処理を抽象化できることです。Dev Geniusでは現在、Webの他にNotion、Slack、Githubのフェッチャーを実装しています。以下のようにURLを使ってフェッチャーを選択するような関数を設けており、拡張を容易にしています。

export const selectFetcher = (url: string): Fetcher => {
  if (url.startsWith("https://www.notion.so/[org-id]/")) {
    return new NotionUbieIncFetcher();
  } else if (url.startsWith("https://[org-id].slack.com/")) {
    return new SlackFetcher();
  } else if (url.startsWith("https://github.com/")) {
    return new GitHubFetcher();
  } else {
    return new WebFetcher();
  }
  // Googleスプレッドシートのフェッチャーもほしい
};

以下はGithubのPull Requestのdiffを読み込んでいる様子です。

プリセットを使ってコードレビューやテスト計画立案もサクッと。URL映っちゃうので見切れてます

社内で扱う情報のコネクターとして拡張していけば、様々な情報をLLMと連携できるようになります。

Function callingを使ってLLM自身に判断させる

データを取得する仕組みが整ったので、LLMと自動的に連携することを考えてみます。
Function callingは、LLMが他のツールと連携して複雑なタスクを実行できるようにする機能です。OpenAIやGoogle、Anthropicなど主要なLLMモデルのAPIの提供者は、大体Function callingをサポートしています。

例えばLLMは今日が何日か知りません。


ググれと言われました

しかしLLMを呼び出すシステムは、簡単に今日が何日か知ることができます。

const today = new Date(); // タイムゾーンのことは一回忘れます

LLMとの対話に際してシステム側から「今日の日付を取得する機能がある」という情報を提供します。これにより、LLMは「今日の日付」が必要になった場合、システムの持つ機能を呼び出して情報を取得できるようになります。これがFunction callingの仕組みです。

LLMに対しては次のようなフォーマットで機能の情報を提供します。

const tools = [
  {
    type: "function",
    function: {
      name: "get_today",
      description: "今日の日付を取得する機能",
      parameters: {
        type: "object",
        properties: {
          format: {
            type: "string",
            description: "日付のフォーマット (例: YYYY-MM-DD)",
            default: "YYYY-MM-DD",
          },
        },
        required: [],
        additionalProperties: false,
      },
    },
  },
];

get_today関数の実装は次のようなものになるでしょう。

export const getToday = (format = "YYYY-MM-DD"): string => {
  const today = new Date();
  const year = today.getFullYear();
  const month = ("0" + (today.getMonth() + 1)).slice(-2);
  const day = ("0" + today.getDate()).slice(-2);

  const formattedDate = format
    .replace("YYYY", year.toString())
    .replace("MM", month)
    .replace("DD", day);

  return formattedDate;
};

これを組み込んで会話をしてみると以下のような結果が得られました。


カレンダーから今日の予定を取り出したり出来るようにするのも面白いかも

Dev GeniusではLangChain.jsAgentExecutorを利用してFunction callingのハンドリングをしています。現在はのAgentExecutorを置き換えるものとしてLangGraphが登場しているので、今から試す場合はLangGraphを用いるのが良いでしょう。

汎用的なフェッチャーをFunction Callingに組み込むことで以下のようにURLを使ったやり取りができるようになります。


コードレビューする前に生成AIにやってもらってます

まとめ

生成AIのモデル(LLM)を外部データと連携可能にする方法や考え方について説明しました。Function callingの拡張は、LLMと外部ツールの連携をより柔軟かつ強力にする可能性を秘めています。最近では、AnthropicがMCP(Model Context Protocol)という新しいプロトコルを提唱しました。MCPは、LLMと外部データソースやツールを接続するためのオープンな標準プロトコルです。Function CallingもLLMと外部ツールを接続する仕組みですが、MCPはこれをより広範なデータソースやツールとの接続を可能にする、より汎用的な枠組みを目指しています。これにより更にLLMの柔軟な拡張を自由に行える可能性が高まります。楽しみですね。

Ubie テックブログ

Discussion