🧠

My first Cloudflare Workers AI

2023/10/01に公開
2

ワークショップ

来週というか今週の金曜日、名古屋でCloudflare Workers + Honoのワークショップをやるのですが、先日Birthday Weekで発表された「Workers AI (AIアプリがGPUで動く!)」が楽しいので、それを使ったアプリを作ろうということになりました。

https://cfm-cts.connpass.com/event/294096/

先日もServerlessDays Tokyo 2023というイベントでCloudflare Workers + Honoのワークショップをやったのですが(4時間!)、その時にドタバタしちゃったんで、今回はもうやることを全部ここに書こうと思います。とはいえ「Cloudflare Workersとは」とか「Honoの特徴は?」とか書き出すとキリがないので、参考文献を参考にしてください。

準備してもらうこと

まず、Cloudflareのアカウントを作って、Dashboardに入れることを確認してください。

次に、持ち込むPCで以下やっておいてください。

GitとNode.jsのインストール

GitとNode.jsが入ってるかを確認してください。入ってなかったら入れてください。分からない方は調べてもらえると!

Honoが動くことを確認

ターミナルで以下を実行してください。

npm create hono@latest my-first-app
cd my-first-app
npm install

これでローカル環境ができます。最低限、ここまでやっておいてください。もし興味のある方は開発サーバーを立ち上げたり、デプロイまでやってみるといいかもです。

開発サーバーを立ち上げる。

npm run dev

デプロイする。

npm run deploy

デプロイした場合、ほっておいても基本的に問題ありませんが、もし気になる方はダッシュボードの「Workes & Pages」というメニューから対象のWorkerを削除してください。

完成形

これが完成形です。1.5倍速のスクリーンキャストです。

SC

レポジトリはこちら。

https://github.com/yusukebe/my-first-workers-ai

さて、以降作っていくので、ワークショップ参加する方で、ネタバレしたくない人は見ないでください!

スターター

本来、Honoのアプリを作る場合は"create-hono"コマンドを使います。

npm create hono@latest my-first-app

ただ、今回はより完成形に近いスターターを用意したので、そちらから始めていきましょう。

ダウンロードとインストール

以下のコマンドでダウンロードできます。

npx degit 'yusukebe/my-first-workers-ai#starter' my-first-workers-ai

ディレクトリに入って、お好きなパッケージマネージャーでインストールしてください。特に指定がなければnpmを使います。

cd my-first-workers-ai
npm install

構成

構成はこんな感じ。

$ tree .
.
├── .gitignore
├── README.md
├── assets
│   └── script.js // 最後にインタラクティブするためのJS
├── package-lock.json
├── package.json
├── src
│   ├── globals.d.ts // .jsをテキストとして扱うための型定義
│   ├── index.tsx // メインのファイル、JSXを書きたいので`.tsx`にしてある
│   └── renderer.tsx // レンダラーの定義
├── tsconfig.json
└── wrangler.toml // Workersの設定が書いてある

はじめてのCloudlfare Workers + Hono

AIアプリを作る前に「Cloudflare Workers上でHonoを動かす」のを入門してみましょう。ここでは最低限のことを書くので、全てを知りたい方は先日のワークショップで使った大作の資料を御覧ください。

https://workshops.yusuke.run/2023-09-24

Hello World

なにはともあれ「Hello World」。といってももう書いてあります。

src/index.tsx
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

export default app

では開発サーバーでローカルで立ち上げてみましょう。WranglerというCLIを使います。もしグローバルにコマンドが入っていれば以下でいけます。

wrangler dev src/index.tsx

今回はもうすでにpackage.jsonでスクリプト定義してあるので、以下を使ってください。

npm run dev

注意したいのは、今回は--remoteというオプションを付与しています。Workers AIを使うには今のところ「リモート」に繋がないといけないからです。ちなみにCloudflareでは、ローカルモードでもAI Bindingsを使えるようにする予定があります。「--remote」のみだとPagesで開発ができないので、それ欲しいですよね。

