robustness: vision/depth render off the event loop (deepcopy snapshot), chat auto-reconnect + friendly errors, demo catches connection failures
This commit is contained in:
@@ -95,3 +95,4 @@
|
|||||||
[ 183147ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
|
[ 183147ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
|
||||||
[ 188179ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
|
[ 188179ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
|
||||||
[ 192874ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
|
[ 192874ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
|
||||||
|
[ 1348483ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.9 KiB |
+14
-5
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
|
import copy
|
||||||
import io
|
import io
|
||||||
import math
|
import math
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -206,11 +207,18 @@ class RoomBridge(Bridge):
|
|||||||
)
|
)
|
||||||
async def vision() -> VisionOutput:
|
async def vision() -> VisionOutput:
|
||||||
tick = self._tick()
|
tick = self._tick()
|
||||||
img, _ = renderer.render(world)
|
# Render off the event loop so frames never stall the bridge under
|
||||||
buf = io.BytesIO()
|
# load; deepcopy keeps the snapshot consistent with this tool call.
|
||||||
img.save(buf, format="PNG")
|
snapshot = copy.deepcopy(world)
|
||||||
|
img, _ = await asyncio.to_thread(renderer.render, snapshot)
|
||||||
|
|
||||||
|
def _encode() -> str:
|
||||||
|
buf = io.BytesIO()
|
||||||
|
img.save(buf, format="PNG")
|
||||||
|
return base64.b64encode(buf.getvalue()).decode("ascii")
|
||||||
|
|
||||||
return VisionOutput(
|
return VisionOutput(
|
||||||
png_b64=base64.b64encode(buf.getvalue()).decode("ascii"),
|
png_b64=await asyncio.to_thread(_encode),
|
||||||
width=VISION_WIDTH,
|
width=VISION_WIDTH,
|
||||||
height=VISION_HEIGHT,
|
height=VISION_HEIGHT,
|
||||||
tick=tick,
|
tick=tick,
|
||||||
@@ -222,7 +230,8 @@ class RoomBridge(Bridge):
|
|||||||
)
|
)
|
||||||
async def depth(max_depth: float = 10.0) -> DepthOutput:
|
async def depth(max_depth: float = 10.0) -> DepthOutput:
|
||||||
tick = self._tick()
|
tick = self._tick()
|
||||||
_, grid = renderer.render(world, max_depth=max_depth)
|
snapshot = copy.deepcopy(world)
|
||||||
|
_, grid = await asyncio.to_thread(renderer.render, snapshot, max_depth)
|
||||||
return DepthOutput(
|
return DepthOutput(
|
||||||
width=DEPTH_WIDTH,
|
width=DEPTH_WIDTH,
|
||||||
height=DEPTH_HEIGHT,
|
height=DEPTH_HEIGHT,
|
||||||
|
|||||||
+28
-6
@@ -191,12 +191,34 @@ async def chat_loop(client: AICCClient, manifest, args: argparse.Namespace) -> i
|
|||||||
|
|
||||||
|
|
||||||
async def run(args: argparse.Namespace) -> int:
|
async def run(args: argparse.Namespace) -> int:
|
||||||
transport = WebSocketClientTransport(args.url)
|
"""Run the chat session, reconnecting to the bridge if the connection dies."""
|
||||||
async with AICCClient(transport) as client:
|
attempts = 0
|
||||||
manifest = await client.handshake()
|
while True:
|
||||||
print(f"[handshake] session {manifest.session_id} world {manifest.world.name}")
|
try:
|
||||||
print(f"[handshake] tools: {[t.id for t in manifest.tools]}")
|
transport = WebSocketClientTransport(args.url)
|
||||||
return await chat_loop(client, manifest, args)
|
async with AICCClient(transport) as client:
|
||||||
|
manifest = await client.handshake()
|
||||||
|
print(
|
||||||
|
f"[handshake] session {manifest.session_id} world {manifest.world.name}"
|
||||||
|
)
|
||||||
|
print(f"[handshake] tools: {[t.id for t in manifest.tools]}")
|
||||||
|
return await chat_loop(client, manifest, args)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
raise
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except Exception as exc: # noqa: BLE001 - connection lost: reconnect
|
||||||
|
attempts += 1
|
||||||
|
print(f"\n[chat] connection lost ({type(exc).__name__}: {exc})")
|
||||||
|
if attempts >= 3:
|
||||||
|
print(
|
||||||
|
"[chat] giving up after 3 attempts — is the bridge running? (scripts/run_bridge.sh)"
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
print(
|
||||||
|
"[chat] reconnecting in 2 seconds… (the room keeps its state on the bridge)"
|
||||||
|
)
|
||||||
|
await asyncio.sleep(2.0)
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
|||||||
+11
-2
@@ -152,7 +152,9 @@ async def run_llm_agent(
|
|||||||
log=lambda role, msg: print(f"[{role}] {msg}"),
|
log=lambda role, msg: print(f"[{role}] {msg}"),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
return await run_llm_agent_loop(controller, max_steps, log=print_log, recorder=recorder)
|
return await run_llm_agent_loop(
|
||||||
|
controller, max_steps, log=print_log, recorder=recorder
|
||||||
|
)
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
return {"steps": 0, "tool_calls": 0, "result": str(exc), "interacted": False}
|
return {"steps": 0, "tool_calls": 0, "result": str(exc), "interacted": False}
|
||||||
|
|
||||||
@@ -398,7 +400,14 @@ def main() -> int:
|
|||||||
"into this directory, plus demo.gif animation and demo_summary.png",
|
"into this directory, plus demo.gif animation and demo_summary.png",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
summary = asyncio.run(run_demo(args))
|
try:
|
||||||
|
summary = asyncio.run(run_demo(args))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
return 2
|
||||||
|
except Exception as exc: # noqa: BLE001 - friendly failure instead of a traceback
|
||||||
|
print(f"\n[demo] failed: {type(exc).__name__}: {exc}")
|
||||||
|
print("[demo] is the bridge running? scripts/run_bridge.sh")
|
||||||
|
return 1
|
||||||
return 0 if summary.get("interacted") else 1
|
return 0 if summary.get("interacted") else 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user