OpenAI APIで作るAIペアプログラミングアシスタント〜新米エンジニアの挑戦〜

# OpenAI APIで作るAIペアプログラミングアシスタント〜新米エンジニアの挑戦〜

## はじめに

こんにちは。数ヶ月前まで「どうしてこうなった？」というコードばかり書いていた新米エンジニアです。みなさんは、プログラミング学習中に壁にぶつかったり、挫折しそうになった経験はありますか？

私の場合、約3時間もタイプミスを探したり、エラーメッセージが理解できずに夜を徹したり...。「隣に親切な先輩エンジニアがいてくれたら」と何度も思いましたし、今でも新しい言語や領域を学習すると、壁にぶつかることはあります。

そんな経験から生まれたのが、この「AIペアプログラミングアシスタント」です。JavaScript学習中の初心者向けに、OpenAI APIを活用して作りました。技術的な挑戦の記録と、実装のポイントを共有します。

## プロジェクトの概要と設計思想

### コンセプト

このアプリケーションは、以下の問題を解決するために設計しました

- 初心者が理解できないエラーメッセージを、自然な言葉で説明する
- コードの問題点を指摘し、修正方法を提案する
- JavaScript学習の基本概念を復習できるリファレンスを提供する
- 質問に対して親切に回答する（StackOverflowで専門用語だらけの回答に苦戦した経験から）

### 技術スタック

**フロントエンド**
- React 19.1.0: 最新のReactを使用
- @monaco-editor/react 4.7.0: VS Codeライクなエディタ体験
- react-markdown 10.1.0: マークダウンレンダリング

**バックエンド & API**
- OpenAI API (openai@4.95.1): GPT-3.5-turboを使用したエラー分析
- JavaScript Error Detection: リアルタイムエラー検出システム

**デプロイ & インフラ**
- Vercel: サーバーレスデプロイメント

### アーキテクチャ

```
src/
├── components/         # UIコンポーネント
│   ├── CodeEditor.js   # Monaco エディター
│   └── AIAssistant.js  # AIレスポンス表示
├── utils/              # ユーティリティ関数
│   ├── errorDetection.js  # エラー検出
│   └── commonIssues.js    # エラーパターン定義
├── services/           # 外部サービス連携
│   └── ai-service.js   # OpenAI API連携
└── App.js              # メインアプリケーション
```

## OpenAI APIの活用方法を理解する

OpenAI APIを使うのは初めてだったので、まずは基本を理解するところから始めました。

### API連携の基本設定

```javascript
// src/services/ai-service.js
import OpenAI from 'openai';

// OpenAIクライアントの初期化
const openai = new OpenAI({
  apiKey: process.env.REACT_APP_OPENAI_API_KEY,
  dangerouslyAllowBrowser: true // ブラウザ環境での使用を許可
});

export const getAIAssistance = async (code, error) => {
  try {
    const response = await openai.chat.completions.create({
      model: "gpt-3.5-turbo",
      messages: [
        {
          role: "system",
          content: "あなたは親切なプログラミング講師です。初心者に優しく、わかりやすく教えてください。エラーの説明と解決策を提供してください。"
        },
        {
          role: "user",
          content: `以下のJavaScriptコードにエラーがあります：\n\n${code}\n\nエラー：${error.message}\n\nこのエラーを解決するためのアドバイスをください。`
        }
      ],
      temperature: 0.7,
      max_tokens: 500
    });
    
    return response.choices[0].message.content;
  } catch (error) {
    console.error('API Error:', error);
    return 'AIアシスタントに接続できませんでした。しばらく待ってから再度お試しください。';
  }
};
```

### システムプロンプトの工夫について

APIの使い方自体はシンプルなのですが、苦労したのはシステムプロンプトの設計です。AIに「初心者に優しく、かつ技術的に正確な回答」をしてもらうために、かなり試行錯誤しました。

特に苦労したのは、AIの回答のトーンと内容のバランスです。最初は単に「わかりやすく説明して」と指示していましたが、AIは時に専門用語を多用したり、逆に簡略化しすぎて正確性を欠いたりしました。そこで、実際のエラーメッセージとコードを何十パターンも試し、その回答を分析しながらプロンプトを調整していきました。

私自身が遭遇したエラーパターン（括弧の不一致、未定義変数の使用、非同期処理の誤用など）を集め、それぞれについてのAIの回答を比較検討することで、最適なプロンプトに近づけていきました。

```javascript
// 例：システムプロンプトの進化
const INITIAL_PROMPT = "JavaScriptのエラーを説明してください。";

const IMPROVED_PROMPT = `
あなたは親切なプログラミング講師です。初心者に優しく、わかりやすく教えてください。
提供されたコードとエラーメッセージを分析し、以下のフォーマットで回答してください：

1. エラーの概要（自然な言葉で説明）
2. 問題の箇所（該当するコードを引用）
3. なぜエラーが起きているのか（具体例を交えて説明）
4. 修正方法（複数の選択肢がある場合は、最もシンプルな方法を優先）
5. 今後同じエラーを防ぐためのアドバイス
`;
```

当初は単に「エラーを解説して」とだけ指示していましたが、回答が専門的すぎたり冗長だったりしました。そこで、回答フォーマットを細かく指定し、「自然な言葉で」「初心者が挫折しないような言葉選び」といった指示を加えていきました。

基本的な基準として「検索で調べたら簡単に理解できるレベルかどうか？」という判断軸を設け、AIへのプロンプトを設計しました。初心者がエラーメッセージを見て検索しても、いきなり高度な専門用語の解説が出てくると挫折してしまうかもしれません。その代わりに、「これを検索すれば詳しく調べられますよ」という道標になるような説明を心がけました。

## エラー検出システムの実装

このアプリの核心部分である、エラー検出システムについて解説します。

### エラーパターンの収集から

まず、JavaScript初心者がよく遭遇するエラーパターンを収集しました。自分が実際に遭遇したエラーや、プログラミングスクールのメンターから受けた過去のフィードバック記録をもとに、以下のようなカテゴリに分類しています。

- 構文エラー（括弧の不一致、セミコロンの欠落など）
- 型関連エラー（undefinedやnullの操作など）
- スコープ関連のエラー（変数のアクセス範囲）
- 非同期処理のエラー（Promiseの扱いなど）
- 範囲エラー（配列の範囲外アクセスなど）
- 代入エラー（constへの再代入など）
- モジュールエラー（インポート/エクスポートの問題）

### エラー前処理機能

ユーザーが入力したコードとエラーメッセージを解析し、AIに送る前に前処理を行います。これにより、AIの回答精度を高めています。

```javascript
// src/utils/errorDetection.js
import { COMMON_ISSUES } from './commonIssues';

export const detectError = (code) => {
  try {
    // 実際にコードを実行してエラーをキャッチ
    new Function(code)();
    return null;
  } catch (error) {
    return {
      message: error.message,
      type: error.name,
      line: error.line || null
    };
  }
};

export const analyzeError = (error) => {
  if (!error) return null;
  
  for (const [key, issue] of Object.entries(COMMON_ISSUES)) {
    if (issue.patterns.some(pattern => error.message.includes(pattern))) {
      return {
        type: key,
        guidance: issue.guidance,
        tips: issue.tips,
        originalError: error
      };
    }
  }
  
  return {
    type: 'unknown',
    guidance: '一般的なエラーです。エラーメッセージを注意深く読んでみましょう。',
    tips: ['エラーメッセージをGoogle検索してみる'],
    originalError: error
  };
};
```

このアプローチは非常に効果的で、コードを実際に実行してエラーを検出し、事前に用意したエラーパターンとマッチングさせることで、より精度の高いガイダンスを提供できるようになりました。

## React + OpenAI APIの連携ポイント

フロントエンドとOpenAI APIの連携で工夫した点をいくつか紹介します。

### ユーザーエクスペリエンスの向上

API呼び出しにはどうしても時間がかかるため、ユーザーが待っている間のエクスペリエンスを向上させる工夫をしました。

```javascript
// App.jsの一部
const handleCodeChange = useCallback(async (newCode) => {
  setCode(newCode);
  
  const error = detectError(newCode);
  if (error) {
    setHasError(true);
    setIsLoading(true);
    
    // ローカル分析
    const analysis = analyzeError(error);
    
    // AI分析を実行
    const aiResponse = await getAIAssistance(newCode, error);
    
    setSuggestion(`
## エラーが検出されました ❌
**${error.message}**
### AIアシスタントの分析
${aiResponse}
### クイックガイダンス
${analysis.guidance}
### ヒント
${analysis.tips.map(tip => `- ${tip}`).join('\n')}
    `);
    
    setIsLoading(false);
  } else {
    setHasError(false);
    setSuggestion(`
