Put a Deepgram Voice Agent on Microsoft Teams calls. Terminates the StandIn media bridge wire protocol on one side and the Deepgram Voice Agent WebSocket on the other. No transcoding: PCM 16k verbatim both ways, barge-in, extensible client-side tools, vision on demand, call governors.
Project description
Microsoft Teams Bridge for Deepgram Voice Agents (Python)
Bridge Microsoft Teams voice/video calls to a Deepgram Voice Agent (Nova STT + your chosen LLM + Aura TTS, with turn-taking, all run by Deepgram).
PyPI package:
deepgram-msteams-bridge- the-pysuffix is only in this repository's name, to distinguish it from the Node.js sibling repo.
This is the Python sibling of @komaa/deepgram-msteams-bridge
(Node.js) - same wire contract, same environment variables, drop-in interchangeable behind the same
.env file. It terminates the StandIn media bridge wire protocol on one side and the Deepgram
Voice Agent WebSocket (wss://agent.deepgram.com/v1/agent/converse) on the other:
- No transcoding: the StandIn wire is base64 PCM 16 kHz mono and the Voice Agent session is
pinned to
linear16at 16 kHz both ways - the hot path is copy-only (base64/binary framing). - No dashboard: the bridge configures each session itself from environment variables - STT
model (
nova-3), LLM (Deepgram-managedopen_ai/anthropic, or any BYO provider via a think endpoint), Aura voice, prompt, greeting. - Protocol-correct:
Settingsonly after the server'sWelcome; no audio until theSettingsAppliedack (buffered, bounded, then flushed oldest-first, with a 10 s ack timeout). - Barge-in:
UserStartedSpeakingmaps to a Teams-side playback flush, with state-based ghost-audio filtering until the agent's next utterance. - Extensible tools: the built-ins (
end_call,express,show_image,look) plus your own client-side function tools, executed in-process inside your trust boundary. - Live context: participant counts, DTMF and active-speaker changes ride
UpdatePromptas a bounded rolling notes section (the Voice Agent API has no non-interrupting context message). - Call governors: a bridge-side hard time cap plus the worker-side governor; goodbyes are the
exact text (standalone Aura TTS with the agent muted, or the live agent via
InjectAgentMessage), never dropped under backpressure, and always backstopped. - On-demand vision: the
looktool answers from the caller's camera or screen-share via your own OpenAI-compatible vision endpoint (the raw frame is sent there - never to Deepgram; optionalVISION_REQUIRES_RECORDINGcompliance gate). - Hardened: HMAC-signed upgrades with replay guard, connection caps, SSRF-guarded image fetches
(one re-validated redirect), dead-peer detection,
*.deepgram.comhost allowlists, graceful SIGTERM drain, Prometheus/metricswith a call-duration histogram.
StandIn is the hosted media bridge that joins the Teams call and dials this bridge - you run no Teams media stack yourself.
Documentation: komaa-com.github.io/deepgram-msteams-bridge-py - getting started, the runnable example, configuration, library API, wire protocol, and troubleshooting. Teams/StandIn setup lives at docs.komaa.com.
Install
pip install deepgram-msteams-bridge
Requires Python 3.10+. One runtime dependency (aiohttp).
Run
DEEPGRAM_API_KEY=dg_... \
WORKER_SHARED_SECRET=... \
deepgram-msteams-bridge
A .env file in the working directory is loaded automatically (existing environment wins). The
bridge listens on ws://0.0.0.0:8080/voice/msteams/stream by default; StandIn appends /{callId}
per call. Expose the port with a tunnel and register the wss:// URL as your identity's
Agent voice URL in the StandIn dashboard.
Optionally shape the agent (all have defaults):
DEEPGRAM_PROMPT="You are Komaa's friendly receptionist. Keep replies short."
DEEPGRAM_GREETING="Hello! You've reached Komaa. How can I help?"
DEEPGRAM_SPEAK_MODEL=aura-2-thalia-en
DEEPGRAM_THINK_MODEL=gpt-4o-mini
Third-party LLMs (google, groq, aws_bedrock, ...) additionally require
DEEPGRAM_THINK_ENDPOINT_URL (+ DEEPGRAM_THINK_ENDPOINT_HEADERS for auth) - Deepgram dials that
endpoint itself. See .env.example for every variable.
Embed in your own asyncio app
import asyncio
from deepgram_msteams_bridge import load_config, start_server
async def main():
server = await start_server(load_config())
try:
await asyncio.Event().wait() # run until cancelled
finally:
await server.close() # drains live calls (session.end + close)
asyncio.run(main())
Custom function tools
Register client-side tools the bridge executes; the handler's returned string goes back to the agent as the function result, and a raise becomes an error result the model can recover from:
from deepgram_msteams_bridge import CustomTool, load_config, start_server
async def lookup_order(params, ctx):
# ctx: call_id, participant_count, recording_active, log
return f"Order {params.get('orderNumber')} shipped yesterday and arrives tomorrow."
tools = [
CustomTool(
name="lookup_order",
description="Look up the status of a customer order by its order number.",
parameters={
"type": "object",
"properties": {"orderNumber": {"type": "string", "description": "e.g. KO-1234"}},
"required": ["orderNumber"],
},
handler=lookup_order,
)
]
server = await start_server(load_config(), tools=tools)
Names must not collide with the built-ins; collisions fail at startup. Keep handlers fast - the caller is waiting on the answer.
Custom vision hook
The vision argument answers the agent's look tool with your own model; pass None to disable
vision entirely, or omit it to use the env-configured describer (VISION_API_URL):
async def describe(frame: dict, question: str) -> str:
# frame: {"source": "camera"|"screenshare", "mime": ..., "dataBase64": ..., ...}
return "a short description the agent relays aloud"
server = await start_server(load_config(), vision=describe)
Know the data flow: the raw frame is sent to the vision endpoint you configure (never to
Deepgram). Set VISION_REQUIRES_RECORDING=true to refuse frames unless Teams recording is active,
and run a local model (Ollama/vLLM) if frames must not leave your infrastructure.
Custom agent transport (testing)
The connect_dg argument substitutes the Deepgram connection; the repository's own
test suite runs entirely against a fake, no Deepgram account needed.
Governors and privacy
- Two governors (StandIn tier cutoffs and the bridge-side
MAX_CALL_MINUTES) both speak a goodbye before hanging up, backstopped so a call can never sit open half-dead. - Transcripts (
ConversationText) are never logged unlessLOG_TRANSCRIPTS=trueand Teams recording is active. Video frames are buffered in memory only. - Regional routing: set
DEEPGRAM_AGENT_HOST/DEEPGRAM_API_HOSTtoapi.eu.deepgram.com/api.au.deepgram.comto keep traffic in-region.
Siblings
The same bridge exists for ElevenLabs, LiveKit (Python), and as Node packages for ElevenLabs, LiveKit, OpenAI Realtime, and Deepgram - same wire protocol, same hardening, pick the agent platform and runtime that fit.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file deepgram_msteams_bridge-0.1.0.tar.gz.
File metadata
- Download URL: deepgram_msteams_bridge-0.1.0.tar.gz
- Upload date:
- Size: 63.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6dc186e94b8b7502af2c3a34d9b0d694a79656f00082f96db55b75fcdd724a2a
|
|
| MD5 |
1a8e3ebd31c2a6135a88e516cb34effb
|
|
| BLAKE2b-256 |
03d8ef02f93a3e7d742e0bc45fed82a242818ac46f7b750ad6b9a016071f1618
|
Provenance
The following attestation bundles were made for deepgram_msteams_bridge-0.1.0.tar.gz:
Publisher:
publish.yml on komaa-com/deepgram-msteams-bridge-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
deepgram_msteams_bridge-0.1.0.tar.gz -
Subject digest:
6dc186e94b8b7502af2c3a34d9b0d694a79656f00082f96db55b75fcdd724a2a - Sigstore transparency entry: 2151437979
- Sigstore integration time:
-
Permalink:
komaa-com/deepgram-msteams-bridge-py@5726e12f5821b9c530bea47c5c54e27533c00f4f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/komaa-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5726e12f5821b9c530bea47c5c54e27533c00f4f -
Trigger Event:
push
-
Statement type:
File details
Details for the file deepgram_msteams_bridge-0.1.0-py3-none-any.whl.
File metadata
- Download URL: deepgram_msteams_bridge-0.1.0-py3-none-any.whl
- Upload date:
- Size: 49.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98b3685fa7a535d47a7e18f542c7de3a099c038b319ce4e810be8154aae23637
|
|
| MD5 |
96bb2e4f99dcfef7b013a642b548505c
|
|
| BLAKE2b-256 |
4cc3feb0dadc92d32f0c53f7f77cd9cb6a78b8023e63ecb1f4a399048450372c
|
Provenance
The following attestation bundles were made for deepgram_msteams_bridge-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on komaa-com/deepgram-msteams-bridge-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
deepgram_msteams_bridge-0.1.0-py3-none-any.whl -
Subject digest:
98b3685fa7a535d47a7e18f542c7de3a099c038b319ce4e810be8154aae23637 - Sigstore transparency entry: 2151438021
- Sigstore integration time:
-
Permalink:
komaa-com/deepgram-msteams-bridge-py@5726e12f5821b9c530bea47c5c54e27533c00f4f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/komaa-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5726e12f5821b9c530bea47c5c54e27533c00f4f -
Trigger Event:
push
-
Statement type: