Zenn
🤖

CloudflareAI+hermes-2-pro-mistral-7b で Function Calling

に公開
3

注意

書いたのはいいが、 最新の Cloudflare の agents-starter を見てるとあんまり Cloudflare 自体が Cloudflare AI のやる気がないように見える。モデル更新頻度も微妙。

https://developers.cloudflare.com/agents/

たぶん Cloudflare AI ではなく Cloudflare AI Gateway 側に軸足が移ってそうな気配がある。そりゃ現状のローカルLLM相当のものしか使えず、性能微妙だもんね...

https://www.cloudflare.com/ja-jp/developer-platform/products/ai-gateway/

とはいえ、手元で Worker に自前 Stream を作ったりして SSE 対応するとこまでやったので、お焚き上げとして置いておく。


なんか Cloudflare AI でいいモデル追加されてないかなと眺めてたら、 hermes-2-pro-mistral-7b というモデルを見つけた。 Function Calling 対応とある。

https://developers.cloudflare.com/workers-ai/models/hermes-2-pro-mistral-7b/

Playground で試したところ、日本語もいける。

これ以外に Function Calling 対応モデルは現状ない。そもそも Cloudflare AI に Function Calling どうするんだと調べたところ、この npm パッケージが見つかった。

https://www.npmjs.com/package/@cloudflare/ai-utils

コミット見ると 10ヶ月には存在していたので、この界隈では情報が古い可能性があるが、今後他のFunction Calling対応ツールが追加される可能性もあるし、一応試しておく。

簡単なAI実行ワーカー

wrangler.jsonc に AI Binding を書いておく。

  "ai": {
    "binding": "AI"
  },
import { runWithTools } from "@cloudflare/ai-utils";

export interface Env {
  AI: Ai;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const response = await runWithTools(
      env.AI as any,
      "@hf/nousresearch/hermes-2-pro-mistral-7b",
      {
        messages: [{ role: "user", content: "今日の天気は" }],
        tools: [
          {
            name: "get-weather",
            description: "Gets weather information of a particular city",
            parameters: {
              type: "object",
              properties: {
                city: {
                  type: "string",
                  description: "The city name",
                },
              },
              required: ["city"],
            },
            function: async ({ city }) => {
              return "晴れ";
            },
          },
        ],
      }
    );
    return new Response(JSON.stringify(response, null, 2));
  },
};

curl http://localhost:8787 たたくと、レスポンスが取れる。

Form+SSE でストリーム対応する

AI はやっぱりストリームで動かしたいので、Stream APIで試した。

import { runWithTools } from "@cloudflare/ai-utils";

export interface Env {
  AI: Ai;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const url = new URL(request.url);
    // stream api
    if (url.pathname === "/api/stream") {
      return handleApiStream(request, env, ctx);
    }
    // html
    return new Response(getHtmlContent(), {
      headers: {
        "Content-Type": "text/html;charset=UTF-8",
      },
    });
  },
};

// APIストリームを処理する関数
async function handleApiStream(
  request: Request,
  env: Env,
  _ctx: ExecutionContext
) {
  // リクエストからユーザーの入力を取得
  if (request.method !== "GET") {
    return new Response("Method Not Allowed", {
      status: 405,
      headers: {
        Allow: "GET",
      },
    });
  }

  const url = new URL(request.url);
  const userInput = url.searchParams.get("userInput") ?? "";
  const response = await runWithTools(
    env.AI as any,
    "@hf/nousresearch/hermes-2-pro-mistral-7b",
    {
      messages: [{ role: "user", content: userInput }],
      tools: [
        {
          name: "get-weather",
          description: "Gets weather information of a particular city",
          parameters: {
            type: "object",
            properties: {
              city: {
                type: "string",
                description: "The city name",
              },
            },
            required: ["city"],
          },
          function: async ({ city }) => {
            if (Math.random() < 0.5) {
              return "晴れ";
            } else {
              return "曇り";
            }
          },
        },
      ],
    },
    {
      streamFinalResponse: true,
    }
  );

  return new Response(response as ReadableStream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
    },
  });
}

// HTMLコンテンツを生成する関数
function getHtmlContent() {
  const inlineScriptContent = inlineScript
    .toString()
    // strip the function name
    .replace(/function\sinlineScript\(\)\s\{/, "")
    // remove last }
    .slice(0, -1)
    .trim();

  return `<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Stream Demo</title>
  <style>
    body { font-family: sans-serif; margin: 20px; }
    #output { white-space: pre-wrap; margin-top: 10px; }
  </style>
</head>
<body>
  <form id="form">
    <input type="text" id="input" value="今日の天気は">
    <button type="submit">送信</button>
  </form>
  <div id="output"></div>
  <script>${inlineScriptContent}</script>
</body>
</html>`;

  function inlineScript() {
    /** INLINE CODE */
    document.getElementById("form")!.addEventListener("submit", (e) => {
      e.preventDefault();
      const input = document.getElementById("input") as HTMLInputElement;
      const output = document.getElementById("output") as HTMLDivElement;
      
      output.textContent = "読み込み中...";
      input.disabled = true;
      const source = new EventSource(
        "/api/stream?userInput=" + encodeURIComponent(input.value)
      );
      source.onopen = () => {
        output.textContent = "";
      };
      source.onmessage = (event) => {
        if (event.data && event.data === "[DONE]") {
          source.close();
          input.disabled = false;
          return;
        }
        const data = JSON.parse(event.data);
        if (data.response) {
          output.textContent += data.response;
        }
      };
      source.onerror = () => {
        source.close();
        input.disabled = false;
      };
    });
  }
}

何に使うの

Cloudflare に閉じて簡単なツール実行を振り分けるのに使えそう。

とはいえ、最初に書いたように、 真面目にやるなら Cloudflare AI Gateway を使ったほうがいい。

3

Discussion

ログインするとコメントできます