## コードは正常です ✅
エラーは検出されませんでした。コードは正常に動作する可能性があります。
### ヒント
- コードの実行結果を確認してみましょう
- 変数名や関数名をわかりやすくすると良いでしょう
- コメントを追加すると、後で見返しやすくなります
    `);
  }
}, []);
```

このコードでは、まずローカルでエラー解析を行い、即座にユーザーへフィードバックを提供しつつ、より詳細なAI分析を並行して取得しています。これにより、ユーザーは長時間待つことなく、基本的なフィードバックをすぐに受け取れます。

また、AIAssistantコンポーネントでは、react-markdownを使用してAIからの応答を整形して表示しています。

```javascript
// src/components/AIAssistant.js
import React from 'react';
import ReactMarkdown from 'react-markdown';

const AIAssistant = ({ suggestion, hasError, isLoading }) => {
  return (
    <div className="assistant-panel">
      <div className="assistant-header">
        <svg className="assistant-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
        </svg>
        <h3>AIアシスタント</h3>
        {isLoading ? (
          <span className="loading-badge">分析中...</span>
        ) : hasError ? (
          <span className="error-badge">エラー検出</span>
        ) : (
          <span className="success-badge">正常</span>
        )}
      </div>
      <div className="assistant-content">
        {isLoading ? (
          <div className="loading-indicator">
            <p>AIが分析しています...</p>
          </div>
        ) : (
          <ReactMarkdown>{suggestion}</ReactMarkdown>
        )}
      </div>
    </div>
  );
};

export default AIAssistant;
```

### 環境変数の正しい設定

API連携において、APIキーなどの秘密情報をどう管理するかは重要な問題です。最初、APIキーをそのまま書こうとしていたのですが、念のため調べたところ、セキュリティ上の問題に気づき、環境変数で管理するように変更しました。

```
// .env（リポジトリにコミットしないファイル）
REACT_APP_OPENAI_API_KEY=sk-あなたのAPIキー

// .env.example（APIキー無しのテンプレートとしてコミット）
REACT_APP_OPENAI_API_KEY=your_api_key_here
```

Create React Appでは、環境変数はprocess.env.REACT_APP_XXXの形式でアクセスします。また、Vercelにデプロイする際には、Vercelのダッシュボードで環境変数を設定する必要がありました。

### APIキー周りでのエラーハンドリング

環境変数周りで特に苦労したのが、APIキーが設定されていない場合のエラーハンドリングです。初めて使う人がつまずきやすい部分だと判断したので、ユーザーフレンドリーなエラーメッセージを表示するようにしました。

```javascript
// src/services/ai-service.js（一部抜粋）
// API呼び出し前のバリデーション
export const getAIAssistance = async (code, error) => {
  try {
    const apiKey = process.env.REACT_APP_OPENAI_API_KEY;
    
    if (!apiKey || apiKey === 'your_api_key_here') {
      return '⚠️ 設定エラー: OpenAI APIキーが正しく設定されていません。README.mdの手順に従って設定してください。';
    }
    
    // 以降のコード（API呼び出しなど）
    // ...
  } catch (error) {
    // ユーザーフレンドリーなエラーメッセージを返す
    console.error('API Error:', error);
    return '⚠️ エラーが発生しました。もう一度お試しください。';
  }
};
```

## 開発中に遭遇した技術的課題とその解決策

### 1. Monaco Editorの実装

VS Codeライクなエディタ体験を提供するため、@monaco-editor/reactパッケージを採用しましたが、これが予想以上に難しかったです。特に、エディタの初期化と設定の最適化に時間がかかりました。

```javascript
// src/components/CodeEditor.js
import React from 'react';
import Editor from '@monaco-editor/react';

const CodeEditor = ({ code, onChange }) => {
  return (
    <div style={{ border: '1px solid #ccc', borderRadius: '4px' }}>
      <Editor
        height="50vh"
        defaultLanguage="javascript"
        defaultValue="// JavaScriptコードを書いてみましょう"
        value={code}
        onChange={onChange}
        theme="vs-dark"
        options={{
          minimap: { enabled: false },
          fontSize: 14,
        }}
      />
    </div>
  );
};

export default CodeEditor;
```

