From 77247a304ead7da60198e9e3057d4d7d5c49f858 Mon Sep 17 00:00:00 2001 From: Chris Gillum Date: Mon, 17 Nov 2025 11:30:45 -0800 Subject: [PATCH] .NET: Change thread_id from entity ID to GUID (#2260) --- .../AzureFunctions/01_SingleAgent/README.md | 4 +-- .../06_LongRunningTools/README.md | 2 +- .../BuiltInFunctions.cs | 17 ++++++----- .../SamplesValidation.cs | 4 +-- .../azure_functions/01_single_agent/README.md | 30 +++++++++++++++---- 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/dotnet/samples/AzureFunctions/01_SingleAgent/README.md b/dotnet/samples/AzureFunctions/01_SingleAgent/README.md index 5727f037c2..d4ac968978 100644 --- a/dotnet/samples/AzureFunctions/01_SingleAgent/README.md +++ b/dotnet/samples/AzureFunctions/01_SingleAgent/README.md @@ -47,7 +47,7 @@ curl -X POST http://localhost:7071/api/agents/Joker/run \ To continue a conversation, include the `thread_id` in the query string or JSON body: ```bash -curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=@dafx-joker@your-thread-id" \ +curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=your-thread-id" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"message": "Tell me another one."}' @@ -64,7 +64,7 @@ The expected `application/json` output will look something like: ```json { "status": 200, - "thread_id": "@dafx-joker@your-thread-id", + "thread_id": "ee6e47a0-f24b-40b1-ade8-16fcebb9eb40", "response": { "Messages": [ { diff --git a/dotnet/samples/AzureFunctions/06_LongRunningTools/README.md b/dotnet/samples/AzureFunctions/06_LongRunningTools/README.md index 9c538298e8..54ed85060b 100644 --- a/dotnet/samples/AzureFunctions/06_LongRunningTools/README.md +++ b/dotnet/samples/AzureFunctions/06_LongRunningTools/README.md @@ -52,7 +52,7 @@ The response will be a text string that looks something like the following, indi ```http HTTP/1.1 200 OK Content-Type: text/plain -x-ms-thread-id: @publisher@351ec855-7f4d-4527-a60d-498301ced36d +x-ms-thread-id: 351ec855-7f4d-4527-a60d-498301ced36d The content generation workflow for the topic "The Future of Artificial Intelligence" has been successfully started, and the instance ID is **6a04276e8d824d8d941e1dc4142cc254**. If you need any further assistance or updates on the workflow, feel free to ask! ``` diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs index f8c4618c9e..ebd378ac3b 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs @@ -83,12 +83,13 @@ internal static class BuiltInFunctions string? threadIdValue = threadIdFromBody ?? threadIdFromQuery; - // If no session ID is provided, use a new one based on the function name and invocation ID. - // This may be better than a random one because it can be correlated with the function invocation. - // Specifying a session ID is how the caller correlates multiple calls to the same agent session. + // The thread_id is treated as a session key (not a full session ID). + // If no session key is provided, use the function invocation ID as the session key + // to help correlate the session with the function invocation. + string agentName = GetAgentName(context); AgentSessionId sessionId = string.IsNullOrEmpty(threadIdValue) - ? new AgentSessionId(GetAgentName(context), context.InvocationId) - : AgentSessionId.Parse(threadIdValue); + ? new AgentSessionId(agentName, context.InvocationId) + : new AgentSessionId(agentName, threadIdValue); if (string.IsNullOrWhiteSpace(message)) { @@ -110,7 +111,7 @@ internal static class BuiltInFunctions } } - AIAgent agentProxy = client.AsDurableAgentProxy(context, GetAgentName(context)); + AIAgent agentProxy = client.AsDurableAgentProxy(context, agentName); DurableAgentRunOptions options = new() { IsFireAndForget = !waitForResponse }; @@ -126,7 +127,7 @@ internal static class BuiltInFunctions req, context, HttpStatusCode.OK, - sessionId.ToString(), + sessionId.Key, agentResponse); } @@ -140,7 +141,7 @@ internal static class BuiltInFunctions return await CreateAcceptedResponseAsync( req, context, - sessionId.ToString()); + sessionId.Key); } public static async Task RunMcpToolAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs index f2bfcf234c..ef9807fddf 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs @@ -75,9 +75,9 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi // The response headers should include the agent thread ID, which can be used to continue the conversation. string? threadId = response.Headers.GetValues("x-ms-thread-id")?.FirstOrDefault(); Assert.NotNull(threadId); + Assert.NotEmpty(threadId); this._outputHelper.WriteLine($"Agent thread ID: {threadId}"); - Assert.StartsWith("@dafx-joker@", threadId); // Wait for up to 30 seconds to see if the agent response is available in the logs await this.WaitForConditionAsync( @@ -289,7 +289,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi startResponse.Headers.TryGetValues("x-ms-thread-id", out IEnumerable? agentIdValues); string? threadId = agentIdValues?.FirstOrDefault(); Assert.NotNull(threadId); - Assert.StartsWith("@dafx-publisher@", threadId); + Assert.NotEmpty(threadId); // Wait for the orchestration to report that it's waiting for human approval await this.WaitForConditionAsync( diff --git a/python/samples/getting_started/azure_functions/01_single_agent/README.md b/python/samples/getting_started/azure_functions/01_single_agent/README.md index 5f7c1887be..38c6ce58f5 100644 --- a/python/samples/getting_started/azure_functions/01_single_agent/README.md +++ b/python/samples/getting_started/azure_functions/01_single_agent/README.md @@ -18,20 +18,40 @@ Follow the common setup steps in `../README.md` to install tooling, configure Az Send a prompt to the Joker agent: +Bash (Linux/macOS/WSL): + ```bash -curl -X POST http://localhost:7071/api/agents/Joker/run \ - -H "Content-Type: text/plain" \ +curl -i -X POST http://localhost:7071/api/agents/Joker/run \ -d "Tell me a short joke about cloud computing." ``` +PowerShell: + +```powershell +Invoke-RestMethod -Method Post -Uri http://localhost:7071/api/agents/Joker/run ` + -Body "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. +> [!TIP] +> 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: +The default plain-text response looks like the following: + +```http +HTTP/1.1 200 OK +Content-Type: text/plain; charset=utf-8 +x-ms-thread-id: 4f205157170244bfbd80209df383757e + +Why did the cloud break up with the server? + +Because it found someone more "uplifting"! +``` + +When you specify the `x-ms-wait-for-response` header or include `"wait_for_response": false` in the request body, the Functions host responds with an HTTP 202 and queues the request to run in the background. A typical response body looks like the following: ```json {