Skip to content

Rad/updated local sync#75

Merged
RadeenXALNW merged 2 commits into
mainfrom
rad/updated_local_sync
Oct 23, 2025
Merged

Rad/updated local sync#75
RadeenXALNW merged 2 commits into
mainfrom
rad/updated_local_sync

Conversation

@RadeenXALNW
Copy link
Copy Markdown
Collaborator

@RadeenXALNW RadeenXALNW commented Oct 11, 2025

Fixed sync with middleware as well as cloud deployment

Summary by CodeRabbit

  • New Features
    • Automatic streaming mode selection and unified JSON-serializable streaming output.
    • Richer status updates, error reporting, and middleware sync with invocation tracking.
  • Refactor
    • Simplified streaming generator handling and clearer local server logs/metadata.
  • Chores
    • Updated default server endpoint.
    • Template updates: added OpenAI dependency, relaxed llama-index version, removed unused entrypoints and OPENAI_API_KEY env var.
  • Tests
    • Example scripts updated with new agent IDs/tags and to print full streamed chunks.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 11, 2025

Walkthrough

Introduces client-side routing between REST and WebSocket based on entrypoint tag, overhauls WebSocket streaming to serialize chunks safely and integrate middleware synchronization, expands server-side middleware sync for start/complete/error paths, updates constants to a new base URL, adjusts templates (dependencies/config), and refreshes test scripts to new agent IDs/tags.

Changes

Cohort / File(s) Summary
Client streaming routing
runagent/client/client.py
Adds is_streaming flag from entrypoint_tag; run delegates to run_stream when streaming; run_stream returns generator directly; removes old entrypoint lookup comments; aligns _run_stream alias.
Config/base URL updates
runagent/constants.py
Changes ENV_RUNAGENT_BASE_URL and DEFAULT_BASE_URL to http://20.84.81.110:8333/.
Middleware sync (SDK)
runagent/sdk/deployment/middleware_sync.py
Replaces async helper with direct rest_client.http calls; detailed logging; revised payloads; explicit response parsing; improved error handling; maintains public signatures.
Local server middleware integration
runagent/sdk/server/local_server.py
Gates middleware sync by agent_synced_to_middleware; logs start/complete/error; includes richer execution_data (completed_at, runtime_seconds, input/result data, metadata); fallback if middleware IDs missing; enhanced error handling; updates startup logs.
WebSocket streaming serialization + tracking
runagent/sdk/server/socket_utils.py
Adds _convert_chunk_to_serializable; serializes all chunks before storing/sending; bounded storage; richer status payloads; improved error path and middleware sync integration; refines lifecycle logs; retains backward-compat wrapper.
Templates: agno
templates/agno/default/requirements.txt, templates/agno/default/runagent.config.json
Adds openai dependency; removes agno_assistant and agno_stream entrypoints from config; keeps stream/print variants.
Templates: llamaindex
templates/llamaindex/default/requirements.txt, templates/llamaindex/default/runagent.config.json
Broadens llama-index version (removes lower bound); removes OPENAI_API_KEY env_vars block.
Test scripts
test_scripts/python/client_test_agno.py, test_scripts/python/client_test_llamaindex.py
Updates agent_id and entrypoint_tag; in agno test, prints entire chunks instead of chunk['content']; minor formatting.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Client as RunAgentClient
  participant REST as REST API
  participant WS as WebSocket Server

  User->>Client: run(payload, entrypoint_tag)
  alt entrypoint_tag endsWith "_stream"
    Note over Client: is_streaming = true
    Client->>WS: run_stream(payload) [open WS, send]
    WS-->>Client: stream chunks (serialized)
    Client-->>User: yields chunks
  else
    Note over Client: is_streaming = false
    Client->>REST: POST /run
    REST-->>Client: JSON response
    Client-->>User: returns result
  end
Loading
sequenceDiagram
  autonumber
  participant Client as WS Client
  participant Server as socket_utils handler
  participant MW as Middleware Sync

  Client->>Server: connect + start stream
  rect rgba(230,245,255,0.6)
    Server->>MW: invocation_start(payload)
    MW-->>Server: {middleware_invocation_id}
    Note right of Server: Track IDs, emit stream_started status
  end

  loop For each chunk
    Server->>Server: _convert_chunk_to_serializable(chunk)
    Server-->>Client: send serialized chunk
    Server->>Server: store (bounded list)
  end

  alt success
    Server->>MW: invocation_complete(execution_data)
    MW-->>Server: ack
    Server-->>Client: stream_completed status
  else error
    Server->>MW: invocation_error(error_data)
    MW-->>Server: ack/fail
    Server-->>Client: error payload
  end

  Client-->>Server: close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • sawradip

Poem

A rabbit taps the WebSocket stream,
Bytes hop by with a serialized gleam.
Middleware nods, “I’ve got the trail,”
Start to finish, we won’t derail.
REST or stream, we choose the lane—
New burrows mapped across the plain.
Thump-thump: shipped again! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Rad/updated local sync” references part of the changeset related to local middleware synchronization but omits other significant updates like cloud deployment fixes and streaming enhancements, making it only a partial summary of the PR’s scope.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rad/updated_local_sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7997fbd and 24ac7a5.

📒 Files selected for processing (11)
  • runagent/client/client.py (4 hunks)
  • runagent/constants.py (1 hunks)
  • runagent/sdk/deployment/middleware_sync.py (2 hunks)
  • runagent/sdk/server/local_server.py (10 hunks)
  • runagent/sdk/server/socket_utils.py (17 hunks)
  • templates/agno/default/requirements.txt (1 hunks)
  • templates/agno/default/runagent.config.json (1 hunks)
  • templates/llamaindex/default/requirements.txt (1 hunks)
  • templates/llamaindex/default/runagent.config.json (0 hunks)
  • test_scripts/python/client_test_agno.py (2 hunks)
  • test_scripts/python/client_test_llamaindex.py (2 hunks)
💤 Files with no reviewable changes (1)
  • templates/llamaindex/default/runagent.config.json
🧰 Additional context used
🧬 Code graph analysis (6)
runagent/sdk/deployment/middleware_sync.py (2)
runagent/sdk/config.py (1)
  • base_url (295-297)
runagent/sdk/rest_client.py (3)
  • post (209-211)
  • get (205-207)
  • put (213-215)
test_scripts/python/client_test_llamaindex.py (2)
runagent-rust/runagent/src/client/runagent_client.rs (1)
  • entrypoint_tag (279-281)
runagent/client/client.py (1)
  • run (46-78)
runagent/client/client.py (2)
runagent/sdk/socket_client.py (1)
  • run_stream (72-111)
runagent/cli/commands.py (1)
  • run_stream (1084-1219)
test_scripts/python/client_test_agno.py (2)
runagent-rust/runagent/src/client/runagent_client.rs (1)
  • entrypoint_tag (279-281)
runagent/client/client.py (1)
  • run (46-78)
runagent/sdk/server/local_server.py (1)
runagent/sdk/deployment/middleware_sync.py (2)
  • is_sync_enabled (52-54)
  • sync_invocation_complete (145-182)
runagent/sdk/server/socket_utils.py (3)
runagent/utils/serializer.py (2)
  • CoreSerializer (8-218)
  • deserialize_message (118-154)
runagent/sdk/deployment/middleware_sync.py (3)
  • get_middleware_sync (269-281)
  • is_sync_enabled (52-54)
  • sync_invocation_start (108-142)
runagent/utils/schema.py (2)
  • WebSocketAgentRequest (109-119)
  • WebSocketActionType (103-106)
🪛 Ruff (0.13.3)
runagent/sdk/deployment/middleware_sync.py

78-78: f-string without any placeholders

Remove extraneous f prefix

(F541)


97-97: Do not catch blind exception: Exception

(BLE001)


98-98: Use explicit conversion flag

Replace with conversion flag

(RUF010)


101-101: Do not catch blind exception: Exception

(BLE001)


102-102: Use explicit conversion flag

Replace with conversion flag

(RUF010)


114-114: f-string without any placeholders

Remove extraneous f prefix

(F541)


125-125: f-string without any placeholders

Remove extraneous f prefix

(F541)


138-138: Consider moving this statement to an else block

(TRY300)


140-140: Do not catch blind exception: Exception

(BLE001)


141-141: Use explicit conversion flag

Replace with conversion flag

(RUF010)


175-175: f-string without any placeholders

Remove extraneous f prefix

(F541)


178-178: Consider moving this statement to an else block

(TRY300)


180-180: Do not catch blind exception: Exception

(BLE001)


181-181: Use explicit conversion flag

Replace with conversion flag

(RUF010)

runagent/client/client.py

52-52: f-string without any placeholders

Remove extraneous f prefix

(F541)

runagent/sdk/server/local_server.py

796-796: f-string without any placeholders

Remove extraneous f prefix

(F541)


821-821: f-string without any placeholders

Remove extraneous f prefix

(F541)


823-823: Do not catch blind exception: Exception

(BLE001)


876-876: f-string without any placeholders

Remove extraneous f prefix

(F541)


887-887: f-string without any placeholders

Remove extraneous f prefix

(F541)


889-889: f-string without any placeholders

Remove extraneous f prefix

(F541)


891-891: Do not catch blind exception: Exception

(BLE001)


