非同期 HTTP インタフェース
非同期 HTTP インタフェース は長時間の音声を文字起こしするためのノンブロッキングな HTTP API です。
この API を利用するには以下のステップを実行します。
- 音声認識ジョブを作成する
- ポーリングして音声認識ジョブの状態をチェックし、結果を取得する
利用の方法
1. 音声認識ジョブを作成する
ジョブの作成の API リクエストは、同期 HTTP インタフェース と同じようにリクエストパラメータを設定し、非同期 HTTP インタフェース のエンドポイントにリクエストを送信します。
POST https://acp-api-async.amivoice.com/v1/recognitions
例えば、curl コマンドで、ログ保存なしで、test.wavファイルの音声認識のリクエストを送るには以下のようにします。
curl https://acp-api-async.amivoice.com/v1/recognitions \
-F u={APP_KEY} \
-F d="grammarFileNames=-a-general loggingOptOut=True" \
-F a=@test.wav
同期 HTTP インタフェース とはエンドポイントが異なりますが、リクエストパラメータの設定の方法は同じです。
感情分析などいくつかのパラメータは、非同期 HTTP インタフェース にしか対応していません。
同期 HTTP インタフェース とは異なり、ログ保存のあり、なしはエンドポイントではなく、リクエストパラメータで指定します。
デフォルトではログ保存ありとなります。ログ保存をしないようにするには、d
パラメータにloggingOptOut=True
を指定します。
成功した場合
成功時のレスポンスにはsessionid
が含まれています。これは、ユーザーの音声認識リクエストに対するジョブの ID でジョブの状態を確認したり、結果を得るために利用します。
text
は常に"..."を返します。
例
{"sessionid":"017ac8786c5b0a0504399999","text":"..."}
失敗した場合
失敗時のレスポンスにはsessionid
が含まれません。code
とmessage
で失敗の原因を判断できます。
レスポンスコードとメッセージを参照してください。
例
{
"results": [{ "tokens": [], "tags": [], "rulename": "", "text": "" }],
"text": "",
"code": "-",
"message": "received illegal service authorization"
}
2. 音声認識ジョブの状態をチェックし、結果を取得する
音声認識の結果のリクエストに成功したら、ジョブの状態を確認し、status
がcompleted
となるかerror
となるまでポーリングしてください。
ジョブの状態の取得
ジョブはサーバ側で順次実行されます。ジョブの状態の確認や、結果を取得するには結果取得用のエンドポイント GET /v1/recognitions/{session_id}
に問い合わせます。
sessionid
には、ジョブ作成時に得られたジョブ ID を設定します。リクエストパラメータの認証情報(authorization
)は、Authorization
ヘッダーに指定してください。
curl で実行する場合は以下のようにします。ここでは、sessionid
が017c25ec12c00a304474a999だとします。
curl -H "Authorization: Bearer {APPKEY}" \
https://acp-api-async.amivoice.com/v1/recognitions/017c25ec12c00a304474a999
queued 状態
リクエストを送った直後は、status
はqueued
の状態になります。
{"service_id":"{YOUR_SERVICE_ID}","session_id":"017c25ec12c00a304474a999","status":"queued"}
started 状態
キューからジョブが取り出されるとstatus
はstarted
状態になります。
{"service_id":"{YOUR_SERVICE_ID}","session_id":"017c25ec12c00a304474a999","status":"started"}
processing 状態
実際に音声認識処理が始まるとstatus
はprocessing
状態になります。以下のサンプルを参照してください。ここでは見やすさのために結果を改行しています。
API が受けとった音声のサイズ(audio_size
)や、MD5 チェックサム(audio_md5
)を使って音声が正しく送信できたかを確認できます。
processing
から次のcompleted
状態になるまでにかかる時間は、音声の長さによりますが、だいたい音声の長さの0.5〜1.5倍程度を目安にしてください。
{
"audio_md5":"40f59fe5fc7745c33b33af44be43f6ad",
"audio_size":306980,
"service_id":"{YOUR_SERVICE_ID}",
"session_id":"017c25ec12c00a304474a999",
"status":"processing"
}
completed 状態
音声認識が完了すると、status
はcompleted
状態となります。このとき、レスポンスのresults
とsegments
に音声認識結果を得ることができます。結果は音声認識サーバの処理が完了してから一定期間サーバに保存されます。保存期間は、非同期HTTPインタフェースの制限の"音声認識結果の保存期間"を参照してください。
認識結果を含むレスポンスの詳細は音声認識の結果を参照してください。
一定期間を過ぎて削除された結果にアクセスすると、404 NOT FOUND
エラーとなります。エラーについてはリファレンスのエラーレスポンス一覧を参照してください。
error 状態
なんらかの理由で音声認識に失敗した場合は、status
がerror
状態となります。このとき、error_messsage
にはエラーの原因が設定されます。
例:
{
"status": "error",
"audio_md5":"40f59fe5fc7745c33b33af44be43f6ad",
"audio_size":306980,
"service_id":"{YOUR_SERVICE_ID}",
"session_id":"017c25ec12c00a304474a999",
"error_message": "ERROR: Failed to transcribe in recognition process - amineth_result=0, amineth_code='o', amineth_message='recognition result is rejected because confidence is below the threshold'"
}
error_message
には、amineth_code='{レスポンスコード}'
とamineth_message='{エラーメッセージ}'
が含まれている場合があります。詳細はレスポンスコードとメッセージの詳細にある表を参照してください。
特にエラーメッセージにamineth_code='o'
が含まれる場合は、クライアントのリクエスト方法や音声ファイルに問題があるため、リトライしても同じ結果になります。詳細は、「リジェクト (レスポンスコード=o)」を参照してください。o
以外のエラーの場合、AmiVoice APIの基盤の問題である可能性が高いため、しばらく待ってからリトライしてください。
コンテンツ ID
リクエスト時にd
パラメータのcontentId
には自由に文字列を設定できます。例えば、アプリケーションが発行した ID や、ファイル名、利用者などの情報を設定しておくことで、認識結果の一部として後からそれらの情報が得られます。
例えば、curl コマンドでcontentId
にファイル名を設定してリクエストを送るには以下のようにします。
curl https://acp-api-async.amivoice.com/v1/recognitions \
-F u={APP_KEY} \
-F d="grammarFileNames=-a-general loggingOptOut=True contentId=test.wav" \
-F a=@test.wav
ジョブの状態や結果を取得する際、以下のようにcontent_id
が含まれるようになります。
{"content_id":"test.wav","service_id":"{YOUR_SERVICE_ID}","session_id":"017c25ec12c00a304474a999","status":"queued"}
サンプルコード
非同期HTTPインタフェースの典型的な処理の流れをPythonのサンプルコードで示します。
リクエストパラメータ
実行にはAmiVoice APIのAPPKEYが必要です。以下の行に自身のAmiVoice APIのAPPKEYを設定します。
app_key = 'TODO: Please set APPKEY here'
d
パラメータに設定するオプションを決めます。ここでは以下のようにします。
- エンジン: 汎用 (
grammarFileNames=-a-general
) - ログ保存: なし (
loggingOptOut=True
) - コンテンツID: ファイル名 (
contentId=filename
) - 話者ダイアライゼーション: 有効 (
speakerDiarization=True
) - 話者数: 最大=最小=2 (
diarizationMinSpeaker=2
,diarizationMaxSpeaker=2
) - 感情分析: 有効 (
sentimentAnalysis=True
)
domain = {
'grammarFileNames': '-a-general',
'loggingOptOut': 'True',
'contentId': filename,
'speakerDiarization': 'True',
'diarizationMinSpeaker': '2',
'diarizationMaxSpeaker': '2',
'sentimentAnalysis': 'True',
...
また、2つの単語を登録しています。profileId
はコメントしておき、登録した単語はそのセッションだけで有効になるようにしています。詳細は、単語登録を参照してください。
#'profileId': 'test',
'profileWords': 'wwww よんこだぶる|www2 とりぷるだぶる',
}
d
パラメータに設定するkey-valueのvalueはURLエンコードします。Pythonでは、urllib.parse.quote
を使います。
params = {
'u': app_key,
'd': ' '.join([f'{key}={urllib.parse.quote(value)}' for key, value in domain.items()]),
}
logger.info(params)
params
は以下のようになります。
{'u': 'XXXX', 'd': 'grammarFileNames=-a-general loggingOptOut=True contentId=www.wav profileWords=wwww%20%E3%82%88%E3%82%93%E3%81%93%E3%81%A0%E3%81%B6%E3%82%8B%7Cwww2%20%E3%81%A8%E3%82%8A%E3%81%B7%E3%82%8B%E3%81%A0%E3%81%B6%E3%82%8B speakerDiarization=True diarizationMinSpeaker=2 diarizationMaxSpeaker=2 sentimentAnalysis=True'}
音声認識ジョブ作成のリクエスト
さきほどのparams
と、音声ファイルをHTTP POSTで送信します。サンプルでは読みやすくHTTPの通信を記述するために、HTTPクライアントライブラリrequests
を使います。
request_response = requests.post(
url=endpoint,
data={key: value for key, value in params.items()},
files={'a': (filename, open(filename, 'rb').read(), 'application/octet-stream')}
)
呼び出しに成功したかどうかをHTTPのステータスコードで確認します。また、ジョブの作成に成功したかどうかは、レスポンスにsessionid
が存在するかどうかで確認します。
if request_response.status_code != 200:
logger.error(f'Failed to request - {request_response.content}')
exit(1)
request = request_response.json()
if 'sessionid' not in request:
logger.error(f'Failed to create job - {request["message"]} ({request["code"]})')
exit(2)
logger.info(request)
ジョブの作成に成功すると以下のようなレスポンスが得られます。レスポンスに含まれるsessionid
を使ってジョブの状態の確認や結果の取得を行います。
{'sessionid': '01838d9535080a304474a07f', 'text': '...'}
音声認識ジョブの状態の確認
HTTP GETでrecognitions/{sessionid}
にリクエストします。レスポンスに含まれるstatus
がcompleted
かerror
になるまでポーリングします。ここでは、10秒ごとに結果を確認しています。
while True:
# HTTP GETで`recognitions/{sessionid}`にリクエスト
result_response = requests.get(
url=f'{endpoint}/{request["sessionid"]}',
headers={'Authorization': f'Bearer {app_key}'}
)
if result_response.status_code == 200:
result = result_response.json()
if 'status' in result and (result['status'] == 'completed' or result['status'] == 'error'):
# レスポンスの`status`が`completed`か`error`になれば、結果を整形して出力
print(json.dumps(result, ensure_ascii=False, indent=4))
exit(0)
else:
# レスポンスの`status`が`completed`か`error`以外の場合は、ジョブの実行中
# なので、再度状態をチェックする前に少し待つ(ここでは10秒)
logger.info(result)
time.sleep(10)
else:
# HTTPのレスポンスコードが200以外の場合は終了する
logger.error(f'Failed. Response is {result_response.content} - {e}')
exit(3)
コード
ここまでの Python コードの全体を示します。
import time
import json
import urllib
import logging
import requests
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s")
endpoint = 'https://acp-api-async.amivoice.com/v1/recognitions'
app_key = 'TODO: Please set APPKEY here'
filename = 'www-2.wav'
# リクエストパラメータ
domain = {
'grammarFileNames': '-a-general',
'loggingOptOut': 'True',
'contentId': filename,
'speakerDiarization': 'True',
'diarizationMinSpeaker': '2',
'diarizationMaxSpeaker': '2',
'sentimentAnalysis': 'True',
#'profileId': 'test',
'profileWords': 'wwww よんこだぶる|www2 とりぷるだぶる',
}
params = {
'u': app_key,
'd': ' '.join([f'{key}={urllib.parse.quote(value)}' for key, value in domain.items()]),
}
logger.info(params)
# ジョブのリクエストの送信
request_response = requests.post(
url=endpoint,
data={key: value for key, value in params.items()},
files={'a': (filename, open(filename, 'rb').read(), 'application/octet-stream')}
)
if request_response.status_code != 200:
logger.error(f'Failed to request - {request_response.content}')
exit(1)
request = request_response.json()
if 'sessionid' not in request:
logger.error(f'Failed to create job - {request["message"]} ({request["code"]})')
exit(2)
logger.info(request)
# 結果が出るまで10秒ごとに状態を確認する
while True:
# HTTP GETで`recognitions/{sessionid}`にリクエスト
result_response = requests.get(
url=f'{endpoint}/{request["sessionid"]}',
headers={'Authorization': f'Bearer {app_key}'}
)
if result_response.status_code == 200:
result = result_response.json()
if 'status' in result and (result['status'] == 'completed' or result['status'] == 'error'):
# レスポンスの`status`が`completed`か`error`になれば、結果を整形して出力
print(json.dumps(result, ensure_ascii=False, indent=4))
exit(0)
else:
# レスポンスの`status`が`completed`か`error`以外の場合は、ジョブの実行中
# なので、再度状態をチェックする前に少し待つ(ここでは10秒)
logger.info(result)
time.sleep(10)
else:
# HTTPのレスポンスコードが200以外の場合は終了する
logger.error(f'Failed. Response is {result_response.content} - {e}')
exit(3)
実行方法
Python3がシステムにインストールされていることを確認してください。
ライブラリのインストールをします。
pip install requests
サンプルの音声ファイル(www-2.wav)をダウンロードして、プログラムと同じディレクトリにコピーしてください。
『トリプル・ダブルは、バスケットボールの記録に関する用語です。』と発話したものを録音した音声ファイルです。サンプルコード中で、『とりぷるだぶる』という読みにたいして、『www2』という単語を登録しているので、これが有効になっていることを確認できるような内容にしています。
サンプルプログラムを実行するにはコマンドラインから以下を実行します。
python async-http-sample.py
実行結果は以下のようになります。
$ python sample.py
2022-12-06 15:01:03,336 {'u': 'XXXX', 'd': 'grammarFileNames=-a-general loggingOptOut=True contentId=www-2.wav speakerDiarization=True diarizationMinSpeaker=2 diarizationMaxSpeaker=2 sentimentAnalysis=True profileWords=wwww%20%E3%82%88%E3%82%93%E3%81%93%E3%81%A0%E3%81%B6%E3%82%8B%7Cwww2%20%E3%81%A8%E3%82%8A%E3%81%B7%E3%82%8B%E3%81%A0%E3%81%B6%E3%82%8B'}
2022-12-06 15:01:03,345 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:01:04,117 https://acp-api-async.amivoice.com:443 "POST /v1/recognitions HTTP/1.1" 200 55
2022-12-06 15:01:04,119 {'sessionid': '0184e605ff170a306b8f9c96', 'text': '...'}
2022-12-06 15:01:04,122 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:01:04,309 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 112
2022-12-06 15:01:04,312 {'status': 'queued', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:01:14,328 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:01:14,517 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 112
2022-12-06 15:01:14,519 {'status': 'queued', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:01:24,523 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:01:24,718 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 112
2022-12-06 15:01:24,721 {'status': 'queued', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:01:34,728 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:01:34,886 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 112
2022-12-06 15:01:34,888 {'status': 'queued', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:01:44,940 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:01:45,114 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 112
2022-12-06 15:01:45,118 {'status': 'queued', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:01:55,124 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:01:56,735 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 112
2022-12-06 15:01:56,736 {'status': 'queued', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:02:06,743 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:02:06,940 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 113
2022-12-06 15:02:06,942 {'status': 'started', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:02:16,948 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:02:17,108 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 113
2022-12-06 15:02:17,109 {'status': 'started', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav'}
2022-12-06 15:02:27,114 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:02:27,281 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 183
2022-12-06 15:02:27,283 {'status': 'processing', 'session_id': '0184e605ff170a306b8f9c96', 'service_id': 'amiyamamoto', 'content_id': 'www-2.wav', 'audio_size': 270444, 'audio_md5': 'fd7d144824e8a5982d3aaa4cda5358a8'}
2022-12-06 15:02:37,290 Starting new HTTPS connection (1): acp-api-async.amivoice.com:443
2022-12-06 15:02:37,476 https://acp-api-async.amivoice.com:443 "GET /v1/recognitions/0184e605ff170a306b8f9c96 HTTP/1.1" 200 2481
{
"status": "completed",
"session_id": "0184e605ff170a306b8f9c96",
"service_id": "amiyamamoto",
"content_id": "www-2.wav",
"audio_size": 270444,
"audio_md5": "fd7d144824e8a5982d3aaa4cda5358a8",
"segments": [
{
"results": [
{
"tokens": [
{
"written": "www2",
"confidence": 1,
"starttime": 1620,
"endtime": 2548,
"spoken": "とりぷるだぶる",
"label": "speaker0"
},
{
"written": "は",
"confidence": 1,
"starttime": 2548,
"endtime": 2788,
"spoken": "は",
"label": "speaker0"
},
{
"written": "バスケットボール",
"confidence": 1,
"starttime": 2916,
"endtime": 3956,
"spoken": "ばすけっとぼーる",
"label": "speaker0"
},
{
"written": "の",
"confidence": 0.99,
"starttime": 3956,
"endtime": 4052,
"spoken": "の",
"label": "speaker0"
},
{
"written": "記録",
"confidence": 1,
"starttime": 4052,
"endtime": 4404,
"spoken": "きろく",
"label": "speaker0"
},
{
"written": "に",
"confidence": 1,
"starttime": 4404,
"endtime": 4532,
"spoken": "に",
"label": "speaker0"
},
{
"written": "関する",
"confidence": 1,
"starttime": 4532,
"endtime": 5060,
"spoken": "かんする",
"label": "speaker0"
},
{
"written": "用語",
"confidence": 1,
"starttime": 5060,
"endtime": 5412,
"spoken": "ようご",
"label": "speaker1"
},
{
"written": "です",
"confidence": 0.96,
"starttime": 5412,
"endtime": 5940,
"spoken": "です",
"label": "speaker0"
},
{
"written": "。",
"confidence": 0.8,
"starttime": 5940,
"endtime": 6196,
"spoken": "_",
"label": "speaker0"
}
],
"confidence": 1,
"starttime": 1300,
"endtime": 6196,
"tags": [],
"rulename": "",
"text": "www2はバスケットボールの記録に関する用語です。"
}
],
"text": "www2はバスケットボールの記録に関する用語です。"
}
],
"utteranceid": "20221206/15/0184e60741170a30522339d0_20221206_150225[nolog]",
"text": "www2はバスケットボールの記録に関する用語です。",
"code": "",
"message": "",
"sentiment_analysis": {
"segments": [
{
"starttime": 1680,
"endtime": 2860,
/* 感情パラメータ */
},
{
"starttime": 3520,
"endtime": 4900,
/* 感情パラメータ */
}
]
}
}
text
は結果のテキストですが、"www2はバスケットボールの記録に関する用語です。"のように得られており、発話内容を正しく音声認識できていることがわかります。また、"トリプルダブル"が、"www2"のように変換されており、単語登録も有効に働いていることが確認できました。結果の詳細は、音声認識の結果を参照してください。
トラブルシューティング
received illegal service authorization
以下のように表示される場合は、AmiVoice APIのAPPKEYを設定していない可能性があります。
2022-10-11 10:10:44,928 Failed to create job - received illegal service authorization (-)
コード中の以下の箇所にAPPKEYが設定されているかどうか、誤りがないかを確認してください。
app_key = 'TODO: Please set APPKEY here'
このページ中のリクエストパラメータのセクションも参照してください。
No such file or directory: 'www-2.wav'
以下のように表示される場合は、音声ファイルが実行ディレクトリに存在しません。
FileNotFoundError: [Errno 2] No such file or directory: 'www-2.wav'
サンプル音声ファイル(www-2.wav)をダウンロードしてコマンドを実行するディレクトリにコピーしてください。ファイルが存在することを確認して再度実行してください。
その他のドキュメント
- API リファレンスは、非同期 HTTP インタフェースを参照してください。