Python: Fix Http Schema (#2112)

* Rename to threadid

* Respond in plain text

* Make snake-case

* Add http prefix

* rename to wait-for-response

* Add query param check

* address comments
This commit is contained in:
Laveesh Rohra
2025-11-12 09:56:19 -08:00
committed by GitHub
Unverified
parent ebab25b196
commit ff28066c9c
24 changed files with 692 additions and 436 deletions
@@ -10,8 +10,8 @@ an HTTP API that can be polled by a web client or dashboard.
- Registers a default `AgentResponseCallbackProtocol` implementation that logs streaming and final
responses.
- Persists callback events in an in-memory store and exposes them via
`GET /api/agents/{agentName}/callbacks/{conversationId}`.
- Shows how to reset stored callback events with `DELETE /api/agents/{agentName}/callbacks/{conversationId}`.
`GET /api/agents/{agentName}/callbacks/{thread_id}`.
- Shows how to reset stored callback events with `DELETE /api/agents/{agentName}/callbacks/{thread_id}`.
- Works alongside the standard `/api/agents/{agentName}/run` endpoint so you can correlate callback
telemetry with agent responses.
@@ -32,6 +32,8 @@ curl -X POST http://localhost:7071/api/agents/CallbackAgent/run \
-d '{"message": "Tell me a short joke"}'
```
> **Note:** The run endpoint waits for the agent response by default. To return immediately, set the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the request body.
Poll callback telemetry (replace `<conversationId>` with the value from the POST response):
```bash
@@ -46,7 +48,7 @@ curl -X DELETE http://localhost:7071/api/agents/CallbackAgent/callbacks/<convers
## Expected Output
When you call `GET /api/agents/CallbackAgent/callbacks/{conversationId}` after sending a request to the agent,
When you call `GET /api/agents/CallbackAgent/callbacks/{thread_id}` after sending a request to the agent,
the API returns a list of streaming and final callback events similar to the following:
```json
@@ -54,7 +56,7 @@ the API returns a list of streaming and final callback events similar to the fol
{
"timestamp": "2024-01-01T00:00:00Z",
"agent_name": "CallbackAgent",
"conversation_id": "<conversationId>",
"thread_id": "<thread_id>",
"correlation_id": "<guid>",
"request_message": "Tell me a short joke",
"event_type": "stream",
@@ -64,7 +66,7 @@ the API returns a list of streaming and final callback events similar to the fol
{
"timestamp": "2024-01-01T00:00:01Z",
"agent_name": "CallbackAgent",
"conversation_id": "<conversationId>",
"thread_id": "<thread_id>",
"correlation_id": "<guid>",
"request_message": "Tell me a short joke",
"event_type": "final",
@@ -3,13 +3,13 @@
###
### Endpoints introduced in this sample:
### - POST /api/agents/{agentName}/run : send a message to the agent
### - GET /api/agents/{agentName}/callbacks/{conversationId} : retrieve callback telemetry
### - DELETE /api/agents/{agentName}/callbacks/{conversationId} : clear stored callback events
### - GET /api/agents/{agentName}/callbacks/{thread_id} : retrieve callback telemetry
### - DELETE /api/agents/{agentName}/callbacks/{thread_id} : clear stored callback events
@baseUrl = http://localhost:7071
@agentName = CallbackAgent
@agentRoute = {{baseUrl}}/api/agents/{{agentName}}
@conversationId = test-stream-00
@thread_id = test-thread-00
### Health Check
GET {{baseUrl}}/api/health
@@ -20,11 +20,11 @@ Content-Type: application/json
{
"message": "Generate a short weather update for Paris and mention streaming callbacks.",
"sessionId": "{{conversationId}}"
"thread_id": "{{thread_id}}"
}
### Inspect callback telemetry
GET {{agentRoute}}/callbacks/{{conversationId}}
GET {{agentRoute}}/callbacks/{{thread_id}}
### Clear stored callback telemetry for the conversation
DELETE {{agentRoute}}/callbacks/{{conversationId}}
### Clear stored callback telemetry for the thread
DELETE {{agentRoute}}/callbacks/{{thread_id}}
@@ -24,7 +24,7 @@ from agent_framework.azurefunctions import AgentFunctionApp, AgentCallbackContex
logger = logging.getLogger(__name__)
# 1. Maintain an in-memory store for callback events (replace with durable storage in production).
# 1. Maintain an in-memory store for callback events keyed by thread ID (replace with durable storage in production).
CallbackStore = DefaultDict[str, list[dict[str, Any]]]
callback_events: CallbackStore = defaultdict(list)
@@ -65,8 +65,8 @@ class ConversationAuditTrail(AgentResponseCallbackProtocol):
"text": getattr(update, "text", None),
}
)
conversation_id = context.conversation_id or ""
callback_events[conversation_id].append(event)
thread_id = context.thread_id or ""
callback_events[thread_id].append(event)
preview = event.get("text") or event.get("update_kind")
self._logger.info(
@@ -85,8 +85,8 @@ class ConversationAuditTrail(AgentResponseCallbackProtocol):
"usage": _serialize_usage(getattr(response, "usage_details", None)),
}
)
conversation_id = context.conversation_id or ""
callback_events[conversation_id].append(event)
thread_id = context.thread_id or ""
callback_events[thread_id].append(event)
self._logger.info(
"[%s][%s] final response recorded",
@@ -96,10 +96,11 @@ class ConversationAuditTrail(AgentResponseCallbackProtocol):
@staticmethod
def _build_base_event(context: AgentCallbackContext) -> dict[str, Any]:
thread_id = context.thread_id
return {
"timestamp": datetime.now(timezone.utc).isoformat(),
"agent_name": context.agent_name,
"conversation_id": context.conversation_id,
"thread_id": thread_id,
"correlation_id": context.correlation_id,
"request_message": context.request_message,
}
@@ -122,12 +123,12 @@ app.add_agent(callback_agent)
@app.function_name("get_callback_events")
@app.route(route="agents/{agent_name}/callbacks/{conversationId}", methods=["GET"])
@app.route(route="agents/{agent_name}/callbacks/{thread_id}", methods=["GET"])
async def get_callback_events(req: func.HttpRequest) -> func.HttpResponse:
"""Return all callback events collected for a conversation."""
"""Return all callback events collected for a thread."""
conversation_id = req.route_params.get("conversationId", "")
events = callback_events.get(conversation_id, [])
thread_id = req.route_params.get("thread_id", "")
events = callback_events.get(thread_id, [])
return func.HttpResponse(
json.dumps(events, indent=2),
status_code=200,
@@ -136,44 +137,44 @@ async def get_callback_events(req: func.HttpRequest) -> func.HttpResponse:
@app.function_name("reset_callback_events")
@app.route(route="agents/{agent_name}/callbacks/{conversationId}", methods=["DELETE"])
@app.route(route="agents/{agent_name}/callbacks/{thread_id}", methods=["DELETE"])
async def reset_callback_events(req: func.HttpRequest) -> func.HttpResponse:
"""Clear the stored callback events for a conversation."""
"""Clear the stored callback events for a thread."""
conversation_id = req.route_params.get("conversationId", "")
callback_events.pop(conversation_id, None)
thread_id = req.route_params.get("thread_id", "")
callback_events.pop(thread_id, None)
return func.HttpResponse(status_code=204)
"""
Expected output when querying `GET /api/agents/CallbackAgent/callbacks/{conversationId}`:
Expected output when querying `GET /api/agents/CallbackAgent/callbacks/{thread_id}`:
HTTP/1.1 200 OK
[
{
"timestamp": "2024-01-01T00:00:00Z",
"agent_name": "CallbackAgent",
"conversation_id": "<conversationId>",
"correlation_id": "<guid>",
"request_message": "Tell me a short joke",
"event_type": "stream",
"update_kind": "text",
"text": "Sure, here's a joke..."
},
{
"timestamp": "2024-01-01T00:00:01Z",
"agent_name": "CallbackAgent",
"conversation_id": "<conversationId>",
"correlation_id": "<guid>",
"request_message": "Tell me a short joke",
"event_type": "final",
"response_text": "Why did the cloud...",
"usage": {
"type": "usage_details",
"input_token_count": 159,
"output_token_count": 29,
"total_token_count": 188
{
"timestamp": "2024-01-01T00:00:00Z",
"agent_name": "CallbackAgent",
"thread_id": "<thread_id>",
"correlation_id": "<guid>",
"request_message": "Tell me a short joke",
"event_type": "stream",
"update_kind": "text",
"text": "Sure, here's a joke..."
},
{
"timestamp": "2024-01-01T00:00:01Z",
"agent_name": "CallbackAgent",
"thread_id": "<thread_id>",
"correlation_id": "<guid>",
"request_message": "Tell me a short joke",
"event_type": "final",
"response_text": "Why did the cloud...",
"usage": {
"type": "usage_details",
"input_token_count": 159,
"output_token_count": 29,
"total_token_count": 188
}
}
}
]
"""