896-896: f-string without any placeholders

Remove extraneous f prefix

(F541)


993-993: f-string without any placeholders

Remove extraneous f prefix

(F541)


1004-1004: f-string without any placeholders

Remove extraneous f prefix

(F541)


1006-1006: f-string without any placeholders

Remove extraneous f prefix

(F541)


1008-1008: Do not catch blind exception: Exception

(BLE001)

runagent/sdk/server/socket_utils.py

46-46: Do not use bare except

(E722)


46-47: try-except-pass detected, consider logging the exception

(S110)


53-53: Do not use bare except

(E722)


53-54: try-except-pass detected, consider logging the exception

(S110)


64-64: Do not use bare except

(E722)


64-65: try-except-pass detected, consider logging the exception

(S110)


73-73: Do not use bare except

(E722)


73-74: try-except-pass detected, consider logging the exception

(S110)


163-163: f-string without any placeholders

Remove extraneous f prefix

(F541)


187-187: Do not catch blind exception: Exception

(BLE001)


210-210: Do not catch blind exception: Exception

(BLE001)


223-223: Do not catch blind exception: Exception

(BLE001)


232-232: Do not catch blind exception: Exception

(BLE001)


237-237: Use explicit conversion flag

Replace with conversion flag

(RUF010)


266-266: f-string without any placeholders

Remove extraneous f prefix

(F541)


268-268: Do not catch blind exception: Exception

(BLE001)


274-274: f-string without any placeholders

Remove extraneous f prefix

(F541)


288-288: f-string without any placeholders

Remove extraneous f prefix

(F541)


290-290: f-string without any placeholders

Remove extraneous f prefix

(F541)


292-292: Do not catch blind exception: Exception

(BLE001)


322-322: f-string without any placeholders

Remove extraneous f prefix

(F541)


334-334: f-string without any placeholders

Remove extraneous f prefix

(F541)


336-336: Do not catch blind exception: Exception

(BLE001)


407-407: Do not catch blind exception: Exception

(BLE001)


408-408: Use explicit conversion flag

Replace with conversion flag

(RUF010)


421-421: Do not catch blind exception: Exception

(BLE001)


422-422: Use explicit conversion flag

Replace with conversion flag

(RUF010)


423-423: Use explicit conversion flag

Replace with conversion flag

(RUF010)

Comment on lines +82 to 99
try:
response = self.rest_client.http.post("/local-agents", data=sync_data, timeout=30)
result = response.json() if hasattr(response, 'json') else response

console.print(f"[cyan]Response status: {response.status_code if hasattr(response, 'status_code') else 'N/A'}[/cyan]")
console.print(f"[cyan]Response: {result}[/cyan]")

if result.get("success"):
console.print("[green]✅ Agent synced successfully[/green]")
return True
else:
error_msg = result.get("error", "Unknown error")
console.print(f"[yellow]⚠️ Agent sync failed: {error_msg}[/yellow]")
return False

except Exception as e:
console.print(f"[red]❌ HTTP request failed: {str(e)}[/red]")
return False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Prevent blocking the event loop in async middleware sync
sync_agent_startup, sync_invocation_start, and sync_invocation_complete now call self.rest_client.http.post/put directly inside async def methods. Those helpers are synchronous (requests-based), so this change runs blocking I/O on the event loop thread. Under load the local FastAPI server will stall while waiting for each middleware call, undoing the previous asyncio.to_thread safeguard. Please offload these calls to a thread (or reuse _make_async_request) so the event loop stays responsive.

-            try:
-                response = self.rest_client.http.post("/local-agents", data=sync_data, timeout=30)
+            try:
+                response = await asyncio.to_thread(
+                    self.rest_client.http.post,
+                    "/local-agents",
+                    data=sync_data,
+                    timeout=30,
+                )
@@
-            response = self.rest_client.http.post("/local-agents/invocations", data=sync_payload, timeout=30)
+            response = await asyncio.to_thread(
+                self.rest_client.http.post,
+                "/local-agents/invocations",
+                data=sync_payload,
+                timeout=30,
+            )
@@
-            response = self.rest_client.http.put(f"/local-agents/invocations/{execution_id}", data=update_payload, timeout=30)
+            response = await asyncio.to_thread(
+                self.rest_client.http.put,
+                f"/local-agents/invocations/{execution_id}",
+                data=update_payload,
+                timeout=30,
+            )

Also applies to: 125-138, 169-177

🧰 Tools
🪛 Ruff (0.13.3)

97-97: Do not catch blind exception: Exception

(BLE001)


98-98: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
In runagent/sdk/deployment/middleware_sync.py around lines 82-99 (and similarly
for 125-138 and 169-177), the code calls synchronous HTTP helpers
(self.rest_client.http.post/put) directly inside async methods which blocks the
event loop; change those direct calls to run in a thread by either awaiting the
existing helper that wraps sync calls (e.g. reuse self._make_async_request) or
by doing: response = await asyncio.to_thread(self.rest_client.http.post,
"/local-agents", sync_data, timeout=30) (and likewise for put), then process
response.json() and status_code as before; add "import asyncio" at top if using
asyncio.to_thread and ensure exception handling and return booleans remain
identical.

Comment on lines +43 to +54
if hasattr(chunk, 'dict') and callable(chunk.dict):
try:
ws_request = WebSocketAgentRequest(**request_msg.data)
except Exception as e:
await self._send_error(websocket, f"Invalid request format: {str(e)}")
return

if ws_request.action == WebSocketActionType.START_STREAM:
await self._handle_stream_start(websocket, ws_request, connection_id, agent_execution_streamer)
elif ws_request.action == WebSocketActionType.PING:
await self._send_pong(websocket)
else:
await self._send_error(websocket, f"Unknown action: {ws_request.action}")

except WebSocketDisconnect:
console.print(f"WebSocket disconnected for agent: [cyan]{agent_id}[/cyan]")
self._cleanup_stream(connection_id)
except Exception as e:
console.print(f"💥 WebSocket error for agent {agent_id}: [red]{str(e)}[/red]")
await self._send_error(websocket, f"Server error: {str(e)}")
self._cleanup_stream(connection_id)
return chunk.dict()
except:
pass

# Objects with model_dump() method (Pydantic v2)
if hasattr(chunk, 'model_dump') and callable(chunk.model_dump):
try:
return chunk.model_dump()
except:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix Pydantic chunk conversion to stay JSON-serializable

chunk.dict() / chunk.model_dump() frequently leave datetime and other Python objects inside the payload, so the later json.dumps(...) call throws TypeError and streaming falls into the error branch. Re-run the conversion through _convert_chunk_to_serializable (or use the JSON mode) before returning so every chunk is safely serializable.

Apply this diff:

-                return chunk.dict()
+                return self._convert_chunk_to_serializable(chunk.dict())
@@
-                return chunk.model_dump()
+                return self._convert_chunk_to_serializable(chunk.model_dump())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if hasattr(chunk, 'dict') and callable(chunk.dict):
try:
ws_request = WebSocketAgentRequest(**request_msg.data)
except Exception as e:
await self._send_error(websocket, f"Invalid request format: {str(e)}")
return
if ws_request.action == WebSocketActionType.START_STREAM:
await self._handle_stream_start(websocket, ws_request, connection_id, agent_execution_streamer)
elif ws_request.action == WebSocketActionType.PING:
await self._send_pong(websocket)
else:
await self._send_error(websocket, f"Unknown action: {ws_request.action}")
except WebSocketDisconnect:
console.print(f"WebSocket disconnected for agent: [cyan]{agent_id}[/cyan]")
self._cleanup_stream(connection_id)
except Exception as e:
console.print(f"💥 WebSocket error for agent {agent_id}: [red]{str(e)}[/red]")
await self._send_error(websocket, f"Server error: {str(e)}")
self._cleanup_stream(connection_id)
return chunk.dict()
except:
pass
# Objects with model_dump() method (Pydantic v2)
if hasattr(chunk, 'model_dump') and callable(chunk.model_dump):
try:
return chunk.model_dump()
except:
pass
if hasattr(chunk, 'dict') and callable(chunk.dict):
try:
return self._convert_chunk_to_serializable(chunk.dict())
except:
pass
# Objects with model_dump() method (Pydantic v2)
if hasattr(chunk, 'model_dump') and callable(chunk.model_dump):
try:
return self._convert_chunk_to_serializable(chunk.model_dump())
except:
pass
🧰 Tools
🪛 Ruff (0.13.3)

46-46: Do not use bare except

(E722)


46-47: try-except-pass detected, consider logging the exception

(S110)


53-53: Do not use bare except

(E722)


53-54: try-except-pass detected, consider logging the exception

(S110)

🤖 Prompt for AI Agents
In runagent/sdk/server/socket_utils.py around lines 43 to 54, the code returns
raw results of chunk.dict() or chunk.model_dump() which can still contain
non-JSON-serializable objects (e.g., datetimes); after successfully calling
chunk.dict() or chunk.model_dump(), pass the resulting dict through the existing
_convert_chunk_to_serializable(...) helper (or call model_dump(mode="json") if
preferred) and return that converted value so all chunks are JSON-serializable;
keep the try/except structure and only return the converted payload on success.

@RadeenXALNW RadeenXALNW merged commit 1fa6e81 into main Oct 23, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant