Skip to content
Closed
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
9 changes: 7 additions & 2 deletions sdk/ai/Azure.AI.Agents/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Azure AI Agents client library for .NET

> [!WARNING]
> This feature branch folder is outdated and pending removal.
>
> Please refer to the `Azure.AI.Projects` and `Azure.AI.Projects.OpenAI` folders for the up-to-date code and information about Agents.

Develop Agents using the Azure AI Foundry platform, leveraging an extensive ecosystem of models, tools, and capabilities from OpenAI, Microsoft, and other LLM providers.

**Note:** While this package can be used independently, we recommend using the Azure AI Projects client library (Azure.AI.Projects) for enhanced experience. The Projects library provides simplified access to advanced functionality, such as creating and managing Agents, enumerating AI models, working with datasets, managing search indexes, evaluating generative AI performance, and enabling OpenTelemetry tracing.
Expand All @@ -24,7 +29,7 @@ Develop Agents using the Azure AI Foundry platform, leveraging an extensive ecos
- [Prompt Agents](#prompt-agents)
- [Agents](#agents)
- [Responses](#responses)
- [Coversations](#coversations)
- [Conversations](#Conversations)
- [Container App](#container-app)
- [File search](#file-search)
- [Code interpreter](#code-interpreter)
Expand Down Expand Up @@ -250,7 +255,7 @@ Finally, we need to remove the `AgentVersion` object.
await agentClient.DeleteAgentAsync(agentName: "myAgent");
```

#### Coversations
#### Conversations

Conversations may be used to store the previous conversation with the agent. To add the responses to a conversation,
set the conversation ID to the `ResponseCreationOptions`.
Expand Down
16 changes: 8 additions & 8 deletions sdk/ai/Azure.AI.Agents/samples/Sample1_Agent_Versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ await foreach (AgentVersion oneAgentVersion in agentVersions)
4. To communicate with the agent, we will need to create a conversation.

Synchronous sample:
```C# Snippet:Sample_CreateCoversation_Sync_2
ConversationClient coversations = client.GetConversationClient();
AgentConversation conversation = coversations.CreateConversation();
```C# Snippet:Sample_CreateConversation_Sync_2
ConversationClient Conversations = client.GetConversationClient();
AgentConversation conversation = Conversations.CreateConversation();
ModelReaderWriterOptions options = new("W");
BinaryData conversationBin = ((IPersistableModel<AgentConversation>)conversation).Write(options);
```

Asynchronous sample:
```C# Snippet:Sample_CreateCoversation_Async_2
ConversationClient coversations = client.GetConversationClient();
AgentConversation conversation = await coversations.CreateConversationAsync();
```C# Snippet:Sample_CreateConversation_Async_2
ConversationClient Conversations = client.GetConversationClient();
AgentConversation conversation = await Conversations.CreateConversationAsync();
ModelReaderWriterOptions options = new("W");
BinaryData conversationBin = ((IPersistableModel<AgentConversation>)conversation).Write(options);
```
Expand Down Expand Up @@ -129,12 +129,12 @@ Console.WriteLine(response.GetOutputText());

Synchronous sample:
```C# Snippet:Sample_Cleanup_Sync_2
coversations.DeleteConversation(conversationId: conversation.Id);
Conversations.DeleteConversation(conversationId: conversation.Id);
client.DeleteAgentVersion(agentName: agentVersion.Name, agentVersion: agentVersion.Version);
```

Asynchronous sample:
```C# Snippet:Sample_Cleanup_Async_2
await coversations.DeleteConversationAsync(conversationId: conversation.Id);
await Conversations.DeleteConversationAsync(conversationId: conversation.Id);
await client.DeleteAgentVersionAsync(agentName: agentVersion.Name, agentVersion: agentVersion.Version);
```
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public async Task CreateAgentVersionAsync()
}
#endregion

#region Snippet:Sample_CreateCoversation_Async_2
ConversationClient coversations = client.GetConversationClient();
AgentConversation conversation = await coversations.CreateConversationAsync();
#region Snippet:Sample_CreateConversation_Async_2
ConversationClient Conversations = client.GetConversationClient();
AgentConversation conversation = await Conversations.CreateConversationAsync();
ModelReaderWriterOptions options = new("W");
BinaryData conversationBin = ((IPersistableModel<AgentConversation>)conversation).Write(options);
#endregion
Expand All @@ -77,7 +77,7 @@ public async Task CreateAgentVersionAsync()
Console.WriteLine(response.GetOutputText());
#endregion
#region Snippet:Sample_Cleanup_Async_2
await coversations.DeleteConversationAsync(conversationId: conversation.Id);
await Conversations.DeleteConversationAsync(conversationId: conversation.Id);
await client.DeleteAgentVersionAsync(agentName: agentVersion.Name, agentVersion: agentVersion.Version);
#endregion
}
Expand Down Expand Up @@ -112,9 +112,9 @@ public void CreateAgentVersion()
}
#endregion

#region Snippet:Sample_CreateCoversation_Sync_2
ConversationClient coversations = client.GetConversationClient();
AgentConversation conversation = coversations.CreateConversation();
#region Snippet:Sample_CreateConversation_Sync_2
ConversationClient Conversations = client.GetConversationClient();
AgentConversation conversation = Conversations.CreateConversation();
ModelReaderWriterOptions options = new("W");
BinaryData conversationBin = ((IPersistableModel<AgentConversation>)conversation).Write(options);
#endregion
Expand Down Expand Up @@ -142,7 +142,7 @@ public void CreateAgentVersion()
Console.WriteLine(response.GetOutputText());
#endregion
#region Snippet:Sample_Cleanup_Sync_2
coversations.DeleteConversation(conversationId: conversation.Id);
Conversations.DeleteConversation(conversationId: conversation.Id);
client.DeleteAgentVersion(agentName: agentVersion.Name, agentVersion: agentVersion.Version);
#endregion
}
Expand Down
10 changes: 8 additions & 2 deletions sdk/ai/Azure.AI.Projects.OpenAI/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Release History

## 1.0.0-beta.3 (Unreleased)
## 1.0.0-beta.3 (2025-11-15)

### Bugs Fixed

- Addressed an issue that caused paginated responses like conversation items to never terminate when large numbers of items are fetched

## 1.0.0-beta.3 (2025-11-14)

### Features Added

Expand All @@ -12,7 +18,7 @@

## 1.0.0-beta.2 (2025-11-14)

### Bugs fixed
### Bugs Fixed

- Addressed a problem where not supplying an options instance to the `ProjectResponsesClient` constructor resulted in fallback to the `https://api.openai.com/v1` endpoint

Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/Azure.AI.Projects.OpenAI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Develop Agents using the Azure AI Foundry platform, leveraging an extensive ecos
- [Prompt Agents](#prompt-agents)
- [Agents](#agents)
- [Responses](#responses)
- [Coversations](#coversations)
- [Conversations](#Conversations)
- [Container App](#container-app)
- [File search](#file-search)
- [Code interpreter](#code-interpreter)
Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/Azure.AI.Projects.OpenAI/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/ai/Azure.AI.Projects.OpenAI",
"Tag": "net/ai/Azure.AI.Projects.OpenAI_8a364fa4af"
"Tag": "net/ai/Azure.AI.Projects.OpenAI_ed5f3bf439"
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,21 @@ OpenAIResponse response = await responseClient.CreateResponseAsync([request]);
Synchronous sample:
```C# Snippet:Sample_WriteOutput_MemoryTool_Sync
string scope = "Joke from conversation";
List<ResponseItem> updateItems = [request];
MemoryUpdateOptions memoryOptions = new(scope);
memoryOptions.Items.Add(request);
Assert.That(response.Status, Is.EqualTo(ResponseStatus.Completed));

foreach (ResponseItem item in response.OutputItems)
{
updateItems.Add(item);
memoryOptions.Items.Add(item);
}
Console.WriteLine(response.GetOutputText());
```

Asynchronous sample:
```C# Snippet:Sample_WriteOutput_MemoryTool_Async
string scope = "Joke from conversation";
List<ResponseItem> updateItems = [request];
MemoryUpdateOptions memoryOptions = new(scope);
memoryOptions.Items.Add(request);
while (response.Status != ResponseStatus.Incomplete && response.Status != ResponseStatus.Failed && response.Status != ResponseStatus.Completed){
await Task.Delay(TimeSpan.FromMilliseconds(500));
response = await responseClient.GetResponseAsync(responseId: response.Id);
Expand All @@ -80,7 +81,7 @@ Assert.That(response.Status, Is.EqualTo(ResponseStatus.Completed));

foreach (ResponseItem item in response.OutputItems)
{
updateItems.Add(item);
memoryOptions.Items.Add(item);
}
Console.WriteLine(response.GetOutputText());
```
Expand All @@ -98,12 +99,12 @@ MemoryStore memoryStore = projectClient.MemoryStores.CreateMemoryStore(
definition: memoryStoreDefinition,
description: "Memory store for conversation."
);
MemoryUpdateOptions updateOptions = new(scope);
foreach (ResponseItem updateItem in updateItems)
MemoryUpdateResult updateResult = projectClient.MemoryStores.UpdateMemories(memoryStoreName: memoryStore.Name, options: memoryOptions);
while (updateResult.Status != MemoryStoreUpdateStatus.Failed && updateResult.Status != MemoryStoreUpdateStatus.Completed)
{
updateOptions.Items.Add(updateItem);
Thread.Sleep(TimeSpan.FromMilliseconds(500));
updateResult = await projectClient.MemoryStores.GetUpdateResultAsync(memoryStore.Name, updateResult.UpdateId);
}
projectClient.MemoryStores.UpdateMemories(memoryStoreName: memoryStore.Name, options: updateOptions);
```

Asynchronous sample:
Expand All @@ -117,7 +118,12 @@ MemoryStore memoryStore = await projectClient.MemoryStores.CreateMemoryStoreAsyn
definition: memoryStoreDefinition,
description: "Memory store for conversation."
);
projectClient.MemoryStores.UpdateMemories(memoryStore.Name, new MemoryUpdateOptions(scope));
MemoryUpdateResult updateResult = await projectClient.MemoryStores.UpdateMemoriesAsync(memoryStoreName: memoryStore.Name, options: memoryOptions);
while (updateResult.Status != MemoryStoreUpdateStatus.Failed && updateResult.Status != MemoryStoreUpdateStatus.Completed)
{
await Task.Delay(TimeSpan.FromMilliseconds(500));
updateResult = await projectClient.MemoryStores.GetUpdateResultAsync(memoryStore.Name, updateResult.UpdateId);
}
```

7. Check that the memory store contain the relevant memories.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
ongoing forward compatibility.
</Description>
<AssemblyTitle>Microsoft Foundry OpenAI .NET Extensions</AssemblyTitle>
<Version>1.0.0-beta.2</Version>
<Version>1.0.0-beta.3</Version>
<PackageTags>Azure.AI.Projects.OpenAI</PackageTags>
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
<NoWarn>$(NoWarn);OPENAI001;CS1591;AZC0007;AZC0012;AZC0015;AZC0030;AZC0031;AZC0034;SA1649;SA1402;AZC0035</NoWarn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public InternalOpenAICollectionResultOptions GetCloneForPage<T>(InternalOpenAIPa
ParentResourceId = ParentResourceId,
Limit = Limit,
Order = Order,
AfterId = AfterId,
BeforeId = BeforeId,
AfterId = page.LastId,
BeforeId = page.FirstId,
};
clonedOptions.Filters.AddRange(Filters);
clonedOptions.Includes.AddRange(Includes);
Expand Down
81 changes: 80 additions & 1 deletion sdk/ai/Azure.AI.Projects.OpenAI/tests/ConversationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.AI.Projects.OpenAI;
using Microsoft.ClientModel.TestFramework;
using NUnit.Framework;
using Azure.AI.Projects.OpenAI;
using OpenAI;
using OpenAI.Files;
using OpenAI.Responses;
Expand Down Expand Up @@ -106,4 +107,82 @@ public async Task ConversationOperationsWork()
}
}
}

[RecordedTest]
public async Task ConversationItemPaginationWorks()
{
ProjectOpenAIClient client = GetTestProjectOpenAIClient();

// Create a conversation
ProjectConversation conversation = await client.Conversations.CreateProjectConversationAsync();
Assert.That(conversation?.Id, Does.StartWith("conv_"));

// Create 40 messages for the conversation
List<ResponseItem> messagesToAdd = new();
for (int i = 1; i <= 40; i++)
{
messagesToAdd.Add(ResponseItem.CreateUserMessageItem($"Message {i}"));
}

// Trying to add all 40 at once should fail
ClientResultException exceptionFromOperation = Assert.ThrowsAsync<ClientResultException>(async () => _ = await client.Conversations.CreateProjectConversationItemsAsync(conversation.Id, messagesToAdd));
Assert.That(exceptionFromOperation.GetRawResponse().Content.ToString(), Does.Contain("20 items"));

List<ResponseItem> firstHalfMessages = [];
for (int i = 0; i < 20; i++)
{
firstHalfMessages.Add(messagesToAdd[i]);
}
List<ResponseItem> secondHalfMessages = [];
for (int i = 20; i < messagesToAdd.Count; i++)
{
secondHalfMessages.Add(messagesToAdd[i]);
}

ReadOnlyCollection<ResponseItem> createdItems = await client.Conversations.CreateProjectConversationItemsAsync(
conversation.Id,
firstHalfMessages);
Assert.That(createdItems, Has.Count.EqualTo(20));
createdItems = await client.Conversations.CreateProjectConversationItemsAsync(conversation.Id, secondHalfMessages);
Assert.That(createdItems, Has.Count.EqualTo(20));

// Test ascending order traversal
List<AgentResponseItem> ascendingItems = [];
await foreach (AgentResponseItem item in client.Conversations.GetProjectConversationItemsAsync(
conversation.Id,
limit: 5,
order: "asc"))
{
ascendingItems.Add(item);
}
Assert.That(ascendingItems, Has.Count.EqualTo(40));

// Test descending order traversal
List<AgentResponseItem> descendingItems = [];
await foreach (AgentResponseItem item in client.Conversations.GetProjectConversationItemsAsync(
conversation.Id,
limit: 5,
order: "desc"))
{
descendingItems.Add(item);
}
Assert.That(descendingItems, Has.Count.EqualTo(40));

// Verify that ascending and descending lists contain the same items but in reverse order
descendingItems.Reverse();
Assert.That(ascendingItems.Count, Is.EqualTo(descendingItems.Count));
for (int i = 0; i < ascendingItems.Count; i++)
{
Assert.That(ascendingItems[i].Id, Is.EqualTo(descendingItems[i].Id),
$"Item at position {i} should be the same in both orderings");
}

// Verify that we can collect all items consistently
List<AgentResponseItem> allItems = [];
await foreach (AgentResponseItem item in client.Conversations.GetProjectConversationItemsAsync(conversation.Id))
{
allItems.Add(item);
}
Assert.That(allItems, Has.Count.EqualTo(40));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,7 @@ public OtherOpenAIParityTests(bool isAsync) : base(isAsync)
[TestCase(OpenAIClientMode.UseFDPOpenAI, "fine-tune")]
public async Task FileUploadWorks(OpenAIClientMode clientMode, string rawPurpose)
{
OpenAIClient openAIClient = clientMode switch
{
OpenAIClientMode.UseExternalOpenAI => GetTestBaseOpenAIClient(),
OpenAIClientMode.UseFDPOpenAI => GetTestProjectOpenAIClient(),
_ => throw new NotImplementedException()
};
OpenAIClient openAIClient = GetTestOpenAIClient(clientMode);
OpenAIFileClient fileClient = openAIClient.GetOpenAIFileClient();

(string filename, string rawFileData) = rawPurpose switch
Expand Down
31 changes: 31 additions & 0 deletions sdk/ai/Azure.AI.Projects.OpenAI/tests/ProjectsOpenAITestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,37 @@ protected OpenAIClient GetTestBaseOpenAIClient(Uri overrideEndpoint = null)
options));
}

protected OpenAIResponseClient GetTestBaseResponsesClient(Uri overrideEndpoint = null, string overrideModel = null)
{
OpenAIClientOptions options = CreateTestOpenAIClientOptions<OpenAIClientOptions>(overrideEndpoint);

return CreateProxyFromClient(
new OpenAIResponseClient(
overrideModel ?? TestEnvironment.MODELDEPLOYMENTNAME,
new ApiKeyCredential(TestEnvironment.PARITY_OPENAI_API_KEY),
options));
}

protected OpenAIClient GetTestOpenAIClient(OpenAIClientMode clientMode)
{
return clientMode switch
{
OpenAIClientMode.UseFDPOpenAI => GetTestProjectOpenAIClient(),
OpenAIClientMode.UseExternalOpenAI => GetTestBaseOpenAIClient(),
_ => throw new NotImplementedException()
};
}

protected OpenAIResponseClient GetTestResponsesClient(OpenAIClientMode clientMode, string overrideModel = null)
{
return clientMode switch
{
OpenAIClientMode.UseFDPOpenAI => GetTestProjectResponsesClient(defaultModelName: overrideModel),
OpenAIClientMode.UseExternalOpenAI => GetTestBaseResponsesClient(overrideModel: overrideModel),
_ => throw new NotImplementedException(),
};
}

private AuthenticationTokenProvider GetTestAuthenticationProvider()
{
// For local testing if you are using non default account
Expand Down
Loading
Loading