このアプローチでは、@monaco-editor/reactが提供するEditorコンポーネントを使用して、コード編集機能を簡単に実装できました。ただ、初期設定の調整やパフォーマンスの最適化には時間がかかりました。

### 2. デプロイでの課題

Vercelへのデプロイ時に、環境変数の設定で躓きました。ローカルでは正常に動作していたのに、デプロイされたアプリではAPIキーが見つからないというエラーが発生しました。

原因は、Vercelの環境変数設定がプロジェクトの.envファイルを自動的に読み込まないことでした。解決策として、Vercelのダッシュボードで環境変数を明示的に設定する必要がありました。

また、デプロイ時のビルドプロセスを最適化するために、package.jsonに以下のようなdeploy scriptを追加しました。

```json
"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject",
  "deploy": "vercel"
},
```

この経験から、フロントエンドアプリケーションにおけるセキュリティとデプロイの実践的なノウハウを学ぶことができました。

## ローカルでの実行方法について

このプロジェクトを自分の環境で試してみたい方向けに、セットアップ手順を簡単に説明します。

```bash
# リポジトリのクローン
git clone https://github.com/oka031/ai-pair-programming-assistant.git

# プロジェクトディレクトリに移動
cd ai-pair-programming-assistant

# 依存関係のインストール
npm install

# .env ファイルを作成
cp .env.example .env
# .env ファイルにOpenAI APIキーを追加
# REACT_APP_OPENAI_API_KEY=your_api_key_here

# 開発サーバーの起動
npm start
```

ブラウザで http://localhost:3000 にアクセスすると、アプリケーションが表示されます。

## 主な機能はこちら

このアプリケーションの主な機能は以下の通りです

### リアルタイムコード編集
- MonacoエディターによるVS Codeライクな体験
- シンタックスハイライト
- コード補完

### 7種類のエラーパターン検出
- シンタックスエラー（括弧の不一致など）
- 変数スコープエラー（未定義変数など）
- 型エラー（nullやundefinedの操作）
- 範囲エラー（配列の範囲外アクセスなど）
- 代入エラー（constへの再代入など）
- 非同期処理エラー（Promiseの扱いなど）
- モジュールエラー（インポート/エクスポートの問題）

### AI解析
- OpenAI GPT-3.5-turboを使用した詳細なエラー分析
- 初心者にわかりやすい日本語での説明
- ステップバイステップの解決策提案

## 実際の使用感と今後の展望

現在、このアプリケーションは個人的なポートフォリオとして開発を進めています。開発中に自分自身で使ってみて感じたメリットとしては、
- エラーメッセージの意味が理解しやすくなった
- コードの問題点を特定する時間が短縮された
- プログラミング時のエラーに対する苦手意識が減った

などがあります。特に、英語のエラーメッセージを日本語で噛み砕いて説明してくれる点は、自分にとって大きな助けになっています。

今後の展望として、
- 多言語対応（Python, Javaなど）
- コード品質のアドバイス機能
- カスタマイズ可能なAIパーソナリティ
- コードエディタとの統合（VSCode拡張など）

を考えています。若輩者ですが、このプロジェクトを通じて学びながら成長していきたいと思います。

## GitHubリポジトリ

このプロジェクトのコードは以下のリポジトリで公開しています↓

https://github.com/oka031/ai-pair-programming-assistant

また、このプロジェクトを開発するきっかけとなった私自身のプログラミング学習体験については、こちらのリポジトリでも紹介しています：
[AI活用によるプログラミング学習の体験談](https://github.com/oka031/ai-learnings/blob/main/README.md)

🔗 デモ: https://ai-pair-programming-assistant-bhf7bvg40-oka031s-projects.vercel.app

※ まだ開発途上のため、バグや改善点があればぜひGitHubリポジトリのIssueやPull Request(PR)をお願いします！

## おわりに

このプロジェクトは、プログラミング初心者だった私自身の「こんなツールがあったら助かったのに」という思いから始まりました。技術的な挑戦も多かったですが、AIとの協業を通じて、自分自身のスキルも向上したと感じています。

特に、OpenAI APIの活用方法や、Reactとの連携、環境変数の管理など、実践的なスキルを身につけることができました。

同じように学習に苦労している方の一助になれば幸いです。

ご質問やフィードバックがあれば、コメントやGitHubでお気軽にどうぞ！