다음을 통해 공유


음성 상호작용을 강화하기: Microsoft Copilot Studio 에이전트와 Azure Communication Services 통합

이 문서에서는 Copilot Studio 에이전트를 만들고 Azure Communication Services와 통합하는 방법에 대한 단계별 지침을 제공합니다. 이 가이드에서는 사용자가 호출할 수 있는 음성 지원 에이전트를 만들 수 있습니다.

샘플 다운로드

GitHub에서 이 샘플에 대한 프로젝트를 찾습니다. 이 코드를 다운로드하고 로컬로 실행하여 직접 사용해 볼 수 있습니다.

필수 조건

시작하기 전에 다음이 있는지 확인합니다.

1. Copilot Studio에서 에이전트 만들기

Copilot Studio에 로그인하거나 등록하면 홈페이지에 표시됩니다. 왼쪽 탐색 창에서 만들기를 선택합니다.

에이전트를 만드는 방법의 스크린샷

만들기 페이지에서 새 에이전트를 선택합니다. 채팅을 사용하고 제공된 질문을 사용하여 에이전트를 설명하십시오.
요청된 정보를 모두 제공하면 만들기를 클릭합니다.

클릭하여 만들기의 스크린샷.

에이전트를 만들고 사용자 지정하는 방법에 대한 자세한 내용은 Copilot Studio 빠른 시작을 참조하세요.

2. 인증 사용 안 함

에이전트를 만든 후에는 Azure Communication Service와 통합할 수 있도록 몇 가지 업데이트를 수행해야 합니다.

  • 설정 탭으로 이동합니다.

설정 탭으로 이동하는 방법의 스크린샷

  • 왼쪽 창에서 보안을 클릭합니다.

보안 탭의 스크린샷.

  • 인증을 선택하고 인증 없음을 선택한 다음 저장을 클릭합니다.

인증 단계의 스크린샷.

3. Webchannel 보안 키 가져오기

보안 섹션으로 다시 이동한 후 웹 채널 보안을 선택합니다. 이 키를 복사하여 어딘가에 저장합니다. 애플리케이션을 배포할 때 필요합니다.

4. 게시 에이전트

이제 에이전트 설정을 업데이트하고 에이전트 키를 저장했으므로 에이전트를 게시할 수 있습니다.

5. 코드 설정

이제 에이전트를 만들었으므로 샘플을 다운로드해야 합니다. 샘플을 다운로드한 후 일부 속성을 업데이트해야 합니다.

  • 연결 문자열: Azure Communication Services 리소스에서 연결 문자열을 가져올 수 있습니다.
  • Microsoft Copilot Studio Direct Line 키: 3단계에서 저장한 당신의 웹 채널 보안 키입니다.
  • Azure AI Services 사용자 지정 엔드포인트: Azure AI Services 리소스에서 이 엔드포인트를 가져올 수 있습니다.
  • Azure Communication Services에서 이벤트 알림을 받으려면 포트를 실행해야 합니다. DevTunnels와 같은 도구를 사용하여 설정할 수 있습니다.

6. 코드 개요

샘플에서 이 워크플로를 빌드하는 데 사용하는 몇 가지 기본 개념을 잘 알고 있어야 합니다.

수신 전화

들어오는 호출 이벤트를 등록하여 애플리케이션이 통화가 들어오는 시기를 알고 응답해야 할 때를 알 수 있도록 합니다.

실시간 대화 내용 기록으로 전화에 답합니다

전화를 받을 때 실시간 음성 변환 스트리밍을 사용하도록 설정하면 발신자가 말하는 콘텐츠를 음성 대화 내용 기록으로 변환하여 거의 실시간으로 전송합니다.

