Skip to content

Commit e8f291f

Browse files
authored
Merge pull request #66 from petehauge/Update-FileSearchBlobStorage-samples
Update File Search with Blob Storage sample
2 parents 49a12a9 + a92ac09 commit e8f291f

File tree

4 files changed

+646
-1
lines changed

4 files changed

+646
-1
lines changed
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
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

Comments
 (0)