Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
</ItemGroup>

<ItemGroup Condition="$(MSBuildProjectName.Contains('Azure.AI.Agents.Persistent'))">
<PackageReference Update="Microsoft.Extensions.AI.Abstractions" Version="9.8.0"/>
<PackageReference Update="Microsoft.Extensions.AI.Abstractions" Version="9.10.0"/>
</ItemGroup>

<ItemGroup Condition="$(MSBuildProjectName.Contains('Azure.Projects'))">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Autorest.CSharp.Core;
Expand Down Expand Up @@ -388,6 +389,51 @@ public virtual async Task<Response> CreateAgentAsync(RequestContent content, Req
}
}

/// <summary> Creates a new agent. </summary>
/// <param name="model"> The ID of the model to use. </param>
/// <param name="name"> The name of the new agent. </param>
/// <param name="description"> The description of the new agent. </param>
/// <param name="instructions"> The system instructions for the new agent to use. </param>
/// <param name="tools"> The collection of tools to enable for the new agent. </param>
/// <param name="toolResources">
/// A set of resources that are used by the agent's tools. The resources are specific to the type of tool. For example, the `code_interpreter`
/// tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.
/// </param>
/// <param name="temperature">
/// What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random,
/// while lower values like 0.2 will make it more focused and deterministic.
/// </param>
/// <param name="topP">
/// An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass.
/// So 0.1 means only the tokens comprising the top 10% probability mass are considered.
///
/// We generally recommend altering this or temperature but not both.
/// </param>
/// <param name="responseFormat"> The response format of the tool calls used by this agent. </param>
/// <param name="metadata"> A set of up to 16 key/value pairs that can be attached to an object, used for storing additional information about that object in a structured format. Keys may be up to 64 characters in length and values may be up to 512 characters in length. </param>
/// <param name="requestContext"> The request context to use. </param>
/// <exception cref="ArgumentNullException"> <paramref name="model"/> is null. </exception>
internal async Task<Response<PersistentAgent>> CreateAgentAsync(string model, string name, string description, string instructions, IEnumerable<ToolDefinition> tools, ToolResources toolResources, float? temperature, float? topP, BinaryData responseFormat, IReadOnlyDictionary<string, string> metadata, RequestContext requestContext)
{
Argument.AssertNotNull(model, nameof(model));

CreateAgentRequest createAgentRequest = new CreateAgentRequest(
model,
name,
description,
instructions,
tools?.ToList() as IReadOnlyList<ToolDefinition> ?? new ChangeTrackingList<ToolDefinition>(),
toolResources,
temperature,
topP,
responseFormat,
metadata ?? new ChangeTrackingDictionary<string, string>(),
null);

Response response = await CreateAgentAsync(createAgentRequest.ToRequestContent(), requestContext).ConfigureAwait(false);
return Response.FromValue(PersistentAgent.FromResponse(response), response);
}

/// <summary>
/// [Protocol Method] Creates a new agent.
/// <list type="bullet">
Expand Down Expand Up @@ -589,5 +635,27 @@ internal virtual Response CreateThreadAndRun(RequestContent content, RequestCont
throw;
}
}

/// <summary> Retrieves an existing agent. </summary>
/// <param name="assistantId"> Identifier of the agent. </param>
/// <param name="requestContext"> The request context to use. </param>
/// <exception cref="ArgumentNullException"> <paramref name="assistantId"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="assistantId"/> is an empty string, and was expected to be non-empty. </exception>
internal virtual Response<PersistentAgent> InternalGetAgent(string assistantId, RequestContext requestContext)
{
Response response = GetAgent(assistantId, requestContext);
return Response.FromValue(PersistentAgent.FromResponse(response), response);
}

/// <summary> Retrieves an existing agent. </summary>
/// <param name="assistantId"> Identifier of the agent. </param>
/// <param name="requestContext"> The request context to use. </param>
/// <exception cref="ArgumentNullException"> <paramref name="assistantId"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="assistantId"/> is an empty string, and was expected to be non-empty. </exception>
internal virtual async Task<Response<PersistentAgent>> InternalGetAgentAsync(string assistantId, RequestContext requestContext)
{
Response response = await GetAgentAsync(assistantId, requestContext).ConfigureAwait(false);
return Response.FromValue(PersistentAgent.FromResponse(response), response);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;
using Microsoft.Extensions.AI;

namespace Azure.AI.Agents.Persistent
Expand Down Expand Up @@ -88,7 +91,7 @@ public virtual async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAs
ThreadRun? threadRun = null;
if (threadId is not null)
{
await foreach (ThreadRun? run in _client!.Runs.GetRunsAsync(threadId, limit: 1, ListSortOrder.Descending, cancellationToken: cancellationToken).ConfigureAwait(false))
await foreach (ThreadRun? run in _client!.Runs.GetRunsAsync(threadId, limit: 1, order: ListSortOrder.Descending, after: null, before: null, requestContext: ToUserAgentRequestContext(cancellationToken)).ConfigureAwait(false))
{
if (run.Status != RunStatus.Completed && run.Status != RunStatus.Cancelled && run.Status != RunStatus.Failed && run.Status != RunStatus.Expired)
{
Expand All @@ -108,21 +111,21 @@ threadRun is not null &&
// There's an active run and we have tool results to submit, so submit the results and continue streaming.
// This is going to ignore any additional messages in the run options, as we are only submitting tool outputs,
// but there doesn't appear to be a way to submit additional messages, and having such additional messages is rare.
updates = _client!.Runs.SubmitToolOutputsToStreamAsync(threadRun, toolOutputs, cancellationToken);
updates = _client!.Runs.SubmitToolOutputsToStreamAsync(threadRun, toolOutputs, ToUserAgentRequestContext(cancellationToken));
}
else
{
if (threadId is null)
{
// No thread ID was provided, so create a new thread.
PersistentAgentThread thread = await _client!.Threads.CreateThreadAsync(runOptions.ThreadOptions.Messages, runOptions.ToolResources, runOptions.Metadata, cancellationToken).ConfigureAwait(false);
PersistentAgentThread thread = await _client!.Threads.CreateThreadAsync(runOptions.ThreadOptions.Messages, runOptions.ToolResources, runOptions.Metadata, ToUserAgentRequestContext(cancellationToken)).ConfigureAwait(false);
runOptions.ThreadOptions.Messages.Clear();
threadId = thread.Id;
}
else if (threadRun is not null)
{
// There was an active run; we need to cancel it before starting a new run.
await _client!.Runs.CancelRunAsync(threadId, threadRun.Id, cancellationToken).ConfigureAwait(false);
await _client!.Runs.CancelRunAsync(threadId, threadRun.Id, ToUserAgentRequestContext(cancellationToken)).ConfigureAwait(false);
threadRun = null;
}

Expand Down Expand Up @@ -151,7 +154,7 @@ threadRun is not null &&
threadId: threadId,
agentId: _agentId,
options: opts,
cancellationToken: cancellationToken
ToUserAgentRequestContext(cancellationToken)
);
}

Expand Down Expand Up @@ -285,7 +288,7 @@ public void Dispose() { }
// Load details about the agent if not already loaded.
if (_agent is null)
{
PersistentAgent agent = await _client!.Administration.GetAgentAsync(_agentId, cancellationToken).ConfigureAwait(false);
PersistentAgent agent = await _client!.Administration.InternalGetAgentAsync(_agentId, ToUserAgentRequestContext(cancellationToken)).ConfigureAwait(false);
Interlocked.CompareExchange(ref _agent, agent, null);
}

Expand Down Expand Up @@ -579,6 +582,62 @@ public void Dispose() { }
return runId;
}

/// <summary>Creates a <see cref="RequestContext"/> configured for use.</summary>
private static RequestContext ToUserAgentRequestContext(CancellationToken cancellationToken)
{
RequestContext requestContext = new()
{
CancellationToken = cancellationToken
};

requestContext.AddPolicy(MeaiUserAgentPolicy.Instance, HttpPipelinePosition.PerCall);

return requestContext;
}

private sealed class MeaiUserAgentPolicy : HttpPipelineSynchronousPolicy
{
public static MeaiUserAgentPolicy Instance { get; } = new MeaiUserAgentPolicy();

private static readonly string _userAgentValue = CreateUserAgentValue();

private static void AddUserAgentHeader(HttpMessage message) =>
message.Request.Headers.Add("User-Agent", _userAgentValue);

private static string CreateUserAgentValue()
{
const string Name = "MEAI";

if (typeof(IChatClient).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion is string version)
{
int pos = version.IndexOf('+');
if (pos >= 0)
{
version = version.Substring(0, pos);
}

if (version.Length > 0)
{
return $"{Name}/{version}";
}
}

return Name;
}

public override ValueTask ProcessAsync(Core.HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
AddUserAgentHeader(message);
return ProcessNextAsync(message, pipeline);
}

public override void Process(Core.HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
AddUserAgentHeader(message);
ProcessNext(message, pipeline);
}
}

[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(JsonNode))]
[JsonSerializable(typeof(JsonObject))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
Expand Down
Loading
Loading