Version: ABP 10.3.0, Volo.AIManagement 10.3.0, Blazor Server, EF Core / SQL Server
Description A workspace without any embedder or vector store still gets the RAG tool on every chat request. As a result, the model often calls search_documents, the tool answers that no embedding provider is configured, and the whole prompt (including attachments) is sent to the model a second time.
Steps to reproduce
Create a workspace in the AI Management UI (OpenAI chat provider) and leave Embedder Provider and Vector Store Provider empty. Save it. In the database, EmbedderProvider and VectorStoreProvider are stored as '' (empty string), not NULL. Send a chat request through this workspace, for example via IChatClientResolver or IAIChatCompletionIntegrationService.
Expected
No RAG tool and no extra system instruction, because the workspace has no embedder.
Actual
AIManagementChatClient.PrepareOptionsAsync adds a search_documents tool (tool_choice = auto) and a system instruction to always call search_documents first. The model calls the tool, which replies that the embedding provider is not configured, and the request is sent again.
In our measurement, a request of about 186K prompt tokens was billed as about 372K. This doubles cost and rate-limit (HTTP 429) pressure.
Cause (version 10.3.0)
Two checks disagree on what "configured" means:
WorkspaceConfigurationStore: HasEmbedderConfiguration = EmbedderProvider != null, so '' counts as configured. DocumentSearchService: uses string.IsNullOrWhiteSpace(...), so '' counts as not configured. The Workspaces page saves empty fields as '', so a workspace edited in the UI can never be stored as NULL.
Suggested fix
Use !string.IsNullOrWhiteSpace(EmbedderProvider) in WorkspaceConfigurationStore, and/or normalise empty provider fields to NULL when saving a workspace.
Workaround Set the columns to NULL directly in the database. Editing the workspace in the UI afterwards brings the problem back.
1 Answer(s)
-
0
Hi,
This is a bug. The workspace configuration treats an empty
EmbedderProvideras configured, and the Workspaces page saves empty fields as''.For now, you can add this class to your
Applicationproject (it runs in the HttpApi.Host in your tiered solution). It saves empty providers asNULLwhen a workspace is created, updated or duplicated:using System; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.AIManagement.ApplicationWorkspaceProviders; using Volo.AIManagement.McpServers; using Volo.AIManagement.VectorStores; using Volo.AIManagement.WorkspaceDataSources; using Volo.AIManagement.Workspaces; namespace MyCompanyName.MyProjectName; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IWorkspaceAppService), typeof(WorkspaceAppService))] public class MyWorkspaceAppService : WorkspaceAppService { public MyWorkspaceAppService( IWorkspaceRepository repository, IRepository<ApplicationAIProvider, Guid> applicationWorkspaceProviderRepository, IMcpServerRepository mcpServerRepository, ApplicationWorkspaceManager applicationWorkspaceManager, VectorStoreInitializer vectorStoreInitializer, IEnumerable<IVectorStoreFactory> vectorStoreFactories, IWorkspaceDataSourceAppService workspaceDataSourceAppService) : base( repository, applicationWorkspaceProviderRepository, mcpServerRepository, applicationWorkspaceManager, vectorStoreInitializer, vectorStoreFactories, workspaceDataSourceAppService) { } public override Task<WorkspaceDetailDto> CreateAsync(WorkspaceCreateInput input) { input.EmbedderProvider = NullIfWhiteSpace(input.EmbedderProvider); input.VectorStoreProvider = NullIfWhiteSpace(input.VectorStoreProvider); return base.CreateAsync(input); } public override async Task<WorkspaceDetailDto> UpdateAsync(Guid id, WorkspaceUpdateInput input) { input.EmbedderProvider = NullIfWhiteSpace(input.EmbedderProvider); input.VectorStoreProvider = NullIfWhiteSpace(input.VectorStoreProvider); // Normalize the stored values first, so '' -> NULL is not treated as an embedder change var workspace = await Repository.GetAsync(id); var embedderProvider = NullIfWhiteSpace(workspace.EmbedderProvider); var vectorStoreProvider = NullIfWhiteSpace(workspace.VectorStoreProvider); if (workspace.EmbedderProvider != embedderProvider || workspace.VectorStoreProvider != vectorStoreProvider) { workspace.EmbedderProvider = embedderProvider; workspace.VectorStoreProvider = vectorStoreProvider; await Repository.UpdateAsync(workspace, autoSave: true); } return await base.UpdateAsync(id, input); } public override async Task<WorkspaceDetailDto> DuplicateAsync(Guid id, WorkspaceDuplicateInput input) { var result = await base.DuplicateAsync(id, input); await CurrentUnitOfWork!.SaveChangesAsync(); var workspace = await Repository.GetAsync(result.Id); workspace.EmbedderProvider = NullIfWhiteSpace(workspace.EmbedderProvider); workspace.VectorStoreProvider = NullIfWhiteSpace(workspace.VectorStoreProvider); await Repository.UpdateAsync(workspace); return ObjectMapper.Map<Workspace, WorkspaceDetailDto>(workspace); } private static string? NullIfWhiteSpace(string? value) { return string.IsNullOrWhiteSpace(value) ? null : value; } }After you deploy it:
- Open each workspace that has no embedder in the UI and save it once. This changes the stored
''values toNULL. - Remove the cached workspace configurations, including the entries cached for tenants. Run this against the Redis server your applications use (add
-h,-p,-nand-ato bothredis-clicalls as needed):
redis-cli --scan --pattern '*Volo.AIManagement.Factory.ChatClientCreationConfiguration*' | while IFS= read -r key; do redis-cli del "$key"; doneIf you use the default in-memory cache instead of Redis, skip this step. The restart in the next step clears it.
- Restart your applications (HttpApi.Host and Blazor), so no running chat client keeps the old configuration.
After that, the
search_documentstool is no longer added for these workspaces, and later edits in the UI keep the values asNULL.We'll also fix this in the AI Management module.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - Open each workspace that has no embedder in the UI and save it once. This changes the stored