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
| Aufgabe | Modell | Latenz | Kosten/1K Requests |
|---|---|---|---|
| Schnelle Antwort (Smalltalk) | o4-mini | ~120ms | ~CHF 0.05 |
| Komplexe Entscheidung | GPT-5.2 | ~350ms | ~CHF 0.90 |
| Objekterkennung (Vision) | GPT-5.2 | ~300ms | ~CHF 0.80 |
| Spracherkennung | Whisper v3 | ~150ms | ~CHF 0.08 |
| Multimodale Analyse | GPT-5.2 (Bild+Text) | ~400ms | ~CHF 1.80 |
| Sprachausgabe | OpenAI 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?
| Aspekt | Einzelne APIs | Azure AI Foundry |
|---|---|---|
| Authentifizierung | Je ein API-Key pro Anbieter | Ein Endpoint, ein Key |
| Modelle austauschen | Neuer SDK, neuer Code | Nur Model-ID ändern |
| Datenresidenz | Unklar, pro Anbieter prüfen | Switzerland North / West |
| Monitoring | Selbst bauen | Azure Monitor + AI Metrics |
| Compliance | Jeder Vertrag einzeln | Ein 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:
- Misty + Claude: Reinforced Prompts
- Luis' Misty-Artikel: OpenAI trifft Robotik
- LLM Chaining: Multi-Model-Architektur
- CNEXT Embodied AI
Extern:

