> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Bot wywołań Graph

## Omówienie

To podejście sprawia, że agent staje się **tożsamością Teams, do której można zadzwonić**. Użytkownik wyszukuje go po nazwie i dzwoni do niego 1:1, a agent odpowiada w czasie rzeczywistym — bez numeru telefonu, PSTN ani Communications Credits. To jedyne podejście, które pozwala dzwonić po nazwie, i najbardziej złożone we wdrożeniu.

Korzysta z **bota mediów w czasie rzeczywistym Microsoft Graph** (platformy połączeń Cloud Communications). SDK mediów (`Microsoft.Skype.Bots.Media`) działa **tylko z .NET na Windows Server** — nie ma ścieżki dla Linuksa ani innych środowisk niż .NET do obsługi surowego audio w połączeniach Teams.

> **Note**
>
> To jedyne podejście, do którego można dzwonić **po nazwie** w Teams. Jeśli chcesz prostszej
> konfiguracji, wybierz [kartę z widgetem ](/docs/pl/eleven-agents/phone-numbers/microsoft-teams/widget-tab), a gdy potrzebujesz konkretnie
> numeru telefonu — [ACS](/docs/pl/eleven-agents/phone-numbers/microsoft-teams/azure-communication-services).

## Jak to działa

![Użytkownik Teams dzwoni do bota po nazwie; Teams przekazuje połączenie do bota mediów na maszynie wirtualnej z systemem Windows, który przez WebSocket przesyła surowe audio PCM 16k do agenta ElevenLabs](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/f411ec0b44d4c5ca1a61598fbd1e6162e46a1c74179eebb4b673ee1428823e84/assets/images/conversational-ai/teams-media-bot-architecture.svg)

Bot odbiera połączenia przez **media hostowane przez aplikację**, odbiera 50 ramek audio na sekundę (PCM 16 kHz co 20 ms), przekazuje je do agenta ElevenLabs przez WebSocket i przesyła audio agenta z powrotem do rozmowy.

## Wymagania

1. Rejestracja **Azure Bot** + aplikacja (rejestracja aplikacji Entra).
2. **Uprawnienia aplikacji Graph** ze zgodą administratora: `Calls.AccessMedia.All` (surowe media) oraz `Calls.Initiate.All`.
3. **Maszyna wirtualna Windows Server** (≥ **2 fizyczne rdzenie** — np. `Standard_D4s_v3`) z publicznym IP i otwartymi portami mediów.
4. **Certyfikat TLS podpisany przez CA** na publicznym FQDN dla punktu końcowego mediów/sygnalizacji (platforma mediów odrzuca certyfikaty z podpisem własnym).
5. [Agent ElevenLabs](/docs/pl/eleven-agents/quickstart) ustawiony na **PCM 16000 Hz** po obu stronach: format wyjściowy TTS na karcie **Voice** oraz format audio wejściowego użytkownika na karcie **Advanced**.

> **Warning**
>
> `D2s_v3` (2 vCPU = **1 fizyczny rdzeń**) kończy się błędem `MediaPlatform needs a system with at least 2
>   cores`. Użyj rozmiaru z ≥ 2 fizycznymi rdzeniami (np. `D4s_v3`).

## Uprawnienia i role