さて、開発サーバーが立ち上がったら、「B」をタイプするか、http://localhost:8787へアクセスしましょう。これがあなたのはじめてのCloudflare Workers、そしてHonoアプリです。

デプロイ

もうこのままデプロイしてしまいましょう。はじめてCloudflareにデプロイするという方はログインしなくてはいけなかったり、アカウントを作っているけどメール認証が終わってなければしなくてはいけなかったりしますが、それは現地で対応します。

デプロイも簡単です。素のWranglerコマンドだと以下です。

wrangler deploy src/index.tsx

package.jsonには--minifyオプションを追加したdeployコマンドを用意しているので、そちらを使ってください。

npm run deploy

すると、"my-first-workers-ai"という名前であなたのWorkerが「全世界」にデプロイされます!おめでとうございます。https://my-first-workers-ai.設定したサブドメイン.workers.devにアクセスしましょう。

上記の"my-first-workers-ai"という名前はwrangler.tomlnameプロパティの値なので、変更したい方はそれを変えてください。

wrangler.toml
name = "my-first-workers-ai"

JSX

あとはJSONを返したり、クエリを受け取ったり試すのですが、ひとつ重要な概念としてJSXがあるので、そこだけ解説しておきます。

JSXとはReactなどで採用されているJavaScriptの中でマークアップを書くSyntaxです。それがHonoでは標準で備わっていて、拡張子を.tsから.tsxに変えるだけで使えるようになっています。ですので、以下のように書いてHTMLを返却できます。

src/index.tsx
app.get('/about', (c) => {
  return c.html(
    <div>
      <h1>About me</h1>
      <h2>Favorites</h2>
      <ul>
        <li>Ramen</li>
        <li>Sushi</li>
        <li>Watching Baseball</li>
      </ul>
    </div>
  )
})

気をつけたいのは、このJSXはサーバーサイドでのレンダリングのため「だけ」であり、クライアントは一切関係ありません。どちらかというとmustacheとかHandlebarsとかのテンプレートエンジンのかわりと考えてください。げんにmustacheミドルウェアを廃止する変わりにJSXが導入されました。

さらに突っ込んだ話をするとHonoの中で、ReactやPreactを使うことは可能で、Reactの場合はreact-domrenderToReadbleSteam()とかrenderToString()を使ってください。そして適切にhydrateすればクライアントも同じように面倒をみることができます。ただ、それはだいぶだるいので、Next.jsなどのフレームワークが存在するのであります。

そして、実はHonoベースのIsland Hydrateにも対応し、File-baseのルーティングができるフレームワーク「Sonik」を開発中です。

https://github.com/yusukebe/sonik

まだ「Dev」ステージでAPIは変更される可能がありますが、なかなかできが良く、今回と似たような「ChatGPT Streaming」のアプリでも大活躍しています。

https://github.com/yusukebe/chatgpt-streaming

気になる方はチェックしてみてください。

で、何が言いたいかというとHonoのJSXは万能ではないということです。最近HonoのJSXが「手軽」という文脈で注目されているのですが、盲目的にならないでください。他のUIライブラリも使えます。また、今回はHonoのJSXをWorkersで動かして、HTMLを出力していますが、HTMLの場合、ページが多いとPagesが選択になります(SonikはPagesが前提です)。

とはいえ、このワークショップでは「サクッと」デモを作るのに、Workers + HonoのJSXという構成をとります。上記のようなメタフレームがある一方で、「サクッと」UIを作るのに、Workers + HonoのJSXはすごく重宝します。

AIアプリを作る

では、Workers AIのアプリを作っていきましょう。

Bindingsと型

WorkersにはBindingsという仕組みがあって、例えば以下が利用できます。

  • KV
  • Durable Objects
  • R2
  • D1
  • Queue
  • など

今回のAIもBindingsとして扱います。ちなみにこのBindingsはCloudflare Workers/Pagesにとって非常に重要な概念で、今回のBirthday Weekで出た「Hyperdrive」もBindings経由で利用することも含め、今後も活用されていきます。

Bindingsを有効にするにはまず、wranlger.tomlを編集します。今回のスターターではもうすでに記述があります。

wrangler.toml
[ai]
binding = "AI"

そして、TypeScriptの型をsrc/index.tsx内で定義します。

src/index.tsx
type Bindings = {
  AI: any
}

anyになっているのはまだ型定義が提供されていないからそうします。

TypeScriptはとっつきにくいし、JavaScriptが好きな方もいますが、とくにHonoを使う場合はTypeScriptを推奨しています。僕も慣れるまで時間がかかったのですが、まぁ型とかジェネリクスとか分からなかったらそういうものだと思ってください。

ついでに、「AI」のリクエストに必要なmessagesとレスポンスのanswerの型定義もしてしまいます。あとで使います。

src/index.tsx
type Answer = {
  response: string
}

type Message = {
  content: string
  role: string
}

new Ai()

いよいよ、AIを使いましょう。今回は「Text Generation = LLM」のモデルを使ってGPTアプリを作ります。このモデルはMetaのLlamaモデルそのもので、Cloudflareで動くようにしただけのものです。ですので、使い方のより詳細を知りたければそれを調べればいいでしょうl

https://ai.meta.com/llama/

AIを使うのは簡単です。以下をやります。

  • @cloudflare/aiからAiをimportする
  • AI Bindings を引数にnew Ai()する
  • モデルを指定しつつプロンプトを渡して実行する
  • レスポンスをテキストで返す
  • 確認する

全体のコードはこんなになりました。

src/index.tsx
import { Hono } from 'hono'
import { Ai } from '@cloudflare/ai'

type Bindings = {
  AI: any
}

type Answer = {
  response: string
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/ai', async (c) => {
  const ai = new Ai(c.env.AI)
  const answer: Answer = await ai.run('@cf/meta/llama-2-7b-chat-int8', {
    messages: [
      {
        role: 'user',
        content: `What is Cloudflare Workers. You respond in less than 100 words.`
      }
    ]
  })
  return c.text(answer.response)
})

export default app

http://localhost:8787/aiにアクセスすると「Cloudflareとは?」の答えがでてます!

いくつか注意事項があるので書いておきます。

  • 英語以外の言語の対応は不十分
  • 返却できるTokenの量に限りがあるので、単語の数を絞るように命令してる
  • ai.run()の第2引数は他にもフォーマットがある
  • roleuserを指定したが、systemassistantが使える
  • Bindings + ライブラリ以外でも直接APIを叩ける、そうするとWorkers以外からも使える

日本語がほぼ対応してないのが、残念ですが、今後なにか策がでるだろうし、テキスト翻訳のAIモデルを組み合わせるのも面白いです。

Streamで返す

ここで面白いのやりましょう。ChatGPTみたいに「ヌルヌル文字が返ってくる」のやりましょう。

例えばChatGPTの場合、APIからのレスポンスが元からStreamで返ってきていい感じですが、Workers AIはまだStreamには未対応です(今後対応する予定あり)。ですので、全体のレスポンスが返ってくるのを待たなくてはいけないのですが、もらったテキストを一度に表示するのではなく、文字を1文字ずつ分割して順々に表示させてみましょう。

c.streamText()

最近Honoに入ったc.streamText()を使います。

これはとてもよくできたAPIで、ベーシックな使い方だとこうします。

src/index.tsx
app.get('/stream', (c) => {
  return c.streamText(async (stream) => {
    stream.writeln('Hello')
    await stream.sleep(1000)
    stream.writeln('Hono!')
  })
})

Helloと表示されて1秒経ってからHono!と出ます。これを利用するわけです。

1文字表示された10ms待って次の文字を表示しましょう。エンドポイントはGET /aiにしておきます。

