유닛에서 왓슨과 잡담 API를 활용한 채트봇 만들기

12917 단어 Unity
유닛의 학습으로 왓슨과 잡담 API를 활용해 차트봇을 만들어 봤는데 필기였다.
참조:
Watson×Unity!VR 공간에서 Unity와 채팅하는 앱!
개발자 워크스 보도×Unity!초보자도 할 수 있는 VR 공간에서 유니티소스와 채팅하는 앱!시험해 보다
실시 내용은 상술한 보도와 대체로 같다.
크레덴티알스의 제작 방법은 다소 다르다.환경에 따라 다른 경우도 있다.
또 왓슨의 컨버세이션은 세션 데이터를 만들어야 하기 때문에 이번에 리크루트가 만든 토크 API를 사용했다.
http://a3rt.recruit-tech.co.jp/product/talkAPI/
이 API를 사용하면 대화 메시지를 보내면 잡담 응답에 답장이 오기 때문에 그런 모습의 챗봇을 간단하게 할 수 있다.
프로세스
1. 마이크에서 사운드를 가져와 SpeechToText를 통해 텍스트화
2. 응답 텍스트를 얻기 위해 잡담 API에 텍스트 보내기
3. TextToSpeech를 통해 응답 사운드를 재현
네.
구현 환경
Unity Version 2018.2.15f1 on Mac
IBM Watson SDK for Unity v2.11.0

