Invocations protocol for Azure AI Hosted Agents
Project description
Azure AI Agent Server Invocations client library for Python
The azure-ai-agentserver-invocations package provides the invocation protocol endpoints for Azure AI Hosted Agent containers. It plugs into the azure-ai-agentserver-core host framework and supports two transports on the same host:
- HTTP (
invocationsprotocol) —POST /invocations,GET /invocations/{id},POST /invocations/{id}/cancel,GET /invocations/docs/openapi.json. - WebSocket (
invocations_wsprotocol) — full-duplex streaming at/invocations_ws, registered with@app.ws_handler.
Getting started
Install the package
pip install azure-ai-agentserver-invocations
This automatically installs azure-ai-agentserver-core as a dependency.
Prerequisites
- Python 3.10 or later
Key concepts
InvocationAgentServerHost
InvocationAgentServerHost is an AgentServerHost subclass that adds invocation protocol endpoints. It provides decorator methods for registering handler functions:
@app.invoke_handler— Required. HandlesPOST /invocations.@app.get_invocation_handler— Optional. HandlesGET /invocations/{id}.@app.cancel_invocation_handler— Optional. HandlesPOST /invocations/{id}/cancel.@app.ws_handler— Optional. Handles WebSocket connections at/invocations_ws.
Protocol endpoints
| Method | Route | Required | Description |
|---|---|---|---|
POST |
/invocations |
Yes | Execute the agent |
GET |
/invocations/{invocation_id} |
No | Retrieve invocation status or result |
POST |
/invocations/{invocation_id}/cancel |
No | Cancel a running invocation |
GET |
/invocations/docs/openapi.json |
No | Serve the agent's OpenAPI 3.x spec |
WS |
/invocations_ws |
No | Full-duplex WebSocket transport (invocations_ws protocol) |
Request and response headers
The SDK automatically manages these headers on every invocation:
| Header | Direction | Description |
|---|---|---|
x-agent-invocation-id |
Request & Response | Echoed if provided, otherwise a UUID is generated |
x-agent-session-id |
Response (POST only) | Resolved from agent_session_id query param, FOUNDRY_AGENT_SESSION_ID env var, or generated UUID |
Session ID resolution
Session IDs group related invocations into a conversation. The SDK resolves the session ID in order:
agent_session_idquery parameter onPOST /invocationsFOUNDRY_AGENT_SESSION_IDenvironment variable- Auto-generated UUID
The resolved session ID is available in handler functions via request.state.session_id.
Handler access to SDK state
Inside handler functions, the SDK sets these attributes on request.state:
request.state.invocation_id— The invocation ID (echoed or generated).request.state.session_id— The resolved session ID (POST /invocations only).
Distributed tracing
When tracing is enabled on the AgentServerHost, invocation spans are automatically created with GenAI semantic conventions:
- Span name:
invoke_agent {FOUNDRY_AGENT_NAME}:{FOUNDRY_AGENT_VERSION} - Span attributes:
gen_ai.system,gen_ai.operation.name,gen_ai.response.id,gen_ai.agent.id,gen_ai.agent.name,gen_ai.agent.version,microsoft.session.id - Error tags:
azure.ai.agentserver.invocations.error.code,.error.message - Baggage keys:
azure.ai.agentserver.invocation_id,.session_id
Examples
Simple synchronous agent
from azure.ai.agentserver.invocations import InvocationAgentServerHost
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
app = InvocationAgentServerHost()
@app.invoke_handler
async def handle(request: Request) -> Response:
data = await request.json()
return JSONResponse({"greeting": f"Hello, {data['name']}!"})
app.run()
Long-running operations with polling
import asyncio
import json
from azure.ai.agentserver.invocations import InvocationAgentServerHost
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
_tasks: dict[str, asyncio.Task] = {}
_results: dict[str, bytes] = {}
app = InvocationAgentServerHost()
@app.invoke_handler
async def handle(request: Request) -> Response:
data = await request.json()
invocation_id = request.state.invocation_id
task = asyncio.create_task(do_work(invocation_id, data))
_tasks[invocation_id] = task
return JSONResponse({"invocation_id": invocation_id, "status": "running"})
@app.get_invocation_handler
async def get_invocation(request: Request) -> Response:
invocation_id = request.state.invocation_id
if invocation_id in _results:
return Response(content=_results[invocation_id], media_type="application/json")
return JSONResponse({"invocation_id": invocation_id, "status": "running"})
@app.cancel_invocation_handler
async def cancel_invocation(request: Request) -> Response:
invocation_id = request.state.invocation_id
if invocation_id in _tasks:
_tasks[invocation_id].cancel()
del _tasks[invocation_id]
return JSONResponse({"invocation_id": invocation_id, "status": "cancelled"})
return JSONResponse({"error": "not found"}, status_code=404)
Streaming (Server-Sent Events)
import json
from azure.ai.agentserver.invocations import InvocationAgentServerHost
from starlette.requests import Request
from starlette.responses import Response, StreamingResponse
app = InvocationAgentServerHost()
@app.invoke_handler
async def handle(request: Request) -> Response:
async def generate():
for word in ["Hello", " ", "world", "!"]:
yield json.dumps({"delta": word}).encode() + b"\n"
return StreamingResponse(generate(), media_type="text/event-stream")
Multi-turn conversation
Use the agent_session_id query parameter to group invocations into a conversation:
# First turn
curl -X POST "http://localhost:8088/invocations?agent_session_id=session-abc" \
-H "Content-Type: application/json" \
-d '{"message": "My name is Alice"}'
# Second turn (same session)
curl -X POST "http://localhost:8088/invocations?agent_session_id=session-abc" \
-H "Content-Type: application/json" \
-d '{"message": "What is my name?"}'
The session ID is available in the handler via request.state.session_id.
Serving an OpenAPI spec
Pass an OpenAPI spec dict to enable the discovery endpoint at GET /invocations/docs/openapi.json:
app = InvocationAgentServerHost(openapi_spec={
"openapi": "3.0.3",
"info": {"title": "My Agent", "version": "1.0.0"},
"paths": { ... },
})
WebSocket protocol (invocations_ws)
The same InvocationAgentServerHost object also exposes a WebSocket transport at /invocations_ws. Container authors do not install or import a second package — registering an @app.ws_handler is the only step. A multi-protocol agent shares one host, one session, and one process.
Quick start
from azure.ai.agentserver.invocations import InvocationAgentServerHost
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.websockets import WebSocket
app = InvocationAgentServerHost()
@app.invoke_handler # POST /invocations (HTTP)
async def invoke(request: Request) -> Response:
payload = await request.json()
return JSONResponse({"echo": payload})
@app.ws_handler # /invocations_ws (WebSocket)
async def ws(websocket: WebSocket) -> None:
async for message in websocket.iter_text():
await websocket.send_text(message)
app.run()
What the SDK does for @app.ws_handler
- Registers
/invocations_wson the same Starlette host as/invocationsand/readiness. - Calls
await websocket.accept()before invoking your handler. - Runs WebSocket Ping/Pong keep-alive in the background — disabled by default; enable by setting the
WS_KEEPALIVE_INTERVALenvironment variable (auto-injected by AgentService into hosted-agent containers). Set the value to0to disable. Frames are sent at the WebSocket protocol layer (RFC 6455 opcode0x9/0xA) by the underlying Hypercorn server, which keeps the connection alive across upstream proxy / load-balancer idle timeouts without any extra application traffic. - Closes the connection cleanly on handler return (close code
1000) or maps an uncaught handler exception to close code1011. - Emits a structured close-event log line carrying
azure.ai.agentserver.invocations_ws.session_id,azure.ai.agentserver.invocations_ws.close_code, andazure.ai.agentserver.invocations_ws.duration_ms. The same fields are recorded as OpenTelemetry span attributes so the connection lifetime is visible end-to-end. - Inherits
/readiness, OpenTelemetry export, graceful shutdown, and thex-platform-serveridentity header fromazure-ai-agentserver-core.
Per-connection tracing
A WebSocket connection is wrapped by the SDK in a single connection-scoped websocket_session OpenTelemetry span. The span carries the GenAI semantic-convention attributes plus azure.ai.agentserver.invocations_ws.session_id, close_code, and duration_ms. Any child spans your handler opens — e.g. via opentelemetry.trace.get_tracer(...).start_as_current_span(...) — are automatically parented to the connection span.
Handler signature
The handler receives a Starlette WebSocket and returns None. The full WebSocket API — iter_text, iter_bytes, iter_json, send_text, send_bytes, send_json, close, headers, query_params, client, state — is available, so application protocols on top of invocations_ws are entirely under your control.
Reference: configuration
| Environment variable | Default | Description |
|---|---|---|
WS_KEEPALIVE_INTERVAL |
unset (disabled) | Platform-injected WebSocket Ping interval, in seconds. 0 disables keep-alive. Surfaced on app.config.ws_ping_interval and wired into Hypercorn's websocket_ping_interval by AgentServerHost. |
Reference: close codes
| Close code | Meaning |
|---|---|
1000 |
Handler returned cleanly (normal close). |
1011 |
Handler raised an unhandled exception (mapped by the SDK). |
4000-4999 |
Application-defined codes (set by the handler via await websocket.close(code=...) — surfaced unchanged to the client). |
Troubleshooting
Reporting issues
To report an issue with the client library, or request additional features, please open a GitHub issue here.
Next steps
Visit the Samples folder for complete working examples:
| Sample | Description |
|---|---|
| simple_invoke_agent | Minimal synchronous request-response |
| async_invoke_agent | Long-running operations with polling and cancellation |
| ws_invoke_agent | Combined POST /invocations (HTTP) and /invocations_ws (WebSocket) host |
| ws_bidirectional_streaming_agent | Full-duplex /invocations_ws agent: concurrent token streams + mid-flight cancel (relies on the SDK's WS protocol Ping/Pong keep-alive, not application-level heartbeats) |
Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file azure_ai_agentserver_invocations-1.0.0b4.tar.gz.
File metadata
- Download URL: azure_ai_agentserver_invocations-1.0.0b4.tar.gz
- Upload date:
- Size: 56.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: RestSharp/106.13.0.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d78748e36b6120fd24161baea6c54de9687b3fe3f607a97766dab30e4f810ed
|
|
| MD5 |
3f4891e9e4173e74b1d26cd0f557f6f6
|
|
| BLAKE2b-256 |
858a1ec5e3212d0fc6ffd11dbbfef252a1f80e3305993f57069654abffa3ac0a
|
File details
Details for the file azure_ai_agentserver_invocations-1.0.0b4-py3-none-any.whl.
File metadata
- Download URL: azure_ai_agentserver_invocations-1.0.0b4-py3-none-any.whl
- Upload date:
- Size: 18.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: RestSharp/106.13.0.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3da2712b4be44afa8f3f25f00f5268588edcddaee08686e40b3589fafa71cdbe
|
|
| MD5 |
ce2964f001df51aff0f0249448931380
|
|
| BLAKE2b-256 |
6bc7882e349459a0e3bfe63ca681f722cc7d5807df5e4aa348d879e3dc05d4c0
|