🐕

ChatGPTに追加されたCodexを使ってみた

に公開

はじめに

日本時間2025年5月17日に、ChatGPTにCodexが追加されました。このCodexはCloud上で動くもので、SWE(Software Engineering) Agentと分類されています。このSWE Agentの競合としては、Anthropic社のClaude CodeやGoogleのGemini Code Assist、Anysphere Cursorがあげられます。

Codexの特徴は下記とのことです。

  1. GitHubとの連携: ユーザーのコードリポジトリを事前に読み込み可能
  2. マルチタスク処理: 複数のソフトウェア開発タスクを同時に実行可能
  3. 自律的作業: 機能実装、バグ修正、テスト実行などを1〜30分程度で完了
  4. コードベース理解: 既存コードに関する質問に回答可能

なお、一次情報は下記となります。

提供状況

現在、Codexは以下のユーザーに提供開始されています:

  • ChatGPT Pro
  • Enterprise
  • Team

ChatGPT PlusおよびEduユーザーへの提供も近日中に予定されています。

現状はプレビュー版なので寛大な利用枠が提供されますが、数週間以内に利用制限が導入され、追加クレジットの購入オプションが設けられる予定だそうです。

codex-mini-latestというモデルがAPIで利用可能になりました。codex-1の小型モデルでCodex CLIのデフォルトのモデルになるとのことです。

Codex CLIでモデルを選択している様子

使ってみた

Codexを開く

ChatGPTの左ナビゲートからCodexへ遷移できます。

また、https://chatgpt.com/codex/onboardingからもCodexを開くことができます!

Codexの初回画面

初回のみこういった画面が表示されてCodexの紹介を見ることができます。

並行してタスクをこなせるのが、すごいなと。

Codexを使うには多要素認証が必要で、Google Authenticator等でQRコードを読み込んで多要素認証の手続きを進めていきましょう。

自身のGitHubアカウントに紐づけられているリポジトリを選択できるようになります。

モデルの改善に協力できるか聞かれます。

ためしに、3つのタスクを実行してくれるようです。お願いしてみましょう。

使っている様子

実際に使っている様子は下記です。並行してタスクを進められるのがかなり良いですね!

Codexを使っている様子

Pull Request

GitHubに紐づいているのでPull Requestもできます。ワンクリックで。
便利な世の中になりましたね。笑

Pull Requestしている様子

ITコンサル/エンジニアの働き方への影響

今回のCodexが登場したことによる我々への影響を考えてみましょう。

短期的影響

  1. プロトタイピングの加速: クライアントへの提案段階で、より短時間で機能的なプロトタイプを作成できるようになります。これにより、アイデアの検証サイクルが大幅に短縮されます。
  2. コード品質の標準化: Codexが生成するコードは一定の品質基準を満たすため、チーム内でのコード品質のばらつきが減少します。特に大規模プロジェクトでの品質管理が容易になります。
  3. ルーティン作業の自動化: ドキュメント作成、テスト実装、バグ修正などの定型作業をCodexに委託することで、コンサルタントやエンジニアはより創造的・戦略的な業務に集中できます。

長期的変化

  1. コンサルタントの役割変化: 技術的な実装詳細よりも、ビジネス要件の正確な理解と適切なAIへの指示能力が重要になります。「AIプロンプトエンジニアリング」のスキルが差別化要因となるでしょう。
  2. チーム構成の変化: 少数の上級エンジニアとAIツールの組み合わせで、従来より大規模なプロジェクトが実現可能になります。これにより、ITコンサル企業の人員構成や採用基準が変化する可能性があります。
  3. 価値提供モデルの転換: コード実装自体の価値は低下し、ビジネス課題の理解、最適なアーキテクチャ設計、AIツールの効果的活用方法の提案などに価値がシフトします。

エンジニアに求められる姿勢

この状況に対して我々はどのような姿勢で臨んでいくべきなのでしょうか。

  1. AIとの協業スキル開発: AIツールを効果的に活用するためのプロンプト設計や出力評価のスキルを磨くことが重要です。AIの長所(大量のコード生成、パターン認識)と短所(コンテキスト理解の限界、最新技術への対応遅れ)を理解し、適切な役割分担を行いましょう。
  2. 高次元の専門性獲得: AIが基本的なコーディングを担う中、システムアーキテクチャ、セキュリティ、パフォーマンス最適化など、より高度な専門知識への投資が差別化につながります。
  3. ビジネス理解の深化: 技術だけでなく、クライアントのビジネス課題や業界特有の要件を深く理解することで、AIツールでは代替できない価値を提供できます。
  4. 継続的学習の習慣化: AIツール自体が急速に進化するため、最新動向を常に把握し、自身のスキルセットを更新し続ける姿勢が不可欠です。

おわりに

ChatGPTに追加されたCodexを見ていきました。単なるコード補完ツールではなく、ソフトウェア開発の自律的なパートナーになりそうな気がしますよね!こういったツールは我々がより価値を出すための強力なツールになると考えたほうが良さそうです。ぜひ、私を含めてこの辺のSWE Agent界隈を追っていきましょう!

おまけ (2025年5月21日追加!)

私が取り組んでいるGitHubリポジトリhttps://github.com/Masa1984a/MCP_Driven_UX_Templateに対して改修の指示をしている様子の動画をおまけで掲載致します!
https://youtu.be/UZU9uGuL2Xo
具体的には下記のような改修案を提示してくれて、Pull Requestできる状態まで持ってきてくれました!

api/package.json
@@ -1,37 +1,40 @@
 {
   "name": "api-functions",
   "version": "1.0.0",
   "description": "API functions with TypeScript and Functions Framework",
   "main": "build/src/index.js",
   "scripts": {
     "build": "tsc",
     "start": "node build/src/index.js",
     "dev": "concurrently \"npm run build:watch\" \"npm run start:watch\"",
     "build:watch": "tsc --watch",
     "start:watch": "nodemon --watch build/ --exec \"npm run start\"",
     "lint": "eslint 'src/**/*.ts'",
-    "test": "jest"
+    "test": "jest",
+    "pgtyped": "pgtyped"
   },
   "dependencies": {
     "@google-cloud/functions-framework": "^3.3.0",
     "express": "^4.18.2",
     "pg": "^8.11.3",
     "winston": "^3.11.0",
-    "dotenv": "^16.3.1"
+    "dotenv": "^16.3.1",
+    "@pgtyped/runtime": "^1.2.0"
   },
   "devDependencies": {
     "@types/express": "^4.17.21",
     "@types/jest": "^29.5.10",
     "@types/node": "^20.9.4",
     "@types/pg": "^8.10.9",
     "@types/dotenv": "^8.2.0",
     "@typescript-eslint/eslint-plugin": "^6.12.0",
     "@typescript-eslint/parser": "^6.12.0",
     "concurrently": "^8.2.2",
     "eslint": "^8.54.0",
     "jest": "^29.7.0",
     "nodemon": "^3.0.1",
     "ts-jest": "^29.1.1",
-    "typescript": "^5.3.2"
+    "typescript": "^5.3.2",
+    "@pgtyped/cli": "^1.2.0"
   }
 }

api/pgtyped.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+  srcDir: './src',
+  glob: '**/*.sql',
+  overwrite: true,
+  camelCaseColumnNames: false
+};
api/src/controllers/ticketController.ts
@@ -1,28 +1,29 @@
 import { Request, Response } from 'express';
 import { logger } from '../utils/logger';
 import { TicketQueryParams, ChangedField } from '../types';
+import { GetTicketDetailById } from '../queries/getTicketDetail';
 
 /**
  * Get ticket list with filtering and pagination
  */
 export const getTicketList = async (req: Request, res: Response) => {
   try {
     // Get query parameters
     const queryParams: TicketQueryParams = {
       personInChargeId: req.query.personInChargeId as string,
       accountId: req.query.accountId as string,
       statusId: req.query.statusId as string,
       scheduledCompletionDateFrom: req.query.scheduledCompletionDateFrom as string,
       scheduledCompletionDateTo: req.query.scheduledCompletionDateTo as string,
       showCompleted: req.query.showCompleted === 'true',
       searchQuery: req.query.searchQuery as string,
       sortBy: req.query.sortBy as string || 'reception_date_time',
       sortOrder: (req.query.sortOrder as 'asc' | 'desc') || 'desc',
       limit: parseInt(req.query.limit as string) || 20,
       offset: parseInt(req.query.offset as string) || 0
     };
 
     // Field mapping (camelCase query params to snake_case DB fields)
     const fieldMapping: Record<string, string> = {
       'receptionDateTime': 'reception_date_time',
       'scheduledCompletionDate': 'scheduled_completion_date',
@@ -130,64 +131,58 @@ export const getTicketList = async (req: Request, res: Response) => {
         };
       });
       
       res.json(tickets);
     } finally {
       client.release();
     }
   } catch (error) {
     logger.error('Error getting ticket list', { 
       error: error instanceof Error ? error.message : String(error) 
     });
     res.status(500).json({ error: 'An error occurred while retrieving tickets' });
   }
 };
 
 /**
  * Get ticket detail by ID
  */
 export const getTicketDetail = async (req: Request, res: Response) => {
   try {
     const ticketId = req.params.id;
 
     // Execute the query
     const client = await req.db.connect();
     try {
-      const query = `
-        SELECT t.*
-        FROM mcp_ux.tickets t
-        WHERE t.id = $1
-      `;
-      
-      const result = await client.query(query, [ticketId]);
-      
-      if (result.rows.length === 0) {
+      const rows = await GetTicketDetailById.run(client, { id: ticketId });
+
+      if (rows.length === 0) {
         return res.status(404).json({ error: 'Ticket not found' });
       }
-      
+
       // Format the ticket data
-      const row = result.rows[0];
+      const row = rows[0];
       
       // Format dates
       const receptionDate = row.reception_date_time ? new Date(row.reception_date_time) : null;
       const receptionDateStr = receptionDate ? 
         `${receptionDate.getFullYear()}-${String(receptionDate.getMonth() + 1).padStart(2, '0')}-${String(receptionDate.getDate()).padStart(2, '0')} ` +
         `${String(receptionDate.getHours()).padStart(2, '0')}:${String(receptionDate.getMinutes()).padStart(2, '0')}` : null;
       
       const scheduledDate = row.scheduled_completion_date ? new Date(row.scheduled_completion_date) : null;
       const scheduledDateStr = scheduledDate ? 
         `${scheduledDate.getFullYear()}-${String(scheduledDate.getMonth() + 1).padStart(2, '0')}-${String(scheduledDate.getDate()).padStart(2, '0')}` : null;
       
       const completionDate = row.completion_date ? new Date(row.completion_date) : null;
       const completionDateStr = completionDate ? 
         `${completionDate.getFullYear()}-${String(completionDate.getMonth() + 1).padStart(2, '0')}-${String(completionDate.getDate()).padStart(2, '0')}` : null;
       
       // Get attachments
       const attachmentsQuery = `
         SELECT id, file_name, file_url, uploaded_at
         FROM mcp_ux.attachments
         WHERE ticket_id = $1
         ORDER BY uploaded_at DESC
       `;
       
       const attachmentsResult = await client.query(attachmentsQuery, [ticketId]);
api/src/queries/getTicketDetail.sql
@@ -0,0 +1,4 @@
+-- @name GetTicketDetailById
+SELECT t.*
+FROM mcp_ux.tickets t
+WHERE t.id = :id;
api/src/queries/getTicketDetail.ts
@@ -0,0 +1,45 @@
+import { PreparedQuery } from '@pgtyped/runtime';
+
+/** 'GetTicketDetailById' parameters type */
+export interface IGetTicketDetailByIdParams {
+  id: string;
+}
+
+/** 'GetTicketDetailById' return type */
+export interface IGetTicketDetailByIdResult {
+  id: string;
+  reception_date_time: Date;
+  requestor_id: string;
+  requestor_name: string;
+  account_id: string;
+  account_name: string;
+  category_id: string;
+  category_name: string;
+  category_detail_id: string;
+  category_detail_name: string;
+  request_channel_id: string;
+  request_channel_name: string;
+  summary: string;
+  description: string | null;
+  person_in_charge_id: string;
+  person_in_charge_name: string;
+  status_id: string;
+  status_name: string;
+  scheduled_completion_date: Date | null;
+  completion_date: Date | null;
+  actual_effort_hours: number | null;
+  response_category_id: string | null;
+  response_category_name: string | null;
+  response_details: string | null;
+  has_defect: boolean;
+  external_ticket_id: string | null;
+  remarks: string | null;
+  created_at: Date;
+  updated_at: Date;
+}
+
+/** 'GetTicketDetailById' query */
+export const GetTicketDetailById = new PreparedQuery<IGetTicketDetailByIdParams, IGetTicketDetailByIdResult>(
+  `SELECT t.* FROM mcp_ux.tickets t WHERE t.id = $1`,
+  (params: IGetTicketDetailByIdParams) => [params.id]
+);

では!

Accenture Japan (有志)

Discussion