APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

AIが型を探す往復をゼロにする — Python型ヒントをAIへのガードレールとして使う

AIコーディングで型エラーを繰り返さないために — Pythonの型ヒントをAIへの仕様書として使う

こんにちは、エーピーコミュニケーションズ ACS事業部の福井です。

AIを使ってコードを書いていると、不思議なことに同じ場所で同じエラーが繰り返し発生することがあります。ある日、そのパターンにハッキリ気づきました。

フォームの選択値(文字列)をDBのID(整数)にマッピングする処理でした。ちょっと込み入った文字列と int 型の連結が絡む関数で、「ここを直して」とAIに依頼するたびに TypeError が飛んでくる。直してもらう、エラーが出る、また直してもらう……。

「……前もこれ見たーーー。」

修正指示を変えても、切り口を変えて依頼しても、どこかで型の不整合が顔を出す。問題はコードの論理ではなく、AIがその関数に何が渡ってきて何を返すべきかを毎回推測から始めていることにありました。

型ヒントを付けた瞬間、ループが止まりました。それ以来、型ヒントは「人間のための可読性向上」だけでなく、AIへの仕様書として書くものだと考えるようになりました。

この記事では、なぜそうなるのかの背景と、AIコーディングで型ヒントを確実に生成・維持させるための実践的な指示方法をまとめます。


型エラーのループはなぜ起きるのか

Pythonの「柔軟性」がAIには「仕様の空白」に見える

C言語畑で育ちまして、こういう場面でついつい「Cならコンパイルが型の不整合を弾いてくれるのに」と思ってしまうクセがあります。その直感、AIコーディングにおいては割と正しいと思っています。

C言語の関数宣言を思い出してください。

/* 宣言が仕様そのもの。省略不可 */
int map_select_value(char *select_value, int min, int max);

引数の型も戻り値の型も、宣言の時点で確定しています。コンパイラが「型が違う」と言えば、ビルドが通りません。

AIがコードを生成する前から、仕様が言語仕様として強制されている。

一方、型ヒントなしのPythonはこうです。

# 仕様ゼロ。AIは何も情報を得られない
def map_select_value(select_value, min, max):

select_valuestr なのか int なのか None なのか――何も書いていない。

AIはこの空白を「典型パターンで補完する」ことで埋めます。呼び出し側の文脈から、あるいは関数名から推測する。

その推測が毎回少しずつズレて、型エラーのループが生まれていました。

言語ごとの型の強制力とAIとの相性

言語 型の強制タイミング 型なし時のAIリスク
C / C++ コンパイル時(必須) そもそも型なしで書けない
TypeScript コンパイル時(strict モード) any 多用で同様の問題は起きうる
Java / C# コンパイル時(必須) 型なしで書けない
Python(型ヒントあり) mypy などの静的解析時(任意) 型ヒントが仕様書として機能する
Python(型ヒントなし) なし(実行時エラーのみ) AIが自由に型を解釈→ミス多発
JavaScript(生) なし 最もリスクが高い

TypeScriptが「JSに型を乗せた」動機も同じです。「なんでも受け取る柔軟性」を意図的に制限することで、人間もAIも迷わなくなる

型ヒントを付けた瞬間、Pythonの関数はCの宣言と同じ情報密度になる

# 型ヒントあり:Cのプロトタイプ宣言と同等の情報量
def map_select_value(select_value: str, min: int, max: int) -> Optional[int]:

str 以外を渡すのはおかしい、intNone が返る――AIはこの宣言から仕様を正確に読み取ることができます。

これがガードレールになります。


実践:AIに型ヒントを確実に書かせる指示方法

最初のリクエストに含める「型ヒント必須」指示テンプレ

コーディングセッションの冒頭、またはシステムプロンプトにそのまま貼って使えます。

Python でコーディングする際のルール:

- すべての関数には型ヒントを付ける。
- 引数の型は明示的に書く(例: def func(value: int) -> ...)。
- 戻り値の型も明示的に書く(例: -> str: など)。
- 型は次に該当するものを使う:
  - int, str, bool, float, None
  - Optional[T], Union[T1, T2], List[T], Dict[str, T], Any など
- None の可能性がある場合は Optional[T] を使う。
  - 例: def get_value() -> Optional[int]: ...
- 関数には docstring を書く(Args / Returns を含める)。
- 変数にも可能な限り型ヒントを付ける(例: count: int = 0)。
- 型が本当にわからない場合のみ Any を使い、最小限にする。

mypy / pyright を意識させる追加指示

静的解析ツールとの整合性まで求める場合はこちらを追加します。

- 作成されたコードは mypy でもエラーが出ない範囲で、型ヒントを充実させる。
- Python 3.9以降は list[T]、dict[str, T] のように組み込み型を直接使う(typing.List / typing.Dict は非推奨)。
- None を返す可能性がある場合は Optional[T](または T | None)を使う。
- 例外処理(try-except)は、例外の型を明示的に書く(例: except ValueError: ...)。

バグ修正指示のテンプレ(型の整合を維持するため)

既存コードを修正させる場合、型ヒントが崩れないよう前提を明示します。

- 修正の前提:
  - すべての関数には型ヒント・docstring を維持する。
  - 引数・戻り値の型は変更しない(変更が必要な場合は明示する)。
- 修正箇所:
  - 1. None チェックの追加
  - 2. str -> int の変換を明示的に記述
- 仕様を維持しつつ、型の整合を確認して修正してください。

型ヒント付き関数の実装例

最初にハマった、あの選択値マッピング関数に型ヒントを付けるとこうなります。

from typing import Optional

def map_select_value_to_db_id(select_value: str) -> Optional[int]:
    """
    セレクトボックスの値を DB の ID にマッピングする

    Args:
        select_value: フロントから送られてくる選択値(文字列)

    Returns:
        マッピングされた整数値。該当しない場合は None
    """
    try:
        mapped_value: int = int(select_value)
        if 1 <= mapped_value <= 10:
            return mapped_value
        else:
            return None
    except (ValueError, TypeError):
        return None

select_value: str-> Optional[int] の2か所が入るだけで、 AIは「文字列が来る、整数か None を返す」という前提をコンテキストとして持てます。 修正依頼のたびに型を推測し直す必要がなくなる。


「コメントを増やせばよいのでは」という意見について

コメントや docstring を充実させれば、AIがコードの文脈を理解できるのでは――という意見は自然です。

コンテキストが増えれば推論の精度も上がる、というのはその通りです。

ただ、型ヒントにはコメントにはない即効性があります。 コメントはAIが「読んで解釈する」情報ですが、型ヒントは関数宣言そのものが仕様になります。 AIはコードを読む前に、引数と戻り値の型をそのまま取り込める。調べる必要すらない。

特にClaude Haiku のようなより軽量なモデルを使うときは、この差が顕著に感じられます。 コメントが増えてコンテキストが膨らむよりも、型ヒントで宣言を固めてしまう方が、 その場で正しい答えを返してくれる確率がずっと高い。 コメントは補足、型ヒントは仕様の強制、という使い分けが現実的だと思います。


エージェントが型を「探す」コストを減らす

AIエージェントが複数ファイルにまたがるタスクを処理しているとき、 ログを眺めていると型情報を検索している場面が意外と多いことに気づきます。 get_value という関数がどんな型を返すのかを確認するために、呼び出し元をたどり、 ドキュメントを検索し、ようやく処理を再開する――というフローです。

「……前にもこれ、検索してなかったっけ。」

型ヒントがあれば、この検索はゼロになります。 エージェントは宣言を見た瞬間に型を確定できる。 タスクが流れているときの「型情報を探す往復」が丸ごとなくなるので、エージェントモードで使うほど体感の差は大きくなります。


全体に一気に型ヒントを追加するのは現実的ではないことも多いです。優先順位の目安です。

  1. コア関数を優先 — DB保存・バリデーション・データ変換など、str / int / None が絡む関数
  2. UI ヘルパー・ログ関数は後回し可 — 動的でも問題が起きにくい
  3. ボトムアップで広げる — コア関数に型を付けると、呼び出し側の曖昧さも自然と見えてくる

型ヒントの有無でAIの挙動はどう変わるか

条件 型ヒントあり 型ヒントなし
LLMにとっての仕様の明確さ 高い 低い(推論ベース)
string / int / None の一貫性 保たれやすい 迷子になりやすい
型エラーループの発生頻度 低い 高い
1リクエストで完成する確率 高い 低い

おわりに

型ヒントは、AIへのガードレールである。

Pythonの型ヒントは長らく「書くと可読性が上がる」という文脈で語られてきました。 AIコーディングの時代には、もう一つの意味が加わっています。「仕様を明示しないと、AIは推測で埋めてしまう」。

型エラーのループに入ったとき、問題はたいていAIの能力ではなく仕様の空白にあります。 型ヒントを書く行為は、AIに指示を出す行為と同義です。

もし「AIに直させるたびに同じ型のエラーが出る」という経験に心当たりがある方は、 まずコア関数の型ヒントを一つ付けるところから始めてみてください。


ACS 事業部のご紹介

私の所属する ACS 事業部では、開発者ポータル Backstage、Azure AI Service などを活用し、Platform Engineering + AI の推進・内製化を支援しています。

www.ap-com.co.jp www.ap-com.co.jp www.ap-com.co.jp

また、GitHub パートナーとしてお客様に GitHub ソリューションの導入支援を行っています。 GitHub Copilot などのトレーニングなども行っておりますので、ご興味を持っていただけましたらぜひお声がけいただけますと幸いです。

一緒に働いていただける仲間も募集中です! ご興味持っていただけましたらぜひお声がけください。 ※求人名の冒頭に【ACSD】と入っている求人が当事業部の求人です。

www.ap-com.co.jp www.ap-com.co.jp

本記事の投稿者: 福井
本記事は GitHub Copilot に伴走してもらいながら書き上げました。構成・表現の整理にAIを活用しつつ、体験談・検証・最終判断はすべて著者本人によるものです。