Misty + Azure AI Foundry: Multimodale Robotik mit beliebigen KI-Modellen

    Zurück zum Blog
    Embodied AIEmpfohlen

    Misty + Azure AI Foundry: Multimodale Robotik mit beliebigen KI-Modellen

    Erfahren Sie, wie SharePoint Agents Ihr Intranet revolutionieren: Automatische Antworten, Dokumentensuche und Workflows – direkt in SharePoint Online.

    1. März 202618 min Lesezeit
    Marcel Haas

    Marcel Haas

    Solution Architect, CEO

    marcel.haas@cnext.ch
    20+ JahreErfahrung·6×Microsoft Applied Skills·SharePoint & Microsoft Copilot
    6x Microsoft Applied Skills
    CNEXT AI Agent

    Kurzantwort

    Erfahren Sie, wie SharePoint Agents Ihr Intranet revolutionieren: Automatische Antworten, Dokumentensuche und Workflows – direkt in SharePoint Online.

    Im letzten Artikel haben wir Misty mit Claude verbunden und ihm Reinforced Prompts beigebracht. Jetzt gehen wir einen Schritt weiter: Wir schliessen Misty an Azure AI Foundry an — Microsofts Model Catalog mit über 1'700 Modellen. Das Ziel: Misty soll nicht an ein einzelnes Modell gebunden sein, sondern je nach Aufgabe das beste Modell wählen. Sehen mit GPT-5.2 Vision, Hören mit Whisper v3, Denken mit GPT-5.2, Sprechen mit OpenAI TTS. Alles orchestriert über Python.

    Was ist Azure AI Foundry?

    Azure AI Foundry (ehemals Azure AI Studio) ist Microsofts zentrale Plattform für KI-Modelle. Statt jede API einzeln zu konfigurieren, gibt Ihnen Foundry einen einheitlichen Zugang zu:

    • OpenAI-Modelle — GPT-5.2, o4-mini, o3, DALL-E 4, Whisper v3, TTS
    • Open-Source-Modelle — Llama 3.3, Mistral Large, Phi-4, DeepSeek-R1, Stable Diffusion
    • Microsoft-eigene Modelle — Phi-4 (Small Language Model), Azure Speech
    • Serverless API Endpoints — Modelle ohne eigene Infrastruktur nutzen
    • Managed Compute — Eigene Modelle auf GPU-VMs deployen

    Für Misty heisst das: Wir können für jede Sinnesmodalität das optimale Modell wählen — und bei Bedarf austauschen, ohne die restliche Architektur anzufassen.

    Die Multimodale Architektur

                        Azure AI Foundry
                        ┌─────────────────────────────────┐
                        │                                 │
    ┌───────────┐       │  ┌───────────┐  ┌────────────┐  │
    │  Misty II │──────▶│  │ Whisper v3 │  │  GPT-5.2   │  │
    │           │       │  │  (Hören)   │  │  (Sehen)   │  │
    │  Kamera   │       │  └─────┬─────┘  └─────┬──────┘  │
    │  Mikrofon │       │        │              │         │
    │  Sensoren │       │  ┌─────▼──────────────▼──────┐  │
    │           │◀──────│  │        GPT-5.2             │  │
    │  REST API │       │  │    (Multimodales Denken)   │  │
    │           │       │  └─────────────┬─────────────┘  │
    └───────────┘       │               │                 │
          ▲             │  ┌────────────▼──────────────┐  │
          │             │  │    OpenAI TTS / Whisper v3  │  │
          └─────────────│  │      (Sprechen)            │  │
                        │  └───────────────────────────┘  │
                        └─────────────────────────────────┘
                                  ▲
                        ┌─────────┴─────────┐
                        │   Python Orchestr. │
                        │   + Model Router   │
                        └───────────────────┘

    Setup: Azure AI Foundry Projekt erstellen

    Bevor wir Code schreiben, richten wir das Foundry-Projekt ein:

    # Azure CLI: Ressourcengruppe und AI Hub erstellen
    az group create --name rg-misty-ai --location switzerlandnorth
    az ml workspace create --kind hub --name misty-ai-hub \
        --resource-group rg-misty-ai --location switzerlandnorth
    
    # AI Foundry Projekt erstellen
    az ml workspace create --kind project --name misty-multimodal \
        --resource-group rg-misty-ai --hub-name misty-ai-hub
    
    # Modelle deployen (Serverless API)
    az ml serverless-endpoint create --name gpt52-misty \
        --model-id azureml://registries/azure-openai/models/gpt-5.2 \
        --resource-group rg-misty-ai --workspace-name misty-multimodal
    
    az ml serverless-endpoint create --name o4mini-misty \
        --model-id azureml://registries/azure-openai/models/o4-mini \
        --resource-group rg-misty-ai --workspace-name misty-multimodal
    
    az ml serverless-endpoint create --name whisper-misty \
        --model-id azureml://registries/azure-openai/models/whisper-v3 \
        --resource-group rg-misty-ai --workspace-name misty-multimodal

    Der Model Router: Das richtige Modell für jede Aufgabe

    Das Kernstück unserer Architektur: Ein Model Router, der je nach Sinnesmodalität automatisch das richtige Modell wählt.

    import os
    from azure.ai.inference import ChatCompletionsClient
    from azure.ai.inference.models import (
        SystemMessage, UserMessage, ImageContentItem,
        ImageUrl, TextContentItem
    )
    from azure.core.credentials import AzureKeyCredential
    
    class ModelRouter:
        """Wählt automatisch das beste OpenAI-Modell für jede Aufgabe – mit Client-Caching."""
    
        def __init__(self):
            self.foundry_endpoint = os.environ["AZURE_AI_FOUNDRY_ENDPOINT"]
            self.api_key = os.environ["AZURE_AI_FOUNDRY_KEY"]
    
            # Client-Cache für bessere Performance
            self.clients: dict = {}
    
            self.models = {
                "reasoning": "gpt-5.2",
                "reasoning_fast": "o4-mini",
                "vision": "gpt-5.2",
                "vision_detailed": "gpt-5.2",
                "speech_to_text": "whisper-v3",
                "text_to_speech": "tts-1-hd",
                "embedding": "text-embedding-3-large",
            }
    
        def get_client(self, model_name: str) -> ChatCompletionsClient:
            if model_name not in self.clients:
                self.clients[model_name] = ChatCompletionsClient(
                    endpoint=self.foundry_endpoint,
                    credential=AzureKeyCredential(self.api_key),
                )
            return self.clients[model_name]
    
        def reason(self, system_prompt: str, user_input: str,
                   fast: bool = False) -> str:
            model = self.models["reasoning_fast" if fast else "reasoning"]
            client = self.get_client(model)
    
            response = client.complete(
                model=model,
                messages=[
                    SystemMessage(content=system_prompt),
                    UserMessage(content=user_input),
                ],
                temperature=0.3,
                max_tokens=1024,
            )
            return response.choices[0].message.content
    
        def see(self, image_bytes: bytes, question: str,
                detailed: bool = False) -> str:
            model = self.models["vision_detailed" if detailed else "vision"]
            client = self.get_client(model)
    
            import base64
            img_b64 = base64.b64encode(image_bytes).decode()
    
            response = client.complete(
                model=model,
                messages=[
                    UserMessage(content=[
                        TextContentItem(text=question),
                        ImageContentItem(
                            image_url=ImageUrl(
                                url=f"data:image/jpeg;base64,{img_b64}"
                            )
                        ),
                    ]),
                ],
                max_tokens=512,
            )
            return response.choices[0].message.content
    
        def hear(self, audio_bytes: bytes) -> str:
            """Whisper v3: Audio zu Text – sicheres Temp-File-Handling"""
            from openai import AzureOpenAI
    
            client = AzureOpenAI(
                azure_endpoint=self.foundry_endpoint,
                api_key=self.api_key,
                api_version="2025-01-01",
            )
    
            import tempfile
            with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
                f.write(audio_bytes)
                temp_path = f.name
    
            try:
                with open(temp_path, "rb") as audio_file:
                    transcript = client.audio.transcriptions.create(
                        model="whisper-v3",
                        file=audio_file,
                        language="de",
                    )
                return transcript.text
            finally:
                if os.path.exists(temp_path):
                    os.unlink(temp_path)
    
        def speak(self, text: str, voice: str = "alloy") -> bytes:
            """OpenAI TTS: Text zu Sprache"""
            from openai import AzureOpenAI
    
            client = AzureOpenAI(
                azure_endpoint=self.foundry_endpoint,
                api_key=self.api_key,
                api_version="2025-01-01",
            )
    
            response = client.audio.speech.create(
                model="tts-1-hd",
                voice=voice,
                input=text,
            )
            return response.content

    Multimodales Wahrnehmen: Sehen + Hören gleichzeitig

    Der entscheidende Vorteil von Azure AI Foundry: Wir können Mistys Kamera und Mikrofon gleichzeitig verarbeiten — mit verschiedenen Modellen parallel.

    import asyncio
    import aiohttp
    import json
    from datetime import datetime
    
    class MultimodalPerception:
        """Verarbeitet alle Sinne parallel über Azure AI Foundry."""
    
        def __init__(self, misty_client, model_router: ModelRouter):
            self.misty = misty_client
            self.router = model_router
    
        async def perceive(self) -> dict:
            """Sammelt und verarbeitet alle Sensordaten parallel."""
    
            image_task = asyncio.to_thread(
                self.misty.get_camera_image
            )
            audio_task = asyncio.to_thread(
                self.misty.get_audio, 3000
            )
    
            image_bytes, audio_bytes = await asyncio.gather(
                image_task, audio_task
            )
    
            vision_task = asyncio.to_thread(
                self.router.see,
                image_bytes,
                "Beschreibe was du siehst. Wie viele Personen? "
                "Zeigt jemand Emotionen? Gibt es Gesten? "
                "Beschreibe die Umgebung kurz.",
            )
            speech_task = asyncio.to_thread(
                self.router.hear,
                audio_bytes,
            )
    
            vision_result, speech_text = await asyncio.gather(
                vision_task, speech_task
            )
    
            return {
                "timestamp": datetime.now().isoformat(),
                "vision": {
                    "description": vision_result,
                    "raw_image": image_bytes,
                },
                "audio": {
                    "transcript": speech_text,
                    "has_speech": len(speech_text.strip()) > 0,
                },
                "sensors": {
                    "touch": self.misty.get_touch_sensors(),
                    "bumpers": self.misty.get_bumper_sensors(),
                    "battery": self.misty.get_battery_level(),
                    "imu": self.misty.get_imu(),
                },
            }

    GPT-5.2 als Multimodaler Orchestrator

    Hier kommt der Clou: GPT-5.2 kann Text, Bilder und strukturierte Daten in einem einzigen Request verarbeiten. Wir schicken die Kamera-Aufnahme und das Transkript zusammen und bekommen eine ganzheitliche Entscheidung:

    import base64
    import json
    from azure.ai.inference import ChatCompletionsClient
    from azure.ai.inference.models import (
        SystemMessage, UserMessage,
        ImageContentItem, ImageUrl, TextContentItem
    )
    
    MULTIMODAL_SYSTEM_PROMPT = """Du bist Misty, ein Roboter bei CNEXT.
    Du erhältst gleichzeitig: ein Kamerabild, ein Sprach-Transkript
    und Sensordaten. Triff EINE Entscheidung basierend auf ALLEN Inputs.
    
    Antworte IMMER als JSON:
    {
      "situation_summary": "Was passiert gerade (1-2 Sätze)",
      "primary_input": "vision|speech|sensor",
      "action": {
        "type": "speak|move|express|navigate|wait|multimodal",
        "speech": "Was du sagst",
        "emotion": "happy|curious|thinking|surprised|neutral",
        "movement": {"head_pitch": 0, "head_yaw": 0, "drive": 0},
        "reason": "Warum diese Aktion"
      },
      "confidence": 0.0-1.0,
      "follow_up": "Was du als nächstes beobachten willst"
    }
    
    SICHERHEITSREGELN:
    - Nie schneller als drive=30
    - Bei confidence < 0.4: action.type = "wait"
    - Bei widersprüchlichen Inputs (Bild zeigt X, Sprache sagt Y):
      Priorisiere Sprache, aber erwähne den Widerspruch
    """
    
    def multimodal_decision(perception: dict,
                             conversation_history: list,
                             router: ModelRouter) -> dict:
        image_b64 = base64.b64encode(
            perception["vision"]["raw_image"]
        ).decode()
    
        context = json.dumps({
            "speech_transcript": perception["audio"]["transcript"],
            "sensor_data": perception["sensors"],
            "timestamp": perception["timestamp"],
        }, indent=2)
    
        client = router.get_client("gpt-5.2")
    
        response = client.complete(
            model="gpt-5.2",
            messages=[
                SystemMessage(content=MULTIMODAL_SYSTEM_PROMPT),
                *conversation_history,
                UserMessage(content=[
                    TextContentItem(
                        text=f"Sensordaten und Transkript:\n{context}"
                    ),
                    ImageContentItem(
                        image_url=ImageUrl(
                            url=f"data:image/jpeg;base64,{image_b64}"
                        )
                    ),
                ]),
            ],
            temperature=0.2,
            max_tokens=1024,
            response_format={"type": "json_object"},
        )
    
        return json.loads(response.choices[0].message.content)

    GPT-5.2 Vision: Spezialisierte Computer Vision

    GPT-5.2 ist nicht nur gut für allgemeine Bildbeschreibungen — es beherrscht auch präzise Aufgaben wie Objektzählung, OCR und Region-Erkennung. Durch spezialisierte Prompts holen wir strukturierte Ergebnisse aus dem gleichen Modell:

    class DetailedVision:
        """GPT-5.2 für spezialisierte Vision-Aufgaben mit strukturierten Prompts."""
    
        def __init__(self, router: ModelRouter):
            self.router = router
    
        def _parse_detections(self, result: str) -> list:
            """Parsing der strukturierten JSON-Antwort."""
            try:
                import json
                return json.loads(result) if isinstance(result, str) else result
            except Exception:
                return []
    
        def detect_objects(self, image_bytes: bytes) -> list:
            result = self.router.see(
                image_bytes,
                "Erkenne alle Objekte im Bild. Antworte als JSON-Array "
                "mit {label, confidence, bbox: [x1,y1,x2,y2]} pro Objekt.",
                detailed=True,
            )
            return self._parse_detections(result)
    
        def read_text(self, image_bytes: bytes) -> str:
            return self.router.see(
                image_bytes,
                "Lies allen sichtbaren Text im Bild. "
                "Gib nur den erkannten Text zurück, zeilenweise.",
                detailed=True,
            )
    
        def describe_region(self, image_bytes: bytes,
                            bbox: tuple) -> str:
            x1, y1, x2, y2 = bbox
            return self.router.see(
                image_bytes,
                f"Beschreibe den Bildausschnitt bei den Koordinaten "
                f"[{x1},{y1},{x2},{y2}] im Detail.",
                detailed=True,
            )
    
        def count_people(self, image_bytes: bytes) -> int:
            detections = self.detect_objects(image_bytes)
            return sum(
                1 for d in detections
                if d.get("label") in ["person", "face"]
            )
    
        def detect_gestures(self, image_bytes: bytes) -> list:
            result = self.router.see(
                image_bytes,
                "Welche Gesten zeigen die Personen im Bild? "
                "Winken, Zeigen, Daumen hoch, Nicken?",
            )
            return result.split(", ") if isinstance(result, str) else [result]

    Warum GPT-5.2 für alles? Statt separate Vision-Modelle zu deployen, nutzen wir GPT-5.2 mit spezialisierten Prompts für jede Aufgabe. Ein Modell, ein Endpoint, maximale Flexibilität. Für Objekterkennung und OCR liefert GPT-5.2 vergleichbare Ergebnisse wie spezialisierte Modelle — bei deutlich einfacherem Setup.

    Modelle austauschen: Hot-Swap zur Laufzeit

    Das mächtigste Feature unserer Architektur: Modelle können zur Laufzeit ausgetauscht werden — ohne Neustart, ohne Code-Änderung:

    class AdaptiveModelRouter(ModelRouter):
        """Wählt Modelle dynamisch basierend auf Kontext."""
    
        def __init__(self):
            super().__init__()
            self.performance_log = []
    
        def select_model(self, task: str, context: dict) -> str:
            battery = context.get("battery", 100)
            latency_budget = context.get("max_latency_ms", 2000)
            crowd_size = context.get("people_count", 0)
    
            if task == "reasoning":
                if battery < 20 or latency_budget < 500:
                    return "o4-mini"
                if crowd_size > 3:
                    return "gpt-5.2"
                return "o4-mini"
    
            if task == "vision":
                if context.get("need_ocr") or context.get("need_counting"):
                    return "gpt-5.2"
                if context.get("need_emotion"):
                    return "gpt-5.2"
                return "o4-mini"
    
            if task == "speech_to_text":
                return "whisper-v3"
    
            return self.models.get(task, "o4-mini")
    
        def log_performance(self, model: str, task: str,
                             latency_ms: float, success: bool):
            self.performance_log.append({
                "model": model,
                "task": task,
                "latency_ms": latency_ms,
                "success": success,
            })
    
            if len(self.performance_log) > 100:
                self._optimize_routing()

    Die Hauptschleife: Alles zusammen

    import asyncio
    import time
    
    class MistyFoundryBrain:
        def __init__(self, misty_ip: str):
            self.misty = MistyClient(misty_ip)
            self.router = AdaptiveModelRouter()
            self.perception = MultimodalPerception(self.misty, self.router)
            self.vision = DetailedVision(self.router)
            self.conversation = []
    
        async def run(self):
            print("Misty + Azure AI Foundry: Starte...")
            self.misty.set_expression("happy")
            self.misty.speak("Hallo! Ich bin bereit.", lang="de-CH")
    
            while True:
                start = time.time()
    
                perception_data = await self.perception.perceive()
    
                people = self.vision.count_people(
                    perception_data["vision"]["raw_image"]
                )
                perception_data["people_count"] = people
    
                decision = multimodal_decision(
                    perception_data, self.conversation, self.router
                )
    
                latency = (time.time() - start) * 1000
                print(f"Entscheidung in {latency:.0f}ms: "
                      f"{decision['action']['type']} "
                      f"(conf: {decision['confidence']})")
    
                self.execute(decision)
    
                self.router.log_performance(
                    "gpt-5.2", "multimodal", latency,
                    decision["confidence"] > 0.5,
                )
    
                await asyncio.sleep(0.3)
    
        def execute(self, decision: dict):
            action = decision["action"]
            action_type = action["type"]
    
            if action_type in ("speak", "multimodal"):
                if action.get("speech"):
                    audio = self.router.speak(action["speech"])
                    self.misty.play_audio(audio)
    
            if action.get("emotion"):
                self.misty.set_expression(action["emotion"])
    
            if action_type in ("move", "multimodal"):
                mv = action.get("movement", {})
                if mv.get("head_pitch") or mv.get("head_yaw"):
                    self.misty.move_head(
                        mv.get("head_pitch", 0),
                        0,
                        mv.get("head_yaw", 0),
                    )
    
    if __name__ == "__main__":
        brain = MistyFoundryBrain("192.168.1.42")
        asyncio.run(brain.run())

    Ergebnisse: Model Routing in der Praxis

    AufgabeModellLatenzKosten/1K Requests
    Schnelle Antwort (Smalltalk)o4-mini~120ms~CHF 0.05
    Komplexe EntscheidungGPT-5.2~350ms~CHF 0.90
    Objekterkennung (Vision)GPT-5.2~300ms~CHF 0.80
    SpracherkennungWhisper v3~150ms~CHF 0.08
    Multimodale AnalyseGPT-5.2 (Bild+Text)~400ms~CHF 1.80
    SprachausgabeOpenAI TTS-1-HD~130ms~CHF 0.06

    Durch das adaptive Routing sparen wir gegenüber einer reinen GPT-5.2-Lösung ca. 60% der API-Kosten, weil einfache Aufgaben an günstigere, schnellere Modelle gehen.

    Warum Azure AI Foundry statt einzelner APIs?

    AspektEinzelne APIsAzure AI Foundry
    AuthentifizierungJe ein API-Key pro AnbieterEin Endpoint, ein Key
    Modelle austauschenNeuer SDK, neuer CodeNur Model-ID ändern
    DatenresidenzUnklar, pro Anbieter prüfenSwitzerland North / West
    MonitoringSelbst bauenAzure Monitor + AI Metrics
    ComplianceJeder Vertrag einzelnEin Azure-Vertrag, DSG-konform

    Für Schweizer Unternehmen ist der letzte Punkt entscheidend: Alle Daten bleiben in der Azure Switzerland Region. Ein Vertrag, ein Datenschutz-Framework, alle Modelle.

    Next Steps: Was wir als Nächstes bauen

    • Video-Streaming statt Einzelbilder — Kontinuierliche Szenen-Analyse mit GPT-5.2 Vision im Stream-Modus
    • Phi-4 lokal auf Misty — Microsofts Small Language Model für Offline-Entscheidungen, wenn die Cloud nicht erreichbar ist
    • Reinforced Prompts + Foundry — Kombination unserer Reinforced-Prompt-Architektur (aus dem vorherigen Artikel) mit dem Model Router
    • Custom Fine-Tuned Models — Eigene Modelle in Foundry trainieren, die auf CNEXT-Sprache und Schweizer Kontext spezialisiert sind

    Interesse an Azure AI Foundry, multimodaler Robotik oder einer Misty-Demo?

    Weiterführende Links

    Intern:

    Extern:

    Embodied AIAzure AIOpenAISchweiz
    Teilen:

    Dieser Artikel wurde mit Unterstützung von KI erstellt und von unserem Team geprüft. Wir setzen KI-Tools ein, um hochwertige Inhalte effizient zu produzieren — die fachliche Verantwortung liegt immer bei unseren Experten.

    Marcel Haas

    Marcel Haas

    Solution Architect, CEO

    6x Microsoft Applied Skills

    Haben Sie Fragen zu diesem Thema?

    Unsere Experten beraten Sie gerne – kostenlos und unverbindlich.