From 5dc74f54c2933920eede1f2d08f42cfc140e6fbe Mon Sep 17 00:00:00 2001 From: Polina Tyureva Date: Fri, 12 Jun 2026 18:54:13 +0400 Subject: [PATCH 1/2] update example --- CS/ReportingApp/Program.cs | 6 ---- CS/ReportingApp/ReportingApp.csproj | 2 -- .../Services/AIReportingChatService.cs | 22 +++++++------- CS/ReportingApp/Services/AgentFactory.cs | 8 +++-- CS/ReportingApp/Services/AgentInstructions.cs | 29 +++++++++++++++++++ .../CustomReportStorageWebExtension.cs | 2 +- CS/ReportingApp/package.json | 6 ++-- 7 files changed, 48 insertions(+), 27 deletions(-) create mode 100644 CS/ReportingApp/Services/AgentInstructions.cs diff --git a/CS/ReportingApp/Program.cs b/CS/ReportingApp/Program.cs index 52427aa..df4a697 100644 --- a/CS/ReportingApp/Program.cs +++ b/CS/ReportingApp/Program.cs @@ -15,8 +15,6 @@ using System; using System.IO; using Azure; -using DevExpress.AIIntegration; -using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; var builder = WebApplication.CreateBuilder(args); @@ -43,13 +41,9 @@ new Uri(EnvSettings.AzureOpenAIEndpoint), new AzureKeyCredential(EnvSettings.AzureOpenAIKey)); -var chatClient = azureOpenAIClient.GetChatClient(EnvSettings.DeploymentName).AsIChatClient(); - -builder.Services.AddSingleton(chatClient); builder.Services.AddSingleton(sp => new(azureOpenAIClient, EnvSettings.DeploymentName, sp.GetRequiredService>())); builder.Services.AddSingleton(); -builder.Services.AddDevExpressAI(config => { }); var app = builder.Build(); using(var scope = app.Services.CreateScope()) { diff --git a/CS/ReportingApp/ReportingApp.csproj b/CS/ReportingApp/ReportingApp.csproj index a962c03..1cef504 100644 --- a/CS/ReportingApp/ReportingApp.csproj +++ b/CS/ReportingApp/ReportingApp.csproj @@ -48,7 +48,6 @@ - @@ -56,7 +55,6 @@ - diff --git a/CS/ReportingApp/Services/AIReportingChatService.cs b/CS/ReportingApp/Services/AIReportingChatService.cs index c737e79..7ee0570 100644 --- a/CS/ReportingApp/Services/AIReportingChatService.cs +++ b/CS/ReportingApp/Services/AIReportingChatService.cs @@ -7,16 +7,14 @@ namespace ReportingApp.Services { public class AIReportingChatService : IAIReportingChatService, IAsyncDisposable { - const string SESSION_NOT_FOUND_ERROR = "Chat session not found"; - const string DOCUMENTATION_FILE_NAME = "documentation.pdf"; - const string DOCUMENT_ASSISTANT_PROMPT = "You are a data analysis assistant. Your task is to read information from PDF files and provide users with accurate data-driven answers based on the contents of these files. \n Key Responsibilities: \n - Perform data analysis, including data summaries, calculations, filtering, and trend identification.\n - Clearly explain your analysis process to ensure users understand how you reached your conclusions.\n - Provide precise and accurate responses strictly based on data in the file.\n - If the requested information is not available in the provided file's content, state: \"The requested information cannot be found in the data provided.\"\n - Avoid giving responses when data is insufficient for a reliable answer.\n - Ask clarifying questions when a user’s query is unclear or lacks detail.\n - Your primary goal is to deliver helpful insights that directly address user questions. Do not make assumptions or infer details not supported by data. Respond in plain text only, without sources, footnotes, or annotations.\n Avoid giving information about provided file name, assistants' IDs and other internal data"; - const string USER_ASSISTANT_PROMPT = "You are a user interface assistant (you help people use a software program). Your role is to read information from documentation files in PDF format. You assist users by providing accurate answers to their questions based on information from these files. \r\n\r\nTasks:\r\nExtract relevant information from PDF documentation to answer user questions.\r\nClearly explain your reasoning process and give step by step solutions to ensure users understand how you arrived at your answers.\r\nAlways provide precise and accurate information based on content from the documentation file.\r\nIf you cannot find an answer based on provided documentation, explicitly state: 'The requested information cannot be found in documentation provided.'\r\n Respond in plain text only, without markdown, sources, footnotes, or annotations."; + const string SessionNotFoundError = "Chat session not found"; + const string DocumentationFileName = "documentation.pdf"; readonly AgentFactory agentFactory; readonly IWebHostEnvironment environment; // Each chat session holds an IChatResponseProvider and a cleanup delegate that - // removes the uploaded OpenAI resources (file + vector store) when the session ends. + // removes the uploaded OpenAI resources (file and vector store) when the session ends. ConcurrentDictionary Cleanup)> sessions = new(); public AIReportingChatService(AgentFactory agentFactory, IWebHostEnvironment environment) { @@ -30,35 +28,35 @@ async Task RegisterSession(IChatResponseProvider provider, Func cl return sessionId; } - // Opens a Data Analysis chat for Web Document Viewer. + // Open a Data Analysis chat for the Web Document Viewer. // The agent analyzes the exported report PDF and answers data-driven questions. public async Task OpenDocumentChatAsync(Stream data) { var (provider, cleanup) = await agentFactory.CreateAgentWithFileAsync( - data, Guid.NewGuid().ToString() + ".pdf", DOCUMENT_ASSISTANT_PROMPT); + data, Guid.NewGuid().ToString() + ".pdf", AgentInstructions.DocumentAssistantPrompt); return await RegisterSession(provider, cleanup); } - // Opens a UI help chat for Web Report Designer. + // Open a UI help chat for the Web Report Designer. // The agent reads the documentation PDF and answers questions about using the Designer. public async Task OpenDesignerChatAsync() { - string filePath = Path.Combine(environment.ContentRootPath, "Data", DOCUMENTATION_FILE_NAME); + string filePath = Path.Combine(environment.ContentRootPath, "Data", DocumentationFileName); using var stream = File.OpenRead(filePath); var (provider, cleanup) = await agentFactory.CreateAgentWithFileAsync( - stream, DOCUMENTATION_FILE_NAME, USER_ASSISTANT_PROMPT); + stream, DocumentationFileName, AgentInstructions.DesignerAssistantPrompt); return await RegisterSession(provider, cleanup); } public IChatResponseProvider GetChatProvider(string sessionId) { if(!string.IsNullOrEmpty(sessionId) && sessions.TryGetValue(sessionId, out var tuple)) return tuple.Provider; - throw new Exception(SESSION_NOT_FOUND_ERROR); + throw new Exception(SessionNotFoundError); } public async Task CloseChatAsync(string sessionId) { if(sessions.TryRemove(sessionId, out var tuple)) await tuple.Cleanup(); else - throw new Exception(SESSION_NOT_FOUND_ERROR); + throw new Exception(SessionNotFoundError); } public async ValueTask DisposeAsync() { diff --git a/CS/ReportingApp/Services/AgentFactory.cs b/CS/ReportingApp/Services/AgentFactory.cs index a6919d5..28d103e 100644 --- a/CS/ReportingApp/Services/AgentFactory.cs +++ b/CS/ReportingApp/Services/AgentFactory.cs @@ -13,6 +13,8 @@ using OpenAI.VectorStores; namespace ReportingApp.Services { + // The OpenAI.Responses API is for evaluation purposes only and is subject to change or removal in a future update. + // The following code suppresses the OPENAI001 diagnostic. #pragma warning disable OPENAI001 public class AgentFactory { readonly AzureOpenAIClient openAIClient; @@ -25,9 +27,9 @@ public AgentFactory(AzureOpenAIClient openAIClient, string deployment, ILogger Cleanup)> CreateAgentWithFileAsync( Stream data, string fileName, string instructions, CancellationToken ct = default) { diff --git a/CS/ReportingApp/Services/AgentInstructions.cs b/CS/ReportingApp/Services/AgentInstructions.cs new file mode 100644 index 0000000..6d0fc96 --- /dev/null +++ b/CS/ReportingApp/Services/AgentInstructions.cs @@ -0,0 +1,29 @@ +namespace ReportingApp.Services { + public static class AgentInstructions { + // Instructions for the Web Document Viewer chat that analyzes an exported report PDF. + public static string DocumentAssistantPrompt = """ + You are a data analysis assistant. Your task is to read information from PDF files and provide users with accurate data-driven answers based on the contents of these files. + Key Responsibilities: + - Perform data analysis, including data summaries, calculations, filtering, and trend identification. + - Clearly explain your analysis process to ensure users understand how you reached your conclusions. + - Provide precise and accurate responses strictly based on data in the file. + - If the requested information is not available in the provided file's content, state: "The requested information cannot be found in the data provided." + - Avoid giving responses when data is insufficient for a reliable answer. + - Ask clarifying questions when a user’s query is unclear or lacks detail. + - Your primary goal is to deliver helpful insights that directly address user questions. Do not make assumptions or infer details not supported by data. Respond in plain text only, without sources, footnotes, or annotations. + Avoid giving information about provided file name, assistants' IDs and other internal data. + """; + + // Instructions for the Web Report Designer chat that answers UI questions from the documentation PDF. + public static string DesignerAssistantPrompt = """ + You are a user interface assistant (you help people use a software program). Your role is to read information from documentation files in PDF format. You assist users by providing accurate answers to their questions based on information from these files. + + Tasks: + Extract relevant information from PDF documentation to answer user questions. + Clearly explain your reasoning process and give step by step solutions to ensure users understand how you arrived at your answers. + Always provide precise and accurate information based on content from the documentation file. + If you cannot find an answer based on provided documentation, explicitly state: 'The requested information cannot be found in documentation provided.' + Respond in plain text only, without markdown, sources, footnotes, or annotations. + """; + } +} diff --git a/CS/ReportingApp/Services/CustomReportStorageWebExtension.cs b/CS/ReportingApp/Services/CustomReportStorageWebExtension.cs index d1bdc2a..08498d2 100644 --- a/CS/ReportingApp/Services/CustomReportStorageWebExtension.cs +++ b/CS/ReportingApp/Services/CustomReportStorageWebExtension.cs @@ -17,7 +17,7 @@ public CustomReportStorageWebExtension(ReportDbContext dbContext) { public override bool CanSetData(string url) { // Determines whether a report with the specified URL can be saved. // Add custom logic that returns **false** for reports that should be read-only. - // Return **true** if no valdation is required. + // Return **true** if no validation is required. // This method is called only for valid URLs (if the **IsValidUrl** method returns **true**). return true; diff --git a/CS/ReportingApp/package.json b/CS/ReportingApp/package.json index ea8369a..06ef917 100644 --- a/CS/ReportingApp/package.json +++ b/CS/ReportingApp/package.json @@ -4,10 +4,10 @@ "license": "MIT", "private": true, "dependencies": { - "@devexpress/analytics-core": "25.2.2-beta", + "@devexpress/analytics-core": "26.1.2-beta", "bootstrap": "^4.3.1", - "devexpress-reporting": "25.2.2-beta", - "devextreme-dist": "25.2.2-beta", + "devexpress-reporting": "26.1.2-beta", + "devextreme-dist": "26.1.2-beta", "marked": "^14.1.3" } } \ No newline at end of file From 4c2b76ff34f4bebacf627f4f6aef1232bd91da6f Mon Sep 17 00:00:00 2001 From: Polina Tyureva Date: Fri, 12 Jun 2026 18:54:22 +0400 Subject: [PATCH 2/2] update readme --- README.md | 153 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index ce575e6..e45a89b 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,9 @@ The AI assistant's role depends on the associated DevExpress Reports component: - **Data Analysis Assistant**: An assistant for the DevExpress *Web Document Viewer*. This assistant analyzes report content and answers questions related to information within the report. - **UI Assistant**: An assistant for the DevExpress *Web Report Designer*. This assistant explains how to use the Designer UI to accomplish various tasks. Responses are based on information from [end-user documentation](https://github.com/DevExpress/dotnet-eud) for DevExpress Web Reporting components. -**Please note that AI Assistant initialization takes time. The assistant tab appears once Microsoft Azure scans the source document on the server side.** +**Note: AI Assistant initialization takes time. The assistant tab becomes available once Azure OpenAI finishes uploading and indexing the source document on the server.** -> [!Note] -> We use the following versions of the `Microsoft.Extensions.AI.*` libraries in our source code: -> -> - Microsoft.Extensions.AI.Abstractions: **9.7.1** -> - Microsoft.Extensions.AI: **9.7.1** -> - Microsoft.Extensions.AI.OpenAI: **9.7.1-preview.1.25365.4** -> -> We do not guarantee compatibility or correct operation with higher versions. +To answer questions, the application uploads ODF documents to Azure OpenAI, and creates a chat agent using the [Azure OpenAI Responses API](https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/responses?tabs=csharp). The agent uses File Search and Code Interpreter tools to read and analyze the document. ## Implementation Details @@ -33,15 +26,15 @@ The AI assistant's role depends on the associated DevExpress Reports component: > [!NOTE] > DevExpress AI-powered extensions follow the "bring your own key" principle. DevExpress does not offer a REST API and does not ship any built-in LLMs/SLMs. You need an active Azure/Open AI subscription to obtain the REST API endpoint, key, and model deployment name. These variables must be specified at application startup to register AI clients and enable DevExpress AI-powered Extensions in your application. -You need to create an Azure OpenAI resource in the Azure portal to use AI Assistants for DevExpress Reporting. Refer to the following help topic for details: [Microsoft - Create and deploy an Azure OpenAI Service resource](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?pivots=web-portal). +Create an Azure OpenAI resource in the Azure portal. Refer to the following help topic for additional information in this regard: [Microsoft - Create and deploy an Azure OpenAI Service resource](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?pivots=web-portal). -Once you obtain a private endpoint and an API key, register them as `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_APIKEY` environment variables. The [EnvSettings.cs](./CS/ReportingApp/EnvSettings.cs) reads these settings. `DeploymentName` in this file is a name of your Azure model, for example, `GPT4o`: +Once you obtain a private endpoint and an API key, register them as `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_APIKEY` environment variables. The [EnvSettings.cs](./CS/ReportingApp/EnvSettings.cs) file reads these settings. `DeploymentName` is the name of your Azure model deployment. The model must support the Responses API, File Search, and Code Interpreter tools (for example, `gpt-5.4`): ```cs public static class EnvSettings { public static string AzureOpenAIEndpoint { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); } } public static string AzureOpenAIKey { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); } } - public static string DeploymentName { get { return "GPT4o"; } } + public static string DeploymentName { get { return "gpt-5.4"; } } } ``` @@ -50,56 +43,63 @@ Files to Review: #### Register AI Services ->[!NOTE] -> The availability of Azure Open AI Assistants depends on region. For additional guidance in this regard, refer to the following document: [Azure OpenAI Service models -- Assistants (Preview)](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=global-standard%2Cstandard-chat-completions#assistants-preview). - Register AI services in your application. Add the following code to the _Program.cs_ file: ```cs -using DevExpress.AIIntegration; using Azure; using Azure.AI.OpenAI; -using Microsoft.Extensions.AI; -using System; +using ReportingApp.Services; +using Microsoft.Extensions.Logging; // ... var azureOpenAIClient = new AzureOpenAIClient( new Uri(EnvSettings.AzureOpenAIEndpoint), new AzureKeyCredential(EnvSettings.AzureOpenAIKey)); - -var chatClient = azureOpenAIClient.GetChatClient(EnvSettings.DeploymentName).AsIChatClient; -builder.Services.AddDevExpressAI(config => -{ - config.RegisterOpenAIAssistants(azureOpenAIClient, EnvSettings.DeploymentName); -}); + +// Create a Responses API agent (with File Search and Code Interpreter tools) for each chat. +builder.Services.AddSingleton(sp => + new(azureOpenAIClient, EnvSettings.DeploymentName, sp.GetRequiredService>())); +builder.Services.AddSingleton(); +// ... ``` Files to Review: - [Program.cs](./CS/ReportingApp/Program.cs) #### AI Assistant Provider - -On the server side, the `AIAssistantProvider` service manages assistants. - + +On the server side, the `AIReportingChatService` service manages chat sessions: + ```cs -public interface IAIAssistantProvider { - IAIAssistant GetAssistant(string assistantName); - Task CreateDocumentAssistant(Stream data); - Task CreateUserAssistant(); - Task DisposeAssistant(string assistantName); +public interface IAIReportingChatService { + IChatResponseProvider GetChatProvider(string sessionId); + Task OpenDocumentChatAsync(Stream data); + Task OpenDesignerChatAsync(); + Task CloseChatAsync(string sessionId); } ``` -The `AIAssistantManager.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, thread and file IDs (an `AIAssistantData` object). 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. +The `AgentFactory` class creates the agent that answers user questions. When a chat opens, `AgentFactory.CreateAgentWithFileAsync`: + +1. Uploads the source PDF to Azure OpenAI and adds it to a short-lived vector store. +2. Creates a Responses API agent with File Search and Code Interpreter tools. The agent reads the document content and runs calculations to answer data-driven questions. +3. Starts a session that preserves the conversation history. +4. Returns an `IChatResponseProvider`. + +`AIReportingChatService` stores each provider by session id and deletes the uploaded file and vector store when the chat is closed. -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) +For information on the OpenAI Responses API, refer to the following documents: +- [Azure OpenAI Responses API](https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/responses?tabs=csharp) +- [OpenAI Responses API](https://developers.openai.com/api/reference/responses/overview) +- [File Search tool](https://developers.openai.com/api/docs/guides/tools-file-search) +- [Code Interpreter tool](https://developers.openai.com/api/docs/guides/tools-code-interpreter) + +You can review and tailor the agent instructions in the following file: [AgentInstructions.cs](./CS/ReportingApp/Services/AgentInstructions.cs). Files to Review: -- [AIAssistantProvider.cs](./CS/ReportingApp/Services/AIAssistantProvider.cs) -- [IAIAssistantProvider.cs](./CS/ReportingApp/Services/IAIAssistantProvider.cs) -- [AIAssistantManager.cs](./CS/ReportingApp/Services/AIAssistantManager.cs) +- [IAIReportingChatService.cs](./CS/ReportingApp/Services/IAIReportingChatService.cs) +- [AIReportingChatService.cs](./CS/ReportingApp/Services/AIReportingChatService.cs) +- [AgentFactory.cs](./CS/ReportingApp/Services/AgentFactory.cs) +- [AgentInstructions.cs](./CS/ReportingApp/Services/AgentInstructions.cs) ### Web Document Viewer (Data Analysis Assistant) @@ -117,7 +117,7 @@ On the `BeforeRender` event, add a new tab (a container for the assistant interf @await Html.PartialAsync("_AILayout")