| Zakres                      | Rola / uprawnienie                                                                                                                                    | Dlaczego                                                                                                                       |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Entra                       | **Application Administrator**                                                                                                                         | tworzenie rejestracji aplikacji + Azure Bot                                                                                    |
| Entra                       | **Global Administrator / Privileged Role Administrator**                                                                                              | udzielenie zgody administratora na uprawnienia połączeń Graph — zgody na uprawnienia aplikacji nie można udzielić samodzielnie |
| Microsoft Graph (aplikacja) | `Calls.AccessMedia.All`, `Calls.Initiate.All`                                                                                                         | odbieranie połączeń 1:1 i dostęp do surowych mediów                                                                            |
| Azure RBAC                  | **Contributor** on the resource group                                                                                                                 | tworzenie maszyny wirtualnej Windows + Azure Bot                                                                               |
| Administrator Teams         | zezwolenie na [**przesyłanie aplikacji niestandardowych**](https://admin.teams.microsoft.com/policies/manage-apps); włączenie kanału bota **Calling** | ładowanie aplikacji lokalnie i odbieranie połączeń                                                                             |

## Krok 1 — Zarejestruj bota i uprawnienia Graph

Utwórz rejestrację aplikacji oraz powiązanego z nią Azure Bota, a następnie nadaj uprawnienia do połączeń i udziel zgody (aby udzielić zgody, potrzebujesz roli Global Admin / Privileged Role Admin):

```bash
APPID=$(az ad app create --display-name "ElevenLabs Teams Agent" \
  --sign-in-audience AzureADMyOrg --query appId -o tsv)
az ad sp create --id "$APPID"
# create a client secret and record it
az ad app credential reset --id "$APPID" --display-name bot --query password -o tsv

# Azure Bot bound to the app
az bot create --resource-group $RG --name el-teams-agent-bot \
  --app-type SingleTenant --appid "$APPID" --tenant-id $TENANT \
  --endpoint "https://YOUR_FQDN/api/messages" --sku S1
```

Nadaj dwie role aplikacji Graph i **zgodę administratora** (wymaga Global Admin / Privileged Role Admin), a potem potwierdź, że przypisania zostały dodane:

```bash
# Graph app roles: Calls.AccessMedia.All, Calls.Initiate.All
az ad app permission add --id "$APPID" --api 00000003-0000-0000-c000-000000000000 \
  --api-permissions a7a681dc-756e-4909-b988-f160edc6655f=Role \
    284383ee-7f6e-4e40-a2a8-e85dcb029101=Role
az ad app permission admin-consent --id "$APPID"

# Verify — should print both role ids
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$APPID')/appRoleAssignments" \
  --query "value[].appRoleId" -o tsv
```

> **Note**
>
> Jeśli `admin-consent` zwróci `Consent validation failed`, nadaj role aplikacji bezpośrednio w jednostce usługi:
>
> ```bash
> GRAPH_SP=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv)
> BOT_SP=$(az ad sp show --id "$APPID" --query id -o tsv)
> for ROLE in a7a681dc-756e-4909-b988-f160edc6655f 284383ee-7f6e-4e40-a2a8-e85dcb029101; do
>   az rest --method POST \
>     --url "https://graph.microsoft.com/v1.0/servicePrincipals/$GRAPH_SP/appRoleAssignedTo" \
>     --body "{\"principalId\": \"$BOT_SP\", \"resourceId\": \"$GRAPH_SP\", \"appRoleId\": \"$ROLE\"}"
> done
> ```

W portalu sprawdź w [centrum administracyjnym Entra](https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/RegisteredAppsListBlade), w sekcji **App registrations → twoja aplikacja → API permissions**: oba uprawnienia powinny mieć status **Granted** z zielonymi znacznikami.

![Panel API permissions rejestracji aplikacji z przyznanymi uprawnieniami Calls.AccessMedia.All i Calls.Initiate.All
](/docs/_fern-img/4abc1e220d3fc03845594aa4f835656425cc1c39448dae30dace98e672fd8fb1.webp)

## Krok 2 — Przygotuj maszynę wirtualną Windows, certyfikat i porty

```bash
az vm create -g $RG -n teams-media-bot --image Win2022Datacenter \
  --size Standard_D4s_v3 --admin-username azureuser --admin-password '<strong-pw>' \
  --public-ip-sku Standard --public-ip-address-dns-name elevenmediabot
az vm open-port -g $RG -n teams-media-bot --port 80,443,8445,9441 --priority 300
```

Na maszynie wirtualnej (kod natywny platformy mediów ich wymaga — Windows Server domyślnie ich nie ma):

```powershell
# VC++ runtime + Media Foundation feature (required by NativeMedia.dll)
choco install -y vcredist140
Install-WindowsFeature Server-Media-Foundation

# CA cert for the VM's FQDN via win-acme (HTTP-01), then import to LocalMachine\My
& wacs.exe --target manual --host <vm-fqdn>.cloudapp.azure.com `
  --validation selfhosting --store pfxfile --pfxfilepath C:\bot\certs --accepttos
```

Otwórz te same porty w **Zaporze systemu Windows** i zanotuj **odcisk palca** certyfikatu — bot wiąże z nim Kestrel (443 + port powiadomień) oraz platformę mediów (8445).

> **Tip**
>
> Własny FQDN `*.cloudapp.azure.com` maszyny wirtualnej działa z certyfikatem Let's Encrypt — nie
> potrzebujesz osobnej domeny.

## Krok 3 — Zbuduj i uruchom bota

Zacznij od **PublicSamples/EchoBot** z [`microsoft-graph-comms-samples`](https://github.com/microsoftgraph/microsoft-graph-comms-samples) Microsoftu — jest przeznaczony dla `net6.0` i buduje się za pomocą SDK .NET (bez Visual Studio Build Tools):

```powershell
git clone --depth 1 https://github.com/microsoftgraph/microsoft-graph-comms-samples.git C:\bot\samples
cd C:\bot\samples\Samples\PublicSamples\EchoBot\src
dotnet build EchoBot.sln -c Release
```

Skonfiguruj sekcję `AppSettings` w pliku `appsettings.json`, podając `AadAppId`, `AadAppSecret`, `ServiceDnsName`/`MediaDnsName` (FQDN maszyny wirtualnej), `CertificateThumbprint` oraz porty (połączenia `443`, powiadomienia `9441`, media `8445`). Dodaj dwie poniższe opcje mostu ElevenLabs: `ElevenLabsAgentId` i `ElevenLabsOrigin` (`wss://api.elevenlabs.io` lub host rezydencji danych). Uruchom go jako zaplanowane zadanie/usługę Windows, aby przetrwał restarty.

> **Warning**
>
> Domyślny **limit czasu wykonania (72 godziny)** w Harmonogramie zadań po cichu zatrzymuje długo działające zadania — bot uruchomiony przy starcie przestaje działać po trzech dniach, a połączenia kończą się komunikatem „nie mogliśmy cię połączyć”. Wyłącz limit i dodaj ponowne uruchamianie po błędzie:
>
> ```powershell
> $s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
>   -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable
> Set-ScheduledTask -TaskName EchoBot -Settings $s
> ```

> **Warning**
>
> Standardowy EchoBot ulega awarii przy połączeniu na standardowy port 443: `HttpHelpers.SetAbsoluteUri`
> wywołuje `req.Host.Port.Value`, które ma wartość null, gdy nagłówek Host nie zawiera jawnego portu. Zmień to na
> `req.Host.Port ?? (req.IsHttps ? 443 : 80)`.

### Zamień echo na ElevenLabs

Warstwa audio EchoBot jest prosta: `SpeechService.AppendAudioBuffer(in)` i zdarzenie `OnSendMediaBufferEventArgs(out)`. Zastąp jego część Azure Speech mostem WebSocket do agenta ElevenLabs, zachowując ten sam interfejs:

**`SpeechService.cs — most ElevenLabs (rdzeń)`**

```csharp title="SpeechService.cs — most ElevenLabs (rdzeń)"
public class SpeechService
{
    private readonly AppSettings _settings;
    private readonly ILogger _logger;
    private ClientWebSocket _ws;
    private bool _started;
    private bool _connecting;

    public event EventHandler<MediaStreamEventArgs> SendMediaBuffer; // agent audio -> call
    public event EventHandler FlushMedia;                            // barge-in: drop queued audio

    public SpeechService(AppSettings settings, ILogger logger) { _settings = settings; _logger = logger; }

    // Caller audio -> ElevenLabs
    public async Task AppendAudioBuffer(AudioMediaBuffer buffer)
    {
        if (!_started)
        {
            if (_connecting) return;            // a connect attempt is already in flight
            _connecting = true;
            try { await Connect(); _started = true; }
            catch (Exception ex) { _logger.Error(ex, "ElevenLabs connect failed; retry on next frame"); return; }
            finally { _connecting = false; }
        }
        if (_ws?.State != WebSocketState.Open || buffer.Length <= 0) return;
        var pcm = new byte[buffer.Length];
        Marshal.Copy(buffer.Data, pcm, 0, (int)buffer.Length);
        var msg = JsonSerializer.Serialize(new { user_audio_chunk = Convert.ToBase64String(pcm) });
        await _ws.SendAsync(Encoding.UTF8.GetBytes(msg), WebSocketMessageType.Text, true, default);
    }

    private async Task Connect()
    {
        _ws = new ClientWebSocket();
        // ElevenLabsOrigin: wss://api.elevenlabs.io, or a residency host (.eu./.in./.sg.)
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await _ws.ConnectAsync(new Uri(url), default);
        await _ws.SendAsync(Encoding.UTF8.GetBytes(
            JsonSerializer.Serialize(new { type = "conversation_initiation_client_data" })),
            WebSocketMessageType.Text, true, default);
        _ = Task.Run(ReceiveLoop);
    }

    private async Task ReceiveLoop()
    {
        var buf = new byte[32768]; var sb = new StringBuilder();
        while (_ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await _ws.ReceiveAsync(buf, default); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            var type = doc.RootElement.GetProperty("type").GetString();
            if (type == "audio")            // ElevenLabs audio -> call
                Emit(Convert.FromBase64String(doc.RootElement
                    .GetProperty("audio_event").GetProperty("audio_base_64").GetString()));
            else if (type == "ping")
                await _ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new {
                    type = "pong", event_id = doc.RootElement.GetProperty("ping_event").GetProperty("event_id").GetInt32() })),
                    WebSocketMessageType.Text, true, default);
            else if (type == "interruption")  // barge-in: drop any agent audio still queued
                FlushMedia?.Invoke(this, EventArgs.Empty);
        }
    }

    // slice PCM into 20 ms / 640-byte frames the media platform expects
    private void Emit(byte[] pcm)
    {
        var all = new List<AudioMediaBuffer>(); long tick = DateTime.Now.Ticks;
        for (int off = 0; off < pcm.Length; off += 640)
        {
            var frame = new byte[640];
            Array.Copy(pcm, off, frame, 0, Math.Min(640, pcm.Length - off));
            all.AddRange(Utilities.CreateAudioMediaBuffers(frame, tick, _logger));
            tick += 20 * 10000;
        }
        if (all.Count > 0) SendMediaBuffer?.Invoke(this, new MediaStreamEventArgs { AudioMediaBuffers = all });
    }
}
```

Obie strony używają monofonicznego PCM 16 kHz, więc wystarczy przekazywanie base64 — ustaw dla agenta `pcm_16000`. Gdy ElevenLabs wyśle `interruption` (przerwanie przez rozmówcę), most wywołuje `FlushMedia`; podłącz je do strumienia mediów, aby odrzucał wszystkie zakolejkowane `AudioMediaBuffer`, inaczej agent będzie mówił dalej równocześnie z rozmówcą. Pełny opis komunikatów znajdziesz w [dokumentacji WebSocket](/docs/pl/eleven-agents/libraries/web-sockets). Rozłączanie po zakończeniu rozmowy i ciepłe przekierowanie opisano w sekcjach poniżej.

> **Note**
>
> URL w `Connect()` łączy się z **publicznym** agentem. W przypadku prywatnego agenta poproś po stronie
> serwera o krótkotrwały podpisany URL — `GET /v1/convai/conversation/get-signed-url?agent_id=...` z kluczem API —
> i połącz się z otrzymanym URL. W przypadku [rezydencji danych](/docs/pl/overview/administration/data-residency) ustaw `ElevenLabsOrigin` na host rezydencji
> (`wss://api.eu.residency.elevenlabs.io`, `.in.` lub `.sg.`) — żądania podpisanego URL używają
> odpowiadającego hosta `https://`.

## Krok 4 — Umożliwiaj połączenia w Teams

1. **Włącz Calling** w kanale Teams Azure Bota i ustaw **webhook połączeń** na `https://YOUR_FQDN/api/calling`:

   ```bash
   az bot msteams create -g $RG -n el-teams-agent-bot \
     --enable-calling --calling-web-hook "https://YOUR_FQDN/api/calling"
   ```

   W portalu znajdziesz to w zasobie Azure Bot → **Channels** → **Microsoft Teams** → karta **Calling**:

   ![Panel Azure Bot Channels z kanałem Microsoft Teams oznaczonym jako
   działający](/docs/_fern-img/01115d26ab645dd6ec6569a36a918cd4de0214c79dcb0070d7a3c3a5e544c93b.webp)

   ![Karta Calling kanału Teams z zaznaczoną opcją Enable calling i ustawionym webhookiem
   połączeń](/docs/_fern-img/7c4819f64878bfb152661adc948c1170f9efd48ba3d68401c4a4d4ba075951b0.webp)

2. Utwórz **manifest aplikacji Teams** z `bots[0].supportsCalling: true` i identyfikatorem aplikacji bota, a potem załaduj go lokalnie (**Apps → Manage your apps → Upload a custom app**) lub opublikuj dla całej organizacji bez interfejsu: `New-TeamsApp -DistributionMethod organization -Path ./bot-app.zip` (moduł PowerShell MicrosoftTeams).

Wyszukaj aplikację po nazwie w Teams i zadzwoń do niej — bot odbierze, a agent ElevenLabs zacznie mówić.

![Aktywne połączenie Teams z botem agenta
ElevenLabs](/docs/_fern-img/0f1fad08a091bcd20ffbbf5c3949b5503f423d8eb6298f464a870b3a0a254726.webp)

> **Note**
>
> Do połączenia 1:1 po nazwie nie potrzebujesz numeru telefonu ani konta zasobu — są one wymagane
> tylko do połączeń PSTN. `Calls.AccessMedia.All` umożliwia most surowego audio.

## Czat tekstowy (ten sam bot)

Ten sam Azure Bot może też odpowiadać na **wiadomości tekstowe** w Teams — użytkownicy mogą więc dzwonić do agenta lub z nim pisać. Połączenia i wiadomości to niezależne kanały bota: webhook połączeń obsługuje głos, a **punkt końcowy wiadomości** Bot Framework (`/api/messages`) obsługuje czat.

![Czat w Teams z botem agenta ElevenLabs odpowiadającym na wiadomości
tekstowe](/docs/_fern-img/fb75ae17848916790d29c1c0a703051946902893e46ed62884742db36be453b2.webp)

Skieruj punkt końcowy wiadomości bota na hosta, który go obsługuje (bot mediów lub dowolna inna usługa — nie musi to być maszyna wirtualna Windows):

```bash
az bot update -g $RG -n el-teams-agent-bot --endpoint "https://YOUR_FQDN/api/messages"
```

Zaimplementuj punkt końcowy za pomocą SDK Bot Framework i przekaż każdą wiadomość do agenta w **trybie tekstowym** przez ten sam WebSocket rozmowy, który służy do głosu — wyślij zdarzenie [`user_message`](/docs/pl/eleven-agents/customization/events/client-to-server-events), a odczytaj zdarzenie [`agent_response`](/docs/pl/eleven-agents/customization/events/client-events). Najpierw włącz pole **pierwszej wiadomości** w ustawieniach [nadpisań](/docs/pl/eleven-agents/customization/personalization/overrides) agenta — poniższy kod nadpisuje je pustą wartością, aby odpowiedź była odpowiedzią na wiadomość użytkownika, a nie powitaniem agenta:

**`ChatBot.cs — Teams text chat -> ElevenLabs (text mode)`**

```csharp title="ChatBot.cs — Teams text chat -> ElevenLabs (text mode)"
public class ChatBot : ActivityHandler
{
    private readonly AppSettings _settings;
    public ChatBot(AppSettings settings) => _settings = settings;

    protected override async Task OnMessageActivityAsync(
        ITurnContext<IMessageActivity> turn, CancellationToken ct)
    {
        var reply = await AskAgent(turn.Activity.Text, ct);
        await turn.SendActivityAsync(MessageFactory.Text(reply), ct);
    }

    private async Task<string> AskAgent(string text, CancellationToken ct)
    {
        using var ws = new ClientWebSocket();
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await ws.ConnectAsync(new Uri(url), ct);
        // Suppress the agent's greeting: with no override, the first agent_response is the
        // configured first message, not the answer to this user_message.
        await Send(ws, new
        {
            type = "conversation_initiation_client_data",
            conversation_config_override = new { agent = new { first_message = "" } },
        }, ct);
        await Send(ws, new { type = "user_message", text }, ct);

        var buf = new byte[16384]; var sb = new StringBuilder();
        while (ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await ws.ReceiveAsync(buf, ct); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            switch (doc.RootElement.GetProperty("type").GetString())
            {
                case "agent_response":
                    return doc.RootElement.GetProperty("agent_response_event")
                              .GetProperty("agent_response").GetString();
                case "ping":
                    await Send(ws, new { type = "pong", event_id = doc.RootElement
                        .GetProperty("ping_event").GetProperty("event_id").GetInt32() }, ct);
                    break;
            }
        }
        return "Sorry, I couldn't reach the agent.";
    }

    private static Task Send(ClientWebSocket ws, object msg, CancellationToken ct) =>
        ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg)),
            WebSocketMessageType.Text, true, ct);
}
```

Zarejestruj go standardowo (`CloudAdapter`, bota przez `AddTransient<IBot, ChatBot>()` oraz kontroler `/api/messages`) i dodaj zakresy czatu do wpisu bota w manifeście:

```json
"bots": [
  { "botId": "YOUR_APP_ID", "supportsCalling": true, "scopes": ["personal", "team", "groupChat"] }
]
```

> **Note**
>
> Fragment kodu otwiera nową rozmowę dla każdej wiadomości, więc każda tura jest niezależna. Aby zachować
> **pamięć** czatu, utrzymuj jeden WebSocket otwarty dla każdego `conversation.id` Teams (używaj go ponownie między turami) i zamykaj
> nieaktywne sesje — agent będzie wtedy pamiętać wcześniejsze wiadomości na tym czacie. Nadpisanie `first_message`
> musi być włączone w ustawieniach
> [nadpisań](/docs/pl/eleven-agents/customization/personalization/overrides) agenta — serwer
> **zamyka rozmowę**, jeśli zostanie wysłane niedozwolone nadpisanie. Jeśli nie możesz go włączyć, pomiń
> nadpisanie i zamiast tego odrzuć pierwsze `agent_response` każdej sesji (powitanie), a zwróć
> kolejne.

> **Tip**
>
> Jeśli odpowiedzi na czacie nigdy nie przychodzą, włącz zdarzenie klienta **`agent_response`**
> [client event](/docs/pl/eleven-agents/customization/events/client-events) w ustawieniach **Zaawansowanych**
> agenta — odpowiedzi tekstowe są dostarczane przez to zdarzenie.

## Koniec połączenia

Gdy ElevenLabs kończy rozmowę (narzędzie **End Call** zamyka WebSocket), rozłącz połączenie Teams:

```csharp
await this.Call.DeleteAsync(); // after a short delay so the goodbye audio finishes
```

## Ciepłe przekazanie do człowieka

Agent uruchamia niestandardowe narzędzie klienta `transfer_to_human`; bot **zaprasza użytkownika Teams** do trwającego połączenia (dodanie konsultacyjne), a potem się wycofuje:

```csharp
var target = new IdentitySet { User = new Identity { Id = humanObjectId } };
await this.Call.Participants.InviteAsync(target, replacesCallId: null);
// suppress the end-call hangup while transferring, and mute the bot
```

> **Tip**
>
> Przekazanie konsultacyjne (`replacesCallId`) wymaga, aby obie strony były użytkownikami Teams w \*\*tej
> samej dzierżawie \*\*; cele przekazania PSTN wymagają instancji aplikacji. Aby najpierw przekazać informacje człowiekowi, przekaż
> parametr `reason` od agenta i odtwórz go człowiekowi przed połączeniem rozmów.

## Rozwiązywanie problemów

#### \`MediaPlatform needs a system with at least 2 cores\`

Maszyna wirtualna ma tylko jeden fizyczny rdzeń. Zmień rozmiar na ≥ 2 fizyczne rdzenie (np. `D4s_v3`) i uruchom ją ponownie.

#### \`Unable to load DLL 'NativeMedia'\`

Zainstaluj **VC++ Redistributable** (`vcredist140`) oraz funkcję Windows **Server-Media-Foundation**,
a potem uruchom bota ponownie.

#### Połączenie przychodzące zwraca 500 / połączenie nie chce się zestawić

Błąd EchoBot z pustym portem na 443 — popraw `HttpHelpers.SetAbsoluteUri` (zobacz krok 3). Sprawdź też,
czy certyfikat jest podpisany przez CA i dostępny na porcie 443.

#### Przy próbie połączenia z botem pojawia się komunikat „nie udało się połączyć”

Sprawdź, czy Calling jest włączone w kanale Teams z poprawnym webhookiem `/api/calling`, czy zgoda na uprawnienie Graph
`Calls.AccessMedia.All` została udzielona oraz czy porty 443/8445/9441 są otwarte zarówno w NSG, jak i w zaporze Windows. Jeśli połączenia
**wcześniej działały, a przestały**, sprawdź, czy proces bota nadal działa na maszynie wirtualnej — domyślny 72-godzinny limit wykonania w Harmonogramie zadań
kończy go kilka dni po uruchomieniu systemu (zobacz ostrzeżenie w kroku 3).

## Przydatne linki

* [Omówienie botów do połączeń i spotkań](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/calls-meetings-bots-overview)
* [Koncepcje mediów w czasie rzeczywistym](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/real-time-media-concepts)
* [Rejestracja bota do połączeń](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/registering-calling-bot)
* [microsoft-graph-comms-samples (EchoBot)](https://github.com/microsoftgraph/microsoft-graph-comms-samples)
* [call: transfer (konsultacyjne)](https://learn.microsoft.com/en-us/graph/api/call-transfer)
* [Protokół WebSocket agenta](/docs/pl/eleven-agents/libraries/web-sockets)