Skip to content
BotServBotServ
Microsoft TeamsBotBot IAOllamaBot FrameworkEnterpriseAzure

Bot de Microsoft Teams con IA local

Crea bots en Teams con Ollama: Bot Framework, Azure Bot Service, Adaptive Cards e IA local empresarial.

S

schutzgeist

7 min read
Bot de Microsoft Teams con IA local

Bot de Microsoft Teams con IA local

Qué cubre este artículo

  • Cómo construir un bot de Teams con Bot Framework y Ollama.
  • Azure Bot Service versus alternativas locales, qué debe estar obligatoriamente en Azure.
  • Adaptive Cards, menciones, canales de Teams y mensajería proactiva.
  • Extensiones: RAG sobre datos de SharePoint/M365, resúmenes de reuniones.
  • Por qué los bots de Teams son la bestia empresarial entre las plataformas de chat.

Introducción

Microsoft Teams es el estándar en empresas con entornos Microsoft 365 y el único chat donde no registras el bot simplemente “en cualquier lado”. Los bots de Teams requieren obligatoriamente un Azure Bot Resource como intermediario: Teams nunca habla directamente con tu servidor, siempre va a través de Azure Bot Service.

Esto hace el punto de entrada más complejo: registro de aplicación, Azure Bot, manifiesto de Teams, configuración de canales. A cambio, obtienes características empresariales: Adaptive Cards, integración con Graph API, SSO con Entra ID. La IA en sí puede ejecutarse localmente, solo el transporte de mensajes pasa por Azure.

Casos de uso típicos

  • Bot de helpdesk interno: responder preguntas de TI desde la wiki corporativa usando RAG.
  • Asistente de RRHH: reglas de vacaciones, beneficios, formularios, los empleados preguntan al bot.
  • Asistente de DevOps: estado de compilaciones, preguntas sobre despliegues, soporte ante incidentes.
  • Asistente de reuniones: resumir transcripciones en combinación con Graph API.
  • Chat de IA seguro para compliance: IA local, solo el canal es Microsoft.

Requisitos previos

  • Tenant de Microsoft 365 con Teams
  • Suscripción a Azure (Bot Service es gratuito en el nivel Free)
  • Endpoint HTTPS públicamente accesible para el bot (Azure llama a tu servidor) o un túnel como devtunnel/cloudflared para desarrollo
  • Python o C# (Bot Framework SDK está disponible para ambos)
  • Ollama con un modelo

Paso 1: Crear Azure Bot Resource

  1. Portal de Azure → crear “Azure Bot” → Tipo “Single Tenant” o “Multi Tenant”
  2. Se genera Microsoft App ID + crear Client Secret
  3. Channels → activar “Microsoft Teams”
  4. Establecer Messaging Endpoint: https://bots.tu-dominio.de/api/messages

.env:

APP_ID=tu-app-id
APP_PASSWORD=tu-client-secret
APP_TYPE=MultiTenant
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b

Paso 2: Bot con 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 = ("Eres el asistente de IA interno de la empresa. "
          "Responde brevemente, en español, útil y directo.")

# Contexto de conversación por conversación de Teams
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 incluye <at>Bot-Name</at> en menciones, eliminarlas
        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:]

        # Indicador de escritura
        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("Error: backend de IA no disponible.")


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)

Paso 3: Manifiesto de Teams y paquete de aplicación

Teams necesita un paquete de aplicación (ZIP con manifest.json + iconos):

{
  "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
  "manifestVersion": "1.16",
  "version": "1.0.0",
  "id": "tu-app-id-del-azure-bot",
  "developer": {
    "name": "Tu Empresa",
    "websiteUrl": "https://tu-dominio.de",
    "privacyUrl": "https://tu-dominio.de/privacy",
    "termsOfUseUrl": "https://tu-dominio.de/terms"
  },
  "name": {"short": "Asistente de IA"},
  "description": {"short": "Bot de IA interno", "full": "Asistente de IA con modelo local"},
  "icons": {"color": "color.png", "outline": "outline.png"},
  "accentColor": "#FFFFFF",
  "bots": [{
    "botId": "tu-app-id-del-azure-bot",
    "scopes": ["personal", "team", "groupChat"],
    "supportsFiles": false,
    "isNotificationOnly": false
  }],
  "permissions": ["identity", "messageTeamMembers"],
  "validDomains": []
}

Empaquetar en ZIP → Teams → Apps → “Manage your apps” → “Upload a custom app”. En tenants corporativos, el administrador debe permitir sideloading o liberar la app en el Admin Center.

Extensiones

Adaptive Cards (botones, formularios)

card = {
    "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4",
    "body": [{"type": "TextBlock", "text": "¿Fue útil la respuesta?"}],
    "actions": [
        {"type": "Action.Submit", "title": "👍", "data": {"vote": "up"}},
        {"type": "Action.Submit", "title": "👎", "data": {"vote": "down"}},
        {"type": "Action.Submit", "title": "Escalar a una persona",
         "data": {"escalate": True}},
    ]
}
await turn_context.send_activity(Activity(
    type=ActivityTypes.message,
    attachments=[{"contentType": "application/vnd.microsoft.card.adaptive",
                  "content": card}]
))

Los clics en botones llegan como on_message_activity con value en lugar de text, procésalos allí.

RAG sobre SharePoint y documentación interna

Exporta documentos de SharePoint mediante Graph API, fragmenta, carga en Qdrant, y el bot responde desde el conocimiento corporativo. Alternativamente, usa documentos locales como se describe en RAG local.

Mensajería proactiva

El bot puede escribir a usuarios (por ejemplo, “tu ticket se resolvió”), necesita la conversationReference guardada en el primer contacto:

async def on_members_added_activity(self, members_added, turn_context):
    # Guardar referencia de conversación para después
    ref = TurnContext.get_conversation_reference(turn_context.activity)
    save_ref(turn_context.activity.from_property.id, ref)

# después, de forma proactiva:
await ADAPTER.continue_conversation(ref, callback)

Entra ID / SSO

Dado que el bot se ejecuta de todos modos en el ecosistema Microsoft, puedes obtener la identidad del usuario a través de Entra ID: el bot verá quién pregunta y podrá personalizar las respuestas o validar permisos.

Implementación

Teams invoca tu endpoint, el servidor del bot necesita HTTPS público:

services:
  teams-bot:
    build: ./bot
    restart: always
    env_file: .env
    ports: ["127.0.0.1:3978:3978"]
    # Reverse Proxy (Caddy/Nginx) maneja TLS + redirección

Consejo para desarrollo: utiliza devtunnel (Microsoft) o cloudflared como túnel temporal, sin necesidad de configurar DNS o proxy.

Seguridad y cumplimiento normativo

  • Protección de datos híbrida: Los mensajes van Teams → Azure Bot Service → Tu servidor. El procesamiento de IA permanece local, pero Microsoft ve el tráfico.
  • App Secret: Guarda el Client Secret en Azure Key Vault o .env, nunca en el manifiesto.
  • Validación de tenant: Configura APP_TYPE correctamente (SingleTenant solo para tu empresa).
  • Permisos: El bot solo ve las conversaciones en las que fue invitado, lo cual es correcto.
  • RGPD: Teams se puede usar de manera conforme en la UE (EU Data Boundary); Azure Bot Service actúa como intermediario. Para máxima confidencialidad, considera una solución completamente on-premise como Mattermost.

Errores comunes

  • 401 Unauthorized: La App ID o Secret son incorrectos, o APP_TYPE no coincide con el tipo de tenant.
  • El bot no responde en canales: El bot debe ser invitado al canal o al equipo (@mention es suficiente para activarlo en el equipo).
  • Markup de menciones: Elimina las etiquetas <at> del texto, de lo contrario aparecerán en el prompt.
  • La app no es instalable: El sideloading está desactivado en el tenant: el administrador debe habilitarlo.
  • El endpoint debe ser HTTPS: Azure solo acepta TLS con certificado válido, sin autofirmados.

Enlaces de referencia

Puntos clave:

  • Los bots de Teams requieren obligatoriamente Azure Bot Service como intermediario, no hay ruta directa.
  • Configuración: Azure Bot + App Registration + manifiesto de Teams, más trabajo que Slack/Telegram.
  • El transporte de mensajes va por Azure, el procesamiento de IA puede mantenerse local (híbrido).
  • Adaptive Cards y Graph API son las funciones empresariales destacadas.
  • Para empresas M365 es el único camino sensato; para otros casos, demasiado complejo.

FAQ

¿Realmente el bot debe ejecutarse a través de Azure?

El código del bot se ejecuta en tu servidor, pero el transporte de mensajes va obligatoriamente a través de Azure Bot Service. Sin un recurso de Azure no hay Teams-Bot. El recurso Azure-Bot mismo es gratuito en el tier gratis.

¿Cuánto cuesta un Teams-Bot?

Azure Bot Service: tier gratis sin costo (10.000 mensajes/mes en canal Premium). Teams en sí requiere licencias M365. Tu IA local: solo hardware. Sin costos de uso del bot como sucede con APIs de GPT.

¿Python o C# para el bot?

Ambos tienen SDKs oficiales. C# es la implementación de referencia con más ejemplos; Python (botbuilder-core) es más sencillo para integración de IA (Ollama, LangChain están orientados a Python).

¿Cómo agrego la app al tenant de la empresa?

Sube un ZIP a través de «Upload a custom app» si el tenant permite sideloading. De lo contrario, el administrador la sube en el Teams Admin Center o la distribuye por App Policy. Para despliegue empresarial: publicarla en el Admin Center.

¿Teams-Bot o Slack-Bot?

Si tu empresa usa M365: Teams, los usuarios ya están allí, SSO sin costo. Slack es más simple de desarrollar (sin dependencia de Azure). Para soberanía de datos: Mattermost.

¿Ve Microsoft los mensajes del bot?

El transporte pasa por Teams/Azure, técnicamente sí, como intermediario. El procesamiento de IA y documentos permanecen locales. Para contenido muy confidencial, un sistema puramente on-premise (Mattermost/Matrix) es mejor.

¿Puede el bot enviar mensajes a los usuarios?

Sí, el envío proactivo es posible, pero requiere una conversationReference guardada del primer contacto. Útil para alertas y notificaciones.

Fuentes y referencias adicionales

Volver al blog
Share:

Entradas relacionadas