Home

2026-01-25 function-calling-ui plan

OpenAI Function Calling による対話式UI操作 実装計画

概要

Ghostrunnerプロジェクトに「OpenAI Function Calling による対話式UI操作」機能を追加する。 音声/テキスト入力からAIがユーザーの意図を解析し、UIアクション(画面遷移、フォーム入力など)およびClaude CLIコマンドの実行を返却する。

将来的な拡張: Gemini API対応を想定した設計とする。


ユーザー確認結果

項目決定
Function目的UI操作 + Claude CLI実行の両方
デプロイ構成別ポート運用(Next.js:3000、Go API:8080)
音声入力初期リリースに含める
APIキー用意可能

現状分析

既存構造

Ghostrunner/
|-- cmd/server/main.go           # Goサーバーエントリポイント
|-- internal/
|   |-- handler/command.go       # HTTPハンドラー(SSEストリーミング対応)
|   |-- service/claude.go        # Claude CLI実行サービス
|   |-- service/types.go         # StreamEvent, CommandResult等
|-- web/index.html               # バニラJS UI(約900行)
|-- docs/BACKEND_API.md          # API仕様書

既存API

エンドポイント説明
POST /api/command/streamコマンドのSSEストリーミング実行
POST /api/command/continue/streamセッション継続(質問への回答)

既存イベントタイプ

init, thinking, tool_use, tool_result, text, question, complete, error


懸念点と選択肢

1. AIプロバイダー抽象化のタイミング

選択肢メリットデメリット
A: 初期から抽象レイヤーGemini対応が容易初期実装が複雑化
B: OpenAI固定で後から抽象化シンプルに開始可能リファクタリング必要

推奨: B案(YAGNI原則)

2. 音声入力方式

選択肢メリットデメリット
A: OpenAI Realtime APIリアルタイム双方向通信実装複雑、コスト高
B: Whisper API(REST)シンプル、確実遅延あり

推奨: B案(まずはWhisperで確実に動作させる)

3. Function Callingの実行場所

選択肢メリットデメリット
A: Goバックエンドで実行APIキー管理集中バックエンド改修必要
B: Next.jsフロントエンドで実行バックエンド変更不要APIキーがフロントに露出
C: Next.js API Routes経由セキュア、柔軟中間レイヤー追加

推奨: A案(Goバックエンドに新規エンドポイント追加)

4. 会話履歴の管理

選択肢メリットデメリット
A: バックエンドで保持フロントが軽量ステートフル、複雑
B: フロントから毎回送信ステートレスリクエストサイズ増大

推奨: B案(ステートレス設計を維持)

5. 既存 web/index.html の扱い

選択肢メリットデメリット
A: 削除してNext.jsに置き換えコード統一既存機能の再実装必要
B: 両方維持移行期間の安全網メンテナンス負荷

推奨: B案(移行完了後に削除検討)


バックエンド計画

1. 新規エンドポイント

エンドポイントメソッド説明
/api/ai/processPOSTFunction Calling実行(同期)
/api/ai/transcribePOST音声文字起こし(Whisper API)

2. API設計

POST /api/ai/process

リクエスト:

{
    "message": "顧客一覧画面を開いて",
    "conversation_history": [
        {"role": "user", "content": "前の発言"},
        {"role": "assistant", "content": "前の応答"}
    ]
}

レスポンス(Function Call検出時):

{
    "success": true,
    "type": "function_call",
    "function_call": {
        "name": "navigate_screen",
        "arguments": {
            "screen_id": "customer_list",
            "params": {}
        }
    },
    "message": "顧客一覧画面に移動します"
}

レスポンス(通常テキスト応答時):

{
    "success": true,
    "type": "text",
    "message": "承知しました。どの画面を開きますか?"
}

POST /api/ai/transcribe

リクエスト: multipart/form-data

  • audio: 音声ファイル(webm, wav, mp3)

レスポンス:

{
    "success": true,
    "text": "顧客一覧画面を開いて"
}

3. Function定義

