mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
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:
committed by
GitHub
Unverified
parent
ebab25b196
commit
ff28066c9c
@@ -7,7 +7,7 @@ This sample demonstrates how to use the Durable Extension for Agent Framework to
|
||||
- Defining a simple agent with the Microsoft Agent Framework and wiring it into
|
||||
an Azure Functions app via the Durable Extension for Agent Framework.
|
||||
- Calling the agent through generated HTTP endpoints (`/api/agents/Joker/run`).
|
||||
- Managing conversation state with session identifiers, so multiple clients can
|
||||
- Managing conversation state with thread identifiers, so multiple clients can
|
||||
interact with the agent concurrently without sharing context.
|
||||
|
||||
## Prerequisites
|
||||
@@ -24,6 +24,13 @@ curl -X POST http://localhost:7071/api/agents/Joker/run \
|
||||
-d "Tell me a short joke about cloud computing."
|
||||
```
|
||||
|
||||
The agent responds with a JSON payload that includes the generated joke.
|
||||
|
||||
> **Note:** To return immediately with an HTTP 202 response instead of waiting for the agent output, set the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the request body. The default behavior waits for the response.
|
||||
|
||||
## Expected Output
|
||||
|
||||
When you send a POST request with plain-text input, the Functions host responds with an HTTP 202 and queues the request for the durable agent entity. A typical response body looks like the following:
|
||||
Expected HTTP 202 payload:
|
||||
|
||||
```json
|
||||
@@ -31,7 +38,7 @@ Expected HTTP 202 payload:
|
||||
"status": "accepted",
|
||||
"response": "Agent request accepted",
|
||||
"message": "Tell me a short joke about cloud computing.",
|
||||
"conversation_id": "<guid>",
|
||||
"thread_id": "<guid>",
|
||||
"correlation_id": "<guid>"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -13,12 +13,10 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"message": "Add a security element to it.",
|
||||
"sessionId": "session-003",
|
||||
"waitForCompletion": true
|
||||
"thread_id": "thread-001"
|
||||
}
|
||||
|
||||
### Ask for a joke (plain text payload)
|
||||
POST {{agentRoute}}/run
|
||||
x-wait-for-completion: true
|
||||
|
||||
Give me a programming joke about race conditions.
|
||||
@@ -6,7 +6,7 @@ This sample demonstrates how to use the Durable Extension for Agent Framework to
|
||||
|
||||
- Using the Microsoft Agent Framework to define multiple AI agents with unique names and instructions.
|
||||
- Registering multiple agents with the Function app and running them using HTTP.
|
||||
- Conversation management (via session IDs) for isolated interactions per agent.
|
||||
- Conversation management (via thread IDs) for isolated interactions per agent.
|
||||
- Two different methods for registering agents: list-based initialization and incremental addition.
|
||||
|
||||
## Prerequisites
|
||||
@@ -15,6 +15,15 @@ Complete the common environment preparation steps described in `../README.md`, i
|
||||
|
||||
## Running the Sample
|
||||
|
||||
With the environment setup and function app running, you can test the sample by sending HTTP requests to the different agent endpoints.
|
||||
|
||||
You can use the `demo.http` file to send messages to the agents, or a command line tool like `curl` as shown below:
|
||||
|
||||
> **Note:** Each endpoint waits for the agent response by default. To receive an immediate HTTP 202 instead, set the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the request body.
|
||||
|
||||
### Test the Weather Agent
|
||||
|
||||
Bash (Linux/macOS/WSL):
|
||||
Weather agent request:
|
||||
|
||||
```bash
|
||||
@@ -30,7 +39,7 @@ Expected HTTP 202 payload:
|
||||
"status": "accepted",
|
||||
"response": "Agent request accepted",
|
||||
"message": "What is the weather in Seattle?",
|
||||
"conversation_id": "<guid>",
|
||||
"thread_id": "<guid>",
|
||||
"correlation_id": "<guid>"
|
||||
}
|
||||
```
|
||||
@@ -50,7 +59,7 @@ Expected HTTP 202 payload:
|
||||
"status": "accepted",
|
||||
"response": "Agent request accepted",
|
||||
"message": "Calculate a 20% tip on a $50 bill",
|
||||
"conversation_id": "<guid>",
|
||||
"thread_id": "<guid>",
|
||||
"correlation_id": "<guid>"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -36,8 +36,7 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"message": "What is the weather in Seattle?",
|
||||
"sessionId": "weather-user-001",
|
||||
"waitForCompletion": true
|
||||
"thread_id": "weather-user-001"
|
||||
}
|
||||
|
||||
###
|
||||
@@ -51,8 +50,7 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"message": "Calculate a 20% tip on a $50 bill",
|
||||
"sessionId": "math-user-001",
|
||||
"waitForCompletion": true
|
||||
"thread_id": "math-user-001"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
+2
@@ -25,6 +25,8 @@ Poll the returned `statusQueryGetUri` until completion:
|
||||
curl http://localhost:7071/api/singleagent/status/<instanceId>
|
||||
```
|
||||
|
||||
> **Note:** The underlying agent run endpoint now waits for responses by default. If you invoke it directly and prefer an immediate HTTP 202, set the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the payload.
|
||||
|
||||
The orchestration first requests an inspirational sentence from the agent, then refines the initial response while
|
||||
keeping it under 25 words—mirroring the behaviour of the corresponding .NET sample.
|
||||
|
||||
|
||||
+2
@@ -27,6 +27,8 @@ Poll the returned `statusQueryGetUri` until completion:
|
||||
curl http://localhost:7071/api/multiagent/status/<instanceId>
|
||||
```
|
||||
|
||||
> **Note:** The agent run endpoints wait for responses by default. If you call them directly and need an immediate HTTP 202, set the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the request payload.
|
||||
|
||||
The orchestration launches both agents simultaneously so their domain-specific answers can be combined for the caller.
|
||||
|
||||
## Expected Output
|
||||
|
||||
+2
@@ -27,6 +27,8 @@ Poll the returned `statusQueryGetUri` or call the status route directly:
|
||||
curl http://localhost:7071/api/spamdetection/status/<instanceId>
|
||||
```
|
||||
|
||||
> **Note:** The spam detection run endpoint waits for responses by default. To opt into an immediate HTTP 202, set the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the POST body.
|
||||
|
||||
## Expected Responses
|
||||
- Spam payloads return `Email marked as spam: <reason>` by invoking the `handle_spam_email` activity.
|
||||
- Legitimate emails return `Email sent: <draft>` after the email assistant agent produces a structured reply.
|
||||
|
||||
+2
@@ -39,6 +39,8 @@ curl -X POST http://localhost:7071/api/hitl/approve/<instanceId> \
|
||||
-d '{"approved": true, "feedback": "Looks good"}'
|
||||
```
|
||||
|
||||
> **Note:** Calls to the underlying agent run endpoint wait for responses by default. If you need an immediate HTTP 202 response, set the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the request body.
|
||||
|
||||
## Expected Responses
|
||||
- `POST /api/hitl/run` returns a 202 Accepted payload with the Durable Functions instance ID.
|
||||
- `POST /api/hitl/approve/{instanceId}` echoes the decision that the orchestration receives.
|
||||
|
||||
Reference in New Issue
Block a user