src/index.tsx
app.get('/ai', async (c) => {
  const ai = new Ai(c.env.AI)
  const answer: Answer = await ai.run('@cf/meta/llama-2-7b-chat-int8', {
    messages: [
      {
        role: 'user',
        content: `What is Cloudflare Workers. You respond in less than 100 words.`
      }
    ]
  })
  const strings = [...answer.response]
  return c.streamText(async (stream) => {
    for (const s of strings) {
      stream.write(s)
      await stream.sleep(10)
    }
  })
})

できましたか!?

SC

簡単なUIを作る

さてUIを作っていきましょう。

Renderer

Honoでは最近、c.render()という機能が入りました。この"Renderer"を使うと、HTMLのレイアウトを定義できて、それを各エンドポイントで使い回せます。今回はGET /のみのUIになるので旨味はありませんが、やってみましょう。

もうすでにsrc/renderer.tsxというファイルがあるので、それをindex.tsx内でimportします。rendererというのがすでにRendererをセットするためのミドルウェアになっているのですね。new Hono()でHonoのインスタンスappができた直後にそれを登録します。

src/index.tsx
import { renderer } from './renderer'

// Bindings、型定義

const app = new Hono<{ Bindings: Bindings }>()

app.get('*', renderer)

これで以降c.render()を使うとテンプレートが適応されています。つまり、以下のように書くと...

src/index.tsx
app.get('/', (c) => {
  return c.render(<h1>Hello AI!</h1>)
})

出力されるHTMLはこのようになります。

<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  </head>
  <body>
    <div>
      <h1>Hello AI!</h1>
    </div>
  </body>
</html>

CSSフレームワークを入れる

そのままだと味気ないので、ちょっとだけかっこよくしましょう。といっても、難しいことはしません。したいのであれば完成してからやりましょう。今回は"new.css"というCSSフレームワークを使います。

https://newcss.net/

このフレームワークの素晴らしい点は、HTMLのマークアップだけでいい感じにスタイリングしてくれるところで、わざわざclass属性を生やさなくてよいです。renderer.tsxを以下のように変更し、new.cssをCDN経由で読むようにしましょう。ついでにヘッダータイトルも追加してあります。

renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'

export const renderer = jsxRenderer(({ children }) => (
  <html>
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css" />
      <script src="/script.js"></script>
    </head>
    <body>
      <header>
        <h1>My first Workers AI</h1>
      </header>
      <div>{children}</div>
    </body>
  </html>
))

何もしないよりだいぶかっこいいです。

SS

POSTを受け取る

「フォームからテキストを入力するとそれにAIが応じてくれる」というのをやりたいので、フォームからのリクエストを受け取り、AIへリクエストをし、返ってきた値を返却するエンドポイントを作りましょう。といっても、先程つくったGET /aiを改修するだけです。

  • GET /aiからPOST /aiへ変更
  • JSON形式でリクエストボディを受け取る
  • その中のmessagesを使ってAIへ投げる
  • Streamで返す

コードはこうなります。

src/index.tsx
app.post('/ai', async (c) => {
  const { messages } = await c.req.json<{ messages: Message[] }>()
  const ai = new Ai(c.env.AI)
  const answer: Answer = await ai.run('@cf/meta/llama-2-7b-chat-int8', {
    messages
  })
  const strings = [...answer.response]
  return c.streamText(async (stream) => {
    for (const s of strings) {
      stream.write(s)
      await stream.sleep(10)
    }
  })
})

Messageというのは上で定義したこれです。

type Message = {
  content: string
  role: string
}

で、その配列messagesを渡しているのが鍵です。具体的にはこのような値になります。つまり今までの履歴を含めて送ってあげることで、会話を続かせているのですね。userの値はこちらが入力したもの、assistantの値はAIからのレスポンスをそのままオウム返ししています。

{
  "messages": [
    {
      "role": "user",
      "content": "You are a helpful assistant. You do not respond as 'User' or pretend to be 'User'. You respond as 'Assistant'. You respond in less than 100 words."
    },
    {
      "role": "assistant",
      "content": "Of course! I'm here to help. How can I assist you today?"
    },
    {
      "role": "user",
      "content": "What is Cloudflare?"
    },
    {
      "role": "assistant",
      "content": "Cloudflare is a web security company that provides a range of services to protect and optimize websites, including DNS, CDN, security, and performance tools. It helps to improve website speed, reduce downtime, and protect against cyber threats."
    },
    {
      "role": "user",
      "content": "What's Workers?"
    },
    {
      "role": "assistant",
      "content": "Workers is a serverless computing platform offered by Cloudflare. It allows developers to run JavaScript code at the edge of the internet, closer to the users, resulting in faster and more efficient web applications. Workers provides a secure and scalable environment for running serverless functions, without the need to manage servers or infrastructure."
    }
  ]
}

では検証してみます。履歴関係なくシンプルにrole:userだけでやりましょう。curlを使ってリクエストを出してみます。せっかくなので-Nオプションをつけてヌルヌル出してみます。

curl -N -s -XPOST -H 'Content-Type: application/json' --data '{"messages":[{"role":"user","content":"hi!"}]}' http://localhost:8787/ai

いい感じですね!

SC

curlだとStreamにならないこともあるのですが、その場合は気にしないでください!

フォームを作成

いよいよ、フォームをつくってユーザーが入力したテキストにAIが答えるのをやってみましょう。

まずsrc/index.tsxを編集します。GET /のエンドポイントでJSXを使ってHTMLを返すようにします。

src/index.tsx
app.get('/', (c) => {
  return c.render(
    <>
      <h2>You</h2>
      <form id="input-form" autocomplete="off" method="post" action="/ai">
        <input
          type="text"
          name="query"
          style={{
            width: '100%'
          }}
        />
        <button type="submit">Send</button>
      </form>
      <h2>AI</h2>
      <pre
        id="ai-content"
        style={{
          'white-space': 'pre-wrap'
        }}
      ></pre>
    </>
  )
})

見た目はこうなりました。

SS

「You」のinputに入力、「Send」を押すかエンターでPOST /aiをfetchして、結果を「AI」に表示します。これを実現するのに、「クライアントサイドのJavaScript」を利用します。これまで書いてきたJavaScript/TypeScriptはWorkersのサーバーサイドだったのですが、「クライアントサイド」です。

今回はReactやjQueryなどのライブラリを使わず素で書いてみます。このJavaScriptについては、不明点があっても今回のワークショップの範疇外であるので気にしないでください。僕もChatGPTに聞いて書きました。

assets/script.js
document.addEventListener('DOMContentLoaded', function () {
  const target = document.getElementById('ai-content')
  document.getElementById('input-form').addEventListener('submit', function (event) {
    event.preventDefault()
    target.innerHTML = 'loading...'
    const formData = new FormData(event.target)
    const query = formData.get('query')
    fetch('/ai', {
      method: 'post',
      headers: {
        'content-type': 'application/json'
      },
      body: JSON.stringify({
        messages: [
          {
            role: 'user',
            content: query
          }
        ]
      })
    }).then((response) => {
      response.text().then((data) => {
        target.innerHTML = data
      })
    })
  })
})

やっていることは、以下の通り。

  • DOMがロードされたらイベントリスナーを追加
  • input-formというIDを持ったフォームのSubmitが押された時の処理を書く
  • ai-contentというIDを持ったターゲットの中を「loading...」にする
  • フォームのinputに入力された値「qeury」の値を取得
  • POST /aiに対してボディをJSONでリクエスト
  • レスポンスのテキストをターゲットの中身とする

そして、このscript.jssrc/index.tsxで読み込ませて、配信します。

src/index.tsx
import script from '../assets/script.js'

// ...

app.get('/script.js', (c) => {
  return c.body(script, 200, {
    'Content-Type': 'text/javascript'
  })
})

そしてRenderer内のHTMLで読み込ませます。以下をメタタグ内に追加してください。

src/renderer.tsx
<script src="/script.js"></script>

いかがでしょうか?これで「ユーザーの入力に対してAIが答える」のがUI付きでできましたね!

SS

