【GPT-5-Codex】OpenAI発の最新コーディングモデルの概要・性能・使い方を実例で徹底解説

- GPT-5をプログラミング用途向けに高度に最適化したモデル
- タスクの複雑さに応じて思考に要する時間を動的に調整してくれるのが特徴
- ChatGPTの有料プランに含まれており、各プランの利用枠でCodex機能を利用可能
2025年9月16日、OpenAIはGPT-5をプログラミング用途向けに高度に最適化したモデル「GPT-5-Codex」をリリースしました!
GPT-5-Codexは、実際のソフトウェア開発で必要なタスク(新規プロジェクト構築、機能・テスト追加、デバッグ、大規模なリファクタリング、コードレビューなど)に重点を置いて訓練されており、人間のコーディングスタイルに近いコードを生成し、与えられた指示に厳密に従ってテストを繰り返す能力を持っています。
このモデルはOpenAIの「Codex」プラットフォーム(ターミナル向けCLI、各種IDE拡張、クラウド環境、GitHub連携、ChatGPTアプリ)で利用可能となっていて、従来のGPT-5よりさらに高品質なコード生成を実現します。
本記事では、GPT-5-Codexの概要や性能、使い方まで徹底的に解説します。
ぜひ最後までご覧ください。
\生成AIを活用して業務プロセスを自動化/
GPT-5-Codexの概要

GPT-5-Codexは、特にエージェント的なコーディングワークフローに適したモデルで、以下のような特徴があります。
高度な開発タスクに対応
GPT-5-Codexは、フルプロジェクトのゼロからの構築、機能やテストの追加、バグの修正、大規模なコードリファクタリング、プルリクエストのコードレビューなど、実際のソフトウェアエンジニアリング業務で必要な作業を高度に学習しています。
そのため、スタイルや可読性に関する細かい指示をせずとも、必要な機能を伝えるだけで高品質なコードを生成できます。
適応的な思考時間
GPT-5-Codexモデルは、タスクの複雑さに応じて思考に要する時間を動的に調整し、対話中の単純なリクエストや小さなスクリプトならすぐに回答してくれる一方、大規模リファクタリングなどの長時間作業には数時間から7時間以上かけて独立して実行します。
これは、内部で複数モデルを切り替えるのではなく、1つのモデルが必要に応じて途中から追加で思考を続ける仕組みとなっています。その結果、処理効率も向上していて、簡単なタスクにおいては、GPT-5と比べて生成トークン数が93.7%削減され、複雑なタスクにおいては、より長い推論時間を確保するようになっています。
コードレビュー機能
GPT-5-Codexは、コードレビュー機能に特化して訓練されており、コードベースをナビゲートして依存関係を把握し、自分でテストを実行して意図どおりに動作しているか検証してくれます。
実際に、オープンソースのコミットを用いた評価が行われており、GPT-5-Codexが出力するコードレビューコメントは誤りや重要でない指摘が少なく、ユーザー側の本質的な意図を汲み取って問題解決していくような設計になっています。
まとめると、GPT-5-Codexは、日常的なバグ修正から大型リファクタリング、テスト生成、コードレビューまで一貫して支援するために開発された高度なモデルと言えます。
GPT-5-Codexの性能
GPT-5-Codexは各種評価でGPT-5を上回る成果を残しています。
例えば、コーディング能力を測るベンチマーク「SWE-bench Verified」500問において、GPT-5-Codexは約74.5%の成功率を記録し、GPT-5の72.8%をわずかに上回りました。また、大規模リファクタリング課題に特化した社内評価では、GPT-5-Codexは51.3%というスコアを出し、元のGPT-5(33.9%)と比較して大幅に改善しています。

これらの結果はコード生成や複雑なタスク処理において、GPT-5-Codexが明らかに優れた性能を発揮していることを示しています。
コードレビュー面でも改善が確認され、GPT-5-Codexのレビューコメントは誤った指摘が少なくなり、より重要度の高いコメントが増えたとの報告も出ています。実際、OpenAI社内でもGPT-5-CodexをPRレビューに利用し、日々数百件の問題点を発見しているそうです。
また、効率性の面では、簡単な作業では、GPT-5-Codexが生成するトークン数が従来モデルより圧倒的に少なくなり、素早いレスポンスを可能にしています。一方、複雑な作業では逆に長い時間をかけて思考し続けられるようになっていて、平均でGPT-5の2倍の推論ステップを使用する能力が備えられています。
GPT-5-Codexのライセンス
GPT-5-Codexの利用にはOpenAIのサービス利用規約が適用されます。基本的に商用利用や私的利用は許可されており、生成したコードの著作権はユーザーに帰属します。一方、モデル本体の重みや内部構造の改変、およびモデルそのものの再配布は禁止されています。
利用用途 | 可否 | 備考 |
---|---|---|
商用利用 | ⭕️ | |
改変 | ❌️ | |
配布 | ❌️ | |
特許使用 | ⭕️ | |
私的使用 | ⭕️ |
GPT-5-Codexの料金
GPT-5-CodexはChatGPTの有料プランに含まれており、各プランに応じた利用枠でCodex機能を利用できます。主なプランの料金は以下の通りです。
プラン | 料金(税込) | 利用制限 |
---|---|---|
無料 | – | Codex非対応 |
Plus | $20/月(約¥2,700) | Codex対応 |
Pro | $200/月(約¥30,000) | Codex対応 |
Team | $30/ユーザー(月額、月払いの場合)※年契約なら$25/ユーザー/月 | Codex対応 |
Enterprise | 要問い合わせ (カスタム契約) | Codex対応 |
Plus/Teamプランでは、Codex利用に一定の制限が設けられており、「数回の集中的なコーディングセッション」程度の利用が想定されています。一方、Proプランでは1週間程度連続作業できる上限があり、Teamでは追加クレジット購入でさらに拡張できるようです。
また、CLIやIDE拡張版CodexをAPIキーで利用することで、ChatGPTプランに加入しなくても従量課金でCodexを動かすことも可能です。
GPT-5-Codexの使い方
GPT-5-Codexは様々な方法で利用できます。代表的な使い方を紹介します。
ChatGPTから利用する
OpenAIアカウントでChatGPT(Plus/Pro等)にログインし、新規チャットで「Codexエージェント」を選択するか、chat.openai.com/codex を開きます。例えば、自然言語で「このプログラムに〇〇機能を追加してください」などと指示すると、モデルがコードを生成・実行し、結果を返してくれます。
Codex CLIを使う
Node.jsをインストールした環境で、ターミナルから npm install -g @openai/codex
を実行し、Codex CLIをインストールします。既にインストール済みの方も上記コマンドでアップデートしましょう。codex login
コマンドでOpenAIアカウントにログインし、次に codex
コマンドでエージェントを起動します。


起動後に自然言語で指示を入力すると、Codexが対話的にコードを生成・実行します。例えば codex ask "新しいReactコンポーネントを生成してテストを作成して下さい"
のように要望することができます。複雑な作業では、Codexが自動的に todo
リストを管理し、Git連携やツール呼び出しなどで開発を進めます。
IDE拡張機能を使う
Visual Studio CodeやCursorなどの対応エディタに「OpenAI Codex」拡張機能をインストールします。以下の画像はCursor上のインストール画面です。

ローカルのコードを開いた状態で、コードの選択やコメントで指示すると、Codexがそのコンテキストを理解してローカルファイルを編集できます。たとえば、既存コード上で右クリックして「Codex: この関数を高速化してください」と命令するだけで、Codexが最適化案を提案してくれます。クラウドタスクと連携する機能もあり、オンラインで作成したプロジェクトをIDEに取り込んで編集を続けることも可能です。
GitHub連携とコードレビュー
GitHubのリポジトリでCodexコードレビュー機能を有効にすると、プルリクエストが投稿されるたびにCodexが自動的にレビューを実行し、コメントを返します。リポジトリの設定でCodexレビューをONにし、プルリクエスト上で “@codex review” とメンションすれば、AIが差分を解析して改善案を示してくれます。
以上のように、GPT-5-Codexは、ChatGPT上でもローカル開発環境でも利用でき、自然言語で指示するだけでコードを書いてくれます。Codex CLIやIDE拡張、GitHub連携などをうまく組み合わせることで、通常の開発作業のワークフローに違和感なくAIアシスタントを取り入れられるかと思います。
ちなみに、GPT-5-Codexはlow、medium(標準)、highの3段階構成でモデル選択できるのですが、mediumだとできなかったタスクがhighだと完遂したりするようですので、特にこだわりがなければ、GPT-5-Codex highを選択することを推奨します。

なお、Codex CLIについて気になる方は、以下の記事も参考にしてみてください。

