Page History
텍스트를 입력하면 음성으로 변환하고~ 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 | ||
|---|---|---|
| ||
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를 받는형태이지만 필요하면 음성 입력스트림을 바로 받아 처리할수도 있습니다.
작동가능 소스및 더 자세한 문서는 다음 깃헙을 통해 확인할수 있습니다.
- https://github.com/psmon/blazor-voice/blob/main/doc/kr/index.md
- 준비된 OPENAI_API_KEY 를 env에 주입하면 실행가능합니다.
