Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

텍스트를 입력하면 음성으로 변환하고~ AI의 LLM응답도 음성으로 변환해 대화를 이어가는 심플한 음성채팅을 OPENAPI를이용해변환해 출력하고~ LLM AI응답도 음성으로 출력해 AI와 음성대화를 하는 심플음성채팅을 OPENAPI를 이용해 작동되는 데모로

Blazor의 특성과 액터모델의 장점을 이용해 더 강력한 커스텀화된 RealTime AI기능을 만들수 있습니다Blazor.net + Actor모델의 조합으로 비교적 간단한 LLM응답형 음성채팅기능을 만들어보겠습니다.

DEMO

  • 진행되는 대화내용은 텍스트로 표시되며 동시에 음성으로 출력됩니다. ( 음성을 내기 힘든 상황에서의 인풋형 음성채팅 )
  • TTS및 컴플리트 LLM기능만 이용하지만 음성간 응답흐름을 최대한 짧게응답해 리얼타임인것처럼
  • 세션및 리얼타임 LLM을 이용하지 않고~ 문맥과 응답을 이어갈수 있는 개인별 상태를 유지하는 액터베이스로 설계

...

  • BlazorPage는 서버 코드의 상태값 변경으로 프론트의 페이지를 부분 갱신할수 있는 장점이 있습니다. 
    • 이러한 방식은 서버렌더링에서는 불가능하며 WS를 이용한 InteractiveServer 방식입니다.
  • JSInvokable 를 통해 JS가 호출할수 있는 원격함수를 만들수도 있으며, InvokeVoidAsync를 통해 프론트의 js함수를 호출할수 있습니다 ( 양방향 호출가능)
  • WebRTC이 제대로 활용되지 않았으나~ webrtc로 부터 발생한 데이터도 서버로 분석되어 작동됩니다.
    • WebRTC에 발생된 음성 오디오 데이터와 볼륨데이터가 전송되어 음성채팅데이터 서버에서 활용가능

VoiceChatActor

Code Block
themeEmacs
public class VoiceChatActor : ReceiveActor, IWithTimers
{    
    private List<String> _conversationHistory = new();

    private string lastAiMessage = string.Empty;

    private Action<string, object[]> _blazorCallback;

    private OpenAIService _openAIService;

    private int MaxAIWordCount = 100; // AI 응답 최대 단어 수 설정

    private sealed class TimerKey
    {
        public static readonly TimerKey Instance = new();
        private TimerKey() { }
    }

    public int RefreshTimeSecForContentAutoUpdate { get; set; } = 30;

    public ITimerScheduler Timers { get; set; } = null!;

    public VoiceChatActor(IServiceProvider serviceProvider)
    {
        logger.Info($"VoiceChatActor : Constructor - {Self.Path}");

        _openAIService = new OpenAIService();

        // 액터별 반복스케줄러 기능을 가져~ 응답이 아닌 능동형기능에 이용될수 있습니다.
        Timers.StartPeriodicTimer(
            key: TimerKey.Instance,
            msg: new ContentAutoUpdateCommand(),
            initialDelay: TimeSpan.FromSeconds(10),
            interval: TimeSpan.FromSeconds(RefreshTimeSecForContentAutoUpdate));

        Receive<ContentAutoUpdateCommand>( command =>
        {
            logger.Info("VoiceChatActor : ContentAutoUpdateCommand");                
        });

        Receive<TTSCommand>( command =>
        {
            logger.Info($"VoiceChatActor : Received Command - {command.GetType().Name}");
            switch (command.From)
            {
                case "Your":
                {
                    int playType = 1;
                    _ = Task.Run(() => GetChatCompletion(command.Text));
                    var recVoice = _openAIService.ConvertTextToVoiceAsync(command.Text, command.Voice).Result;
                    _blazorCallback?.Invoke("AddMessage", new object[] { command.From, command.Text });
                    _blazorCallback?.Invoke("PlayAudioBytes", new object[] { recVoice, 0.5f, playType });
                }                        
                break;
                case "AI":
                {
                    var msg = lastAiMessage;
                    int playType = 2;
                    var recVoice = _openAIService.ConvertTextToVoiceAsync(msg, command.Voice).Result;
                    _blazorCallback?.Invoke("AddMessage", new object[] { command.From, msg });
                    _blazorCallback?.Invoke("PlayAudioBytes", new object[] { recVoice, 0.5f, playType });
                }
                break;
                default:
                    logger.Warning($"Unknown command received: {command.From}");
                break;
            }
        });
    }

    /// <summary>
    /// 주어진 메시지에 대한 ChatCompletion을 생성합니다.
    /// </summary>
    /// <param name="message">보낼 메시지</param>
    /// <returns>ChatCompletion 결과</returns>
    public async Task<string> GetChatCompletion(string message)
    {
        _conversationHistory.Add($"User:{message}");

        // 최근 20개의 대화 기록을 가져옵니다.
        var recentHistory = _conversationHistory.Skip(Math.Max(0, _conversationHistory.Count - 20)).ToList();

        // 수정된 코드: ChatMessage 생성 시 올바른 정적 메서드 사용
        var aiResponse = await _openAIService.GetChatCompletion(

            $"요청메시지는 : {message} 이며 첨부메시지는 현재 대화내용의 히스토리이며 이 맥락을 유지하면서 답변, 답변은 {MaxAIWordCount}자미만으로 줄여서 답변을 항상해~ AI는 너가답변한것이니 언급없이 너인것처럼하면됨",
            recentHistory
        );            

        _conversationHistory.Add($"AI:{aiResponse}");
        lastAiMessage = aiResponse;

        return aiResponse;
    }
}
  • 음성채팅 기능의 이벤트를 처리하며 OpenAI API를 이용하고~ blazor에게 완료를 수행해 UI업데이트및 최종 프론트에게 재생 스트림을 전달해 재생시킬수도 있습니다.
  • 비교적 간단한 LLM ChatCompletion이 이용되었으며 이 부분을 개선해 더 스마트한 음성챗봇을 만들수도 있습니다.
  • 사용자로부터 Input Text를 받는형태이지만 필요하면 음성 입력스트림을 바로 받아 처리할수도 있습니다.


작동가능 소스및 더 자세한 문서는 다음 깃헙을 통해 확인할수 있습니다.