app.MapPost("/api/incomingCall", async (
    [FromBody] EventGridEvent[] eventGridEvents,
    ILogger<Program> logger) =>
{
    foreach (var eventGridEvent in eventGridEvents)
    {
        logger.LogInformation($"Incoming Call event received : {JsonConvert.SerializeObject(eventGridEvent)}");
        // Handle system events
        if (eventGridEvent.TryGetSystemEventData(out object eventData))
        {
            // Handle the subscription validation event.
            if (eventData is SubscriptionValidationEventData subscriptionValidationEventData)
            {
                var responseData = new SubscriptionValidationResponse
                {
                    ValidationResponse = subscriptionValidationEventData.ValidationCode
                };
                return Results.Ok(responseData);
            }
        }
        var jsonObject = JsonNode.Parse(eventGridEvent.Data).AsObject();
        var incomingCallContext = (string)jsonObject["incomingCallContext"];
        var callbackUri = new Uri(baseUri + $"/api/calls/{Guid.NewGuid()}");
        
        var answerCallOptions = new AnswerCallOptions(incomingCallContext, callbackUri)
        {
            CallIntelligenceOptions = new CallIntelligenceOptions()
            {
                CognitiveServicesEndpoint = new Uri(cognitiveServicesEndpoint)
            },
            TranscriptionOptions = new TranscriptionOptions(new Uri($"wss://{baseWssUri}/ws"), "en-US", true, TranscriptionTransport.Websocket)
            {
                EnableIntermediateResults = true
            }
        };

        try
        {
            AnswerCallResult answerCallResult = await client.AnswerCallAsync(answerCallOptions);

            var correlationId = answerCallResult?.CallConnectionProperties.CorrelationId;
            logger.LogInformation($"Correlation Id: {correlationId}");

            if (correlationId != null)
            {
                CallStore[correlationId] = new CallContext()
                {
                    CorrelationId = correlationId
                };
            }
        }
        catch (Exception ex)
        {
            logger.LogError($"Answer call exception : {ex.StackTrace}");
        }
    }
    return Results.Ok();
});

Copilot 연결 설정

호출이 연결되면 애플리케이션은 websocket과 함께 직접 회선 API를 사용하여 빌드한 AI 에이전트에 대한 연결을 설정해야 합니다.

대화 시작

var response = await httpClient.PostAsync("https://directline.botframework.com/v3/directline/conversations", null);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(content);

웹소켓 수신 대기

await webSocket.ConnectAsync(new Uri(streamUrl), cancellationToken);

var buffer = new byte[4096]; // Set the buffer size to 4096 bytes
var messageBuilder = new StringBuilder();

while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
    messageBuilder.Clear(); // Reset buffer for each new message
    WebSocketReceiveResult result;
    do
    {
        result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
        messageBuilder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
    }
    while (!result.EndOfMessage); // Continue until we've received the full message
}

기본 제공된 끼어들기 논리:

애플리케이션은 실시간 전사에서 받은 중간 결과를 사용하여 호출자의 방해를 감지하고 재생 작업을 취소합니다.

if (data.Contains("Intermediate"))
{
    Console.WriteLine("\nCanceling prompt");
    if (callMedia != null)
    {
        await callMedia.CancelAllMediaOperationsAsync();
    }
}
  • AI 에이전트가 응답을 제공하는 경우 애플리케이션은 Play API 를 사용하여 텍스트를 텍스트 음성 변환 서비스 오디오로 변환합니다.
var ssmlPlaySource = new SsmlSource($"{message}");

var playOptions = new PlayToAllOptions(ssmlPlaySource)
{
    OperationContext = "Testing"
};

await callConnectionMedia.PlayToAllAsync(playOptions);
  • 호출자가 담당자를 요청할 때 통화 에스컬레이션: 사용자가 담당자에게 말하도록 요청하면 AI 에이전트가 호출을 인간 에이전트로 전송합니다.
if (botActivity.Type == "handoff")
{
    var transferOptions = new TransferToParticipantOptions(agentPhoneIdentity)
    {
        SourceCallerIdNumber = acsPhoneIdentity
    };

    await Task.Delay(6000);
    await callConnection.TransferCallToParticipantAsync(transferOptions);
}

7. 실행

이제 전화를 걸고 에이전트와 통신할 수 있습니다.

토픽

음성을 최적화하려면 음성 시나리오에 대한 에이전트 응답을 최적화하므로 "메시지" 형식의 Text to Speech를 사용하는 항목을 업데이트하는 것이 좋습니다.

시스템 토픽을 처리하는 방법

에이전트에는 기본적으로 시스템 토픽이 기본 제공됩니다. 이러한 항목을 사용하지 않도록 선택할 수 있지만 계속 사용하려면 애플리케이션에서 이러한 항목을 처리하는 논리를 빌드해야 합니다. 예:

  • 에스컬레이션: 이 부조종사 에이전트에서 사용자 담당자로 호출을 에스컬레이션하려면 애플리케이션으로 에이전트 전송을 빌드해야 합니다.