bolt.newのローカル環境構築におけるトラブルシューティングガイド

bolt.newをローカル環境で動作させる際、APIエンドポイントの変更や機能拡張が必要になる場合があります。本記事では、OpenAI APIへの切り替え、AI SDKのエラー対処、およびファイルダウンロード機能の実装方法について解説します。

AIプロバイダーの切り替え設定

まず、AnthropicからOpenAIへプロバイダーを変更するための設定を行います。@ai-sdk/openaiパッケージをインストールします。

pnpm install @ai-sdk/openai

次に、LLMモデルの設定ファイルを作成します。環境変数を活用した柔軟な設定が可能です。

// app/lib/.server/llm/provider.ts
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';

type ProviderConfig = {
  baseURL: string;
  apiKey: string;
  modelName: string;
};

export function initializeOpenAIProvider(config: ProviderConfig) {
  const openai = createOpenAI({
    baseURL: config.baseURL,
    apiKey: config.apiKey,
  });
  
  return openai(config.modelName);
}

export function initializeAnthropicProvider(config: ProviderConfig) {
  const anthropic = createAnthropic({
    baseURL: config.baseURL,
    apiKey: config.apiKey,
  });
  
  return anthropic(config.modelName);
}

ストリーミング処理のロジックを更新し、プロバイダーを切り替えます。

// app/lib/.server/llm/text-stream.ts
import { streamText as vercelStreamText, convertToCoreMessages } from 'ai';
import { initializeOpenAIProvider } from '~/lib/.server/llm/provider';
import { getSystemPrompt } from './system-prompt';
import { TOKEN_LIMIT } from './limits';

export async function streamText(messages: ChatMessage[], env: Env, options?: StreamOptions) {
  const modelConfig = {
    baseURL: env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
    apiKey: env.OPENAI_API_KEY,
    modelName: env.OPENAI_MODEL || 'gpt-4o',
  };
  
  return vercelStreamText({
    model: initializeOpenAIProvider(modelConfig),
    system: getSystemPrompt(),
    maxTokens: TOKEN_LIMIT,
    messages: convertToCoreMessages(messages),
    ...options,
  });
}

トークン制限の調整

OpenAIのモデル仕様に合わせて、最大トークン数を適切な値に設定します。

// app/lib/.server/llm/limits.ts
// OpenAI GPT-4oの制限に基づいて設定
export const TOKEN_LIMIT = 4096;

// レスポンスセグメント数の制限
export const MAX_SEGMENTS = 2;

AI SDKの互換性対策

AI SDKで未対応のチャンクタイプが送信される場合、エラーが発生します。これを回避するため、カスタムストリームラッパーを実装します。

// app/lib/.server/llm/safe-stream.ts
import { createAsyncIterableStream } from 'ai';

export function createSafeIterableStream(sourceStream: AsyncIterable<any>) {
  return createAsyncIterableStream(sourceStream, {
    transform(chunk, controller) {
      if (!chunk || typeof chunk !== 'object') {
        return;
      }
      
      switch (chunk.type) {
        case 'object':
          controller.enqueue(chunk.object);
          break;
        case 'text-delta':
        case 'finish':
          // これらのタイプは正常に処理
          break;
        case 'error':
          controller.error(chunk.error);
          break;
        default:
          // 未知のチャンクタイプはログに記録して無視
          console.warn(`未対応のチャンクタイプを検知: ${JSON.stringify(chunk)}`);
          // 例外を投げずに処理を継続
      }
    }
  });
}

プロジェクトファイルのダウンロード機能

作業中のプロジェクトをZIP形式でダウンロードする機能を追加します。必要なパッケージをインストールします。

pnpm add jszip file-saver

ダウンロードボタンコンポーネントを作成します。

// app/components/workbench/DownloadButton.tsx
import { useCallback } from 'react';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import { useWorkbenchStore } from '~/stores/workbench';

export function ProjectDownloadButton() {
  const fileStore = useWorkbenchStore();
  
  const handleDownload = useCallback(async () => {
    try {
      const projectFiles = fileStore.getAllFiles();
      const archive = new JSZip();
      const projectFolder = archive.folder('project');
      
      if (!projectFolder) {
        throw new Error('ZIPフォルダの作成に失敗しました');
      }
      
      Object.entries(projectFiles).forEach(([path, file]) => {
        if (file?.type === 'file' && file.content) {
          const relativePath = path.replace(/^\/home\/project\//, '');
          projectFolder.file(relativePath, file.content);
        }
      });
      
      const zipContent = await archive.generateAsync({ 
        type: 'blob',
        compression: 'DEFLATE',
        compressionOptions: { level: 6 }
      });
      
      saveAs(zipContent, `bolt-project-${Date.now()}.zip`);
    } catch (error) {
      console.error('ダウンロード処理でエラーが発生:', error);
    }
  }, [fileStore]);
  
  return (
    <button
      onClick={handleDownload}
      className="flex items-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
    >
      <svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
      </svg>
      プロジェクトをダウンロード
    </button>
  );
}

このコンポーネントをワークベンチに統合します。

// app/components/workbench/WorkbenchLayout.tsx
import { ProjectDownloadButton } from './DownloadButton';

export const WorkbenchLayout = ({ isStreaming }: WorkbenchProps) => {
  return (
    <div className="flex h-full flex-col">
      <div className="flex items-center justify-between border-b px-4 py-2">
        <h2 className="text-lg font-semibold">ワークベンチ</h2>
        <div className="flex items-center gap-2">
          {!isStreaming && <ProjectDownloadButton />}
          <!-- その他のコントロール -->
        </div>
      </div>
      <!-- エディタ領域 -->
    </div>
  );
};

以上の設定により、bolt.newをOpenAI APIで動作させ、プロジェクトファイルをダウンロードできるようになります。環境変数を適切に設定することで、異なるモデルやエンドポイントにも柔軟に対応可能です。

タグ: bolt.new OpenAI Anthropic AI SDK JSZip

5月16日 20:35 投稿