Skip to main content

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)

CI PyPI version Python versions MIT License Ruff PRs Welcome

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 -py suffix 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 linear16 at 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-managed open_ai/anthropic, or any BYO provider via a think endpoint), Aura voice, prompt, greeting.
  • Protocol-correct: Settings only after the server's Welcome; no audio until the SettingsApplied ack (buffered, bounded, then flushed oldest-first, with a 10 s ack timeout).
  • Barge-in: UserStartedSpeaking maps 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 UpdatePrompt as 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 look tool 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; optional VISION_REQUIRES_RECORDING compliance gate).
  • Hardened: HMAC-signed upgrades with replay guard, connection caps, SSRF-guarded image fetches (one re-validated redirect), dead-peer detection, *.deepgram.com host allowlists, graceful SIGTERM drain, Prometheus /metrics with 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 unless LOG_TRANSCRIPTS=true and Teams recording is active. Video frames are buffered in memory only.
  • Regional routing: set DEEPGRAM_AGENT_HOST / DEEPGRAM_API_HOST to api.eu.deepgram.com / api.au.deepgram.com to 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

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deepgram_msteams_bridge-0.1.0.tar.gz (63.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deepgram_msteams_bridge-0.1.0-py3-none-any.whl (49.8 kB view details)

Uploaded Python 3

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

Hashes for deepgram_msteams_bridge-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6dc186e94b8b7502af2c3a34d9b0d694a79656f00082f96db55b75fcdd724a2a
MD5 1a8e3ebd31c2a6135a88e516cb34effb
BLAKE2b-256 03d8ef02f93a3e7d742e0bc45fed82a242818ac46f7b750ad6b9a016071f1618

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file deepgram_msteams_bridge-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for deepgram_msteams_bridge-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 98b3685fa7a535d47a7e18f542c7de3a099c038b319ce4e810be8154aae23637
MD5 96bb2e4f99dcfef7b013a642b548505c
BLAKE2b-256 4cc3feb0dadc92d32f0c53f7f77cd9cb6a78b8023e63ecb1f4a399048450372c

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page