사전 준비(외부 서비스)

  • SpeechToText 서비스 만들기
  • TextToSpeech 서비스 만들기
  • 이번에 둘 다 Lite 프로젝트로 제작됐는데 제 경우는 SpeechToText의 Credential이 API 키입니다.
    {
      "apikey": "xxxxxxx",
      "iam_apikey_description": "Auto generated apikey during resource-key operation for Instance - xxxxxxxxxxx",
      "iam_apikey_name": "xxxxxxxxxxxxxxxxxxx",
      "iam_role_crn": "xxxxxxxxxxxxxxxx",
      "iam_serviceid_crn": "xxxxxxxxxxxxxxxxxx",
      "url": "https://stream.watsonplatform.net/speech-to-text/api"
    }
    
    Watson 서비스는 물건에 따라 다르거나 환경에 따라 사용자/비밀번호 형식일 때도 있고 API 키일 때도 있다.
    제작 방법의 차이는 각 서비스 제작에 모두 관련되어 있다.
  • API Key
  • A3RT 획득
    사이트 지침에 따라 사용 등록을 하면 API 키를 받을 수 있습니다.

    사전 준비(Unity 자산)

  • Unity-chan! Model 가져오기
  • IBM Watson SDK for Unity
  • 가져오기
    각각 자산상점에서 도입하다.

    Scene에 Unity, 스크립트 할당


    Assets/UnityChan/Prefabs/unitychan
    Hierarchy에 Unity를 떨어뜨려 Scene에 등장시키다.
    Hierrarchy 의 unitychan 을 선택하면 Inspector 에 자세한 매개 변수 표시
    [Add Component]→[New Script]에서 새 스크립트를 만들고 할당합니다.
    자산 프로덕션 센터에 기본적으로 연결되는 Idle Changer와 Face Update 두 스크립트는 이번에 사용하지 않기 때문에 무효화됩니다.
    지금부터 새로 분배된 스크립트에 실크를 쓰기 시작합니다.

    환경 변수 세트


    DeveloperWorks의 보도에 따르면 Watson SDK를 가져온 후 Unity의 Watson 메뉴에는Configuration Editor라고 쓰여 있지만 이 메뉴는 현재 없습니다.
    따라서 Example의 방식대로 인증 정보는 Inspector로 관리됩니다.

    Speech To Text

    #region Watson SpeechToTextの設定
    [Space(10)]
    [Header("Watson SpeechToText Config")]
    [Tooltip("SpeechToText service URL")]
    [SerializeField]
    private string sttServiceUrl = "";
    [Tooltip("The authentication api key.")]
    [SerializeField]
    private string sttApiKey = "";
    #endregion
    
    Inspector에서 작성된 SpeechToText 서비스에 대한 인증 정보를 설정합니다.

    TalkAPI

    #region TalkAPIの設定
    [Space(10)]
    [Header("TalkAPI Config")]
    [Tooltip("A3RT API URL")]
    [SerializeField]
    private string a3rtURL = "";
    [Tooltip("A3RT API Key")]
    [SerializeField]
    private string a3rtApiKey = "";
    #endregion
    

    Text To Speech

    #region Watson TextToSpeechの設定
    [Space(10)]
    [Header("Watson TextToSpeech Config")]
    [Tooltip("TextToSpeech service URL")]
    [SerializeField]
    private string ttsServiceUrl = "";
    [Tooltip("The authentication username.")]
    [SerializeField]
    private string ttsUsername = "";
    [Tooltip("The authentication password.")]
    [SerializeField]
    private string ttsPassword = "";
    #endregion
    

    Speech To Text (STT)


    인증 정보를 설정하면 실현됩니다.
    마이크에서 사운드를 가져와 STT로 재생합니다.
    이번 실크 코드에 관해서는 왓슨SDK에 포함된 Examples를 참고하였다.
    Start 시 서비스 인스턴스를 초기화합니다.
    API 키 내부
    API 키를 통한 토큰→토큰을 통한 서비스 생성
    백그라운드에서 영패를 생성합니다.
    void Start () {
        // 今回は無条件でデフォルトのマイクIDを取得
        microphoneID = Microphone.devices[0];
        ...
        // サービスインスタンスを初期化
        StartCoroutine(SetSttToken());
    }
    
    private IEnumerator SetSttToken(){
        TokenOptions iamTokenOptions = new TokenOptions()
        {
            IamApiKey = sttApiKey
        };
    
        //  Create credentials using the IAM token options
        var credentials = new Credentials(iamTokenOptions, sttServiceUrl);
        while (!credentials.HasIamTokenData())
            yield return null;
    
        sttService = new SpeechToText(credentials)
        {
            StreamMultipart = true
        };
    }
    
    다음은 대화의 촉발을 설정합니다.이번에 키보드로 "s"키를 입력할 때 소리를 얻기 시작한다.
    void Update () {
        if (Input.GetKeyDown("s")) {
            Active = true;
            StartRecording();
        }
        ...
    }
    
    Active는 사운드의 상태를 관리하는 속성입니다.
    RecognizeModel을 일본어용ja-JP_BroadbandModel으로 변경합니다.
    public bool Active
    {
        get { return sttService.IsListening; }
        set
        {
            if (value && !sttService.IsListening)
            {
                // 音声言語モデル
                sttService.RecognizeModel = "ja-JP_BroadbandModel";
                sttService.DetectSilence = true;
                sttService.EnableWordConfidence = true;
                sttService.EnableTimestamps = true;
                sttService.SilenceThreshold = 0.01f;
                sttService.MaxAlternatives = 0;
                sttService.EnableInterimResults = true;
                sttService.OnError = SttOnError;
                sttService.InactivityTimeout = -1;
                sttService.ProfanityFilter = false;
                sttService.SmartFormatting = true;
                sttService.SpeakerLabels = false;
                sttService.WordAlternativesThreshold = null;
                // 音声認識結果を受け取るコールバック
                sttService.StartListening(OnRecognize);
            }
            else if (!value && sttService.IsListening)
            {
                sttService.StopListening();
            }
        }
    }
    
    StartRecording과 IEnumerator Recording Handler를 통해 다른 스레드에서 소리를 얻어 STT로 보냅니다.
    private void StartRecording()
    {
        if (recordingRoutine == 0)
        {
            UnityObjectUtil.StartDestroyQueue();
            recordingRoutine = Runnable.Run(RecordingHandler());
        }
    }
    
    private IEnumerator RecordingHandler()
    {
        Debug.LogFormat("Start recording. devices: {0}", microphoneID);
        recording = Microphone.Start(microphoneID, true, recordingBufferSize, recordingHZ);
        yield return null;
    
        if (recording == null)
        {
            StopRecording();
            yield break;
        }
    
        bool bFirstBlock = true;
        int midPoint = recording.samples / 2;
        float[] samples = null;
    
        while (recordingRoutine != 0 && recording != null)
        {
            int writePos = Microphone.GetPosition(microphoneID);
            if (writePos > recording.samples || !Microphone.IsRecording(microphoneID))
            {
                Debug.LogErrorFormat("Recording Error. Microphone disconnected.");
    
                StopRecording();
                yield break;
            }
    
            if ((bFirstBlock && writePos >= midPoint)
              || (!bFirstBlock && writePos < midPoint))
            {
                samples = new float[midPoint];
                recording.GetData(samples, bFirstBlock ? 0 : midPoint);
    
                AudioData record = new AudioData();
                record.MaxLevel = Mathf.Max(Mathf.Abs(Mathf.Min(samples)), Mathf.Max(samples));
                record.Clip = AudioClip.Create("Recording", midPoint, recording.channels, recordingHZ, false);
                record.Clip.SetData(samples, 0);
    
                sttService.OnListen(record);
    
                bFirstBlock = !bFirstBlock;
            }
            else
            {
                int remaining = bFirstBlock ? (midPoint - writePos) : (recording.samples - writePos);
                float timeRemaining = (float)remaining / (float)recordingHZ;
    
                yield return new WaitForSeconds(timeRemaining);
            }
    
        }
    
        yield break;
    }
    
    음성 인식이 완료되면 On Recognize가 콜백되므로 체크 아웃 결과는 recognizeText 내부 변수에 저장됩니다.
    private void OnRecognize(SpeechRecognitionEvent result, Dictionary<string, object> customData)
    {
        if (result != null && result.results.Length > 0)
        {
            foreach (var res in result.results)
            {
                foreach (var alt in res.alternatives)
                {
                    var reply = alt.transcript;
                    if (res.final)
                    {
                        Debug.Log("[音声]" + reply);
                        Active = false;
                        StopRecording();
                        recognizeText = reply;
                        return;
                    }
                }
            }
        }
    }
    
    STT에서는 음성인식 과정에서도 일정 간격으로 콜백해 중도 식별 결과를 얻는다.
    소리가 일정 시간 중단된 후에 대화가 중단된 것을 판단하고 해석을 확정한다.final 로고가 서 있는 상태에서 되돌아와 이때의 결과를 얻고 소리를 정지하면 완성된다.

    잡담 API


    음성 인식 결과recognizeText를 트리거로 저장하고 API 호출을 실행합니다.
    void Update()
    {
        ...
        if (recognizeText != null)
        {
            var text = recognizeText;
            recognizeText = null;
            StartCoroutine(Chat(text));
        }
    }
    
    일반적인 REST API 요청입니다.
    응답하는 JSON은 MiniJSON을 사용합니다.
    MiniJSON은 이번 WatsonsDK에 포함되어 있습니다.
    internal IEnumerator Chat(string text)
    {
        Debug.Log("Start Chat");
        WWWForm form = new WWWForm();
        form.AddField("apikey", a3rtApiKey);
        form.AddField("query", text);
        var url = a3rtURL;
        var request = UnityWebRequest.Post(url, form);
        yield return request.SendWebRequest();
    
        if (request.isHttpError || request.isNetworkError)
        {
            Debug.LogFormat("chat request failed. {0}", request.error);
        }
        else
        {
            if (request.responseCode == 200)
            {
                var json = Json.Deserialize(request.downloadHandler.text) as Dictionary<string, object>;
                if (json.ContainsKey("results"))
                {
                    var r = json["results"] as List<object>;
                    if (r.Count > 0)
                    {
                        var c = r[0] as Dictionary<string, object>;
                        var res = (string)c["reply"];
                        Debug.Log("[AI]" + res);
                        yield return Speech(res);
                    }
                }
            }
            else
            {
                Debug.LogFormat("chat response failed. response code:{0}", request.responseCode);
            }
        }
    }
    
    여기서 받은 응답을 아래쪽Speech 코르크에 건네주고 소리 재생에 연결한다.

    Text To Speech (TTS)


    STT와 마찬가지로 서비스 인스턴스를 미리 작성합니다.
    이곳은 사용자/비밀번호 형식이기 때문에 크리덴셜 제작은 간단하다.
    void Start () {
        ...
        // サービスインスタンスを初期化
        Credentials ttsCredentials = new Credentials(ttsUsername, ttsPassword, ttsServiceUrl);
        ttsService = new TextToSpeech(ttsCredentials)
        {
            // 音声を日本語に設定
            Voice = VoiceType.ja_JP_Emi
        };
        ...
    }
    
    TalkAPI의 응답을 받은 후 TTS에 텍스트를 전달하고 사운드 데이터로 변환합니다.
    private IEnumerator Speech(string message)
    {
        Debug.Log("Start Speech To Text");
        ttsService.ToSpeech(HandleToSpeechCallback, TtsOnError, message, true);
        while (!synthesizeTested)
            yield return null;
    
    }
    
    HandleToSpeechCallback는 TTS 기반 사운드 변환이 완료되면 콜백이라고 합니다.
    SDK를 통해 Audio Clip으로 돌아가 Audio Source를 만들고 재생합니다.
    void HandleToSpeechCallback(AudioClip clip, Dictionary<string, object> customData = null)
    {
        if (Application.isPlaying && clip != null)
        {
            Debug.Log("Play speech");
            GameObject audioObject = new GameObject("AudioObject");
            AudioSource source = audioObject.AddComponent<AudioSource>();
            source.spatialBlend = 0.0f;
            source.loop = false;
            source.clip = clip;
            source.Play();
            Destroy(audioObject, clip.length);
    
            synthesizeTested = true;
        }
    }
    
    그러면 TalkAPI에서 반환된 텍스트가 사운드로 재생됩니다.

    소감 같은 거.


    이번 샘플 코드는 Giithub에 있습니다.
    https://github.com/kazuhiro1982/unity-talk-sample
    WatsonsDK의 규격은 비교적 빈번하게 변화하고 개발자워크스의 보도는 낡아서 익숙하지 않으면 상당히 고생한다.
    SDK 자체에 포함된 Examples는 가장 믿을 수 있는 느낌이다.
    한담 API로 편하게 대화하는 것도 즐겁다.

    좋은 웹페이지 즐겨찾기