메인 콘텐츠로 건너뛰기

비동기 HTTP 인터페이스

비동기 HTTP 인터페이스는 장시간의 음성을 텍스트로 변환하기 위한 논블로킹 HTTP API입니다.

이 API를 사용하려면 다음 단계로 실행합니다.

  1. 음성 인식 작업 생성하기
  2. 폴링하여 음성 인식 작업 상태를 확인하고 결과 가져오기

사용 방법

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가 포함되지 않습니다. codemessage로 실패 원인을 판단할 수 있습니다. 응답 코드와 메시지를 참조하십시오.

{
"results": [{ "tokens": [], "tags": [], "rulename": "", "text": "" }],
"text": "",
"code": "-",
"message": "received illegal service authorization"
}

2. 음성 인식 작업 상태 확인 및 결과 가져오기

음성 인식 결과 요청이 성공하면 작업 상태를 확인하고 statuscompleted 또는 error가 될 때까지 폴링하십시오.

작업 상태 가져오기

작업은 서버 측에서 순차적으로 실행됩니다. 작업 상태를 확인하거나 결과를 가져오려면 결과 조회용 엔드포인트 GET /v1/recognitions/{session_id}에 요청합니다.

sessionid에는 작업 생성 시 얻은 작업 ID를 설정합니다. 요청 파라미터의 인증 정보(authorization)는 Authorization 헤더에 지정하십시오.

curl로 실행하는 경우 다음과 같습니다. 여기서는 sessionid017c25ec12c00a304474a999라고 가정합니다.

curl -H "Authorization: Bearer {APPKEY}" \
https://acp-api-async.amivoice.com/v1/recognitions/017c25ec12c00a304474a999

queued 상태

요청을 보낸 직후에는 statusqueued 상태가 됩니다.

{"service_id":"{YOUR_SERVICE_ID}","session_id":"017c25ec12c00a304474a999","status":"queued"}

started 상태

큐에서 작업이 꺼내지면 statusstarted 상태가 됩니다.

{"service_id":"{YOUR_SERVICE_ID}","session_id":"017c25ec12c00a304474a999","status":"started"}

processing 상태

실제로 음성 인식 처리가 시작되면 statusprocessing 상태가 됩니다. 아래 샘플을 참조하십시오. 여기서는 가독성을 위해 결과를 줄바꿈 처리합니다. 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 상태

음성 인식이 완료되면 statuscompleted 상태가 됩니다. 이때 응답의 resultssegments에서 음성 인식 결과를 얻을 수 있습니다. 결과는 음성 인식 서버의 처리가 완료된 후 일정 기간 서버에 저장됩니다. 저장 기간은 비동기 HTTP 인터페이스의 제한의 "음성 인식 결과 보존 기간"을 참조하십시오.

인식 결과를 포함한 응답의 자세한 내용은 음성 인식 결과를 참조하십시오.

노트

일정 기간이 지나 삭제된 결과에 접근하면 404 NOT FOUND 오류가 발생합니다. 오류에 대해서는 레퍼런스의 오류 응답 목록을 참조하십시오.

error 상태

어떤 이유로 음성 인식에 실패한 경우 statuserror 상태가 됩니다. 이때 error_message에는 오류의 원인이 설정됩니다.

예:

{
"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',
...

또한 두 개의 단어를 등록하고 있습니다. 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%EC%9A%94%EC%BD%94%EB%8B%A4%EB%B6%80%EB%A3%A8%7Cwww2%20%ED%8A%B8%EB%A6%AC%ED%94%8C%EB%8D%94%EB%B8%94 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}에 요청합니다. 응답에 포함된 statuscompleted 또는 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 코드 전체를 보여드립니다.

async-http-sample.py
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)을 다운로드하여 명령을 실행할 디렉토리에 복사하십시오. 파일이 존재하는지 확인한 후 다시 실행하십시오.

기타 문서