-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
🎤 Tell us your idea
Agent Engine :streamQuery?alt=sse + AG-UI: yielding SSE strings gets JSON-encoded (double-encoding), breaking direct CopilotKit compatibility
Summary
I’m deploying an ADK agent to Vertex AI Agent Engine (using vertexai.agent_engines.templates.adk.AdkApp) and trying to expose it as an AG-UI-compatible streaming endpoint so that a frontend like CopilotKit can connect directly.
However, when my async_stream_query generator yields SSE-formatted strings (e.g. "data: {...}\n\n" produced by an AG-UI EventEncoder), Agent Engine appears to serialize those strings as JSON strings inside the SSE transport. This results in “SSE inside JSON inside SSE” (double-encoding) and breaks clients that expect standard AG-UI SSE.
Goal
Enable a clean, direct path:
- Agent Engine
:streamQuery?alt=sse➜ valid AG-UI SSE
without requiring an external proxy/middleware.
Current behavior
If the generator yields a string like:
data: {"type":"RUN_STARTED",...}\n\n
The client receives SSE where the payload is a quoted JSON string containing escaped newlines, e.g. (illustrative):
data: "data: {\"type\":\"RUN_STARTED\",\"threadId\":\"t1\",\"runId\":\"r1\"}\n\n"So the consumer would need to:
parse SSE
JSON-decode the string
parse SSE again
Expected behavior (one of these would work)
Option A (recommended): Document/enable a pattern where the generator yields structured events/objects (dict) and Agent Engine’s SSE transport emits:
data: {"type":"RUN_STARTED","threadId":"t1","runId":"r1"}
Option B: Support a “raw SSE passthrough” mode so that if the generator yields an SSE-formatted string, it is streamed verbatim when alt=sse is used.
Minimal repro
Backend (simplified)
from vertexai.agent_engines.templates.adk import AdkApp
from ag_ui.encoder import EventEncoder
from ag_ui.core import RunStartedEvent
class MyAgentEngineApp(AdkApp):
async def async_stream_query(self, *, user_id: str, message: str, **kwargs):
encoder = EventEncoder()
# encoder.encode(...) returns a string like: "data: {...}\n\n"
yield encoder.encode(RunStartedEvent(thread_id="t1", run_id="r1"))
# ... later
yield encoder.encode({"type": "RUN_FINISHED"})Call
curl \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://LOCATION-aiplatform.googleapis.com/v1/projects/PROJECT/locations/LOCATION/reasoningEngines/RESOURCE_ID:streamQuery?alt=sse" \
-d '{
"class_method":"stream_query",
"input": {
"user_id":"u_123",
"session_id":"s_123",
"message":"hello"
}
}'Why this matters
AG-UI clients (and CopilotKit integrations that rely on AG-UI) expect standard SSE where each data: line is a JSON object for the event. Double-encoding forces brittle client-side hacks or extra infrastructure.
Questions
What is the recommended way to expose AG-UI from an ADK agent deployed on Agent Engine?
Is there a supported way to stream raw SSE (passthrough) from :streamQuery?alt=sse?
If the correct approach is “yield dicts/objects”:
could docs include a concrete example showing how to translate ADK/agent events → AG-UI events without using EventEncoder.encode() when running on Agent Engine?
References
References
-
Agent Engine streaming API (:streamQuery?alt=sse): https://docs.cloud.google.com/agent-builder/agent-engine/use/custom
-
AdkApp template docs: https://docs.cloud.google.com/agent-builder/agent-engine/develop/adk
-
AG-UI server/streaming expectations: https://docs.agentwire.io/quickstart/build