GPT-5-Codexを使ってみた
ここからは実際にGPT-5-Codexの性能を体感してみましょう。今回はCodex CLI経由で使っていきます。
バグ再現→修正
まずは、テスト駆動開発における不具合再現と原因特定、修正までを問題なくやれるか検証してみます。
テキトーに以下の2つのファイルを準備します。
# shopping_cart.py
class Cart:
def __init__(self):
self.items = [] # (price, qty, tax_rate, discount_pct)
def add(self, price: float, qty: int = 1, tax_rate: float = 0.1, discount_pct: float = 0.0):
self.items.append((price, qty, tax_rate, discount_pct))
def total(self) -> float:
# BUG: 行単位での丸めをせず合算して最後に丸めている
total = 0.0
for price, qty, tax, disc in self.items:
line = price * qty
line = line * (1 + tax)
if disc:
line = line * (1 - disc / 100)
total += line
return round(total, 2)
# test_cart.py
def test_line_rounding_then_sum():
from shopping_cart import Cart
c = Cart()
# 値と税率・割引率の組み合わせで「行丸め」と「最後丸め」がズレるケース
c.add(58.97, 8, 0.05, 0.0)
c.add(53.78, 2, 0.08, 5.0)
c.add(69.28, 9, 0.08, 5.0)
c.add(70.16, 1, 0.10, 0.0)
assert c.total() == 1322.62 # 期待: 行ごとに2桁丸め→合計
入力プロンプトはこちら。
リポジトリに shopping_cart.py と tests/test_cart.py を置きました。
まずテストを実行して失敗を再現してください。
行単位の四捨五入(2桁)→合計、という業務要件に合わせて shopping_cart.py を修正し、テストをグリーンにしてください。
なぜ1セント誤差が生じたかを、丸め順序・小数演算の観点で短く解説してください。
変更点の要約と、わかりやすいコミットメッセージを提案してください。

実行結果はこちら。
• Explored
└ List ls
List test
• Updated Plan
└ □ 実装前にテストを実行し失敗を確認する
□ shopping_cart.py を要件に合わせて修正する
□ テストを再実行し、結果を確認する
□ 調査結果・変更点・コミットメッセージ案をまとめる
• Ran pytest
• Explored
└ Read shopping_cart.py
Search find . -maxdepth 2 -type f
Read test_cart.py
• Ran python -c "import shopping_cart; print(shopping_cart.__file__)"
• Ran python -m pytest
• Updated Plan
└ pytest 実行で 1 セントの丸め誤差による失敗を再現しました。
✔ 実装前にテストを実行し失敗を確認する
□ shopping_cart.py を要件に合わせて修正する
□ テストを再実行し、結果を確認する
□ 調査結果・変更点・コミットメッセージ案をまとめる
> Thinking
I'm figuring out how to modify shopping_cart.py so each line total is rounded
to two decimals using Decimal for precise rounding before summing. This should
avoid float binary issues and meet the requirement of per-line rounding with
typical rounding rules. I'll draft a plan to convert inputs to Decimal, apply
tax and discount, quantize each line, then sum as Decimal, finally returning a
float rounded properly. I'll add comments explaining the new rounding behavior
and carefully handle conversions from floats to Decimals to prevent precision
errors.
• Edited shopping_cart.py (+17 -6)
1 +from decimal import Decimal, ROUND_HALF_UP
2 +
3 +
4 +_TWOPLACES = Decimal("0.01")
5 +
6 +
7 +def _to_decimal(value: float) -> Decimal:
8 + """Convert float-like inputs via str to avoid binary rounding art
efacts."""
9 + return Decimal(str(value))
10 +
11 +
12 class Cart:
⋮
19 def total(self) -> float:
9 - # BUG: 行単位での丸めず合算して最後に丸めている
10 - total = 0.0
20 + total = Decimal("0")
21 for price, qty, tax, disc in self.items:
12 - line = price * qty
13 - line = line * (1 + tax)
22 + line = _to_decimal(price) * Decimal(qty)
23 + line *= Decimal("1") + _to_decimal(tax)
24 if disc:
15 - line = line * (1 - disc / 100)
25 + line *= Decimal("1") - _to_decimal(disc) / Decimal("1
00")
26 + line = line.quantize(_TWOPLACES, rounding=ROUND_HALF_UP)
27 total += line
17 - return round(total, 2)
28 + return float(total.quantize(_TWOPLACES, rounding=ROUND_HALF_U
P))
• Updated Plan
└ 小数演算を Decimal 化し、行ごとに 2 桁丸めた後に合計するように修正しました。
✔ 実装前にテストを実行し失敗を確認する
✔ shopping_cart.py を要件に合わせて修正する
□ テストを再実行し、結果を確認する
□ 調査結果・変更点・コミットメッセージ案をまとめる
• Ran python -m pytest
• Updated Plan
└ pytest の実行でテストがグリーンになりました。
✔ 実装前にテストを実行し失敗を確認する
✔ shopping_cart.py を要件に合わせて修正する
✔ テストを再実行し、結果を確認する
□ 調査結果・変更点・コミットメッセージ案をまとめる
• Ran git diff
• Updated Plan
└ 修正内容と原因の整理ができました。
✔ 実装前にテストを実行し失敗を確認する
✔ shopping_cart.py を要件に合わせて修正する
✔ テストを再実行し、結果を確認する
✔ 調査結果・変更点・コミットメッセージ案をまとめる
> Thinking
I'm putting together a clear summary of changes made to shopping_cart.py,
including line references for imports and helper functions, explaining the
1-cent rounding discrepancy due to binary float accumulation versus per-
line Decimal rounding, noting tests run with pytest, and suggesting a concise
commit message. I'll format this with concise headers and bullet points to
keep everything clear and organized.
> Changes
- Reworked totals to convert inputs to Decimal and round each line to two
decimals with ROUND_HALF_UP before summing, ensuring the returned total is
quantized as required (shopping_cart.py:1, shopping_cart.py:14).
Cause
- The original code added unrounded float line totals and only rounded once at
the end; accumulating binary floating-point artifacts meant the final rounding
lost 1 cent compared with rounding each line first.
Tests
- python -m pytest
Commit
- Round cart totals with per-line Decimal rounding
良い感じですね。失敗の再現→原因特定→最小差分で修正→テスト緑化→要約とコミットメッセージ案、まで一通りやってくれています。とくに、行単位での2桁丸めをDecimalで強制させている部分は要件を満たしてくれていてGoodです。
仕様駆動開発
続いて仕様(mdファイル)からロジック実装、テストまでを実行してもらいます。
まず、以下の2つのファイルを準備します。
# feature.md
# Email Normalization Spec
- 入力は任意の大文字小文字/全角半角/前後空白を許す文字列
- 出力は小文字、前後空白除去、Unicode正規化(NFKC)
- ドメインはIDNA(punycode)を正規化
- Gmailのみ:
- ローカル部の '.' は無視
- '+' 以降のタグは除去
- ただし "gmail.com" と "googlemail.com" は等価として扱う
- 不正な形式は ValueError
# email_utils.py
# 実装は未完成なのでCodexに書かせる
def normalize_email(s: str) -> str:
"""
要件:
- 前後空白除去・小文字化・Unicode正規化(NFKC)
- ドメインのIDNA正規化
- Gmail系は '.' 無視と '+' タグ除去
- 形式不正は ValueError
"""
raise NotImplementedError
入力プロンプトはこちら。
仕様は feature.md、コードの骨子は email_utils.py にあります。
仕様を満たすように email_utils.py を実装してください。
pytest のユニットテストを作成
GitHub ActionsのCI定義
Gmail/Outlook/Yahooの挙動差について注意書きを追加
仕上げに、テストで固定したい条件を3つ列挙してください。
実行結果はこちら。
• Explored
└ List ls
List ls
Read feature.md, email_utils.py
• Updated Plan
└ Outlining multi-part work: implement function, create tests, docs, CI, and
invariants list.
□ Understand spec and plan implementation and tests for email normalization
□ Implement normalize_email with required behavior and documentation
□ Add pytest unit tests (including boundary/property tests) and Hypothesis if
feasible
□ Add README fragment covering usage/design notes and provider behavior
differences
□ Add GitHub Actions workflow for pytest on Python 3.11/3.12
□ List invariants to enforce via tests
• Updated Plan
└ ✔ Understand spec and plan implementation and tests for email normalization
□ Implement normalize_email with required behavior and documentation
□ Add pytest unit tests (including boundary/property tests) and Hypothesis if
feasible
□ Add README fragment covering usage/design notes and provider behavior
differences
□ Add GitHub Actions workflow for pytest on Python 3.11/3.12
□ List invariants to enforce via tests
• Edited email_utils.py (+59 -9)
1 -# 実装は未完成なのでCodexに書かせる
2 -def normalize_email(s: str) -> str:
1 +"""Utility helpers for email-address normalization."""
2 +
3 +from __future__ import annotations
4 +
5 +import re
6 +import unicodedata
7 +from typing import Final
8 +
9 +_ADDRESS_PATTERN: Final[re.Pattern[str]] = re.compile(r"^[^@\s]+@[^@\s]+$
")
10 +_GMAIL_DOMAINS: Final[frozenset[str]] = frozenset({"gmail.com", "googlema
il.com"})
11 +
12 +
13 +def normalize_email(address: str) -> str:
14 + """Normalize an email address according to the project specification.
15 +
16 + The normalization pipeline performs Unicode normalization (NFKC), tri
ms
17 + surrounding whitespace, lower-cases the address, converts the domain
to its
18 + IDNA/ASCII representation, and applies Gmail-specific canonicalizatio
n.
19 +
20 + Args:
21 + address: User-provided email address that may contain arbitrary c
asing
22 + and spacing.
23 +
24 + Returns:
25 + The canonicalized email address string.
26 +
27 + Raises:
28 + TypeError: If *address* is not a string.
29 + ValueError: If the input cannot be parsed as an email address or
if the
30 + resulting local part is empty after Gmail canonicalization.
31 """
4 - 要件:
5 - - 前後空白除去・小文字化・Unicode正規化(NFKC)
6 - - ドメインのIDNA正規化
7 - - Gmail系は '.' 無視と '+' タグ除去
8 - - 形式不正は ValueError
9 - """
10 - raise NotImplementedError
32 +
33 + if not isinstance(address, str):
34 + raise TypeError("address must be a str")
35 +
36 + # Normalize Unicode before trimming; normalization can introduce ASCI
I
37 + # whitespace that should be stripped.
38 + normalized = unicodedata.normalize("NFKC", address).strip()
39 + if not normalized:
40 + raise ValueError("email address is empty after trimming")
41 +
42 + normalized = normalized.lower()
43 + if not _ADDRESS_PATTERN.match(normalized):
44 + raise ValueError("invalid email address format")
45 +
46 + local_part, domain = normalized.rsplit("@", 1)
47 + try:
48 + domain_ascii = domain.encode("idna").decode("ascii")
49 + except UnicodeError as exc: # pragma: no cover - defensive branch
50 + raise ValueError("invalid domain name") from exc
51 +
52 + is_gmail = domain_ascii in _GMAIL_DOMAINS
53 + if is_gmail:
54 + # Gmail treats dots as insignificant and strips tags starting wit
h '+'.
55 + local_part = local_part.split("+", 1)[0].replace(".", "")
56 + if not local_part:
57 + raise ValueError("gmail local part empty after normalization"
)
58 + domain_ascii = "gmail.com"
59 +
60 + return f"{local_part}@{domain_ascii}"
• Updated Plan
└ ✔ Understand spec and plan implementation and tests for email normalization
✔ Implement normalize_email with required behavior and documentation
□ Add pytest unit tests (including boundary/property tests) and Hypothesis if
feasible
□ Add README fragment covering usage/design notes and provider behavior
differences
□ Add GitHub Actions workflow for pytest on Python 3.11/3.12
□ List invariants to enforce via tests
> Thinking
I'm planning a new pytest-based test suite with Hypothesis to cover many cases
for normalize_email, including Unicode normalization, Gmail-specific local-part
tweaks, invalid inputs, and property-based tests ensuring idempotence with various
generated local and domain parts. I'll carefully build string strategies to
generate valid test emails reflecting real-world quirks, making sure assumptions
avoid pathological cases that would break normalization. This should give robust
validation of email normalization behavior!
• Added tests/test_email_utils.py (+82 -0)
1 +"""Tests for the email normalization utilities."""
2 +
3 +from __future__ import annotations
4 +
5 +import string
6 +
7 +import pytest
8 +from hypothesis import given, strategies as st
9 +
10 +from email_utils import normalize_email
11 +
12 +
13 +def test_basic_normalization() -> None:
14 + assert normalize_email(" Foo.Bar@Example.COM ") == "foo.bar@example.c
om"
15 +
16 +
17 +def test_nfkc_and_fullwidth_handling() -> None:
18 + # Full-width characters should collapse to ASCII equivalents and be l
ower-cased.
19 + assert normalize_email(" Foo@example.com ") ==
"foo@example.com"
20 +
21 +
22 +def test_idna_domain_normalization() -> None:
23 + expected = "ユーザー@xn--r8jz45g.xn--zckzah"
24 + assert normalize_email("ユーザー@例え.テスト") == expected
25 +
26 +
27 +def test_gmail_specific_rules() -> None:
28 + assert (
29 + normalize_email("Test.Email+spam@Gmail.Com") == "testemail@gmail.
com"
30 + )
31 +
32 +
33 +def test_googlemail_equivalence() -> None:
34 + assert (
35 + normalize_email("Test.Email+spam@GoogleMail.com") == "testemail@g
mail.com"
36 + )
37 +
38 +
39 +def test_invalid_format_raises() -> None:
40 + with pytest.raises(ValueError):
41 + normalize_email("invalid@@example.com")
42 +
43 +
44 +def test_gmail_empty_after_cleanup_raises() -> None:
45 + with pytest.raises(ValueError):
46 + normalize_email("...+tag@gmail.com")
47 +
48 +
49 +def test_non_string_input_raises() -> None:
50 + with pytest.raises(TypeError):
51 + normalize_email(123) # type: ignore[arg-type]
52 +
53 +
54 +LOCAL_ALPHABET = string.ascii_letters + string.digits + "._+-"
55 +LABEL_ALPHABET = string.ascii_lowercase + string.digits + "-"
56 +
57 +
58 +def _domain_strategy() -> st.SearchStrategy[str]:
59 + label = st.text(LABEL_ALPHABET, min_size=1, max_size=10).filter(
60 + lambda s: s[0] != "-" and s[-1] != "-"
61 + )
62 + generic = st.lists(label, min_size=2, max_size=3).map(lambda parts: "
.".join(parts))
63 + return st.one_of(generic, st.sampled_from(["gmail.com", "googlemail.c
om"]))
64 +
65 +
66 +@given(
67 + local=st.text(LOCAL_ALPHABET, min_size=1, max_size=20),
68 + domain=_domain_strategy(),
69 +)
70 +def test_normalization_idempotent(local: str, domain: str) -> None:
71 + canonical_candidate = local
72 + if domain in {"gmail.com", "googlemail.com"}:
73 + canonical_candidate = canonical_candidate.split("+", 1)[0].replac
e(".", "")
74 + assume_msg = ""
75 + if not canonical_candidate:
76 + pytest.skip("hypothesis generated gmail local without signifi
cant characters")
77 +
78 + address = f" {local}@{domain} "
79 + first = normalize_email(address)
80 + second = normalize_email(first)
81 + assert first == second
82 +
• Edited tests/test_email_utils.py (+2 -7)
7 import pytest
8 -from hypothesis import given, strategies as st
8 +from hypothesis import assume, given, strategies as st
9
⋮
70 def test_normalization_idempotent(local: str, domain: str) -> None:
71 - canonical_candidate = local
71 if domain in {"gmail.com", "googlemail.com"}:
73 - canonical_candidate = canonical_candidate.split("+", 1)[0].replac
e(".", "")
74 - assume_msg = ""
75 - if not canonical_candidate:
76 - pytest.skip("hypothesis generated gmail local without signifi
cant characters")
72 + assume(local.split("+", 1)[0].replace(".", "") != "")
73
⋮
77 assert first == second
82 -
• Ran pytest
> Thinking
I’m working out how to keep property-based tests running even without Hypothesis
installed, since network restrictions prevent easy installation. My plan is to try
importing Hypothesis and, if unavailable, skip those tests or provide a fallback
using deterministic, randomized tests with pytest, ensuring the property tests
still cover core checks without breaking. This way I balance following the user's
request with environment constraints.
• Edited tests/test_email_utils.py (+51 -17)
4
5 +import random
6 import string
⋮
8 import pytest
8 -from hypothesis import assume, given, strategies as st
9
⋮
11
12 +try:
13 + from hypothesis import assume, given, strategies as st
14 +except ModuleNotFoundError: # pragma: no cover - environment guard
15 + HYPOTHESIS_AVAILABLE = False
16 +else: # pragma: no cover - import success path exercised via tests
17 + HYPOTHESIS_AVAILABLE = True
18 +
19
⋮
64
58 -def _domain_strategy() -> st.SearchStrategy[str]:
59 - label = st.text(LABEL_ALPHABET, min_size=1, max_size=10).filter(
60 - lambda s: s[0] != "-" and s[-1] != "-"
65 +if HYPOTHESIS_AVAILABLE:
66 +
67 + def _domain_strategy() -> st.SearchStrategy[str]:
68 + label = st.text(LABEL_ALPHABET, min_size=1, max_size=10).filter(
69 + lambda s: s[0] != "-" and s[-1] != "-"
70 + )
71 + generic = (
72 + st.lists(label, min_size=2, max_size=3).map(lambda parts: "."
.join(parts))
73 + )
74 + return st.one_of(generic, st.sampled_from(["gmail.com", "googlema
il.com"]))
75 +
76 +
77 + @given(
78 + local=st.text(LOCAL_ALPHABET, min_size=1, max_size=20),
79 + domain=_domain_strategy(),
80 )
62 - generic = st.lists(label, min_size=2, max_size=3).map(lambda parts: "
.".join(parts))
63 - return st.one_of(generic, st.sampled_from(["gmail.com", "googlemail.c
om"]))
81 + def test_normalization_idempotent(local: str, domain: str) -> None:
82 + if domain in {"gmail.com", "googlemail.com"}:
83 + assume(local.split("+", 1)[0].replace(".", "") != "")
84 +
85 + address = f" {local}@{domain} "
86 + first = normalize_email(address)
87 + second = normalize_email(first)
88 + assert first == second
89
90 +else:
91
66 -@given(
67 - local=st.text(LOCAL_ALPHABET, min_size=1, max_size=20),
68 - domain=_domain_strategy(),
69 -)
70 -def test_normalization_idempotent(local: str, domain: str) -> None:
71 - if domain in {"gmail.com", "googlemail.com"}:
72 - assume(local.split("+", 1)[0].replace(".", "") != "")
92 + def test_normalization_idempotent_fallback() -> None:
93 + random.seed(0)
94 + domains = [
95 + "example.com",
96 + "sub.example.net",
97 + "gmail.com",
98 + "googlemail.com",
99 + ]
100 + for _ in range(200):
101 + domain = random.choice(domains)
102 + local_length = random.randint(1, 20)
103 + local = "".join(random.choice(LOCAL_ALPHABET) for _ in range(
local_length))
104 + if domain in {"gmail.com", "googlemail.com"}:
105 + if local.split("+", 1)[0].replace(".", "") == "":
106 + continue
107
74 - address = f" {local}@{domain} "
75 - first = normalize_email(address)
76 - second = normalize_email(first)
77 - assert first == second
108 + address = f" {local}@{domain} "
109 + first = normalize_email(address)
110 + second = normalize_email(first)
111 + assert first == second
• Ran pytest
> Thinking
I’m figuring out why pytest can’t find the top-level email_utils module despite
running from root. It looks like pytest isn’t adding root to sys.path as expected,
so I’m planning to tweak the test files to insert the repo root manually into
sys.path before imports to fix this.
• Edited tests/test_email_utils.py (+7 -0)
4
5 +import pathlib
6 import random
⋮
10
11 +import sys
12 +
13 +PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[1]
14 +if str(PROJECT_ROOT) not in sys.path:
15 + sys.path.insert(0, str(PROJECT_ROOT))
16 +
17 from email_utils import normalize_email
• Ran pytest
• Updated Plan
└ ✔ Understand spec and plan implementation and tests for email normalization
✔ Implement normalize_email with required behavior and documentation
✔ Add pytest unit tests (including boundary/property tests) and Hypothesis if
feasible
□ Add README fragment covering usage/design notes and provider behavior
differences
□ Add GitHub Actions workflow for pytest on Python 3.11/3.12
□ List invariants to enforce via tests
• Added README.md (+24 -0)
1 +# Email Normalization Utility
2 +
3 +## 使い方
4 +```python
5 +from email_utils import normalize_email
6 +
7 +canonical = normalize_email(" Test.Email+spam@Gmail.Com ")
8 +print(canonical) # => "testemail@gmail.com"
9 +```
10 +
11 +- 例外 `ValueError` は入力の形式が不正な場合に発生します。
12 +- 非文字列を渡した場合は `TypeError` が送出されます。
13 +
14 +## 設計上の注意
15 +- Unicode は常に NFKC に正規化した上で小文字化し、末端の空白を除去し
ます。
16 +- ドメイン部分は IDNA 正規化(punycode 化)して ASCII ドメインに統一し
ます。
17 +- Gmail 系ドメインではローカル部のドットと `+` タグを無視し、`gmail.com`
に揃えます。
18 +- 期待される入力は単一の `@` を含むメールアドレス文字列のみです。内部空白
や制御文字はサポート対象外です。
19 +
20 +## プロバイダごとの注意
21 +- **Gmail/Googlemail**: ドットや `+tag` を無視する仕様のため、正規化後に
別の実アドレスへ集約されます。
22 +- **Outlook (Hotmail/Live)**: ドットは重要で、`+` タグも通常のローカル部
として扱われるため変換は行いません。
23 +- **Yahoo! Mail**: ドットの扱いはローカル部の一部であり、`-` などを使った
エイリアスに注意が必要です。本ライブラリでは Ya
hoo 固有の別名仕様(`-base` 等)までは正規化しません。
24 +
• Updated Plan
└ ✔ Understand spec and plan implementation and tests for email normalization
✔ Implement normalize_email with required behavior and documentation
✔ Add pytest unit tests (including boundary/property tests) and Hypothesis if
feasible
✔ Add README fragment covering usage/design notes and provider behavior
differences
□ Add GitHub Actions workflow for pytest on Python 3.11/3.12
□ List invariants to enforce via tests
> Thinking
I'll set up a CI workflow in .github/workflows/ci.yml that runs pytest across
Python 3.11 and 3.12 using a matrix strategy. I'll include the Hypothesis library
in the install step to support property-based testing if present. I'll check for
the directory and create it if missing, then add this workflow file.
• Ran mkdir -p .github/workflows
• Added .github/workflows/ci.yml (+30 -0)
1 +name: CI
2 +
3 +on:
4 + push:
5 + branches: [ main ]
6 + pull_request:
7 +
8 +jobs:
9 + tests:
10 + runs-on: ubuntu-latest
11 + strategy:
12 + matrix:
13 + python-version: ["3.11", "3.12"]
14 + steps:
15 + - name: Checkout source
16 + uses: actions/checkout@v4
17 +
18 + - name: Set up Python
19 + uses: actions/setup-python@v5
20 + with:
21 + python-version: ${{ matrix.python-version }}
22 +
23 + - name: Install dependencies
24 + run: |
25 + python -m pip install --upgrade pip
26 + python -m pip install pytest hypothesis
27 +
28 + - name: Run pytest
29 + run: python -m pytest
30 +
• Updated Plan
└ ✔ Understand spec and plan implementation and tests for email normalization
✔ Implement normalize_email with required behavior and documentation
✔ Add pytest unit tests (including boundary/property tests) and Hypothesis if
feasible
✔ Add README fragment covering usage/design notes and provider behavior
differences
✔ Add GitHub Actions workflow for pytest on Python 3.11/3.12
□ List invariants to enforce via tests
• Ran git status --short
• Updated Plan
└ Prepared invariants to describe alongside tests; remaining work is reporting
back.
✔ Understand spec and plan implementation and tests for email normalization
✔ Implement normalize_email with required behavior and documentation
✔ Add pytest unit tests (including boundary/property tests) and Hypothesis if
feasible
✔ Add README fragment covering usage/design notes and provider behavior
differences
✔ Add GitHub Actions workflow for pytest on Python 3.11/3.12
✔ List invariants to enforce via tests
• Explored
└ Read email_utils.py, test_email_utils.py, README.md, ci.yml
> Thinking
I'm preparing a final summary that highlights the pytest run and the key
invariants enforced through tests, like normalization idempotence, Gmail-specific
canonicalization, and domain ASCII enforcement. I'll also mention the Hypothesis
fallback mechanism ensuring coverage even when optional dependencies are missing,
along with referencing code locations and changes consistently.
> Changes
- Implemented a fully spec-compliant normalize_email with NFKC trimming, IDNA
domain handling, and Gmail-specific canonicalization plus validation and docstring
in email_utils.py:1-60.
- Added boundary/unit/property coverage (Hypothesis when available, deterministic
fallback otherwise) and ensured repository importability inside tests/
test_email_utils.py:1-118.
- Documented usage, design considerations, and Gmail/Outlook/Yahoo behavior
differences in README.md:1-24.
- Set up GitHub Actions CI matrix to run python -m pytest on Python 3.11/3.12 and
install pytest/Hypothesis in .github/workflows/ci.yml:1-29.
Tests
- pytest
Invariants
- Normalization is idempotent: normalize_email(normalize_email(x)) always equals
normalize_email(x) for valid addresses.
- Gmail-family inputs canonicalize to gmail.com with a non-empty local part free
of dots and tags after normalization.
- Every normalized address contains exactly one @, has no surrounding whitespace,
and uses an ASCII domain (IDNA applied).
こちらも良い感じですね。実装・テスト・CIまで過不足なく整ってると思いますし、工程の見通しも良好でこのまま本番採用しても良さそうな仕上がりです。仕様にもちゃんと遵守していて、期待どおりの実装を再現してくれています。
与えた指示に対して、厳密に再現してくれているのを実際に体感することができました。
みなさんもぜひご自身のタスクで試してみてください。
まとめ
GPT-5-Codexは、OpenAIが開発した最新のコーディング特化型LLMで、従来のモデルを上回る性能と柔軟性を持っています。
リファクタリングやコードレビューなど実践的なタスクに強みを持ち、短い対話から長時間の自動処理までスムーズに対応します。
ChatGPTの有料プランに登録することで利用でき、CLIやIDE拡張、クラウドサービスを通じて幅広く利用することも可能です。API提供も予定されているので、GPT-5-Codexの活用範囲はさらに拡大していくことが期待されます。
気になる方は、本記事を参考にぜひ試してみてください。
最後に
いかがだったでしょうか?
弊社では、AI導入を検討中の企業向けに、業務効率化や新しい価値創出を支援する情報提供・導入支援を行っています。最新のAIを活用し、効率的な業務改善や高度な分析が可能です。
株式会社WEELは、自社・業務特化の効果が出るAIプロダクト開発が強みです!
開発実績として、
・新規事業室での「リサーチ」「分析」「事業計画検討」を70%自動化するAIエージェント
・社内お問い合わせの1次回答を自動化するRAG型のチャットボット
・過去事例や最新情報を加味して、10秒で記事のたたき台を作成できるAIプロダクト
・お客様からのメール対応の工数を80%削減したAIメール
・サーバーやAI PCを活用したオンプレでの生成AI活用
・生徒の感情や学習状況を踏まえ、勉強をアシストするAIアシスタント
などの開発実績がございます。
生成AIを活用したプロダクト開発の支援内容は、以下のページでも詳しくご覧いただけます。
➡︎株式会社WEELのサービスを詳しく見る。
まずは、「無料相談」にてご相談を承っておりますので、ご興味がある方はぜひご連絡ください。
➡︎生成AIを使った業務効率化、生成AIツールの開発について相談をしてみる。

「生成AIを社内で活用したい」「生成AIの事業をやっていきたい」という方に向けて、生成AI社内セミナー・勉強会をさせていただいております。
セミナー内容や料金については、ご相談ください。
また、大規模言語モデル(LLM)を対象に、言語理解能力、生成能力、応答速度の各側面について比較・検証した資料も配布しております。この機会にぜひご活用ください。