This project is based in part on the Microsoft Graph Communications EchoBot sample.
The original sample is licensed under the MIT License. See the LICENSE file for details.
Note:
Public Samples are provided by developers from the Microsoft Graph community.
Public Samples are not official Microsoft Communication samples, and not supported by the Microsoft Communication engineering team. It is recommended that you contact the sample owner before using code from Public Samples in production systems.
Description: This application receives Teams meeting audio through the media socket. In Echo mode it sends the received audio back for compatibility with the upstream sample. In Speech Service mode it continuously transcribes the audio with Azure AI Speech and forwards transcript segments to the DeciScope Go API. It does not synthesize or send text-to-speech audio. The repository also contains the upstream VMSS deployment pipelines. Authors: @bcage29 and @brwilkinson
- Introduction
- Getting Started
- Bot Registration
- Prerequisites
- Deploy
- Running the Sample
- Video Walkthrough of Deployment Process
This project adapts the Teams Voice Echo Bot sample for DeciScope. It demonstrates how to receive the audio stream from a Teams call or meeting and includes scripts and pipelines for running the Bot on Azure VMSS.
After the Bot joins a meeting, Echo mode sends received audio back to the meeting. Speech Service mode instead performs continuous speech-to-text recognition and forwards the resulting transcript segments to the DeciScope Go API. Refer to the supported languages in the Speech Service documentation.
This is the default mode when deployed (UseSpeechService == false). In this mode, the bot will listen to the inbound audio stream and will send the same stream of data back on the Audio Socket. This will create an echo and you will hear yourself repeated.
In this mode, the Bot continuously sends meeting audio to Azure AI Speech for speech-to-text recognition. Recognized partial and final transcript segments are queued and forwarded to the configured DeciScope Go API endpoint. There is no keyword trigger, text-to-speech synthesis, or synthesized audio response.
To use Speech Service mode, set the following environment variables:
- Clone the Git repo for the Microsoft Graph Calling API Samples. Please see the instructions here to get started with VSTS Git.
- Log in to your Azure subscription to host web sites and bot services.
- Launch Visual Studio Code or open a terminal to the root folder of the sample.
- Fork the repo or clone it and then push it to your own repo in GitHub.
The Bot requires an SSL certificate signed by a Certificate Authority. If you don't have a certificate for your domain, you can create a free SSL certificate.
- Verify you have access to make DNS changes to your domain or buy a new domain.
- Install certbot
- a. Follow the installation instructions
- b. If 'certbot' command is not recognized in the terminal, add the path to the certbot.exe to the environment variables path ($env:Path)
- Open a terminal as an Adminstrator where certbot is loaded
- Execute
certbot certonly --manual --preferred-challenges=dns -d '*.example.com' --key-type rsa
- This will create a wildcard certificate for example.com.
- Follow the instructions and add the TXT record to your domain
- This will create PEM certificates and the default location is 'C:\Certbot\live\example.com'
- Install OpenSSL to convert the certifcate from PEM to PFX
- Execute
openssl pkcs12 -export -out C:\Certbot\live\example.com\star_example_com.pfx -inkey C:\Certbot\live\example.com\privkey.pem -in C:\Certbot\live\example.com\cert.pem
- Copy the path to the PFX certificate "C:\Certbot\live\example.com\star_example_com.pfx
-
Follow the instructions Register a Calling Bot. Take a note of the registered config values (Bot Id, MicrosoftAppId and MicrosoftAppPassword). You will need these values in the code sample config. NOTE: This step is creating an Azure Bot. If the bot is not created and configured correctly, the bot will not be able to join the meeting.
-
Add the following Application Permissions to the bot:
- Calls.AccessMedia.All
- Calls.JoinGroupCall.All
-
The permissions need to be consented by tenant admin. Go to "https://login.microsoftonline.com/common/adminconsent?client_id=<app_id>&state=<any_number>&redirect_uri=<any_callback_url>" using tenant admin to sign-in, then consent for the whole tenant.
- Visual Studio (only needed if running locally). You can download the community version here for free.
- PowerShell 7.0+
- Mirosoft Azure Subscription (If you do not already have a subscription, you can register for a free account)
- An Office 365 tenant enabled for Microsoft Teams, with at least two user accounts enabled for the Calls Tab in Microsoft Teams (Check here for details on how to enable users for the Calls Tab)
- Install .Net Framework 4.7.1. The solution will not build if you do not install this.
- You will need Postman, Fiddler, or an equivalent installed to formulate HTTP requests and inspect the responses. The following tools are widely used in web development, but if you are familiar with another tool, the instructions in this sample should still apply.
- PowerShell 7.0+
- Azure Az PowerShell Module
- Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
- GitHub CLI
- This is not a hard requirement, but will automate the step to save the secret in your repo.
- Must be an owner of the Azure subscription where you are deploying the infrastructure.
- Must have permissions to create an Azure AD Application.
- Note: The Azure Bot must be created in a tenant where you are an adminstrator because the bot permissions require admin consent. The bot infrastructure does not need to be in the same tenant where the Azure bot was created. This is useful if you are not an administrator in your tenant and you can use a separate tenant for the Azure Bot and Teams calling.
| Secret Name | Message |
|---|---|
| localadmin | 'localadmin' is the username for the admin on the provisioned VMSS VMs. The password entered is the password to login and will be configured for all VMs. |
| AadAppId | This is the Azure AD Application Client Id that was created when creating an Azure Bot. Refer to the registration instructions |
| AadAppSecret | Client Secret created for the Azure AD Application during the Azure Bot registration. |
| ServiceDNSName | Your public domain that will be used to join the bot to a call (ie bot.example.com) |
| UseSpeechService | True or False setting to set the bot in Echo mode or Speech Service mode. If 'true', the following secrets need to be set. |
| SpeechConfigKey | The Speech Service Key |
| SpeechConfigRegion | The region where the Speech Service is deployed |
| BotLanguage | Legacy recognition language setting (for example, en-US, es-MX, or fr-FR). |
PowerShell Desired State Configuration (DSC) enables you to manage your IT development infrastructure with configuration as code. This sample uses DSC to configure the VMs to run the Teams Voice Echo Bot. Here are a few examples of where we are using DSC:
- Set environment variables on the VM
- Install software
- Install the windows service
DSC Resources
- https://docs.microsoft.com/en-us/powershell/scripting/dsc/overview?view=powershell-5.1
- https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/dsc-overview
- https://github.com/dsccommunity
- Navigate to the root directory of the sample in PowerShell.
- Run
Get-AzContextto ensure you are deploying to the correct subscription.- You need to have the owner role on the subscription
- You need permissions to create a Service Principal
- Run .\deploy.ps1 -OrgName <Your 2 - 7 Character Length Letter Abbreviation>
- ie .\deploy.ps1 -OrgName TEB -Location eastus2
# Option 1. Execute all pre-req steps i.e. run setup to deploy
. .\deploy.ps1 -orgName <yourOrgName> -Location centralus
# E.g.
. .\deploy.ps1 -orgName DNA -Location centralus
# Option 2. After you have run setup the first time, re-execute setup
. .\deploy.ps1 -orgName <yourOrgName> -Location centralus -RunSetup
# E.g.
. .\deploy.ps1 -orgName DNA -Location centralus -RunSetup
# Option 3a. After you have run setup you can deploy from the commandline
. .\deploy.ps1 -orgName <yourOrgName> -Location centralus -RunDeployment
# E.g.
. .\deploy.ps1 -orgName DNA -Location centralus -RunDeployment
# Option 3b. Alternatively skip 4a, check your code changes in and push to your repo
# The deployment will exectute via GitHub workflow instead
- You can manually run the 'BUILD' workflow to build the code
- You can manually run the 'INFRA' workflow after the previous workflow to deploy the infrastructureThis script will do the following:
- Create a resource group with the naming convention ACU1-TEB-BOT-RG-D1 (Region Abbreviation - Your Org Name - BOT - Resource Group - Environment)
- Create a storage account
- Grant current user the 'Storage Blob Data Contributor' role
- Grant the service principal the 'Storage Blob Data Contributor' role
- Create a Key Vault
- And grant current user the 'Key Vault Administrator' role
- Create an Azure AD Application
- The Application will be granted the 'Owner' role to the subscription.
- Crete a GitHub Secret wiht name AZURE_CREDENTIALS__BOT
{ "clientId": "<GitHub Service Principal Client Id>", "clientSecret": "<GitHub Service Principal Secret>", "tenantId": "<Tenant ID>", "subscriptionId": "<Subscription ID>", "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", "resourceManagerEndpointUrl": "https://management.azure.com/", "activeDirectoryGraphResourceId": "https://graph.windows.net/", "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", "galleryEndpointUrl": "https://gallery.azure.com/", "managementEndpointUrl": "https://management.core.windows.net/" } - Generate the deployment parameters file, build workflow and infrastructure workflow
- Upload the PFX certificate to Key Vault
- Add the secrets and environment variables to Key Vault
After the script runs successfully, you should see the following:
- New resource group with the following resources:
- Storage Account
- Key Vault
- Azure AD Application in Azure AD
- In your GitHub Repo, Navigate to Settings > Secrets. You should see a new secret named 'AZURE_CREDENTIALS__BOT'
- Three new files have been created. Check these files in and push them to your repo.
- app-build-.yml
- app-infra-release-.yml
- azuredeploy.parameters.json
- Once these files have been pushed to your repo, they will kick of the infrastructure and code deployment workflows.
The GitHub Action app-infra-release-.yml deploys the infrastructure.
You can also run the infrastructure deployment locally using the -RunDeployment flag.
.\deploy.ps1 -OrgName TEB -RunDeployment
Your DNS Name for your bot needs to point to the public load balacer in order to call your bot and have it join a meeting.
- Find the public IP resource for the load balancer and copy the DNS name.
- Navigate to your DNS settings for your domain and create a new CNAME record. ie CNAME bot acu1-teb-bot-d1-lbplb01-1.eastus2.cloudapp.azure.com
The GitHub Action app-build-.yml builds the solution and uploads the output to the storage account. Once the infrastructure is deployed, DSC will pull the code from the storage account.
会議への参加は、次節の認証付き DeciScope Bot 制御 API を使用します。
フロントエンドは VM Bot を直接呼びません。会議 URL 登録後の流れは常に
Frontend -> Go API -> VM Bot -> Teams meeting です。VM Bot は WebSocket に
接続せず、WebSocket 配信は Go API からフロントエンドへの経路で扱います。
現在のプロダクト構成では、VM Bot はWindows VM上のプロセスまたはWindows Serviceとして 配置し、Docker Composeには含めません。Docker側ではGo API/PostgreSQL/Webを起動し、 Go APIからVM Botの制御APIへTailscale経由で接続します。
VM Bot は Go API から次の制御 API を受け付けます。
POST /internal/bot/join
POST /internal/bot/meeting-sessions/{sessionId}/end
GET /healthz
POST /internal/bot/join のリクエスト:
{
"sessionId": "session_...",
"joinUrl": "https://teams.microsoft.com/l/meetup-join/..."
}ヘッダー:
X-DeciScope-Bot-Control-Token: <DECISCOPE_BOT_CONTROL_TOKEN>
POST /internal/bot/meeting-sessions/{sessionId}/end も同じヘッダーで認証します。
リクエスト本文は省略可能です。指定する場合は次の形式で、本文の sessionId は
ルートの値と一致する必要があります。
{
"sessionId": "session_...",
"botCallId": "call_...",
"reason": "manual_end_requested"
}終了命令は 202 Accepted を返し、レスポンスの activeCallFound で対象callが
見つかったかを示します。Go APIは DECISCOPE_BOT_CONTROL_URL の末尾
/internal/bot/join から終了用URLを組み立てるため、VMではjoinとendの両方の
パスを同じ待受ポートで到達可能にしてください。
制御 API 用の環境変数:
DECISCOPE_BOT_JOIN_MODE: join 許可モード。未設定時は既存互換のauto_user_trigger。DECISCOPE_BOT_CONTROL_TOKEN: Go API からの制御命令用共有トークン。 未設定時は制御 API の join を有効にしません。ログには出しません。DECISCOPE_BOT_CONTROL_BIND_URL: 制御 API の追加待受 URL。例:http://0.0.0.0:7071。未設定時は既存の Bot ホスト URL だけで待ち受けます。DECISCOPE_DEFAULT_TENANT_ID:https://teams.microsoft.com/meet/{meetingId}?p={passcode}形式の短い Teams 会議 URL で使う既定 tenant ID。単一テナント運用ではこの値を 設定してください。値そのものはログに出しません。
DECISCOPE_BOT_JOIN_MODE の値:
command: Go API からのPOST /internal/bot/joinでのみ join します。auto_user_trigger: 既存の特定ユーザー入室トリガーでのみ join します。both: 移行期間用。制御 API と既存トリガーの両方を許可します。disabled: join しません。/healthzは確認できます。
本番想定は command です。移行期間は both を使えます。command または
both でも DECISCOPE_BOT_CONTROL_TOKEN が未設定なら
POST /internal/bot/join は有効になりません。
Go API 側には VM Bot の制御 API URL を設定します。Tailscale を使う場合の例:
http://<VM_TAILSCALE_IP>:7071/internal/bot/join
VM Bot 側の待受例:
[Environment]::SetEnvironmentVariable("DECISCOPE_BOT_JOIN_MODE", "command", "Machine")
[Environment]::SetEnvironmentVariable("DECISCOPE_BOT_CONTROL_TOKEN", "<長いランダム値>", "Machine")
[Environment]::SetEnvironmentVariable("DECISCOPE_BOT_CONTROL_BIND_URL", "http://0.0.0.0:7071", "Machine")
[Environment]::SetEnvironmentVariable("DECISCOPE_DEFAULT_TENANT_ID", "<tenant-guid>", "Machine")Machine スコープの環境変数を変更した後は、Bot プロセスまたは Windows サービス を再起動してください。
可能なら DECISCOPE_BOT_CONTROL_BIND_URL は VM の Tailscale IP に bind して
ください。難しい場合は http://0.0.0.0:7071 で待ち受け、Windows Firewall で
Tailscale 側からの接続だけを許可します。
New-NetFirewallRule `
-DisplayName "DeciScope Bot Control API from Tailscale" `
-Direction Inbound `
-Action Allow `
-Protocol TCP `
-LocalPort 7071 `
-RemoteAddress <GO_API_TAILSCALE_IP>接続確認:
Invoke-WebRequest `
-Uri "http://<VM_TAILSCALE_IP>:7071/healthz" `
-UseBasicParsing手動 join 命令テスト:
$body = @{
sessionId = "manual-session-1"
joinUrl = "https://teams.microsoft.com/l/meetup-join/..."
} | ConvertTo-Json
Invoke-WebRequest `
-Uri "http://localhost:7071/internal/bot/join" `
-Method POST `
-Headers @{ "X-DeciScope-Bot-Control-Token" = $env:DECISCOPE_BOT_CONTROL_TOKEN } `
-ContentType "application/json" `
-Body $body `
-UseBasicParsing短い Teams 会議 URL 形式も利用できます。
$body = @{
sessionId = "manual-session-meet-url"
joinUrl = "https://teams.microsoft.com/meet/<meetingId>?p=<passcode>"
} | ConvertTo-Json
Invoke-WebRequest `
-Uri "http://localhost:7071/internal/bot/join" `
-Method POST `
-Headers @{ "X-DeciScope-Bot-Control-Token" = $env:DECISCOPE_BOT_CONTROL_TOKEN } `
-ContentType "application/json" `
-Body $body `
-UseBasicParsing/meet/{meetingId}?p={passcode} 形式では URL に tenant ID が含まれないため、
tenant ID は次の順で解決します。
- 制御 API リクエスト本文の
tenantId DECISCOPE_DEFAULT_TENANT_ID- どちらも無い場合は join せず
failedを Go API に報告
既存の https://teams.microsoft.com/l/meetup-join/... 形式は引き続き対応します。
短い URL の passcode と joinUrl 全文、control token はログに出しません。
制御 API は命令受付後すぐ 202 Accepted を返し、join 処理はバックグラウンドで
実行します。同じ sessionId が処理中の場合も二重実行せず 202 Accepted を
返します。ログには sessionId を出しますが、トークンと joinUrl 全文は出し
ません。
command join 経由でも、会議参加後は auto user trigger 経由と同じ
CallHandler / BotMediaStream / Azure Speech / TranscriptForwarder の
パイプラインを使います。command join では sessionId と callId を紐づけ、
文字起こし POST に sessionId を含めます。
Go APIへraw audioを送るMedia Ingressは使いません。Teams音声のSTTはこのBot内の Azure Speech pipelineで行い、Go APIへはtranscript segmentだけをHTTP POSTします。
制御 API 経由 join の状態は、既存の DECISCOPE_TRANSCRIPT_API_URL から
/api/v1 のベース URL を推定し、次の Go API へ PATCH します。
PATCH /api/v1/bot/meeting-sessions/{sessionId}/status
認証ヘッダーは文字起こし送信と同じ X-DeciScope-Api-Key、値は
DECISCOPE_TRANSCRIPT_API_KEY です。送信する status は joining、joined、
recording、ended、failed です。状態更新に失敗した場合はログに残しますが、
無限再試行はしません。
会議タイトルを DeciScope API へ反映する場合は、Bot から
PATCH /api/v1/bot/meeting-sessions/{sessionId}/metadata へ title、titleSource、
provider、threadId、joinMeetingId、organizerId などを送信します。Bot は
join URL の query/context に subject 相当が含まれる場合にまずそれを使い、取得できない
場合は Microsoft Graph で次の順に解決を試します。
- 短い
https://teams.microsoft.com/meet/{meetingId}?p=...URL の canonical URL 解決 - canonical URL の
contextに含まれるTid/Oidの抽出 joinMeetingIdによる/users/{candidateUserId}/onlineMeetings検索joinWebUrlによる/users/{candidateUserId}/onlineMeetings検索joinUrlによる/users/{candidateUserId}/events検索
candidateUserId は Entra object id です。URL context の Oid、join URL から解析できた organizer id、
AppSettings__DefaultMeetingOrganizerUserId、
AppSettings__MeetingTitleLookupUserIds の順に重複を除いて試します。
AppSettings__MeetingTitleLookupUserIds はカンマ、セミコロン、空白区切りで複数指定できます。UPN/email が
渡された場合は、先に /users/{upn}?$select=id,userPrincipalName,mail で object id に解決してから
onlineMeeting / calendar lookup に使います。Go API からの command join では、object id は
candidateUserIds、UPN/email は candidateUserPrincipalNames として Bot join command に渡されます。
Graph 取得には、実行主体に OnlineMeetings.Read.All、必要に応じて
Calendars.Read / Calendars.ReadBasic.All 相当のアプリケーション権限と管理者同意が
必要です。/users/{id}/onlineMeetings を application permission で使う場合は、
対象ユーザーに対する Teams application access policy が必要になることがあります。
権限・ポリシー・organizer 不明などで取得できない場合は、permission_missing、
admin_consent_missing、application_access_policy_missing、
user_not_allowed_by_policy、meeting_not_found、candidate_user_not_found、
calendar_permission_missing などの reason を Bot ログと
session metadata に残します。取得できない場合、PC 側は user input title、それもなければ
Teams会議 を fallback title として表示します。
会議参加を伴わずタイトル解決だけを確認する場合は、Bot control token 付きで 次の debug endpoint を呼びます。
POST /api/v1/debug/resolve-meeting-title
X-DeciScope-Bot-Control-Token: <control-token>
{
"joinUrl": "https://teams.microsoft.com/meet/...",
"tenantId": "...",
"joinMeetingId": "4426674024458",
"candidateUserIds": ["00000000-0000-0000-0000-000000000000"],
"candidateUserPrincipalNames": ["organizer@example.com"]
}response には title、titleSource、canonicalJoinWebUrl、organizerId、
titleResolutionErrorCode、attempts が含まれます。
status の意味:
joining: join 命令を受け付け、Teams 会議への参加処理を開始しました。joined: Graph の join 要求が成功し、Bot の call が作られました。recording: Azure Speech へ音声を流せる状態になりました。ended: call が終了しました。failed: join または Speech pipeline の開始に失敗しました。
joined だけでは文字起こし開始を意味しません。recording まで進むと、
Speech recognizer、PushAudioInputStream、audio frame queue が ready になって
います。
Speech audio frame dropped が出る場合は、ログの Reason と
SpeechPipelineReady を確認してください。
Reason=SpeechPipelineNotStarted: call は作られていますが、Speech pipeline がまだ開始されていません。Reason=SpeechPipelineNotReady: Speech 開始処理中、または開始失敗後です。Reason=SpeechQueueFull: Speech queue が詰まっています。PeakAmplitude=0; RmsAmplitude=0: 会議側から届いた audio frame が無音の可能性 があります。SpeechPipelineReady=Trueなら pipeline 自体は動いています。
command join の手動確認では、joined の後に Speech pipeline started と
Status=recording が出ることを確認してください。その後、会議内で発話すると
Speech recognized.、Transcript forwarding queued.、
Transcript forwarding succeeded. の順に進みます。
制御 API 経由で参加した会議の文字起こし POST には sessionId が追加されます。
既存の auto_user_trigger 経由では sessionId は省略されます。既存の JSON
項目は変更しません。
文字起こし送信は環境変数だけで設定します。共有 API キーは、ソースコード、
appsettings、Git 管理対象ファイル、ログ、エラーメッセージには記載しないで
ください。
VM 側で必須の環境変数:
DECISCOPE_TRANSCRIPT_FORWARD_ENABLED: 送信を有効にする場合はtrue。DECISCOPE_TRANSCRIPT_API_URL: Go API の文字起こし受信エンドポイントの 絶対 URL。DECISCOPE_TRANSCRIPT_API_KEY: 共有キー。Go API 側のDECISCOPE_INGEST_API_KEYと同じ値を設定します。
任意の環境変数:
DECISCOPE_TRANSCRIPT_API_TIMEOUT_SECONDS: HTTP 送信 1 回ごとのタイムアウト。 既定値は5秒。DECISCOPE_TRANSCRIPT_API_MAX_RETRY_ATTEMPTS: 再試行対象の失敗に対する最大 試行回数。既定値は3回。DECISCOPE_TRANSCRIPT_FORWARD_QUEUE_CAPACITY: メモリ上の有界送信キューの 容量。既定値は1000件。DECISCOPE_BOT_HEARTBEAT_SECONDS: Bot 生存確認のハートビート送信間隔(秒)。 既定値は20秒。0以下を指定するとハートビート送信は無効になります。 Go API 側 watchdog のDECISCOPE_SESSION_BOT_LOST_AFTER_SECONDS(既定60秒)より十分小さい値(1/3 以下を推奨)にしてください。この値がLOST_AFTER以上だと喪失/復旧の誤検知が、DECISCOPE_SESSION_BOT_END_AFTER_SECONDS(既定180秒)以上だと正常な会議の自動終了が発生します。
既存の文字起こし受信パス:
/api/v1/transcript-segments
Go API を PC ホスト上の Docker コンテナで動かす場合、VM からは PC ホストの
Tailscale IP と、Go API のホスト側公開ポートへ接続します。Docker Compose の
サービス名、コンテナ IP、PostgreSQL のアドレス、VM から見た localhost は
使用しないでください。
$env:DECISCOPE_TRANSCRIPT_FORWARD_ENABLED = "true"
$env:DECISCOPE_TRANSCRIPT_API_URL = "http://<Tailscale IP>:<公開ポート>/api/v1/transcript-segments"
$env:DECISCOPE_TRANSCRIPT_API_KEY = "<Go側と同じ共有キー>"Machine スコープへ永続設定する例:
[Environment]::SetEnvironmentVariable(
"DECISCOPE_TRANSCRIPT_FORWARD_ENABLED",
"true",
"Machine"
)
[Environment]::SetEnvironmentVariable(
"DECISCOPE_TRANSCRIPT_API_URL",
"http://<Tailscale IP>:<公開ポート>/api/v1/transcript-segments",
"Machine"
)
[Environment]::SetEnvironmentVariable(
"DECISCOPE_TRANSCRIPT_API_KEY",
"<Go側と同じ共有キー>",
"Machine"
)Machine スコープの環境変数を変更した後は、Bot プロセスまたは Windows サービス を再起動してください。Bot を Windows Service Control Manager、タスク スケジューラ、IIS、その他のプロセス管理方式で起動している場合は、その起動 プロセスが Machine スコープの環境変数を読み取れることを確認してください。
Go API がヘルスチェックエンドポイントを公開している場合は、会議を開始する前に VM から Tailscale 経由で接続できることを確認します。
Invoke-WebRequest `
-Uri "http://<Tailscale IP>:<公開ポート>/healthz" `
-UseBasicParsing
Invoke-WebRequest `
-Uri "http://<Tailscale IP>:<公開ポート>/readyz" `
-UseBasicParsing動作確認の順序:
- PC 側で PostgreSQL、マイグレーション、Go API を Docker Compose で起動する。
- PC 側で
/healthzと/readyzを確認する。 - VM から Tailscale 経由で
/healthzと/readyzを確認する。 - Bot を起動し、Teams 会議へ参加させる。
Speech recognized.ログを確認する。- C# 側で
CallIdとSequenceNoを含む文字起こし送信成功ログを確認する。 - Go コンテナ側の受信ログを確認する。
- PostgreSQL に文字起こし行が保存され、
(call_id, sequence_no)の重複がない ことを確認する。
Build and test:
dotnet restore src\EchoBot.sln
dotnet build src\EchoBot.sln -c Release -p:Platform=x64
dotnet test src\EchoBot.sln -c Release -p:Platform=x64External configuration required for a real Teams meeting test:
- Azure Bot registration configured as a calling bot.
- Entra ID app registration for the bot application.
- Microsoft Graph application permissions such as
Calls.AccessMedia.AllandCalls.JoinGroupCall.All, with tenant admin consent. - Valid public TLS certificate and
CertificateThumbprint. - Public DNS name and calling webhook URL that match the configured certificate and bot endpoints.
- Reachable media ports, Windows Firewall rules, and Azure NSG rules for the media platform.
- Teams application policy and Microsoft 365 licensing appropriate for calling and meeting access in the tenant.
Refer to the Microsft Graph Documentation on (Local Testing)[https://microsoftgraph.github.io/microsoft-graph-comms-samples/docs/articles/Testing.html]
Note: The certificate is used by the MediaPlatformInstanceSettings and needs to match the ServiceFqdn property of that class.
- Domain: example.com
- Certificate: *.contoso.com
- ServiceDnsName: bot.contoso.com
- MediaDnsName: tcp.contoso.com
- MediaInstanceExternalPort: 12332
| Type | Name | Value |
|---|---|---|
| CNAME | bot | ra8sxx2z.cname.us.ngrok.io. |
| CNAME | tcp | 1.tcp.ngrok.io. |
authtoken: <yourAuthToken>
tunnels:
bot-signaling:
proto: http
addr: "9442"
hostname: bot.contoso.com
bot-media:
proto: tcp
addr: 8445
remote_addr: 1.tcp.ngrok.io:12332- Domain: contoso.com
- Certificate: *.bot.contoso.com
- ServiceDnsName: bot.contoso.com
- MediaDnsName: 5.bot.contoso.com
- MediaInstanceExternalPort: 12332
| Type | Name | Value |
|---|---|---|
| CNAME | 5.bot | 5.tcp.ngrok.io. |
authtoken: <yourAuthToken>
tunnels:
bot-signaling:
proto: http
addr: "9442"
hostname: signal.ngrok.io
bot-media:
proto: tcp
addr: 8445
remote_addr: 5.tcp.ngrok.io:12332