Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 3 additions & 3 deletions CS/ReportingApp/ReportingApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@
<PackageReference Include="Azure.AI.OpenAI.Assistants" Version="1.0.0-beta.4" />
<PackageReference Include="DevExpress.AIIntegration.OpenAI" Version="25.1.*-*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageReference Include="Microsoft.Extensions.AI" Version="9.4.3-preview.1.25230.7" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.4.3-preview.1.25230.7" />
<PackageReference Include="Microsoft.Extensions.AI" Version="9.5.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.5.0-preview.1.25265.7" />
<PackageReference Include="System.Data.SQLite" Version="1.0.117" />
<PackageReference Include="DevExpress.AspNetCore.Reporting" Version="25.1.*-*" />
<PackageReference Include="DevExpress.AIIntegration.Web" Version="25.1.*-*" />
<PackageReference Include="DevExpress.AIIntegration.Web" Version="25.1.*-*" />
<PackageReference Include="DevExpress.Drawing.Skia" Version="25.1.*-*" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
Expand Down
13 changes: 5 additions & 8 deletions CS/ReportingApp/Services/AIAssistantCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,26 @@ public AIAssistantCreator(OpenAIClient client, string deployment) {
this.deployment = deployment;
}

public async Task<(string assistantId, string threadId)> CreateAssistantAsync(Stream data, string fileName, string instructions, bool useFileSearchTool = true, CancellationToken ct = default) {
public async Task<(string assistantId, string threadId)> CreateAssistantAndThreadAsync(Stream data, string fileName, string instructions, CancellationToken ct = default) {
data.Position = 0;

ClientResult<OpenAIFile> fileResponse = await fileClient.UploadFileAsync(data, fileName, FileUploadPurpose.Assistants, ct);
OpenAIFile file = fileResponse.Value;

var resources = new ToolResources() {
CodeInterpreter = new CodeInterpreterToolResources(),
FileSearch = useFileSearchTool ? new FileSearchToolResources() : null
FileSearch = new FileSearchToolResources()
};
resources.FileSearch?.NewVectorStores.Add(new VectorStoreCreationHelper([file.Id]));
resources.CodeInterpreter.FileIds.Add(file.Id);

AssistantCreationOptions assistantCreationOptions = new AssistantCreationOptions() {
Name = Guid.NewGuid().ToString(),
Instructions = instructions,
ToolResources = resources
ToolResources = resources,
Tools = { new CodeInterpreterToolDefinition(),
new FileSearchToolDefinition() }
};
assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition());
if (useFileSearchTool) {
assistantCreationOptions.Tools.Add(new FileSearchToolDefinition());
}

ClientResult<Assistant> assistantResponse = await assistantClient.CreateAssistantAsync(deployment, assistantCreationOptions, ct);
ClientResult<AssistantThread> threadResponse = await assistantClient.CreateThreadAsync(cancellationToken: ct);

Expand Down
14 changes: 10 additions & 4 deletions CS/ReportingApp/Services/AIAssistantProvider.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System;
using DevExpress.AIIntegration.Services.Assistant;
using Microsoft.AspNetCore.Hosting;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using DevExpress.AIIntegration.Services.Assistant;

namespace ReportingApp.Services {
public class AIAssistantProvider : IAIAssistantProvider {
Expand All @@ -19,7 +19,7 @@ public class AIAssistantProvider : IAIAssistantProvider {
private ConcurrentDictionary<string, IAIAssistant> Assistants { get; set; } = new ();

private async Task<string> CreateAssistant(Stream data, string fileName, string prompt) {
(string assistantId, string threadId) = await assistantCreator.CreateAssistantAsync(data, fileName, prompt);
(string assistantId, string threadId) = await assistantCreator.CreateAssistantAndThreadAsync(data, fileName, prompt);

IAIAssistant assistant = await assistantFactory.GetAssistant(assistantId, threadId);
await assistant.InitializeAsync();
Expand All @@ -35,9 +35,15 @@ public AIAssistantProvider(IAIAssistantFactory assistantFactory, IWebHostEnviron
this.environment = environment;
this.assistantCreator = assistantCreator;
}

// Creates a Data Analysis Assistant for Web Document Viewer.
// This assistant analyzes report content and answers questions related to information within the report.
public async Task<string> CreateDocumentAssistant(Stream data) {
return await CreateAssistant(data, Guid.NewGuid().ToString() + ".pdf", DOCUMENT_ASSISTANT_PROMPT);
}

// Creates a UI Asisstant for Web Report Designer.
// This assistant explains how to use the Designer UI to accomplish various tasks.
public async Task<string> CreateUserAssistant() {
string dirPath = Path.Combine(environment.ContentRootPath, "Data");
string filePath = Path.Combine(dirPath, DOCUMENTATION_FILE_NAME);
Expand Down
17 changes: 11 additions & 6 deletions CS/ReportingApp/wwwroot/js/aiIntegration.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const createAssistantTab = (function() {
const createAssistantTab = (function() {

let lastUserQuery;
let errorList = [];
Expand Down Expand Up @@ -34,11 +34,16 @@ const createAssistantTab = (function() {
}

function normalizeAIResponse(text) {
text = text.replace(/【\d+:\d+†[^\】]+】/g, "");
let html = marked.parse(text);
if(/<p>\.\s*<\/p>\s*$/.test(html))
html = html.replace(/<p>\.\s*<\/p>\s*$/, "")
return html;
if (text) {
text = text.replace(/【\d+:\d+†[^\】]+】/g, "");
let html = marked.parse(text);
if (/<p>\.\s*<\/p>\s*$/.test(html))
html = html.replace(/<p>\.\s*<\/p>\s*$/, "")
return html;
}
else {
return "Please try again later."
}
}

function copyText(text) {
Expand Down
59 changes: 38 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<!-- default badges list -->
![](https://img.shields.io/endpoint?url=https://codecentral.devexpress.com/api/v1/VersionRange/853003889/25.1.2%2B)
[![](https://img.shields.io/badge/Open_in_DevExpress_Support_Center-FF7200?style=flat-square&logo=DevExpress&logoColor=white)](https://supportcenter.devexpress.com/ticket/details/T1252182)
[![](https://img.shields.io/badge/📖_How_to_use_DevExpress_Examples-e9f6fc?style=flat-square)](https://docs.devexpress.com/GeneralInformation/403183)
[![](https://img.shields.io/badge/💬_Leave_Feedback-feecdd?style=flat-square)](#does-this-example-address-your-development-requirementsobjectives)
Expand All @@ -18,7 +17,9 @@ The AI assistant's role depends on the associated DevExpress Reports component:
> [!Note]
> We use the following versions of the `Microsoft.Extensions.AI.*` libraries in our source code:
>
> v25.1.2+ | **9.4.3-preview.1.25230.7**
> - Microsoft.Extensions.AI.Abstractions: **9.5.0**
> - Microsoft.Extensions.AI: **9.5.0**
> - Microsoft.Extensions.AI.OpenAI: **9.5.0-preview.1.25265.7**
>
> We do not guarantee compatibility or correct operation with higher versions.

Expand Down Expand Up @@ -76,20 +77,28 @@ Files to Review:

#### AI Assistant Provider

On the server side, the `AIAssistantProvider` service manages assistants. An `IAIAssistantFactory` instance creates assistants with keys specified in previous steps.
On the server side, the `AIAssistantProvider` service manages assistants.

```cs
public interface IAIAssistantProvider {
IAIAssistant GetAssistant(string assistantName);
Task<string> CreateAssistant(AssistantType assistantType, Stream data);
Task<string> CreateAssistant(AssistantType assistantType);
Task<string> CreateDocumentAssistant(Stream data);
Task<string> CreateUserAssistant();
void DisposeAssistant(string assistantName);
}
```

The `AIAssistantCreator.CreateAssistantAsync` method uploads a file to OpenAI, configures tool resources, creates an assistant with specified instructions and tools, initializes a new thread, and returns the assistant and thread IDs. The generated assistant and thread IDs are then passed to the `IAIAssistantFactory.GetAssistant` method, which returns an `IAIAssistant` instance. The created instance is added to the application's assistant collection and is referenced by its unique name.

For information on OpenAI Assistants, refer to the following documents:
- [OpenAI Assistants API overview](https://platform.openai.com/docs/assistants/overview)
- [Azure OpenAI: OpenAI Assistants client library for .NET](https://learn.microsoft.com/en-us/dotnet/api/overview/azure/ai.openai.assistants-readme?view=azure-dotnet-preview)
- [OpenAI .NET API library](https://github.com/openai/openai-dotnet)

Files to Review:
- [AIAssistantProvider.cs](./CS/ReportingApp/Services/AIAssistantProvider.cs)
- [IAIAssistantProvider.cs](./CS/ReportingApp/Services/IAIAssistantProvider.cs)
- [AIAssistantCreator.cs](./CS/ReportingApp/Services/AIAssistantCreator.cs)


### Web Document Viewer (Data Analysis Assistant)
Expand Down Expand Up @@ -132,7 +141,7 @@ On the `BeforeRender` event, add a new tab (a container for the assistant interf

#### Access the Assistant

Once the document is ready, the `DocumentReady` event handler sends a request to the server and obtains the assistant's ID:
Once the document is ready, the `DocumentReady` event handler sends a request to the server and obtains the assistant name:

```js
async function DocumentReady(sender, args) {
Expand All @@ -144,7 +153,27 @@ async function DocumentReady(sender, args) {
}
```

The [`PerformCustomDocumentOperation`](https://docs.devexpress.com/XtraReports/js-ASPxClientWebDocumentViewer?p=netframework#js_aspxclientwebdocumentviewer_performcustomdocumentoperation) method exports the report to PDF and creates an assistant based on the exported document. See [AIDocumentOperationService.cs](./CS/ReportingApp/Services/AIDocumentOperationService.cs) for implementation details.
The [`PerformCustomDocumentOperation`](https://docs.devexpress.com/XtraReports/js-ASPxClientWebDocumentViewer?p=netframework#js_aspxclientwebdocumentviewer_performcustomdocumentoperation) method exports the report to PDF and creates an assistant based on the exported document:

```cs
// ...
public override async Task<DocumentOperationResponse> PerformOperationAsync(DocumentOperationRequest request, PrintingSystemBase printingSystem, PrintingSystemBase printingSystemWithEditingFields) {
using(var stream = new MemoryStream()) {
printingSystem.ExportToPdf(stream, printingSystem.ExportOptions.Pdf);
var assistantName = await AIAssistantProvider.CreateDocumentAssistant(stream);
return new DocumentOperationResponse {
DocumentId = request.DocumentId,
CustomData = assistantName,
Succeeded = true
};
}
}
```

See the following files for implementation details:

- [AIDocumentOperationService.cs](./CS/ReportingApp/Services/AIDocumentOperationService.cs)
- [AIAssistantProvider.cs](./CS/ReportingApp/Services/AIAssistantProvider.cs)

#### Communicate with the Assistant

Expand Down Expand Up @@ -239,21 +268,9 @@ async function BeforeRender(sender, args) {
}
```

The `AIAssistantProvider` service creates an assistant using the provided PDF documentation (the *documentation.pdf* file):
The `AIAssistantProvider.CreateUserAssistant` method creates an assistant using the provided PDF documentation (the *documentation.pdf* file) and prompt. See the [AIAssistantProvider.cs](./CS/ReportingApp/Services/AIAssistantProvider.cs) file for implementation details.


```cs
// ...
public async Task<string> CreateAssistant(AssistantType assistantType, Stream data) {
var assistantName = Guid.NewGuid().ToString();
var assistant = await assistantFactory.CreateAssistant(assistantName);
Assistants.TryAdd(assistantName, assistant);
var prompt = GetPrompt(assistantType);
if(assistantType == AssistantType.UserAssistant) {
await LoadDocumentation(assistant, prompt);
}
return assistantName;
}
```
#### Communicate with the Assistant

Each time a user sends a message, the [`onMessageEntered`](https://js.devexpress.com/jQuery/Documentation/24_2/ApiReference/UI_Components/dxChat/Configuration/#onMessageEntered) event handler passes the request to the assistant:
Expand Down