Function名説明パラメータ
navigate_screen画面遷移screen_id, params
fill_formフォーム入力form_id, fields
submit_actionアクション実行action_id, params
show_dialogダイアログ表示dialog_type, message
execute_claude_commandClaude CLIコマンド実行command, project, args

execute_claude_command 詳細

{
    "name": "execute_claude_command",
    "description": "Claude Code CLIコマンドを実行する",
    "parameters": {
        "type": "object",
        "properties": {
            "command": {
                "type": "string",
                "enum": ["plan", "fullstack", "go", "nextjs"],
                "description": "実行するコマンド"
            },
            "project": {
                "type": "string",
                "description": "対象プロジェクトのパス"
            },
            "args": {
                "type": "string",
                "description": "コマンドの引数"
            }
        },
        "required": ["command", "project", "args"]
    }
}

4. 変更ファイル一覧

ファイル変更内容
internal/service/types.goAI関連の型定義を追加
internal/service/openai.go (新規)OpenAI API クライアント実装
internal/handler/ai.go (新規)AI処理用HTTPハンドラー
cmd/server/main.goルーティング追加

5. 実装ステップ

  1. 型定義追加 (internal/service/types.go)

    • AIRequest/AIResponse型
    • FunctionCall型
    • TranscribeRequest/TranscribeResponse型
  2. OpenAIサービス実装 (internal/service/openai.go)

    • Chat Completions API呼び出し(Function Calling対応)
    • Whisper API呼び出し(音声文字起こし)
    • Function定義の構築
  3. HTTPハンドラー作成 (internal/handler/ai.go)

    • /api/ai/process エンドポイント
    • /api/ai/transcribe エンドポイント
    • リクエストバリデーション
  4. ルーティング追加 (cmd/server/main.go)

    • AIHandler初期化と登録

フロントエンド計画

1. プロジェクト構造

frontend/
|-- src/
|   |-- app/
|   |   |-- layout.tsx           # ルートレイアウト
|   |   |-- page.tsx             # メインページ
|   |   |-- globals.css          # グローバルスタイル
|   |-- components/
|   |   |-- chat/
|   |   |   |-- ChatContainer.tsx    # チャット全体コンテナ
|   |   |   |-- MessageList.tsx      # メッセージ一覧
|   |   |   |-- MessageItem.tsx      # 個別メッセージ
|   |   |   |-- InputArea.tsx        # 入力エリア
|   |   |-- event/
|   |   |   |-- EventList.tsx        # SSEイベント一覧
|   |   |   |-- EventItem.tsx        # 個別イベント表示
|   |   |-- question/
|   |   |   |-- QuestionModal.tsx    # 質問モーダル
|   |   |-- ui/
|   |       |-- Button.tsx           # 汎用ボタン
|   |       |-- LoadingSpinner.tsx   # ローディング
|   |-- hooks/
|   |   |-- useSSEStream.ts          # SSEストリーミング
|   |   |-- useVoiceInput.ts         # 音声入力
|   |   |-- useAIChat.ts             # AI対話管理
|   |-- lib/
|   |   |-- api.ts                   # Go API クライアント
|   |   |-- whisper.ts               # Whisper API クライアント
|   |-- types/
|       |-- api.ts                   # API型定義
|       |-- events.ts                # イベント型定義

2. 型定義

// types/api.ts

// AI API
interface AIProcessRequest {
  provider?: 'openai' | 'gemini';
  message: string;
  conversation_history?: ConversationMessage[];
}

interface ConversationMessage {
  role: 'user' | 'assistant';
  content: string;
}

interface AIProcessResponse {
  success: boolean;
  type?: 'function_call' | 'text';
  function_call?: FunctionCall;
  content?: string;
  error?: string;
}

interface FunctionCall {
  name: string;
  arguments: Record<string, unknown>;
}

// SSE イベント
type StreamEventType =
  | 'init' | 'thinking' | 'tool_use' | 'tool_result'
  | 'text' | 'question' | 'complete' | 'error';

