Sign in

Python SDK — ADK V2 Streaming

Agent execution, ADK V2 WebSocket streaming, callbacks, local tools, sessions, async requests, and skill generation

sdkpythonadkstreamingwebsockettools

Updated 2026-11-03

Verified 2026-11-03

The Python SDK exposes ADK through client.adk and the higher-level client.send_message() helper. Use REST converse() for simple request/response calls. Use ADK V2 streaming when you need real-time chunks, local tool execution, model overrides, skills, images, or prompt-only agents.

Conversation User ID

For normal user tokens, user_id must match the Cosmos user id in the token. It is not a display id and it is not the email address.

python
claims = client.auth.decode_token()
conversation_user_id = claims.sub or claims.user_id

Passing a different value returns HTTP 403 with Token user is not authorized for the requested conversation user.

REST Converse

python
result = await client.adk.converse(
    query="What were the main Q1 risks?",
    user_id=conversation_user_id,
    agent_id="agent-uuid",
    agent_version="1.0",
    channel="chat",
    reasoning_event=True,
)

print(result["session_id"])
print(result["response"]["content"])

ADK owns session creation and persistence. Pass the same user_id and agent/app identity for normal multi-turn behavior.

V2 Streaming

client.send_message() keeps an ADK V2 WebSocket session open and auto-wires client.tool_manager definitions and handlers.

python
from cosmos_sdk import ADKV2SendOptions, ADKV2TurnCallbacks


callbacks = ADKV2TurnCallbacks(
    on_chunk=lambda text: print(text, end="", flush=True),
    on_reasoning=lambda content: print(f"\nReasoning: {content}"),
    on_error=lambda code, message, fatal: print(f"\n{code}: {message}"),
)

result = await client.send_message(
    "Analyze this support case.",
    user_id=conversation_user_id,
    callbacks=callbacks,
    options=ADKV2SendOptions(
        agent_id="agent-uuid",
        agent_version="1.0",
        provider_id="azure_openai",
        model_id="gpt-4o",
        session_state={"project_id": "project-uuid"},
    ),
)

For a follow-up turn, call send_message() again. Close the WebSocket when the conversation is done:

python
await client.send_message("What should we do next?", user_id=conversation_user_id)
client.close_adk_session()

Start Options

Common ADKV2SendOptions fields:

FieldPurpose
agent_id, agent_versionRun a configured agent
app_id, app_versionRun a configured app
system_promptPrompt-only mode or additional prompt input
additional_instructionsAppend request-specific instructions without replacing the agent prompt
additional_collection_idsAttach extra KB collections for the turn
provider_id, model_idOverride model selection for this session
temperature, max_output_tokensModel generation controls
skillsAttach explicit skill versions, e.g. [{"id": "...", "version": "1.0"}]
toolsClient-side tool definitions
exclude_toolsRemove named server-side tools from the agent prompt/tool set
imagesDMS URLs or image payloads for multimodal requests
session_stateInitial state merged into the ADK session
mcp_serversPer-session server-side MCP server definitions
execution_mode"direct" or "plan_execute"
app_nameObservability label override
max_tool_roundtripsClient-side guard against runaway tool loops

Advanced fields such as mcp_servers and execution_mode should be used only when the target ADK deployment supports them.

ADK owns context compaction and history filtering. ADKV2SendOptions still accepts token_threshold, compaction_interval, overlap_size, and event_retention_size and forwards them on the start frame, but the ADK V2 start schema does not define them, so they have no effect. Leave them unset in new code.

Callbacks

python
callbacks = ADKV2TurnCallbacks(
    on_chunk=lambda text: ...,
    on_tool_start=lambda name: ...,
    on_tool_complete=lambda name, result: ...,
    on_tool_call_args_delta=lambda name, json_path, delta: ...,
    on_reasoning=lambda content: ...,
    on_error=lambda code, message, fatal: ...,
    on_custom_event=lambda event_type, payload: ...,
    on_plan_proposed=lambda plan_id, steps, summary: ...,
    on_plan_step_start=lambda plan_id, step_id: ...,
    on_plan_step_complete=lambda plan_id, step_id, status, preview: ...,
)

Plan approval and rejection helpers are intentionally not exposed by the Python SDK. Plan events are observable through callbacks, while execution control remains with the ADK protocol.

Local Tool Execution

Register tools on client.tool_manager; send_message() includes the schemas and dispatches tool calls to handlers.

python
from cosmos_sdk import ToolDefinition, ToolResult


async def get_order(args: dict) -> ToolResult:
    order_id = args["order_id"]
    return ToolResult(ok=True, data={"order_id": order_id, "status": "shipped"})


client.tool_manager.register_tool(
    "get_order",
    handler=get_order,
    definition=ToolDefinition(
        name="get_order",
        description="Get an order by ID",
        input_schema={
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    ),
)

await client.send_message(
    "Where is order 1001?",
    user_id=conversation_user_id,
    options=ADKV2SendOptions(agent_id="agent-uuid"),
)

The server sends tool_call; the SDK invokes the Python handler and sends tool_result back over the same WebSocket.

Tool Results and Errors

Handler outcomeresult senterror sent
ToolResult(ok=True, data=...)data (strings as-is, other values JSON-encoded)Not sent
ToolResult(ok=False, error={"code": ..., "message": ...})JSON of the error dicterror["message"], or "Tool failed" when no message is set
Handler raisesJSON of {"code": "TOOL_EXECUTION_FAILED", "message": str(exc)}str(exc)
No handler registeredJSON of {"code": "TOOL_NOT_FOUND", ...}No handler registered for tool: <name>

When error is present, ADK returns {"error": "<message>"} to the model as the tool response, so the model sees the real failure reason. ADK also returns error responses itself when a client tool times out, is cancelled, or the connection closes.

python
async def get_order(args: dict) -> ToolResult:
    order = await orders.find(args["order_id"])
    if order is None:
        # The model receives: {"error": "Order 1001 not found"}
        return ToolResult(ok=False, error={"code": "TOOL_EXECUTION_FAILED", "message": f"Order {args['order_id']} not found"})
    return ToolResult(ok=True, data=order)

Server Events

ADK V2 emits lower-case event names:

EventMeaning
sessionSession ID assigned or resumed
chunkStreamed assistant text
tool_callRequest for the client to run a local tool
tool_call_args_deltaPartial function-call arguments while the model is still generating
tool_start, tool_completeServer-side tool lifecycle
reasoningAgent transfer, tool, file, image, skill, or RAG reasoning update
custom_eventService-defined extension event
plan_proposed, plan_step_start, plan_step_completePlan/execute progress events
turn_completeTurn finished with usage and session ID
errorError with code, user-facing message, and fatal flag
pingKeepalive; SDK responds automatically

Fatal errors close the WebSocket and should be treated as terminal for that turn.

Async Requests

Use async requests for long-running work where the caller does not need a live stream.

python
queued = await client.adk.submit_async(
    query="Generate a compliance report",
    user_id=conversation_user_id,
    agent_id="agent-uuid",
    agent_version="1.0",
    callback_url="https://app.example.com/webhooks/adk",
)

final = await client.adk.poll_async_until_done(queued["request_id"])
print(final["status"])

Sessions

python
sessions = await client.adk.list_sessions(user_id=conversation_user_id, agent_id="agent-uuid")
events = await client.adk.get_session_events(sessions[0]["id"])

await client.adk.update_session_metadata(
    sessions[0]["id"],
    {"title": "Q1 risk review", "tags": ["risk", "q1"]},
)

Skill Generation

python
result = await client.generate_skills(
    source="description",
    description="Help support agents resolve refund requests",
    provider_id="azure_openai",
    model_id="gpt-4o",
    max_skills=3,
)

See Also