Skip to content

Instantly share code, notes, and snippets.

@mizchi
Created June 19, 2024 12:10
Show Gist options
  • Save mizchi/3160fb02bc22c8e1e3ca6975b6cba843 to your computer and use it in GitHub Desktop.
Save mizchi/3160fb02bc22c8e1e3ca6975b6cba843 to your computer and use it in GitHub Desktop.
marp theme paginate
true
gaia
true

LLMによるフロントエンド生成自動化

  • mizchi | Plaid Inc
  • TechFeed Expert Night 31

LLM 使ってますか?

  • 便利ですよね ChatGPT / GitHub Copilot
  • WebUI じゃなくて CLI で自動化したいですよね?
  • 黎明期でまともにモジュール化しても無駄そうだし、 Deno の書き捨てスクリプト量産するぞ!

Deno for LLM

  • 設定や依存のインストール不要のTS実行環境
    • 書き捨ての CLI スクリプトが書きやすい => 実験しやすい
    • npm 資産が使える import {z} from "npm:zod"
  • コード評価に Deno Sandbox を用意できる(後述)
  • @luca/esbuild-deno-loader でビルドしてフロントエンド/Cloudflare にデプロイ
    • API_KEY をインライン化しないように!
  • Python が多いが、HTTP Request できれば何の言語でもいい
    • OSS みると自然言語のプロンプトが埋め込まれてるだけ

Deno: gpt-4o

// deno run --allow-net --allow-env run.ts
import { OpenAI } from 'npm:openai@4.51.0';
const openai = new OpenAI({ apiKey: Deno.env.get('OPENAI_API_KEY') });
const result = openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: "hello"}]
});
console.log(data.content[0]);
// { type: "text", text: "Hello! How can I assist you today?" }
  • これだけ
  • API_KEY は自分で取る

Deno: claude-3-opus

import AntrophicAI from "npm:@anthropic-ai/sdk@0.23.0";
const client = new AntrophicAI({ apiKey: Deno.env.get("ANTHROPICAI_API_KEY")! });
const result = await client.messages.create({
  model: "claude-3-opus-20240229",
  max_tokens: 1024,
  messages: [{ role: "user", content: "hello" }]
});
  • API は OpenAI に寄せてる
  • (AnthropicAI の API_KEY を取るのが面倒だった)

mizchi/previs ~ マークアップ自動化CLI

  • https://github.com/mizchi/previs
  • deno 製のコード生成/修正のCLIツール
  • React Component の生成/修正/プレビューができる
    • previs fix button.tsx "背景を赤くして"
  • とりあえず自作してから LLM周辺ツールを理解していこうという趣旨の、mizchiの学習用のプロダクト という側面が強いのは注意

previs の使い方

$ deno install -Af https://deno.land/x/previs@0.1.13/previs.ts

  1. コードを生成: previs src/button.tsx "ボタンのコンポーネントを作って"
  2. コードを修正: previs src/button.tsx "背景を黒くして"
  3. テストが通るまでリトライ: previs src/button.tsx -- vitest --run __FILE__

previs の内側

  • 編集対象のコードをエントリポイントにする一時ファイルを生成
.previs/
  index.html
  run.ts # ... render(<Component ...>, root);
src/components/button.tsx
src/components/button.__previs__.tsx # 一時ファイル
previs.config.ts # vite.config
  • vite サーバーを起動 / puppeteer でスクリーンショットを撮影
  • gpt-4o にスクリーンショットを添付してコード修正を依頼
  • 元コードに適用して再プレビュー or テストの実行

previs: システムプロンプトの設計(一部)

あなたは優秀なプログラマです。 あなたの役割は ユーザーの要望に応えて TypeScript のコードを生成することです。

  • 応答にコード以外の解説やコメント等は不要です。Markdown コードブロックだけを出力してください。
  • 出力するコードは省略せず、必ずコード全文を生成してください。 ...

詳細はこちら https://zenn.dev/mizchi/scraps/35fb02e3693275


previs の規約: __PREVIEW__

type Props = {text: string, onClick: () => void}
export default Button({text, onClick}: Props) {
  return <button type="button" onClick={onClick}>{text}</button>
}
export const __PREVIEW__ = () => {
  return <Button text="click!", onClick={() => {}}>
}
  • 大抵のコンポーネントは直接実行できるわけではなく Props が必要
  • LLM は Props の型から 精度高くモックデータを生成できる
  • 普通に人間に対するコード例としても有用

previs の規約: Tailwind

export default function Button() {
  return (
    <button
      type="button"
      className="bg-blue-500 text-white font-semibold py-2 px-4 rounded shadow"
    >
      &#x1F4C8; Click me {/* Text remains updated */}
    </button>
  );
}
  • LLM/Attension を考えると実装と装飾は一体化してる方が効く
  • 編集/出力対象が単一ファイルなので、取り扱いが簡単
  • Panda CSS 等でもいいが Tailwind は学習量が多く、精度が段違い

previs の規約: In-Source Testing

export function add(a: number, b: number) {
  return a + b;
}
if (import.meta.vitest) {
  const {test, expect} = import.meta.vitest;
  test("1+2=3", () => {
    expect(add(1, 2)).toBe(3);
  });
}
  • 実装に近い場所にテストを書くことで関連付けやすくする
  • UI 向けには testing-library を試したがワークしない(後述)

previs: テスト評価と Deno Sandbox

  • LLMが生成したコードをチェックせずに評価するのは危険
    • 「文字列からの数値の変換には eval を使います」というドキュメントを学習してしまっていたら?
    • 誤った学習資料をばらまいてLLMを汚染する攻撃は存在する
  • そこで Deno Sandbox / Node: Process Based Permissions
    • deno test --allow-read=./mocks code.ts でアクセス制御
    • node にもあるが experimental node --experimental-permission --allow-fs-read='*'...
  • プログラマを介さない BtoB ならサンドボックス設計がむしろ本番

previs: 作ってみた感想

  • 基本的な構成要素は悪くないが、肝心のLLMの精度が悪い
  • tailwind の className を「よくある羅列」として列挙してるだけ
    • 崩れても修正できない
  • 画像をアップロードしてもコードの該当箇所と関連付けられない
    • 崩れても修正できない!
  • インラインテストで書いても、実装を紐づける能力がない
    • 崩れても修正できない!!!!!!!!!!!!!!!!!!
  • 編集するコードが大きいほど、出力が顕著に遅い
    • 自分で書いたほうが速いのでは?

previs に足りないもの

  • 依存なしの単体ファイルしかまともに扱えない
  • 周辺データを動的に取り出したい
  • フレームワークを縛っている
  • 小手先のハックを試したり、他の実装例を見てみることにした
    • 常にゼロから再生成
    • .d.ts をぶちこむ
    • cover-agent

小手先のハック: 常にゼロから再生成

  • 修正が苦手なら、毎回ゼロから生成すればいいのでは?
  • プロンプト1:装飾を取り除いて JS ロジックだけを残した状態にして
  • プロンプト2:元のコードは忘れてTailwind で装飾して
  • あとは並列数をあげて試行回数で対処(v0的アプローチ)
    • 一瞬でAPI使用上限に達した👼
  • Excalidraw で雑なワイヤフレームだけ

小手先のハック: .d.ts as prompt

  • *.d.ts は型とコメントが残るのでAI向け
  • 全ての依存を deps.ts で 再 export することを要件にする
  • dts-bundle-generator で deps.d.ts を単一ファイルとして生成
import { generateDtsBundle } from "npm:dts-bundle-generator@9.3.1";
const results = generateDtsBundle([{ filePath: './deps.ts' }]);
  • 追加プロンプト: あなたは ./deps.ts から import できます。その d.ts は以下です。(埋め込まれたコード)
  • スコープを限定してあげるのが精度を出すコツっぽい

小手先のハック: Headless as prompt

// shadcnui
import { Avatar,AvatarFallback,AvatarImage} from "@/components/ui/avatar"
export function AvatarDemo() {
  return (
    <Avatar>
      <AvatarImage src="/avatar.png" alt="user-image" />
      <AvatarFallback>Ja</AvatarFallback>
    </Avatar>
  )
}
  • 命名されたコンポーネントやアクセシビリティはプロンプトとして使える
  • shadcnui や radix なら Tailwind でさらに拡張できる
  • 既知のフレームワークにしか対応できない

もうRAG を作るしかねえ!

  • Retrieval-Augmented Generation: 検索拡張生成
  • 要はAIに検索能力を与えるもの
    • Google に限らず任意のドキュメントを探す機能を与えること
    • 今回はローカルのソースコードを検索 / テスト実行ログの取得
  • 私見: 人間だって別に一発で動くコードを書けるわけではないし、AI に不満をいうなら、プログラマと同等の試行錯誤の能力を与えてから評価する必要がある

Function Calling (OpenAI / AnthropicAI)


Function Calling 実装例

$ deno run --allow-net --allow-env 'pydantic について詳しく教えて'
<thinking>
pydantic の詳細を答えるのに必要な情報は質問文には含まれていない。
pydantic について詳細に答えるには、以下のような情報が必要となる:
1. pydantic とはどのようなライブラリか
2. pydantic の主な機能は何か
3. pydantic の使い方やサンプルコード
これらの情報を得るには、Google 検索で "pydantic とは""pydantic チュートリアル"
などのキーワードで検索するのが良さそうだ。
</thinking>
[tool_use] search_google { query: "pydantic とは" }
[tool_result] "Python開発に欠かせない!pydanticの使い方とメリット..."
  • tool_use で実行依頼が来るので、tool_result で返すと処理を続行

AnthropicAI Tools: Function Calling の凄さ

  • 入力に対して、動的に、ステップバイステップで考えてくれる
    • 入力が足りなかったら追加のリクエストをしてくる
  • こちらが大量にツールを提供しておくことで、コンテキストに応じた拡張が可能
  • プログラマは jsonschema とそれを引数にとる関数を実装するだけ

CoverAgent

$ cover-agent \
  --source-file-path "app.py" \
  --test-file-path "test_app.py" \
  ...
  • 手元で簡単なコードに対して実行したら 34% -> 97%
  • LLMは修正が苦手なのであって、テストの追加は向いてそう

コード生成の高速化

  • LLM は diff が作れず、自動化するには全文生成が必要
  • 試しに行数を与えてそれに対する操作をさせたらうまくいった
1: 
2:export function add(a: number, b: number): number {
3:  return 0;
4:}
5: 
// output
3:   return a + b;

https://zenn.dev/mizchi/articles/better-llm-code-gen


ソースコード検索RAG(いる?)

  • ローカルのソースコードを Embedding に変換して検索できるようにしておけばいいのでは?
  • Embedding: テキストをベクトルに変換して類似度を計算する
    • 要はLLMの内部アルゴリズムを利用した検索の実装方法
  • とはいえ、コード検索なら別に大規模にならないし不要かも?
    • 最悪 GitHub のソースコード検索能力与えとけばよさそう
    • やるなら依存グラフの GraphRAG がいいのではと考えてる

フロントエンドコード生成: まとめ

  • 雑なワンショットだと、簡単なものしか作れなかった
  • 今の段階で必要なのは Function Calling でプログラマのドメイン知識を提供すること
  • ビルディングブロックを作ったので、もう少しマシにできそう
  • 割と早い段階でマークアップに関しては自動化できそうな予感

プロンプトエンジニアリング雑感

  • インターン向けの手順書を作ってる感覚に近い
  • とりあえず https://www.promptingguide.ai/jp を読もう
  • やっぱりモデルの素の性能(IQ)が足りない
    • モデルに claude-3-opus やローカルの Llama3 でも厳しい
    • もう一回ぐらいイノベーション待ったほうがいい
  • さすがに Pythonの方が知見や作例が多い

今気になってるもの

この世界では新人なので、いいのがあったら教えてください


おわり

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment