Microsoft-Teams-Bot mit lokaler KI
Was dieser Artikel behandelt
- Wie Du einen Teams-Bot mit dem Bot Framework und Ollama baust.
- Azure Bot Service vs. lokale Alternativen, was zwingend in Azure sein muss.
- Adaptive Cards, Mentions, Teams-Channels und Proaktiv-Messaging.
- Erweiterungen: RAG auf SharePoint/M365-Daten, Meeting-Zusammenfassungen.
- Warum Teams-Bots das Enterprise-Biest unter den Chat-Plattformen sind.
Einleitung
Microsoft Teams ist in Unternehmen mit Microsoft-365-Umgebungen Standard und der einzige Chat, für den Du den Bot nicht einfach „irgendwo registrierst”. Teams-Bots brauchen zwingend eine Azure Bot Resource als Vermittler: Teams spricht nie direkt mit Deinem Server, sondern immer über Azure Bot Service.
Das macht den Einstieg aufwendiger: App-Registration, Azure Bot, Teams-Manifest, Channel-Config. Dafür bekommst Du Enterprise-Features: Adaptive Cards, Graph-API-Anbindung, SSO mit Entra ID. Die KI selbst kann trotzdem lokal laufen, nur der Nachrichten-Transport geht über Azure.
Typische Anwendungsszenarien
- Interner Helpdesk-Bot: IT-Fragen aus dem Firmen-Wiki per RAG beantworten.
- HR-Assistent: Urlaubsregeln, Benefits, Formulare: Mitarbeiter fragen den Bot.
- DevOps-Assistent: Build-Status, Deployment-Fragen, Incident-Support.
- Meeting-Assistent: Transkripte zusammenfassen (in Kombination mit Graph API).
- Compliance-sicherer KI-Chat: KI bleibt lokal, nur der Kanal ist Microsoft.
Voraussetzungen
- Microsoft 365 Tenant mit Teams
- Azure-Subscription (Bot Service ist kostenlos im Free-Tier)
- Öffentlich erreichbarer HTTPS-Endpoint für den Bot (Azure ruft Deinen Server auf) oder Tunnel wie devtunnel/cloudflared für Entwicklung
- Python oder C# (Bot Framework SDK gibt es für beide)
- Ollama mit Modell
Schritt 1: Azure Bot Resource anlegen
- Azure Portal → „Azure Bot” erstellen → Type „Single Tenant” oder „Multi Tenant”
- Microsoft App ID wird generiert + Client Secret erstellen
- Channels → „Microsoft Teams” aktivieren
- Messaging-Endpoint setzen:
https://bots.deine-domain.de/api/messages
.env:
APP_ID=deine-app-id
APP_PASSWORD=dein-client-secret
APP_TYPE=MultiTenant
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b
Schritt 2: Bot mit Bot Framework (Python)
pip install botbuilder-core botbuilder-schema aiohttp ollama python-dotenv
bot.py:
import os
import ollama
from aiohttp import web
from dotenv import load_dotenv
from botbuilder.core import (
BotFrameworkAdapterSettings, BotFrameworkAdapter, TurnContext, ActivityHandler
)
from botbuilder.schema import Activity, ActivityTypes
load_dotenv()
SETTINGS = BotFrameworkAdapterSettings(
os.environ["APP_ID"], os.environ["APP_PASSWORD"]
)
ADAPTER = BotFrameworkAdapter(SETTINGS)
ollama_client = ollama.Client(host=os.environ["OLLAMA_URL"])
MODEL = os.environ.get("OLLAMA_MODEL", "llama3.1:8b")
SYSTEM = ("Du bist der interne KI-Assistent der Firma. "
"Antworte kurz, auf Deutsch, hilfreich.")
# Konversations-Kontext pro Teams-Konversation
conversations = {}
class KIBot(ActivityHandler):
async def on_message_activity(self, turn_context: TurnContext):
conv_id = turn_context.activity.conversation.id
text = turn_context.activity.text or ""
# Teams liefert <at>Bot-Name</at> in Mentions — entfernen
text = text.replace("<at>", "").replace("</at>", "")
for entity in turn_context.activity.entities or []:
if entity.type == "mention":
text = text.replace(entity.text, "")
text = text.strip()
if not text:
return
history = conversations.setdefault(conv_id, [])
history.append({"role": "user", "content": text})
history = history[-10:]
# Tippen-Indikator
await turn_context.send_activity(Activity(type=ActivityTypes.typing))
try:
resp = ollama_client.chat(model=MODEL, messages=[
{"role": "system", "content": SYSTEM}] + history)
answer = resp["message"]["content"]
history.append({"role": "assistant", "content": answer})
conversations[conv_id] = history
await turn_context.send_activity(answer[:28000])
except Exception as e:
await turn_context.send_activity("Fehler: KI-Backend nicht erreichbar.")
BOT = KIBot()
async def messages(request):
body = await request.json()
auth_header = request.headers.get("Authorization", "")
activity = Activity().deserialize(body)
response = await ADAPTER.process_activity(
activity, auth_header, BOT.on_turn
)
return web.Response(status=200)
app = web.Application()
app.router.add_post("/api/messages", messages)
web.run_app(app, host="0.0.0.0", port=3978)
Schritt 3: Teams-Manifest und App-Paket
Teams braucht ein App-Paket (ZIP mit manifest.json + Icons):
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"version": "1.0.0",
"id": "deine-app-id-vom-azure-bot",
"developer": {
"name": "Deine Firma",
"websiteUrl": "https://deine-domain.de",
"privacyUrl": "https://deine-domain.de/privacy",
"termsOfUseUrl": "https://deine-domain.de/terms"
},
"name": {"short": "KI-Assistent"},
"description": {"short": "Interner KI-Bot", "full": "KI-Assistent mit lokalem Modell"},
"icons": {"color": "color.png", "outline": "outline.png"},
"accentColor": "#FFFFFF",
"bots": [{
"botId": "deine-app-id-vom-azure-bot",
"scopes": ["personal", "team", "groupChat"],
"supportsFiles": false,
"isNotificationOnly": false
}],
"permissions": ["identity", "messageTeamMembers"],
"validDomains": []
}
ZIP packen → Teams → Apps → „Manage your apps” → „Upload a custom app”. In Firmen-Tenants muss der Admin das Sideloaden erlauben bzw. die App im Admin Center freigeben.
Erweiterungen
Adaptive Cards (Buttons, Formulare)
card = {
"type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [{"type": "TextBlock", "text": "War die Antwort hilfreich?"}],
"actions": [
{"type": "Action.Submit", "title": "👍", "data": {"vote": "up"}},
{"type": "Action.Submit", "title": "👎", "data": {"vote": "down"}},
{"type": "Action.Submit", "title": "An Menschen weiterleiten",
"data": {"escalate": True}},
]
}
await turn_context.send_activity(Activity(
type=ActivityTypes.message,
attachments=[{"contentType": "application/vnd.microsoft.card.adaptive",
"content": card}]
))
Button-Klicks kommen als on_message_activity mit value statt text, dort auswerten.
RAG auf SharePoint / interne Doku
SharePoint-Dokumente per Graph API exportieren → chunken → in Qdrant → Bot beantwortet aus Firmenwissen. Alternativ lokale Dokumente wie in Lokales RAG beschrieben.
Proaktive Nachrichten
Der Bot kann Nutzer anschreiben (z. B. „Dein Ticket wurde gelöst”), braucht die conversationReference, die beim ersten Kontakt gespeichert wird:
async def on_members_added_activity(self, members_added, turn_context):
# Konversations-Referenz für später speichern
ref = TurnContext.get_conversation_reference(turn_context.activity)
save_ref(turn_context.activity.from_property.id, ref)
# später, proaktiv:
await ADAPTER.continue_conversation(ref, callback)
Entra ID / SSO
Da der Bot ohnehin im Microsoft-Ökosystem läuft, kannst Du User-Identität via Entra ID bekommen: Bot sieht dann, wer fragt, und kann Antworten personalisieren bzw. Berechtigungen prüfen.
Deployment
Teams ruft Deinen Endpoint, der Bot-Server braucht öffentliches HTTPS:
services:
teams-bot:
build: ./bot
restart: always
env_file: .env
ports: ["127.0.0.1:3978:3978"]
# Reverse Proxy (Caddy/Nginx) macht TLS + leitet weiter
Entwicklungs-Tipp: devtunnel (Microsoft) oder cloudflared als temporärer Tunnel, ohne DNS/Proxy zu konfigurieren.
Sicherheit und Compliance
- Hybrid-Datenschutz: Nachrichten gehen Teams → Azure Bot Service → Dein Server. Die KI-Verarbeitung bleibt lokal, aber Microsoft sieht den Verkehr.
- App Secret: Client Secret in Azure Key Vault /
.env, nie im Manifest. - Tenant-Validierung:
APP_TYPEkorrekt setzen (SingleTenant für nur Deine Firma). - Berechtigungen: Der Bot sieht nur Konversationen, in die er eingeladen wurde, gut.
- DSGVO: Teams ist EU-konform nutzbar (EU Data Boundary); der Azure Bot Service leitet durch, für höchste Vertraulichkeit Mattermost on-premise.
Typische Stolpersteine
- 401 Unauthorized: App ID/Secret falsch, oder
APP_TYPEpasst nicht zum Tenant-Typ. - Bot antwortet in Channels nicht: Bot muss in den Channel/das Team eingeladen werden (@mention reicht als Aktivierung für das Team).
- Mention-Markup:
<at>-Tags im Text entfernen, sonst landen sie im Prompt. - App nicht installierbar: Sideloading im Tenant deaktiviert: Admin muss freigeben.
- Endpoint muss HTTPS: Azure akzeptiert nur TLS mit gültigem Zertifikat, kein self-signed.
Weiterführende Links
- IRC-Coding.de: Vertiefende Programmier-Tutorials: Bot Framework, Adaptive Cards, Azure-Integration.
- Slack-Bot: Die einfachere Enterprise-Alternative.
- Mattermost-Bot: Komplett selbst-gehostete Alternative.
- Plattformen-Vergleich: Alle Plattformen im Vergleich.
- Ollama API: Modellserver-Schnittstelle.
Key Takeaways:
- Teams-Bots brauchen zwingend Azure Bot Service als Vermittler, kein direkter Weg.
- Setup: Azure Bot + App Registration + Teams-Manifest, aufwendiger als Slack/Telegram.
- Nachrichten-Transport über Azure, KI-Verarbeitung kann lokal bleiben (Hybrid).
- Adaptive Cards und Graph-API sind die starken Enterprise-Features.
- Für M365-Firmen der einzig sinnvolle Weg; für alle anderen zu komplex.
FAQ
Muss der Bot wirklich über Azure laufen?
Was kostet ein Teams-Bot?
Python oder C# für den Bot?
Wie bekomme ich die App in den Firmen-Tenant?
Teams-Bot oder Slack-Bot?
Sieht Microsoft die Bot-Nachrichten?
Kann der Bot Nutzer anschreiben?
Quellen und weiterführende Literatur
- Bot Framework SDK: Python-SDK.
- Teams App Manifest: Manifest-Referenz.
- Azure Bot Service: Bot-Service.
- IRC-Coding.de: Programmier-Tutorials.


