|
| 1 | +# Sample enterprise file search with agent in Azure.AI.Agents. |
| 2 | + |
| 3 | +In the enterprise file search, as opposed to regular file search, this sample assumes that user has uploaded the file to Azure Blob Storage and have registered it in the Azure AI Foundry. In the example below we will utilize the asset ID from Azure as a data source for the `VectorStore`. |
| 4 | + |
| 5 | +1. First we need to create agent client and read the environment variables, which will be used in the next steps. |
| 6 | +```C# Snippet:AgentsEnterpriseFileSearch_CreateProject |
| 7 | + |
| 8 | +var projectEndpoint = configuration["ProjectEndpoint"]; |
| 9 | +var modelDeploymentName = configuration["ModelDeploymentName"]; |
| 10 | +var blobURI = configuration["AzureBlobUri"]; |
| 11 | + |
| 12 | +// Create the Agent Client |
| 13 | +PersistentAgentsClient agentClient = new( |
| 14 | + projectEndpoint, |
| 15 | + new DefaultAzureCredential(), |
| 16 | + new PersistentAgentsAdministrationClientOptions( |
| 17 | + PersistentAgentsAdministrationClientOptions.ServiceVersion.V2025_05_01 |
| 18 | + )); |
| 19 | +``` |
| 20 | + |
| 21 | +2. To create agent capable of using Enterprise file search, we will create `VectorStoreDataSource` and will supply it to `VectorStore` constructor. The ID of the created vector store will be used in the `FileSearchToolResource` used for agent creation. |
| 22 | +Synchronous sample: |
| 23 | +```C# Snippet:AgentsEnterpriseFileSearch_CreateVectorStore |
| 24 | +// Create the vector store used when creating the agent |
| 25 | +var ds = new VectorStoreDataSource( |
| 26 | + assetIdentifier: blobURI, |
| 27 | + assetType: VectorStoreDataSourceAssetType.UriAsset |
| 28 | +); |
| 29 | + |
| 30 | +VectorStore vectorStore = agentClient.VectorStores.CreateVectorStore( |
| 31 | + name: "sample_vector_store", |
| 32 | + storeConfiguration: new VectorStoreConfiguration( |
| 33 | + dataSources: [ds] |
| 34 | + ) |
| 35 | +); |
| 36 | + |
| 37 | +FileSearchToolResource fileSearchResource = new([vectorStore.Id], null); |
| 38 | + |
| 39 | +List<ToolDefinition> tools = [new FileSearchToolDefinition()]; |
| 40 | + |
| 41 | +// Create the Agent leveraging the FileSearchTool |
| 42 | +PersistentAgent agent = agentClient.Administration.CreateAgent( |
| 43 | + model: modelDeploymentName, |
| 44 | + name: "my-agent", |
| 45 | + instructions: "You are helpful agent.", |
| 46 | + tools: tools, |
| 47 | + toolResources: new ToolResources() |
| 48 | + { |
| 49 | + FileSearch = fileSearchResource |
| 50 | + } |
| 51 | +); |
| 52 | +``` |
| 53 | +Asynchronous sample: |
| 54 | +```C# Snippet:AgentsEnterpriseFileSearchAsync_CreateVectorStore |
| 55 | +// Create the vector store used when creating the agent |
| 56 | +var ds = new VectorStoreDataSource( |
| 57 | + assetIdentifier: blobURI, |
| 58 | + assetType: VectorStoreDataSourceAssetType.UriAsset |
| 59 | +); |
| 60 | + |
| 61 | +VectorStore vectorStore = await agentClient.VectorStores.CreateVectorStoreAsync( |
| 62 | + name: "sample_vector_store", |
| 63 | + storeConfiguration: new VectorStoreConfiguration( |
| 64 | + dataSources: [ds] |
| 65 | + ) |
| 66 | +); |
| 67 | + |
| 68 | +FileSearchToolResource fileSearchResource = new([vectorStore.Id], null); |
| 69 | + |
| 70 | +List<ToolDefinition> tools = [new FileSearchToolDefinition()]; |
| 71 | + |
| 72 | +// Create the Agent leveraging the FileSearchTool |
| 73 | +PersistentAgent agent = await agentClient.Administration.CreateAgentAsync( |
| 74 | + model: modelDeploymentName, |
| 75 | + name: "my-agent", |
| 76 | + instructions: "You are helpful agent.", |
| 77 | + tools: tools, |
| 78 | + toolResources: new ToolResources() |
| 79 | + { |
| 80 | + FileSearch = fileSearchResource |
| 81 | + } |
| 82 | +); |
| 83 | +``` |
| 84 | + |
| 85 | +3. In this example we will ask a question to the file contents and add it to the thread; we will create run and wait while it will terminate. |
| 86 | + |
| 87 | +Synchronous sample: |
| 88 | +```C# Snippet:AgentsEnterpriseFileSearch_CreateThreadMessage |
| 89 | +PersistentAgentThread thread = agentClient.Threads.CreateThread(); |
| 90 | + |
| 91 | +// Create message and run the agent |
| 92 | +ThreadMessage message = agentClient.Messages.CreateMessage( |
| 93 | + threadId: thread.Id, |
| 94 | + role: MessageRole.User, |
| 95 | + content: "What feature does Smart Eyewear offer?" |
| 96 | +); |
| 97 | +ThreadRun run = agentClient.Runs.CreateRun(thread.Id, agent.Id); |
| 98 | + |
| 99 | +// Wait for the agent to finish running |
| 100 | +do |
| 101 | +{ |
| 102 | + Thread.Sleep(TimeSpan.FromMilliseconds(500)); |
| 103 | + run = agentClient.Runs.GetRun(thread.Id, run.Id); |
| 104 | +} |
| 105 | + |
| 106 | +while (run.Status == RunStatus.Queued |
| 107 | + || run.Status == RunStatus.InProgress); |
| 108 | + |
| 109 | +// Confirm that the run completed successfully |
| 110 | +if (run.Status != RunStatus.Completed) |
| 111 | +{ |
| 112 | + throw new Exception("Run did not complete successfully, error: " + run.LastError?.Message); |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +Asynchronous sample: |
| 117 | +```C# Snippet:AgentsEnterpriseFileSearchAsync_CreateThreadMessage |
| 118 | +PersistentAgentThread thread = await agentClient.Threads.CreateThreadAsync(); |
| 119 | + |
| 120 | +// Create message and run the agent |
| 121 | +ThreadMessage message = await agentClient.Messages.CreateMessageAsync( |
| 122 | + threadId: thread.Id, |
| 123 | + role: MessageRole.User, |
| 124 | + content: "What feature does Smart Eyewear offer?" |
| 125 | +); |
| 126 | +ThreadRun run = await agentClient.Runs.CreateRunAsync(thread.Id, agent.Id); |
| 127 | + |
| 128 | +// Wait for the agent to finish running |
| 129 | +do |
| 130 | +{ |
| 131 | + await Task.Delay(TimeSpan.FromMilliseconds(500)); |
| 132 | + run = await agentClient.Runs.GetRunAsync(thread.Id, run.Id); |
| 133 | +} |
| 134 | + |
| 135 | +while (run.Status == RunStatus.Queued |
| 136 | + || run.Status == RunStatus.InProgress); |
| 137 | + |
| 138 | +// Confirm that the run completed successfully |
| 139 | +if (run.Status != RunStatus.Completed) |
| 140 | +{ |
| 141 | + throw new Exception("Run did not complete successfully, error: " + run.LastError?.Message); |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +4. When we create `VectorStore`, it ingests the contents of the Azure Blob, provided in the `VectorStoreDataSource` object and associates it with File ID. To provide the file name we will need to get the file name by ID, which in our case will be Azure Resource ID and take its last segment. |
| 146 | + |
| 147 | +Synchronous sample: |
| 148 | +```C# Snippet:AgentsEnterpriseFileSearch_EnableMapFileIds |
| 149 | +// Build the map of file IDs to file names. |
| 150 | +Dictionary<string, string> filesMap = []; |
| 151 | + |
| 152 | +var storeFiles = agentClient.VectorStoreFiles.GetVectorStoreFiles(vectorStore.Id); |
| 153 | + |
| 154 | +// Build the map of file IDs to file names (used in updating messages) |
| 155 | +foreach (VectorStoreFile storeFile in storeFiles ) |
| 156 | +{ |
| 157 | + PersistentAgentFileInfo agentFile = agentClient.Files.GetFile(storeFile.Id); |
| 158 | + Uri uriFile = new(agentFile.Filename); |
| 159 | + filesMap.Add(storeFile.Id, uriFile.Segments[uriFile.Segments.Length - 1]); |
| 160 | +} |
| 161 | + |
| 162 | +// Helper method for replacing references |
| 163 | +static string replaceReferences(Dictionary<string, string> fileIds, string fileID, string placeholder, string text) |
| 164 | +{ |
| 165 | + if (fileIds.TryGetValue(fileID, out string replacement)) |
| 166 | + return text.Replace(placeholder, $" [{replacement}]"); |
| 167 | + else |
| 168 | + return text.Replace(placeholder, $" [{fileID}]"); |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +Asynchronous sample: |
| 173 | +```C# Snippet:AgentsEnterpriseFileSearchAsync_EnableMapFileIds |
| 174 | +// Build the map of file IDs to file names. |
| 175 | +Dictionary<string, string> filesMap = []; |
| 176 | + |
| 177 | +var storeFiles = agentClient.VectorStoreFiles.GetVectorStoreFilesAsync(vectorStore.Id); |
| 178 | + |
| 179 | +// Build the map of file IDs to file names (used in updating messages) |
| 180 | +await foreach (VectorStoreFile storeFile in storeFiles) |
| 181 | +{ |
| 182 | + PersistentAgentFileInfo agentFile = agentClient.Files.GetFile(storeFile.Id); |
| 183 | + Uri uriFile = new(agentFile.Filename); |
| 184 | + filesMap.Add(storeFile.Id, uriFile.Segments[uriFile.Segments.Length - 1]); |
| 185 | +} |
| 186 | + |
| 187 | +// Helper method for replacing references |
| 188 | +static string replaceReferences(Dictionary<string, string> fileIds, string fileID, string placeholder, string text) |
| 189 | +{ |
| 190 | + if (fileIds.TryGetValue(fileID, out string replacement)) |
| 191 | + return text.Replace(placeholder, $" [{replacement}]"); |
| 192 | + else |
| 193 | + return text.Replace(placeholder, $" [{fileID}]"); |
| 194 | +} |
| 195 | + |
| 196 | +``` |
| 197 | + |
| 198 | +5. Print the agent messages to console in chronological order (including formatting URL citations). To properly render the file names we call the `replaceReferences` method to replace reference placeholders by file IDs or by file names. |
| 199 | + |
| 200 | +Synchronous sample: |
| 201 | +```C# Snippet:AgentsEnterpriseFileSearch_WriteMessages |
| 202 | +// Retrieve all messages from the agent client |
| 203 | +Pageable<ThreadMessage> messages = agentClient.Messages.GetMessages( |
| 204 | + threadId: thread.Id, |
| 205 | + order: ListSortOrder.Ascending |
| 206 | +); |
| 207 | + |
| 208 | +foreach (ThreadMessage threadMessage in messages) |
| 209 | +{ |
| 210 | + Console.Write($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: "); |
| 211 | + foreach (MessageContent contentItem in threadMessage.ContentItems) |
| 212 | + { |
| 213 | + if (contentItem is MessageTextContent textItem) |
| 214 | + { |
| 215 | + if (threadMessage.Role == MessageRole.Agent && textItem.Annotations.Count > 0) |
| 216 | + { |
| 217 | + string strMessage = textItem.Text; |
| 218 | + |
| 219 | + // If we file path or file citation annotations - rewrite the 'source' FileId with the file name |
| 220 | + foreach (MessageTextAnnotation annotation in textItem.Annotations) |
| 221 | + { |
| 222 | + if (annotation is MessageTextFilePathAnnotation pathAnnotation) |
| 223 | + { |
| 224 | + strMessage = replaceReferences(filesMap, pathAnnotation.FileId, pathAnnotation.Text, strMessage); |
| 225 | + } |
| 226 | + else if (annotation is MessageTextFileCitationAnnotation citationAnnotation) |
| 227 | + { |
| 228 | + strMessage = replaceReferences(filesMap, citationAnnotation.FileId, citationAnnotation.Text, strMessage); |
| 229 | + } |
| 230 | + } |
| 231 | + Console.Write(strMessage); |
| 232 | + } |
| 233 | + else |
| 234 | + { |
| 235 | + Console.Write(textItem.Text); |
| 236 | + } |
| 237 | + } |
| 238 | + else if (contentItem is MessageImageFileContent imageFileItem) |
| 239 | + { |
| 240 | + Console.Write($"<image from ID: {imageFileItem.FileId}"); |
| 241 | + } |
| 242 | + Console.WriteLine(); |
| 243 | + } |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +Asynchronous sample: |
| 248 | +```C# Snippet:AgentsEnterpriseFileSearchAsync_WriteMessages |
| 249 | + // Retrieve all messages from the agent client |
| 250 | + AsyncPageable<ThreadMessage> messages = agentClient.Messages.GetMessagesAsync( |
| 251 | + threadId: thread.Id, |
| 252 | + order: ListSortOrder.Ascending |
| 253 | + ); |
| 254 | + |
| 255 | + await foreach (ThreadMessage threadMessage in messages) |
| 256 | + { |
| 257 | + Console.Write($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: "); |
| 258 | + foreach (MessageContent contentItem in threadMessage.ContentItems) |
| 259 | + { |
| 260 | + if (contentItem is MessageTextContent textItem) |
| 261 | + { |
| 262 | + if (threadMessage.Role == MessageRole.Agent && textItem.Annotations.Count > 0) |
| 263 | + { |
| 264 | + string strMessage = textItem.Text; |
| 265 | + |
| 266 | + // If we file path or file citation annotations - rewrite the 'source' FileId with the file name |
| 267 | + foreach (MessageTextAnnotation annotation in textItem.Annotations) |
| 268 | + { |
| 269 | + if (annotation is MessageTextFilePathAnnotation pathAnnotation) |
| 270 | + { |
| 271 | + strMessage = replaceReferences(filesMap, pathAnnotation.FileId, pathAnnotation.Text, strMessage); |
| 272 | + } |
| 273 | + else if (annotation is MessageTextFileCitationAnnotation citationAnnotation) |
| 274 | + { |
| 275 | + strMessage = replaceReferences(filesMap, citationAnnotation.FileId, citationAnnotation.Text, strMessage); |
| 276 | + } |
| 277 | + } |
| 278 | + Console.Write(strMessage); |
| 279 | + } |
| 280 | + else |
| 281 | + { |
| 282 | + Console.Write(textItem.Text); |
| 283 | + } |
| 284 | + } |
| 285 | + else if (contentItem is MessageImageFileContent imageFileItem) |
| 286 | + { |
| 287 | + Console.Write($"<image from ID: {imageFileItem.FileId}"); |
| 288 | + } |
| 289 | + Console.WriteLine(); |
| 290 | + } |
| 291 | + } |
| 292 | +``` |
| 293 | + |
| 294 | +6. Finally, we delete all the resources, we have created in this sample. |
| 295 | + |
| 296 | +Synchronous sample: |
| 297 | +```C# Snippet:AgentsEnterpriseFileSearch_Cleanup |
| 298 | +// Clean up resources |
| 299 | +VectorStoreDeletionStatus delTask = agentClient.VectorStores.DeleteVectorStore(vectorStore.Id); |
| 300 | +if (delTask.Deleted) |
| 301 | +{ |
| 302 | + Console.WriteLine($"Deleted vector store {vectorStore.Id}"); |
| 303 | +} |
| 304 | +else |
| 305 | +{ |
| 306 | + Console.WriteLine($"Unable to delete vector store {vectorStore.Id}"); |
| 307 | +} |
| 308 | +agentClient.Threads.DeleteThread(thread.Id); |
| 309 | +agentClient.Administration.DeleteAgent(agent.Id); |
| 310 | +``` |
| 311 | + |
| 312 | +Asynchronous sample: |
| 313 | +```C# Snippet:AgentsEnterpriseFileSearchAsync_Cleanup |
| 314 | +// Clean up resources |
| 315 | +VectorStoreDeletionStatus delTask = await agentClient.VectorStores.DeleteVectorStoreAsync(vectorStore.Id); |
| 316 | +if (delTask.Deleted) |
| 317 | +{ |
| 318 | + Console.WriteLine($"Deleted vector store {vectorStore.Id}"); |
| 319 | +} |
| 320 | +else |
| 321 | +{ |
| 322 | + Console.WriteLine($"Unable to delete vector store {vectorStore.Id}"); |
| 323 | +} |
| 324 | +await agentClient.Threads.DeleteThreadAsync(thread.Id); |
| 325 | +await agentClient.Administration.DeleteAgentAsync(agent.Id); |
| 326 | +``` |
0 commit comments