日程Fit|「いつ空いてますか?」の往復はもう不要。候補日を選んでURLを送るだけ|登録不要|今すぐ無料で使う →

azure-ai-projects 2.4.0でcreate_generation_jobが消えた原因とLROPoller対応

azure-ai-projectsを2.4.0へ更新した後、create_generation_jobが見つからなくなった場合、原因はSDKのインストール不良ではなく、beta APIに導入された破壊的変更です。

2.4.0では、評価生成とデータ生成のcreate_generation_job、エージェント最適化のcreate_optimization_jobが、それぞれbegin_プレフィックス付きのメソッドへ変更されました。さらに、戻り値が処理結果そのものではなく、長時間実行処理を管理するLROPollerへ変わっています。

したがって、メソッド名を置換するだけでは修正が完了しません。基本形は次のとおりです。

poller = project_client.beta.evaluators.begin_create_generation_job(
    job=generation_job
)
result = poller.result()

この記事では、Azure AI Projects Python SDK 2.4.0で変更された3つのbeta APIについて、具体的な修正コード、LROPollerの扱い方、同期・非同期クライアントの違い、移行時に起きやすい失敗まで整理します。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

Azure AI Projects 2.4.0でcreate_generation_jobが見つからない原因

2.4.0へ更新すると、従来のコードでは次のようなエラーが発生することがあります。

AttributeError: ... object has no attribute 'create_generation_job'

これは、次のメソッドが2.4.0で改名されたためです。

project_client.beta.evaluators.create_generation_job(...)
project_client.beta.datasets.create_generation_job(...)

新しいメソッド名は次のとおりです。

project_client.beta.evaluators.begin_create_generation_job(...)
project_client.beta.datasets.begin_create_generation_job(...)

エージェント最適化APIも同様に変更されています。

# 旧
project_client.beta.agents.create_optimization_job(...)

# 新
project_client.beta.agents.begin_create_optimization_job(...)

公式リリースノートでは、これら3つがbetaメソッドのBreaking Changesとして明記されています。単なる名称変更ではなく、長時間実行操作、いわゆるLROとして扱われるようになった点が重要です。(GitHub)

変更された3つのbeta API

2.4.0で影響を受けるAPIを整理すると、次のようになります。

用途2.4.0より前のメソッド2.4.0のメソッドpoller.result()の戻り値
評価器の生成beta.evaluators.create_generation_jobbeta.evaluators.begin_create_generation_jobEvaluatorVersion
評価・学習用データの生成beta.datasets.create_generation_jobbeta.datasets.begin_create_generation_jobDataGenerationJobResult
エージェント最適化beta.agents.create_optimization_jobbeta.agents.begin_create_optimization_jobOptimizationJobResult

特に評価器生成APIは、従来のEvaluatorGenerationJobではなく、処理完了後のEvaluatorVersionが最終結果として返る設計になりました。つまり、メソッド名だけでなく、後続処理が参照するオブジェクトの意味も変わっています。(GitHub)

最短でPythonコードを修正する方法

評価器生成APIを例にすると、変更前後のコードは次のようになります。

2.4.0より前のコード

generation_job = project_client.beta.evaluators.create_generation_job(
    job=evaluator_generation_job
)

print(generation_job)

2.4.0以降のコード

poller = project_client.beta.evaluators.begin_create_generation_job(
    job=evaluator_generation_job
)

evaluator_version = poller.result()

print(evaluator_version)

修正ポイントは2つです。

  1. create_generation_jobbegin_create_generation_jobへ変更する
  2. 戻り値をpollerとして受け、poller.result()で最終結果を取得する

次のように、メソッド名だけを変更するのは不十分です。

# 不完全な修正
generation_job = project_client.beta.evaluators.begin_create_generation_job(
    job=evaluator_generation_job
)

# generation_jobの実体はEvaluatorVersionではなくLROPoller
print(generation_job.name)

このコードでは、次のようなエラーにつながります。

AttributeError: 'LROPoller' object has no attribute 'name'

nameversionを持っているのはpoller.result()で取得したEvaluatorVersionです。

poller = project_client.beta.evaluators.begin_create_generation_job(
    job=evaluator_generation_job
)

evaluator_version = poller.result()

print(evaluator_version.name)
print(evaluator_version.version)

LROPollerとは何か

LROPollerは、すぐに完了しない長時間実行操作の状態確認と結果取得を担当するオブジェクトです。