返答が途中で切れちゃうことが多いと思いますが、それは前述した通り、Workers AIのLLMでは現在、返答されるtokenの数に限りがあるからです。のちほど対策します。

完成させる

さて最後の仕上げです。どうせなら以下2つをやりましょう。

  1. Streamで文字をヌルヌル出す
  2. 履歴を保持する

実現するにはassets/script.jsを変更するだけでOKです。

メッセージの履歴と初期値

まず履歴を保持するための配列messagesを用意します。めんどくさいんでグローバルに置きます。

assets/script.js
const messages = [
  {
    role: 'user',
    content: `You are a helpful assistant. You do not respond as 'User' or pretend to be 'User'. You respond as 'Assistant'. You respond in less than 100 words.`
  }
]

もう初期値を入れてます。ユーザーからAIへの命令が入ってます。

  • あなたは優秀なアシスタントです
  • あなたはUserもしくはUserに似せて反応してはいけません
  • アシスタントとして返信してください
  • 100単語以内で返答してください

最後の「100単語以内」というのは現在のWorkers AIのLLMの場合、返答できるtoken数に限りがあるので、適当な短さを指定しています。

Streamを表示させる

Streamを表示させるための関数はこちらです。

assets/script.js
function fetchChunked(target) {
  target.innerHTML = 'loading...'
  fetch('/ai', {
    method: 'post',
    headers: {
      'content-type': 'application/json'
    },
    body: JSON.stringify({ messages })
  }).then((response) => {
    const reader = response.body.getReader()
    let decoder = new TextDecoder()
    target.innerHTML = ''
    reader.read().then(function processText({ done, value }) {
      if (done) {
        messages.push({
          role: 'assistant',
          content: target.innerHTML
        })
        return
      }
      target.innerHTML += decoder.decode(value)
      return reader.read().then(processText)
    })
  })
}

キモは後半で、response.body.getReader()で取得したリーダーをread()をするとStream、つまり今回は1文字ずつ受け取ると中身が実行され、追加された文字を含んだ文字列で、ターゲットの中身を書き換えています。全部受け取った段階で、messagesへ次に送るためにroleassistantとしてmessagesへ追加しています。

addEventListenerは以下としています。初期メッセージを表示するためにfetchChunked()を最初に呼び出しています。

assets/script.js
document.addEventListener('DOMContentLoaded', function () {
  const target = document.getElementById('ai-content')
  fetchChunked(target)
  document.getElementById('input-form').addEventListener('submit', function (event) {
    event.preventDefault()
    const formData = new FormData(event.target)
    const query = formData.get('query')
    messages.push({
      role: 'user',
      content: query
    })
    fetchChunked(target)
  })
})

assets/script.jsの全部のコードはこちらです。コピペしちゃってください!

assets/script.js 全コード
assets/script.js
const messages = [
  {
    role: 'user',
    content: `You are a helpful assistant. You do not respond as 'User' or pretend to be 'User'. You respond as 'Assistant'. You respond in less than 100 words.`
  }
]

document.addEventListener('DOMContentLoaded', function () {
  const target = document.getElementById('ai-content')
  fetchChunked(target)
  document.getElementById('input-form').addEventListener('submit', function (event) {
    event.preventDefault()
    const formData = new FormData(event.target)
    const query = formData.get('query')
    messages.push({
      role: 'user',
      content: query
    })
    fetchChunked(target)
  })
})

function fetchChunked(target) {
  target.innerHTML = 'loading...'
  fetch('/ai', {
    method: 'post',
    headers: {
      'content-type': 'application/json'
    },
    body: JSON.stringify({ messages })
  }).then((response) => {
    const reader = response.body.getReader()
    let decoder = new TextDecoder()
    target.innerHTML = ''
    reader.read().then(function processText({ done, value }) {
      if (done) {
        messages.push({
          role: 'assistant',
          content: target.innerHTML
        })
        return
      }
      target.innerHTML += decoder.decode(value)
      return reader.read().then(processText)
    })
  })
}

これで完成!できたかな?

SC

デプロイ