interface StreamEvent {
  type: StreamEventType;
  session_id?: string;
  message?: string;
  tool_name?: string;
  tool_input?: Record<string, unknown>;
  result?: CommandResult;
}

interface CommandResult {
  session_id: string;
  output: string;
  questions?: Question[];
  completed: boolean;
  cost_usd?: number;
}

interface Question {
  question: string;
  header: string;
  options: Option[];
  multiSelect: boolean;
}

interface Option {
  label: string;
  description: string;
}

3. 主要コンポーネント

コンポーネント責務
ChatContainerAI対話全体の状態管理、Function Call実行
MessageList対話履歴表示
InputAreaテキスト/音声入力切り替え
EventListSSEイベントのリアルタイム表示(Claude Code実行時)
QuestionModalClaude Codeからの質問への回答UI

4. 実装ステップ

  1. プロジェクト初期化

    • create-next-app でNext.js 15作成
    • Tailwind CSS設定
    • TypeScript strict mode
  2. 型定義とAPIクライアント

    • types/api.ts
    • lib/api.ts(Go API連携)
  3. SSEストリーミングフック

    • useSSEStream.ts
  4. 基本UI構築

    • ChatContainer
    • EventList
    • InputArea
  5. AI対話機能

    • useAIChat.ts(Function Calling連携)
  6. 音声入力

    • useVoiceInput.ts(Whisper API)

対話フロー図

メインフロー

Loading diagram...

デプロイ構成

別ポート運用(採用)

┌─────────────────────────────┐     ┌─────────────────────────────┐
│   Next.js Frontend          │     │   Go API Server             │
│   http://localhost:3000     │────▶│   http://localhost:8080     │
│                             │     │                             │
│   - AI対話UI                │     │   - /api/ai/process         │
│   - 音声入力                │     │   - /api/ai/transcribe      │
│   - Function Call実行       │     │   - /api/command/stream     │
└─────────────────────────────┘     │   - /api/command/continue   │
                                    │                             │
┌─────────────────────────────┐     │   既存:                     │
│   既存 Web UI               │────▶│   - /api/plan/*             │
│   http://localhost:8080/    │     │   - StaticFile: /           │
│   (web/index.html)          │     └─────────────────────────────┘
└─────────────────────────────┘

CORS設定(Go API側)

cmd/server/main.go にCORS設定を追加:

r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"http://localhost:3000"},
    AllowMethods:     []string{"GET", "POST", "OPTIONS"},
    AllowHeaders:     []string{"Content-Type"},
    AllowCredentials: true,
}))

環境変数

バックエンド(Go)

変数名説明
OPENAI_API_KEYOpenAI APIキー(Function Calling + Whisper)sk-...

フロントエンド(Next.js)

変数名説明
NEXT_PUBLIC_API_URLGo APIベースURLhttp://localhost:8080

テスト計画

バックエンド

対象テスト種別
OpenAI APIレスポンスパースユニットテスト
ハンドラーバリデーションユニットテスト

フロントエンド

対象テスト種別
useSSEStreamユニットテスト
api.tsユニットテスト
対話フローE2Eテスト

確認済み事項

項目決定内容
Function定義の範囲UI操作(navigate_screen, fill_form, submit_action, show_dialog)+ Claude CLI実行(execute_claude_command)
OpenAI APIキー用意可能(バックエンドで一元管理)
音声入力初期リリースに含める
デプロイ構成別ポート運用(Next.js:3000、Go API:8080)

型定義の整合性

Go API 既存型との対応

Go型 (service/types.go)TypeScript型 (types/api.ts)備考
StreamEventStreamEvent完全互換
CommandResultCommandResult完全互換
QuestionQuestion完全互換
OptionOption完全互換

新規追加型:

  • AIProcessRequest / AIProcessResponse(新規API用)
  • TranscribeResponse(Whisper API用)
  • FunctionCall(Function Calling用)

実装コマンド

計画承認後、以下のコマンドで実装を開始:

/fullstack