Azure SDKでは、処理開始後に結果が確定するまで時間がかかる操作に、begin_から始まるメソッド名がよく使われます。begin_はPythonの非同期関数を意味するものではありません。同期クライアントでも、LROであればbegin_が付きます。

通常は、次のようにresult()を呼び出せば十分です。

poller = project_client.beta.datasets.begin_create_generation_job(
    job=data_generation_job
)

job_result = poller.result()

LROPollerには、主に次のメソッドがあります。

メソッド用途
result()完了まで待機し、最終結果を返す
done()処理が完了しているかを真偽値で返す
status()現在の状態を文字列で返す
wait()指定時間または完了まで待機する
add_done_callback()処理完了時に呼び出す関数を登録する

result()は、待機と最終結果の取得をまとめて行います。単純なバッチ処理や管理用スクリプトでは、まずresult()を利用するのが分かりやすい実装です。(Microsoft Learn)

待機時間を制御する例

一定時間だけ待機し、完了していなければ別の処理へ切り替えたい場合は、wait()done()を組み合わせられます。

poller = project_client.beta.evaluators.begin_create_generation_job(
    job=evaluator_generation_job
)

poller.wait(timeout=30)

if not poller.done():
    print(f"処理は継続中です。現在の状態: {poller.status()}")
else:
    evaluator_version = poller.result()
    print(evaluator_version.name, evaluator_version.version)

ただし、Web APIのリクエスト処理中に長時間待機すると、タイムアウトやワーカースレッドの占有につながります。Webアプリでは非同期クライアントやジョブキューを使い、HTTPリクエストと長時間処理を分離する設計も検討してください。

評価器生成APIを修正する

評価器生成では、beta.evaluators.begin_create_generation_jobを使用します。

poller = project_client.beta.evaluators.begin_create_generation_job(
    job=evaluator_generation_job,
    operation_id="rubric-generation-request-001",
    polling_interval=10,
)

evaluator_version = poller.result()

print(f"Evaluator: {evaluator_version.name}")
print(f"Version: {evaluator_version.version}")

公式サンプルでも、begin_create_generation_jobから返されたポーラーに対してresult()を呼び、生成済みのEvaluatorVersionを取得しています。(GitHub)

operation_idは再試行を考えて設定する

評価器生成APIのoperation_idは、同じ処理を安全に再試行するために利用できます。公式サンプルでは、同じoperation_idで再送信すると、既存の生成ジョブへ接続する動作として説明されています。(GitHub)

実務では、次のような値を使うと管理しやすくなります。

operation_id = f"evaluator-{application_id}-{request_id}"

重要なのは、再試行時にも同じ値を使えるように保存しておくことです。

毎回ランダムなUUIDを生成すると、通信エラー後の再試行が別ジョブとして作成される可能性があります。反対に、異なる生成要求で同じoperation_idを使い回すのも避けてください。

データ生成APIを修正する

データ生成APIは、次のように変更します。

poller = project_client.beta.datasets.begin_create_generation_job(
    job=data_generation_job,
    polling_interval=10,
)

job_result = poller.result()

ここで返るのは、直接のDatasetVersionではなくDataGenerationJobResultです。

生成されたデータセットを利用するには、outputsからDatasetDataGenerationJobOutputを探し、名前とバージョンを取得します。

from azure.ai.projects.models import DatasetDataGenerationJobOutput

poller = project_client.beta.datasets.begin_create_generation_job(
    job=data_generation_job,
    polling_interval=10,
)

job_result = poller.result()

dataset_output = next(
    (
        output
        for output in (job_result.outputs or [])
        if isinstance(output, DatasetDataGenerationJobOutput)
    ),
    None,
)

if (
    dataset_output is None
    or not dataset_output.name
    or not dataset_output.version
):
    raise RuntimeError(
        "データ生成ジョブからデータセット出力を取得できませんでした"
    )

dataset_version = project_client.datasets.get(
    name=dataset_output.name,
    version=dataset_output.version,
)

print(f"Dataset: {dataset_version.name}")
print(f"Version: {dataset_version.version}")
print(f"ID: {dataset_version.id}")

公式サンプルでも、job_result.outputsを走査し、DatasetDataGenerationJobOutputから生成されたデータセットの名前とバージョンを取得しています。(GitHub)

outputsの先頭要素を決め打ちしない

次のようなコードは避けた方が安全です。

dataset_output = job_result.outputs[0]

理由は、outputsが空の場合にIndexErrorとなるほか、先頭要素が期待するデータセット出力である保証をコード上で確認できないためです。

次の3点を確認してください。

  • outputsNoneまたは空でないか
  • 出力がDatasetDataGenerationJobOutput
  • nameversionの両方が取得できているか

生成処理自体が成功していても、想定した種類の出力が得られないケースを明示的にエラーとして扱えるようになります。

エージェント最適化APIを修正する

エージェント最適化APIは、create_optimization_jobからbegin_create_optimization_jobへ変更されました。

poller = project_client.beta.agents.begin_create_optimization_job(
    job=optimization_job,
    polling_interval=10,
)

optimization_result = poller.result()

print(f"Baseline: {optimization_result.baseline}")
print(f"Best: {optimization_result.best}")

for candidate in optimization_result.candidates or []:
    print(
        candidate.name,
        candidate.avg_score,
        candidate.avg_tokens,
    )

公式サンプルでも、begin_create_optimization_job(...).result()で最適化完了まで待機し、baselinebestcandidatesを参照しています。(GitHub)

ジョブIDや進捗を外部へ返したい場合

管理画面やWeb APIで、ジョブ作成直後にIDを返し、後から状態を確認したいケースもあります。

2.4.0には、SDKの自動ポーリングを使わず、polling=Falseraw_response_hookget_optimization_jobを組み合わせて手動監視する公式サンプルも追加されています。(GitHub)

ただし、この方法は通常のpoller.result()より実装が複雑です。単純に最適化結果が必要なバッチ処理では、まず自動ポーリングを使用してください。

手動ポーリングを採用する判断基準は次のとおりです。

要件推奨方式
コマンド実行中に完了まで待てるpoller.result()
バッチ処理内で結果を続けて使用するpoller.result()
Web画面へジョブIDをすぐ返したい手動ポーリングまたはジョブキュー
複数ジョブをまとめて監視したい非同期処理または手動ポーリング
処理を別プロセスで再開したいジョブIDや継続情報を永続化

同期クライアントと非同期クライアントの違い

begin_という名前だけを見て、すべての呼び出しにawaitが必要だと誤解しやすいため注意してください。

同期クライアント

同期版のAIProjectClientでは、begin_メソッドとresult()awaitは付けません。

from azure.ai.projects import AIProjectClient

poller = project_client.beta.agents.begin_create_optimization_job(
    job=optimization_job,
    polling_interval=10,
)

result = poller.result()

非同期クライアント

非同期版では、begin_メソッドとポーラーのresult()の両方をawaitします。

from azure.ai.projects.aio import AIProjectClient

poller = await project_client.beta.agents.begin_create_optimization_job(
    job=optimization_job,
    polling_interval=10,
)

result = await poller.result()

公式の非同期最適化サンプルでも、この2段階のawaitが使われています。(GitHub)

同期・非同期の違いを表にすると、次のとおりです。

処理同期クライアント非同期クライアント
LRO開始poller = client.begin_...()poller = await client.begin_...()
結果取得result = poller.result()result = await poller.result()
クライアントのimportazure.ai.projectsazure.ai.projects.aio
認証クラスの例azure.identity.DefaultAzureCredentialazure.identity.aio.DefaultAzureCredential

インストールされているSDKのバージョンを確認する

コードを修正する前に、実際にアプリが利用しているazure-ai-projectsのバージョンを確認します。

python -m pip show azure-ai-projects

Pythonから直接確認する方法もあります。

python -c "from importlib.metadata import version; print(version('azure-ai-projects'))"

仮想環境の取り違えも確認したい場合は、Python実行ファイルのパスも表示します。

python -c "import sys; from importlib.metadata import version; print(sys.executable); print(version('azure-ai-projects'))"

pip showでは2.4.0と表示されるのに、アプリの挙動が一致しない場合、pippythonが別の環境を参照している可能性があります。

そのため、インストールや更新には単独のpipコマンドではなく、次の形式を使うのが確実です。

python -m pip install --upgrade "azure-ai-projects==2.4.0"

requirements.txtでも、移行確認が終わるまではバージョンを固定しておくと、開発環境とCIの差を減らせます。

azure-ai-projects==2.4.0

プロジェクト内の影響箇所を一括検索する

影響を受ける呼び出しが複数ある場合は、手作業で探すより文字列検索を使う方が確実です。

VS Codeでは、CtrlShiftFで次の文字列を検索します。

create_generation_job
create_optimization_job

ripgrepを利用できる場合は、次のコマンドでも検索できます。

rg -n "create_generation_job|create_optimization_job" .

PowerShellでは、次のように検索できます。

Get-ChildItem -Recurse -Filter *.py |
    Select-String -Pattern 'create_generation_job|create_optimization_job'

