Robotyka z przesyłaniem strumieniowym

Punkt końcowy modelu gemini-robotics-er-2-streaming-preview udostępnia dedykowany punkt końcowy przesyłania strumieniowego, który jest zintegrowany z interfejsem Live API, co umożliwia dwukierunkową interakcję w czasie rzeczywistym między aplikacją a robotem. Dzięki temu nadaje się do agentów, którzy potrzebują szybkich pętli informacji zwrotnych i reaktywnych odpowiedzi na środowisko.

Przypadki użycia

  • Koordynacja wielu robotów: wiele robotów, które komunikują stan zadania i delegują podzadania w ramach wspólnej sesji.
  • Ciągłe monitorowanie: roboty, które obserwują scenę i uruchamiają działania, gdy wystąpią określone zdarzenia, np. gdy pojemnik osiągnie określony poziom napełnienia.
  • Magazyn i logistyka: pracownicy magazynu, którzy wizualnie weryfikują produkty, śledzą postęp pakowania i korygują błędy.

Specyfikacja techniczna

W tabeli poniżej znajdziesz dane techniczne interfejsu Live API:

Kategoria Szczegóły
Metody wprowadzania Audio (surowe 16-bitowe audio PCM, 16 kHz, little-endian), obrazy (JPEG <= 1 kl./s), tekst
Rodzaje danych wyjściowych Tekst
Protokół Połączenie WebSocket z zachowywaniem stanu (WSS)

Tworzenie konfiguracji opartej na agentach

Każdy agent robotyczny oparty na interfejsie Live API wykonuje 3 kroki:

  1. Deklarowanie możliwości robota jako narzędzi. Każda czynność, jaką może wykonać robot – nawigacja, chwytanie, mówienie – staje się deklaracją funkcji z nazwą, opisem i schematem parametrów. Działania fizyczne muszą używać "behavior": "BLOCKING", aby model czekał na zakończenie działania robota przed wybraniem następnego kroku.
  2. Przesyłanie strumieniowe danych wejściowych w sesji ciągłej Otwórz live.connectsesję i pozostaw ją otwartą przez cały czas trwania zadania. wysyłać klatki wideo, dźwięk lub tekst w miarę ich docierania z czujników robota;
  3. Obsługuj wywołania narzędzi w pętli odbioru. Za każdym razem, gdy model wybierze działanie, wysyła wiadomość tool_call. Pętla odbioru wykonuje funkcję w pakiecie SDK robota i odsyła wartość tool_response. Sesja pozostaje otwarta, a model wybiera następne działanie na podstawie wyniku.

W sekcjach poniżej pokazujemy, jak zastosować te kroki w przypadku 3 często spotykanych wzorców: podstawowej pętli agenta, proaktywnego monitorowania sceny z sygnałem i przekierowywania mowy przez TTS jako narzędzia.

Sterowanie robotem za pomocą wywoływania funkcji

W tym przykładzie pokazano wszystkie 3 kroki połączone w jednym skrypcie w języku Python.

Krok 1 – definicje narzędzi – deklaruje możliwości robota jako deklaracje funkcji. Funkcja navigate korzysta z funkcji "behavior": "BLOCKING", więc model czeka, aż robot dotrze do punktu pośredniego, zanim wywoła kolejne narzędzie. Dodaj więcej deklaracji funkcji na tej samej liście, aby udostępnić dodatkowe możliwości robota.

Krok 2 – pomocnicy wprowadzania danych – pokazuje 3 funkcje, które przesyłają do sesji różne dane wejściowe: send_text w przypadku poleceń, send_image w przypadku klatek z kamery z opcjonalnym promptem tekstowym i send_audio w przypadku surowego dźwięku PCM z mikrofonu.

Krok 3 – pętla odbioru – działa równolegle i obsługuje 2 rodzaje wiadomości: server_content (tekst wygenerowany przez model) i tool_call (model prosi o wykonanie działania przez robota). Gdy nadejdzie wywołanie narzędzia, pętla wywoła execute_tool — stub, który zastąpisz prawdziwym pakietem SDK robota —, a następnie odeśle tool_response, aby model mógł wybrać kolejne działanie.

import asyncio
from google import genai
from google.genai import types

MODEL = "gemini-robotics-er-2-streaming-preview"

# ── Tool definitions ─────────────────────────────────────────────────────────
tools = [
   {
       "function_declarations": [
           {
               "name": "navigate",
               "description": "Navigate the robot to a named waypoint.",
               "behavior": "BLOCKING",
               "parameters": {
                   "type": "OBJECT",
                   "properties": {"name": {"type": "STRING"}},
                   "required": ["name"],
               },
           },
           # Add more function definitions here
       ]
   }
]

# ── Stub tool executor (replace with real robot SDK calls) ───────────────────
def execute_tool(name: str, args: dict) -> dict:
   print(f"  [Tool] {name}({args})")
   return {"status": "success"}

# ── Input helpers ────────────────────────────────────────────────────────────
def send_text(session, text: str):
   """Send a text turn."""
   return session.send_client_content(
       turns=types.Content(role="user", parts=[types.Part(text=text)]),
       turn_complete=True,
   )

def send_image(session, image_bytes: bytes, prompt: str = ""):
   """Send a JPEG image with an optional text prompt."""
   parts = [
       types.Part(
           inline_data=types.Blob(data=image_bytes, mime_type="image/jpeg")
       )
   ]
   if prompt:
       parts.append(types.Part(text=prompt))
   return session.send_client_content(
       turns=types.Content(role="user", parts=parts),
       turn_complete=True,
   )

def send_audio(session, audio_chunk: bytes):
   """Stream a chunk of raw PCM audio (16-bit, 16 kHz, mono)."""
   return session.send_realtime_input(
       media=types.Blob(data=audio_chunk, mime_type="audio/pcm;rate=16000")
   )

# ── Receive loop ─────────────────────────────────────────────────────────────
async def receive_loop(session):
   """Print model text and handle tool calls until the session ends."""
   async for message in session.receive():
       if message.server_content:
           sc = message.server_content
           if sc.model_turn and sc.model_turn.parts:
               for part in sc.model_turn.parts:
                   if part.text:
                       print(f"Model: {part.text}", end="", flush=True)
           if sc.turn_complete:
               print("\n[Turn Complete]")
       elif message.tool_call:
           responses = []
           for call in message.tool_call.function_calls:
               print(f"\n[Tool Call] {call.name}({call.args})")
               result = execute_tool(call.name, call.args)
               responses.append(
                   types.FunctionResponse(
                       name=call.name,
                       response=result,
                       id=call.id,
                   )
               )
           await session.send_tool_response(function_responses=responses)

# ── Main ─────────────────────────────────────────────────────────────────────
async def main():
   client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
   config = types.LiveConnectConfig(
       response_modalities=["TEXT"],
       tools=tools,
       system_instruction=types.Content(
           parts=[types.Part(text="You are a robot controller. Use tools to execute commands.")]
       ),
   )
   async with client.aio.live.connect(model=MODEL, config=config) as session:
       recv_task = asyncio.create_task(receive_loop(session))
       # Connect robot perception callbacks and user inputs to the helpers above.
       recv_task.cancel()

asyncio.run(main())

Pętla odbioru pozostaje aktywna po każdej odpowiedzi narzędzia. Model tworzy i weryfikuje plan długoterminowy bez konieczności kodowania z wyprzedzeniem całej sekwencji działań.

Proaktywne rozumowanie przestrzenno-czasowe

Interfejs Live API przesyła strumieniowo wideo, ale same klatki wideo nie wywołują nowej tury rozumowania. Klatkom wideo musi towarzyszyć prompt tekstowy lub audio, aby wywołać odpowiedź modelu. Więcej informacji znajdziesz w sekcji Funkcje interfejsu Live API.

Aby włączyć proaktywne rozumowanie, zaimplementuj pakiet podtrzymujący: okresowo wysyłaj najnowszą klatkę z kamery, a następnie krótki prompt tekstowy, który zmusza model do zbadania sceny i podjęcia wyraźnej decyzji. Dane wejściowe wideo są ograniczone do 1 klatki na sekundę.

Wdrożenie sygnału

Współprogram sygnału aktywności jest uruchamiany jako osobne zadanie asyncio w ramach tej samej sesji. Wykorzystuje on możliwość osiągnięcia częstotliwości 1 Hz (zgodnej z limitem szybkości danych wejściowych wideo), czekając na zakończenie każdej tury (er_turn_done), aby uniknąć przerywania trwającego procesu rozumowania:

async def heartbeat(session, camera, er_turn_done: asyncio.Event):
    TARGET_INTERVAL_SEC = 1.0

    while True:
        start_time = asyncio.get_running_loop().time()

        frame = await camera.latest_jpeg()
        await session.send_realtime_input(
            video=types.Blob(data=frame, mime_type="image/jpeg")
        )
        await session.send_realtime_input(
            text=(
                "[HEARTBEAT] If no task is active, call 'ack' and wait for user"
                " input. If a task is active: observe the scene. If the current"
                " step is progressing correctly, call 'ack'. If the current step"
                " is complete, call 'run_instruction' with the next step. If the"
                " overall goal is achieved, call 'reset' and inform the user."
            )
        )

        # Wait for the model to finish responding before sending the next heartbeat
        await er_turn_done.wait()
        er_turn_done.clear()

        # Sleep only the remaining time to maintain ~1 Hz cadence
        elapsed = asyncio.get_running_loop().time() - start_time
        remaining = TARGET_INTERVAL_SEC - elapsed
        if remaining > 0:
            await asyncio.sleep(remaining)

Aktualizowanie pętli odbioru

Aby zasygnalizować, że model zakończył swoją turę, zaktualizuj receive_loop, aby ustawić er_turn_done:

# In receive_loop: signal when the model finishes its turn
if sc.turn_complete:
    er_turn_done.set()

Wyjście audio przez zewnętrzny system TTS

Gemini Robotics ER 2 zwraca tekst. Aplikacja kieruje gotowe odpowiedzi do osobnego dostawcy TTS (np. Gemini TTS) za pomocą wstrzykniętego wywołania zwrotnego. Dzięki temu masz kontrolę nad opóźnieniem mowy, wyborem głosu i zachowaniem w przypadku przerwania, a także możesz przełączać interfejsy TTS bez zmiany logiki agenta.

Możesz też zadeklarować zamianę tekstu na mowę jako narzędzie, aby model traktował „powiedz coś” tak samo jak „przesuń ramię”. Dodaj do listy tools z pierwszej sekcji tę deklarację funkcji:

TOOLS = [
    {
        "name": "send_message",
        "description": (
            "Speak a message aloud via TTS, then deliver it to the"
            " specified target. Use target='user' to speak directly"
            " to the user, or a peer agent name (e.g., 'duo') to"
            " communicate with another robot."
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "target": {
                    "type": "string",
                    "description": "Recipient: 'user' or a peer agent name.",
                },
                "message": {
                    "type": "string",
                    "description": "The message to speak and deliver.",
                },
            },
            "required": ["target", "message"],
        },
    },
]

Umieszczając TTS w deklaracji funkcji, model obsługuje mowę za pomocą tej samej ścieżki wywoływania narzędzi co inne działania robota. Aplikacja realizuje wywołanie za pomocą wstrzykniętego wywołania zwrotnego.

Przykłady w GitHubie

Pełne przykłady działania, w tym demonstrację pobierania przekąsek przez robota Spot i przykład „hello world” z użyciem robota Tinybot z głowicą obrotowo-uchylną, znajdziesz w sekcji Przykłady interfejsu Robotics Live API.

Co dalej?