Azure OpenAI Response API を試してみました。基本的にリンク先の内容に準じて確認しています。
これらは注意点です。
- 使えるリージョン限定
- 使えるモデルが限定
- Web検索は使用できない
- トレースは試しましたが、残らなかったです
- 入力候補を試しましたが、残らなかったです(APIにパラメータはあるのに)
試したこと
環境
Ubuntu 22.4.05 で Python3.11.7 で試しています。
パッケージは以下の通り。
- azure-ai-projects: 1.0.0
共通箇所
import base64
from pprint import pprint
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
MODEL = "gpt-4.1-nano"
project = AIProjectClient(
endpoint="https://<Resource>.services.ai.azure.com/api/projects/<project>",
credential=DefaultAzureCredential())
client = project.get_openai_client(api_version="2025-04-01-preview")
基本使い方
まずは、基本の使い方です。
instruction
がシステムプロンプトの役割をします。input
がシンプルなのがいいですね。
response = client.responses.create(
model=MODEL,
instructions="You are a helpful assistant.",
input="This is a test.",
)
print(response.model_dump_json(indent=2))
{
"id": "resp_689705ce028c819ab9a27f2d18cccdb704fa2fc07ff11bcc",
"created_at": 1754727886.0,
"error": null,
"incomplete_details": null,
"instructions": "You are a helpful assistant.",
"metadata": {
"purpose": "response-test"
},
"model": "gpt-4.1-nano",
"object": "response",
"output": [
{
"id": "msg_689705ce6998819aace609464bdc3daa04fa2fc07ff11bcc",
"content": [
{
"annotations": [],
"text": "Hello! I see you've sent a test message. How can I assist you today?",
"type": "output_text"
}
],
"role": "assistant",
"status": "completed",
"type": "message"
}
],
"parallel_tool_calls": true,
"temperature": 1.0,
"tool_choice": "auto",
"tools": [],
"top_p": 1.0,
"max_output_tokens": null,
"previous_response_id": null,
"reasoning": {
"effort": null,
"generate_summary": null,
"summary": null
},
"service_tier": "default",
"status": "completed",
"text": {
"format": {
"type": "text"
}
},
"truncation": "disabled",
"usage": {
"input_tokens": 22,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 18,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 40
},
"user": null,
"background": false,
"content_filters": null,
"max_tool_calls": null,
"prompt_cache_key": null,
"safety_identifier": null,
"store": true
}
以下の方法で、後で結果を取得できます。30日菅保持されるそうです。
既定では、応答データは 30 日間保持されます。
print(client.responses.retrieve(response.id).model_dump_json(indent=2))
マルチターン(2回目)
previous_response_id
を指定することで、マルチターンにできます。2度目のコールで履歴を渡さなくて済むのが便利です(ただ、内部的には前の履歴をLLMに渡しており、その分のToken課金もされています)。
response.idは1回目も2回目以降もずっと一意なので、途中からやり直す(N回目の履歴までのIDを取得、渡す)ことはできません(このブログに書いたIDは別になっていますが、単純に別セッションで実行したから)。
前のターンのinstruction
は引き継がないので、再度指定するか、別のinsutrution
を渡すかします。
second_response = client.responses.create(
model=MODEL,
previous_response_id=response.id,
input="さっき何を言ったの?",
# 下の形式でもOK
# input=[{"role": "user", "content": "さっき何を言ったの?"}],
)
print(second_response.model_dump_json(indent=2))
{
"id": "resp_6896f3681efc819abdec84ccf94da5810382c6866b5581f7",
"created_at": 1754723176.0,
"error": null,
"incomplete_details": null,
"instructions": null,
"metadata": {
"purpose": "response-test"
},
"model": "gpt-4.1-nano",
"object": "response",
"output": [
{
"id": "msg_6896f36888d8819ab100a9f3d93406d00382c6866b5581f7",
"content": [
{
"annotations": [],
"text": "あなたが「これがテストです」と言ったのを覚えています。",
"type": "output_text"
}
],
"role": "assistant",
"status": "completed",
"type": "message"
}
],
"parallel_tool_calls": true,
"temperature": 1.0,
"tool_choice": "auto",
"tools": [],
"top_p": 1.0,
"max_output_tokens": null,
"previous_response_id": "resp_6896f2e478f4819aa584902c63a541380382c6866b5581f7",
"reasoning": {
"effort": null,
"generate_summary": null,
"summary": null
},
"service_tier": "default",
"status": "completed",
"text": {
"format": {
"type": "text"
}
},
"truncation": "disabled",
"usage": {
"input_tokens": 47,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 18,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 65
},
"user": null,
"background": false,
"content_filters": null,
"max_tool_calls": null,
"prompt_cache_key": null,
"safety_identifier": null,
"store": true
}
ストリーミング
ストリームでの取得。
response = client.responses.create(
input="1000文字程度の小噺をして",
model=MODEL,
stream=True,
)
for event in response:
if event.type == 'response.output_text.delta':
print(event.delta, end='')
Tool
Tool呼出。
response = client.responses.create(
model=MODEL,
tools=[
{
"type": "function",
"name": "get_weather",
"description": "Get the weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
},
"required": ["location"],
},
}
],
input="What's the weather in San Francisco?",
)
print(response.model_dump_json(indent=2))
{
"id": "resp_6896f5196414819b9dc0aa542f3e4b000526d04af4c796fa",
"created_at": 1754723609.0,
"error": null,
"incomplete_details": null,
"instructions": null,
"metadata": {
"purpose": "response-test"
},
"model": "gpt-4.1-nano",
"object": "response",
"output": [
{
"arguments": "{\"location\":\"San Francisco\"}",
"call_id": "call_dD9gAonX1GxjsNU0qtebOojA",
"name": "get_weather",
"type": "function_call",
"id": "fc_6896f519f59c819ba52e0d89e45da6210526d04af4c796fa",
"status": "completed"
}
],
"parallel_tool_calls": true,
"temperature": 1.0,
"tool_choice": "auto",
"tools": [
{
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"required": [
"location"
]
},
"strict": true,
"type": "function",
"description": "Get the weather for a location"
}
],
"top_p": 1.0,
"max_output_tokens": null,
"previous_response_id": null,
"reasoning": {
"effort": null,
"generate_summary": null,
"summary": null
},
"service_tier": "default",
"status": "completed",
"text": {
"format": {
"type": "text"
}
},
"truncation": "disabled",
"usage": {
"input_tokens": 45,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 32,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 77
},
"user": null,
"background": false,
"content_filters": null,
"max_tool_calls": null,
"prompt_cache_key": null,
"safety_identifier": null,
"store": true
}
先ほどの結果をinputに入れて、元のPrompt「What's the weather in San Francisco?」に回答させる
input = []
for output in response.output:
if output.type == "function_call":
match output.name:
case "get_weather":
input.append(
{
"type": "function_call_output",
"call_id": output.call_id,
"output": '{"temperature": "70 degrees"}',
}
)
case _:
raise ValueError(f"Unknown function call: {output.name}")
second_response = client.responses.create(
model=MODEL,
previous_response_id=response.id,
input=input,
)
print(f"{input=}")
print(second_response.model_dump_json(indent=2))
input=[{'type': 'function_call_output', 'call_id': 'call_LeGcTqo26z5V6mYTfsW9HXpC', 'output': '{"temperature": "70 degrees"}'}]
{
"id": "resp_68975675b99c8198989d1c880fc5520d04fd65c204c1ce76",
"created_at": 1754748533.0,
"error": null,
"incomplete_details": null,
"instructions": null,
"metadata": {},
"model": "gpt-4.1-nano",
"object": "response",
"output": [
{
"id": "msg_6897567645408198a7dd7b17617467d104fd65c204c1ce76",
"content": [
{
"annotations": [],
"text": "The current temperature in San Francisco is approximately 70 degrees.",
"type": "output_text"
}
],
"role": "assistant",
"status": "completed",
"type": "message"
}
],
"parallel_tool_calls": true,
"temperature": 1.0,
"tool_choice": "auto",
"tools": [],
"top_p": 1.0,
"max_output_tokens": null,
"previous_response_id": "resp_6897567463c88198a3d0406072262e5804fd65c204c1ce76",
"reasoning": {
"effort": null,
"generate_summary": null,
"summary": null
},
"service_tier": "default",
"status": "completed",
"text": {
"format": {
"type": "text"
}
},
"truncation": "disabled",
"usage": {
"input_tokens": 42,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 13,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 55
},
"user": null,
"background": false,
"content_filters": null,
"max_tool_calls": null,
"prompt_cache_key": null,
"safety_identifier": null,
"store": true
}
画像(URL)読込
画像をURLから読み込んで内容を答えさせる。
response = client.responses.create(
model=MODEL,
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "この画像は何?"},
{
"type": "input_image",
"image_url": "https://pbs.twimg.com/media/EJr1V-8UUAAV9Qj?format=jpg&name=large",
},
],
}
],
)
print(response.model_dump_json(indent=2))
ちなみに画像はこいつ。深い意味までは読み取れていないですね。
{
"id": "resp_6896f6868ebc819a90904f222f842f07097b2ce2873828ab",
"created_at": 1754723974.0,
"error": null,
"incomplete_details": null,
"instructions": null,
"metadata": {
"purpose": "response-test"
},
"model": "gpt-4.1-nano",
"object": "response",
"output": [
{
"id": "msg_6896f6886c44819aa919522abc133bb0097b2ce2873828ab",
"content": [
{
"annotations": [],
"text": "この画像は、猫のキャラクターが人間のように洋服を着て、テーブルに座っているイラストです。猫は赤い制服のような服を着ており、にこやかに笑っている様子です。",
"type": "output_text"
}
],
"role": "assistant",
"status": "completed",
"type": "message"
}
],
"parallel_tool_calls": true,
"temperature": 1.0,
"tool_choice": "auto",
"tools": [],
"top_p": 1.0,
"max_output_tokens": null,
"previous_response_id": null,
"reasoning": {
"effort": null,
"generate_summary": null,
"summary": null
},
"service_tier": "default",
"status": "completed",
"text": {
"format": {
"type": "text"
}
},
"truncation": "disabled",
"usage": {
"input_tokens": 1550,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 46,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 1596
},
"user": null,
"background": false,
"content_filters": null,
"max_tool_calls": null,
"prompt_cache_key": null,
"safety_identifier": null,
"store": true
}
PDF読込(エンコード実施)
PDFの読込(アップロードではなく、エンコードして渡す)。
with open("./data/PerksPlus.pdf", "rb") as f:
data = f.read()
base64_string = base64.b64encode(data).decode("utf-8")
response = client.responses.create(
model=MODEL,
input=[
{
"role": "user",
"content": [
{
"type": "input_file",
"filename": "test.pdf",
"file_data": f"data:application/pdf;base64,{base64_string}",
},
{
"type": "input_text",
"text": "Summarize this PDF",
},
],
},
]
)
print(response.output_text)
ちなみに、PDF内に絵が多いとこんなエラー。
BadRequestError: Error code: 400 -
{'error':
{'message': 'Too many images in request: 11, maximum allowed: 10.',
'type': 'invalid_request_error', 'param': None, 'code': None}}
MCPリモートサーバー使用
Microsoft Learn MCP Server 使います。
LEARN_MCP_TOOL = {
"type": "mcp",
"server_label": "learn",
"server_url": "https://learn.microsoft.com/api/mcp",
"require_approval": "never",
}
def ask_learn(question: str) -> str:
# 1回目: 通常の問い合わせ。モデルが必要に応じて MCP を呼び出します。
resp = client.responses.create(
model=MODEL,
input=question,
tools=[LEARN_MCP_TOOL],
instructions=(
"If the user's question mentions Microsoft/Azure technologies, "
"use the MCP tool labeled 'learn' to fetch up-to-date docs from Microsoft Learn."
),
)
# 承認が必要な場合は mcp_approval_request が返ってくるので承認して再実行
approval_ids = [
item.id
for item in (resp.output or [])
if getattr(item, "type", "") == "mcp_approval_request"
and getattr(item, "server_label", "") == "learn"
]
for approval_id in approval_ids:
resp = client.responses.create(
model=MODEL,
previous_response_id=resp.id,
tools=[LEARN_MCP_TOOL],
input=[{
"type": "mcp_approval_response",
"approve": True,
"approval_request_id": approval_id,
}],
)
# 最終テキストを返す
return resp.output_text
question = "Azure Storage アカウントを az cli で作成する手順を、コード例と注意点込みで教えて。"
print(ask_learn(question))
Azure StorageアカウントをAzure CLI (`az`) で作成する手順を、例コードと注意点を含めてご紹介します。
### コマンド例
```bash
az storage account create \
--name <your-unique-account-name> \
--resource-group <your-resource-group> \
--location eastus \
--sku Standard_RAGRS \
--kind StorageV2 \
--min-tls-version TLS1_2 \
--allow-blob-public-access false
```
### 各パラメータのポイント
- `<your-unique-account-name>`:Azure全体でユニークな名前にしてください。
- `<your-resource-group>`:既存のリソースグループ名を指定します。ない場合は作成が必要です。
- `--location`:リージョンを指定します。
- `--sku`:冗長性の設定です。例では地域冗長ストレージ(RA-GRS)を使用。
- `--kind StorageV2`:汎用 v2 ストレージアカウントを作成。
- `--min-tls-version TLS1_2`:セキュリティ向上のために TLS 1.2 を最小バージョンに設定。
- `--allow-blob-public-access false`:Blobの公開アクセスを禁止。
### 注意点
...
- 必要に応じて他のオプション(ネットワーク制御やデータレイク機能など)も指定可能です。
- `az storage account create` コマンドを実行する前に、Azure CLI にログインしておく必要があります(例:`az login`)。
これにより、Azure CLI で効率的にAzureストレージアカウントを作成できます。
その他
以下の方法を試しましたが、両方ともに Response APIではできませんでした。