見つかったコードは、次の対応表に沿って修正します。

beta.evaluators.create_generation_job
    ↓
beta.evaluators.begin_create_generation_job

beta.datasets.create_generation_job
    ↓
beta.datasets.begin_create_generation_job

beta.agents.create_optimization_job
    ↓
beta.agents.begin_create_optimization_job

ただし、一括置換した後は必ず戻り値の使われ方も確認してください。

result = client.beta.evaluators.begin_create_generation_job(...)

このresultは実際にはLROPollerです。変数名がresultのままだと、レビュー時に最終結果と誤認しやすくなります。

次のように役割を分けると明確です。

poller = client.beta.evaluators.begin_create_generation_job(...)
evaluator_version = poller.result()

2.4.0移行で失敗しやすいポイント

メソッド名だけを変更している

最も多いのは、次のような修正です。

result = client.beta.datasets.begin_create_generation_job(job=job)
print(result.outputs)

outputsを持つのはDataGenerationJobResultであり、LROPollerではありません。

poller = client.beta.datasets.begin_create_generation_job(job=job)
job_result = poller.result()
print(job_result.outputs)

result()の呼び出しを忘れている

処理開始だけを行い、結果を取得しないまま関数を終了するコードにも注意が必要です。

def generate_dataset(client, job) -> None:
    client.beta.datasets.begin_create_generation_job(job=job)

生成を開始するだけでよい設計なら成立する場合もありますが、生成結果を後続処理で使用するなら、ポーラーを保持して完了を待つ必要があります。

def generate_dataset(client, job):
    poller = client.beta.datasets.begin_create_generation_job(job=job)
    return poller.result()

戻り値の型ヒントが古い

評価器生成では、関数の戻り値がEvaluatorGenerationJobのまま残っていないか確認します。

# 旧設計の型
def generate_evaluator(...) -> EvaluatorGenerationJob:
    ...

2.4.0で完了後の評価器を返す関数なら、EvaluatorVersionへ変更します。

from azure.ai.projects.models import EvaluatorVersion

def generate_evaluator(...) -> EvaluatorVersion:
    poller = ...
    return poller.result()

データ生成と最適化についても、用途に応じて次の型へ更新します。

DataGenerationJobResult
OptimizationJobResult

テストのモックが旧APIのまま

本番コードを修正しても、単体テストが旧メソッドをモックしていると失敗します。

client.beta.evaluators.create_generation_job.return_value = expected

2.4.0では、begin_create_generation_jobがポーラーを返し、そのresult()が最終結果を返す形でモックします。

from unittest.mock import Mock

def test_generate_evaluator_waits_for_completion():
    expected = Mock(name="EvaluatorVersion")

    poller = Mock()
    poller.result.return_value = expected

    client = Mock()
    client.beta.evaluators.begin_create_generation_job.return_value = poller

    actual = generate_evaluator(
        project_client=client,
        job=Mock(),
        operation_id="operation-001",
    )

    assert actual is expected
    poller.result.assert_called_once_with()

アプリケーション側の関数は次のような形です。

def generate_evaluator(project_client, job, operation_id):
    poller = project_client.beta.evaluators.begin_create_generation_job(
        job=job,
        operation_id=operation_id,
    )
    return poller.result()

このテストでは、メソッド名だけでなく、result()を確実に呼び出していることも検証できます。

例外をresult()の外側でしか処理していない

LROの実行中にサービス側エラーが発生すると、result()HttpResponseErrorが送出されることがあります。begin_メソッドとresult()の両方を例外処理の範囲に入れてください。LROPoller.result()がサービス側の問題でHttpResponseErrorを送出し得ることは、Azure CoreのAPIリファレンスにも記載されています。(Microsoft Learn)

import logging

from azure.core.exceptions import HttpResponseError

logger = logging.getLogger(__name__)

try:
    poller = project_client.beta.agents.begin_create_optimization_job(
        job=optimization_job,
        polling_interval=10,
    )
    optimization_result = poller.result()
except HttpResponseError:
    logger.exception("エージェント最適化ジョブに失敗しました")
    raise

例外を記録した後に握りつぶすと、後続処理が不完全な結果を正常値として扱う可能性があります。復旧方法が決まっていない場合は、再送出して呼び出し元に失敗を伝える方が安全です。

beta APIを本番で使う場合の対策

今回の変更対象はすべて.beta配下です。

公式READMEでも、betaサブクライアントで公開されるプレビュー機能は、将来のバージョンで変更または削除される可能性があり、本番利用には推奨されないと説明されています。(GitHub)

本番システムで利用する必要がある場合は、少なくとも次の対策を行ってください。

SDK呼び出しを専用クラスへ隔離する

業務ロジックから直接.beta APIを呼ばず、アダプターやリポジトリクラスに閉じ込めます。

class EvaluatorService:
    def __init__(self, project_client):
        self._client = project_client

    def generate(self, job, operation_id):
        poller = self._client.beta.evaluators.begin_create_generation_job(
            job=job,
            operation_id=operation_id,
        )
        return poller.result()

SDKの仕様が再び変わっても、このクラスを中心に修正できます。

依存関係を固定する

次のような広い指定は、将来のbeta API変更を自動的に取り込む可能性があります。

azure-ai-projects>=2.4.0

本番環境では、検証済みのバージョンを固定し、更新時にリリースノートとテスト結果を確認する運用が安全です。

azure-ai-projects==2.4.0

Poetry、uv、pip-toolsなどを使っている場合は、ロックファイルもCIと本番環境で共有します。

実サービスに近い契約テストを用意する

モックだけでは、SDKのメソッド名や戻り値モデルが変わったことを検出できない場合があります。

検証用のAzure AI Projectを用意できるなら、次の流れを定期的に確認します。

  1. begin_メソッドで処理を開始できる
  2. ポーラーが正常に完了する
  3. result()から期待するモデルを取得できる
  4. 必須のプロパティが存在する
  5. 後続の取得・評価処理まで成功する

特に、EvaluatorVersionDataGenerationJobResultOptimizationJobResultのどこまでをアプリケーションの契約とするかを明確にしておくことが重要です。

2.3.xへ戻すべきか、2.4.0へ移行すべきか

障害発生時には、2.4.0へ対応するか、旧バージョンへ戻すかを判断する必要があります。

状況推奨対応
開発中でコードを修正できる2.4.0へ移行する
CIやステージングで検出した2.4.0へ移行してテストを更新する
本番障害を直ちに復旧したい旧版固定を一時的に検討する
SDKを利用する共通ライブラリを配布しているバージョン範囲を限定し、戻り値の差を吸収する
長期運用する本番システムbeta API依存を隔離し、更新手順を定める

緊急避難として旧バージョンへ戻す場合は、例えば次のように固定します。

python -m pip install "azure-ai-projects==2.3.0"

ただし、これは恒久対応ではありません。今回の変更は2.4.0のBreaking Changesとして明示されており、今後のサンプルやSDK改善は新しいLRO形式を前提に進むと考えるのが自然です。(GitHub)

また、次のような単純な互換コードには注意してください。

if hasattr(client.beta.evaluators, "begin_create_generation_job"):
    return client.beta.evaluators.begin_create_generation_job(job=job).result()

return client.beta.evaluators.create_generation_job(job=job)

旧APIはEvaluatorGenerationJob、新APIのresult()EvaluatorVersionを返すため、両者を同じ戻り値として扱えません。複数バージョンをサポートするなら、アプリケーション独自の結果型へ変換する必要があります。

Azure AI Projects 2.4.0への移行チェックリスト

最後に、修正漏れを防ぐための確認項目を整理します。

  • 実行環境のazure-ai-projectsが2.4.0か確認する
  • create_generation_jobをプロジェクト全体から検索する
  • create_optimization_jobをプロジェクト全体から検索する
  • 対象メソッドをbegin_付きへ変更する
  • 戻り値をpollerとして受ける
  • poller.result()で最終結果を取得する
  • 評価器生成の戻り値をEvaluatorVersionとして扱う
  • データ生成ではjob_result.outputsから対象出力を探す
  • 最適化ではOptimizationJobResultを参照する
  • 同期版と非同期版のawaitの有無を確認する
  • 型ヒントと単体テストのモックを更新する
  • HttpResponseErrorを処理する
  • SDKバージョンをロックする
  • beta APIの呼び出しを専用クラスへ隔離する

azure-ai-projects 2.4.0でcreate_generation_jobが見つからない場合、最初に行うべきことは、旧メソッドをbegin_create_generation_jobへ置き換えることです。そのうえで、戻り値を直接利用せず、LROPoller.result()から最終結果を取得するように後続処理を修正します。

評価器生成、データ生成、エージェント最適化では最終結果の型が異なります。単純な一括置換だけで終わらせず、型ヒント、モック、例外処理、結果の参照箇所まで確認することが、2.4.0移行を安全に完了させるポイントです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次