🐷

Fastly AI Acceleratorでセマンティックキャッシュを試してみた

に公開

初めに

私の所属するNCDC株式会社では年に1回、社内ハッカソンを行なっています。
チームに分かれたり、個人で2日間でなんか面白いもの作ろうというものです。

そこで私は今年、「セマンティックキャッシュ」を試してみました。
(今年は1人チームでちょっと寂しかったので、来年は誰かとやる)

セマンティックキャッシュとは

一言で言うと、LLMの応答をキャッシュする仕組みの1つです。
LLMに与えるプロンプトの意味が似ていたら、LLMにリクエストせずに過去の同じ回答を返すというものです。
意味が似ているかはエンベディイングして類似度の計算により判定しています。
そしてこの辺の仕組みを提供してくれているのがFastly AI Acceleratorです。

Fastly AI Accelerator

Fastlyはエッジクラウドサービスです。
そのサービスの一つとしてFastly AI Acceleratorがあります。
https://www.fastly.com/documentation/ja/guides/platform/about-the-ai-accelerator-page/

AI Accelerator は、OpenAI や Azure OpenAI、Gemini、および OpenAI 互換の API を使用する LLM をサポートしています。

本記事はBedrockのOpenAI Chat Completions API 、Azure OpenAIで試してみた結果を書いています。

やってみたこと概要

  • BedrockとFastly AI Acceleratorを組み合わせようとした
    →うまくいかなかった
    (うまくいかなかったという事例にも意味があると信じて書きます)

  • AzureOpenAIとFastly AI Acceleratorを組み合わせた
    →うまくいった

BedrockとFastly AI Acceleratorを組み合わせようとした(うまくいかなかった)

手元にAWSで構築したAIチャットボットがあったので、Fastly AI Acceleratorを組み込んでセマンティックキャッシュを試してみることにしました。

フロントエンド:AWS Amplify
バックエンド:AWS AppSync + AWS Lambda
AI部分:Amazon Bedrock
という構成でした。

Fastly AI AcceleratorはOpenAI互換のAPIを使用するLLMをサポートしているとあったので、BedorckのOpenAI Chat Completions APIを使いました。 https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/inference-chat-completions.html

書いたコードは以下でLambda上で動かしました。

import type { Schema } from "../../data/resource";
import OpenAI from "openai";

// AWS BedrockのOpenAI互換エンドポイントを使用
const client = new OpenAI({
  baseURL: "https://bedrock-runtime.us-west-2.amazonaws.com/openai/v1",
  apiKey: process.env.AWS_BEARER_TOKEN_BEDROCK || process.env.AWS_ACCESS_KEY_ID,  
});

const SYSTEMPROMPT = "You are the best teacher in the world.";

export const handler: Schema["BedrockChat"]["functionHandler"] = async (
  event,
) => {
  const prompt = event.arguments.prompt;
  const modelId = "openai.gpt-oss-20b-1:0";

  console.log("prompt", prompt);
  console.log("modelId", modelId);


  try {
    const response = await client.chat.completions.create({
      model: modelId,
      messages: [
        {
          role: "system",
          content: SYSTEMPROMPT,
        },
        {
          role: "user",
          content: prompt,
        },
      ],
      max_tokens: 1000,
      temperature: 0.5,
    });

    return response.choices[0]?.message?.content || "";
  } catch (error) {
    console.error("Error calling Bedrock:", error);
    throw new Error("Failed to get response from Bedrock");
  }
};

が、これはSerializationExceptionになり、うまくいきませんでした。

{"Output":{"__type":"com.amazon.coral.service#SerializationException"}

curlコマンドで同様のリクエストをしてみた際もSerializationExceptionが起きていたので、Lambda固有のエラーではなさそうでした。
おそらく、Fastlyのサーバーにはリクエストが届いたものの、FastlyがそのリクエストをAWS Bedrockが理解できる形式に正しく中継・変換する過程でエラーが発生したのだと思います。(たぶん)

Fastlyの方に問い合わせしたところ、
「現状 OpenAI 互換の API を処理するロジックにおいて特に Bedrock の場合に不具合が出ている可能性があります」
とのことでした。
続報があればまた追記します。

AzureOpenAIとFastly AI Acceleratorを組み合わせた(うまくいった)

こっちはうまくいきました。
書いたコードのポイントは以下の部分です。

const client = new AzureOpenAI({
  apiKey: AZURE_OPENAI_API_KEY,
  apiVersion: "2024-12-01-preview",
  deployment: "gpt-4.1",
  endpoint: AZURE_OPENAI_ENDPOINT,
  defaultHeaders: {
    "Fastly-Key": FASTLY_KEY,
  },
});

AZURE_OPENAI_API_KEYはAzureOpenAIのキーを発行して入れました。
apiVersionやdeployment、endpointもAzureOpenAIのキーを発行した時のAzureの画面に出てくるのでそれを使いました。
FASTLY_KEYはFastlyのコンソールからAccount>API Tokens>Personal tokensのページにcreare tokenボタンがあるのでそこから発行してください。
発行するトークンは読み取り専用(Read-only access)でOKです。
https://www.fastly.com/documentation/ja/guides/account-info/account-management/using-api-tokens/

公式ドキュメントにはPythonでのコード例しかありませんでしたが、Typescriptでも問題なく動作しました。

    const response = await client.chat.completions.create({
      model: "gpt-4.1",
      messages: [
        {
          role: "developer",
          content: SYSTEMPROMPT,
        },
        {
          role: "user",
          content: prompt,
        },
      ]
    },{
      headers: {
        'x-settings-overrides': `{"semantic_cache_enabled": ${cashEnabled},"x-semantic-threshold": "0.95","Cache-Control":"300"}`
      }
    },);

headersの指定方法はドキュメントからは分かりづらいですが、Fastlyのエンジニアの方が教えてくださいました。
semantic_cache_enabledでセマンティックキャッシュの有効・無効を切り替えています。
私の場合、フロントから指定したかったので、値は変数でもらうようにしています。
x-semantic-thresholdはデフォルト0.75ですが、違う回答が欲しい時も同じ回答が返ってくることが多いので高くしてみました。
この辺は探り探りやっていく必要がありそうです。

実際にやってみた結果

いくつか質問を投げかけて以下2点を見てみました。

  • キャッシュが効くかどうか(同じ回答であればキャッシュ効いています)
  • LLM呼び出す部分の関数の実行時間

①「丸の内に関する雑学を3つ教えて」


タメ口で質問してみました。
最初の質問なので、キャッシュはありません。
実行時間は5016 msでした。

②「丸の内に関する雑学を3つ教えてください」


敬語で質問してみました。
①と全く同じ回答が返ってきています。
①と似た意味の質問だと判定され、キャッシュから回答が返ってきました。
実行時間は1729 msでした。

③「丸の内に関する歴史を3つ教えてもらえますか?」


「雑学」ではなく「歴史」を聞くようにしてみました。
結果としては似た意味の質問だと判定され、キャッシュから回答が返ってきました。
実行時間は1640 msでした。
これはキャッシュが効いていいのか微妙なところですね。
セマンティックキャッシュはこの辺の塩梅が難しそうです。

④「丸の内でおすすめのスポットを3つ教えてください」


「雑学」ではなく「おすすめのスポット」を聞くようにしました。
実行時間は4803 msでした。
これはキャッシュからではなく、LLMから新たな回答が返ってきました。
私の感覚的にも違う回答を返して欲しかったのでよかったです。

感想

Fastly AI Acceleratorを初めて触ってみました。
まだ世の中に事例が少ない(たぶんやってみた系の記事は1つもなかった)ので、情報不足という苦労はありました。
しかし、Fastlyのエンジニアの方が問い合わせに関して爆速で返答してくださり、2日間のハッカソンでセマンティックキャッシュを既存のAIチャットアプリに組み込み色々な検証をすることができました。
組み込むこと自体は1日目でできたので、2日目はパラメータを色々試して検証する時間にできました。
本当にありがとうございます!

LLMの利用料金を減らしたい、応答速度を速くしたいという課題への解決手段の1つだと感じました。
ただ、キャッシュが効いて嬉しい場合と嬉しくない場合があるので、ユースケースを吟味する必要はあるなぁと感じました。

また、LangChainのセマンティックキャッシュとの違いは何かという質問ももらったりしたので、その辺り今後深ぼっていきたいと思います。

NCDCエンジニアブログ

Discussion