さあいよいよデプロイだ。冒頭と同じように以下を実行しよう。

npm run deploy

これで全世界にあなたの最初のWorkers AIアプリが公開されました。素晴らしいのは、これがエッジで動いてることです。そしてそのエッジにはGPUがのっています!

答え合わせ

僕が作ったアプリはこちらです。

https://my-first-workers-ai.yusukebe.workers.dev/

しばらく置いておくと思うので、答え合わせに使ってください。

発展編

これだけでも十分面白いのですが、時間が余った方、家で続きをやれる方はこれから紹介することをやるといいでしょう。

見え目を工夫する

同じ機能でも見栄えが違うだけでまるで違うアプリケーションになったりします。例えば、ChatGPTのGatewayアプリを作っているのですが、工夫するだけでだいぶそれっぽくなります。

  • 入力欄を下にする
  • メッセージが出てくる順番を反対にして下から出す
  • テキストが出てる時、テキストの末尾は「❚」などを点滅させる

SS

他のモデルを使う

今回使ったLLMのモデル以外にもWorkers AIには以下のモデルを使えます。

  • Speech to text
  • Translation
  • Sentiment Analysis
  • Image classification
  • Embedding

例えば、今回使ったLLMは日本語に弱いので、入力と出力、特に出力の際に日⇔英の翻訳をかます、なんてのをすごい面白いです。やってみたい。

OpenAIをやってみる

さきほど紹介したGatewayアプリのようにOpenAIをやってみるのもいいでしょう。今回やったStreamの表示や、フォームでの入力などをそのまま活かせるでしょう。

また、ChatGPTのPlugin作成も面白いです。HonoはChatGPTする際に使うOpenAPIをエレガントに作れる「Zod OpenAPI」拡張があるので、ぜひ使ってください。そのあたりのHonoがAIに向いてるという話は英語ですが以下にあります。

https://blog.yusu.ke/hono-ai-ready/

Honoを別のプラットフォームで動かす

Honoは他のプラットフォーム、ランタイムで動きます。ぜひ動かしてみましょう。基本的にStreamも動きます。

以下は用意されているスターターの一覧です。npm create hono@latest my-appコマンドを実行すれば選べるでレッツトライ。

  1. aws-lambda
  2. bun
  3. cloudflare-pages
  4. cloudflare-workers
  5. deno
  6. fastly
  7. lagon
  8. lambda-edge
  9. netlify
  10. nextjs
  11. nodejs
  12. vercel

その他

その他の事柄。

消しておく

デプロイした場合Workerを「DELETE」しておくのをお忘れなく!ダッシュボードの左側「Workers & Pages」から対象のWorkerを選んで、「設定」>「プロジェクトの削除」をしてください。

まとめ

以上、お疲れ様でした!果たして終わったでしょうか?当日、名古屋では見事に全員がWorkers AIのアプリを完成させているのを願っています。

もし終わらなかったら!完成品のレポジトリをcloneして、実行しちゃえばいいじゃないでしょうか!

git clone git@github.com:yusukebe/my-first-workers-ai.git
cd my-first-workers-ai
npm install
npm run dev
npm run deploy

https://github.com/yusukebe/my-first-workers-ai

名古屋に直接来てもらってもいいですよ!

ではさようなら。

参考文献

Discussion

たつきちたつきち

ワークショップ参加させていただきました!

#cssフレームワークを入れるrender.tsx の内容が間違っていたのでお手隙の際に修正いただけましたら🙏

動くやつ
import { jsxRenderer } from 'hono/jsx-renderer'

export const renderer = jsxRenderer(({ children }) => (
  <html>
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <script src="/script.js"></script>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css" />
    </head>
    <body>
      <header>
        <h1>My first Workers AI</h1>
      </header>
      <div>{children}</div>
    </body>
  </html>
))
yusukebeyusukebe

ありがとうございます!

#cssフレームワークを入れる の render.tsx の内容が間違っていたのでお手隙の際に修正いただけましたら🙏

ですね、ですね。修正しておきました!