diff --git a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json index bf17c63ce..3f2651440 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json +++ b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json @@ -153,6 +153,7 @@ "ai": { "title": "Artificial Intelligence", "workspaces": "Workspaces", - "conversations": "Conversations" + "conversations": "Conversations", + "tools": "AI Tools" } } diff --git a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json index 3e5fe5888..cd4ef5771 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json +++ b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json @@ -153,6 +153,7 @@ "ai": { "title": "人工智能", "workspaces": "工作区管理", - "conversations": "会话管理" + "conversations": "会话管理", + "tools": "工具管理" } } diff --git a/apps/vben5/apps/app-antd/src/views/ai-management/tools/index.vue b/apps/vben5/apps/app-antd/src/views/ai-management/tools/index.vue new file mode 100644 index 000000000..ad9286716 --- /dev/null +++ b/apps/vben5/apps/app-antd/src/views/ai-management/tools/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/apps/vben5/packages/@abp/ai-management/package.json b/apps/vben5/packages/@abp/ai-management/package.json index ae1b893e5..74d3a525e 100644 --- a/apps/vben5/packages/@abp/ai-management/package.json +++ b/apps/vben5/packages/@abp/ai-management/package.json @@ -37,10 +37,12 @@ "ant-design-vue": "catalog:", "ant-design-x-vue": "catalog:", "dayjs": "catalog:", + "lodash.debounce": "catalog:", "markdown-it": "catalog:", "vue": "catalog:" }, "devDependencies": { + "@types/lodash.debounce": "catalog:", "@types/markdown-it": "catalog:" } } diff --git a/apps/vben5/packages/@abp/ai-management/src/api/index.ts b/apps/vben5/packages/@abp/ai-management/src/api/index.ts index f25a7a2c2..b25425b54 100644 --- a/apps/vben5/packages/@abp/ai-management/src/api/index.ts +++ b/apps/vben5/packages/@abp/ai-management/src/api/index.ts @@ -1,3 +1,4 @@ +export { useAIToolsApi } from './useAIToolsApi'; export { useChatsApi } from './useChatsApi'; export { useConversationsApi } from './useConversationsApi'; export { useWorkspaceDefinitionsApi } from './useWorkspaceDefinitionsApi'; diff --git a/apps/vben5/packages/@abp/ai-management/src/api/useAIToolsApi.ts b/apps/vben5/packages/@abp/ai-management/src/api/useAIToolsApi.ts new file mode 100644 index 000000000..c433657ac --- /dev/null +++ b/apps/vben5/packages/@abp/ai-management/src/api/useAIToolsApi.ts @@ -0,0 +1,114 @@ +import type { ListResultDto, PagedResultDto } from '@abp/core'; + +import type { + AIToolDefinitionRecordCreateDto, + AIToolDefinitionRecordDto, + AIToolDefinitionRecordGetPagedListInput, + AIToolDefinitionRecordUpdateDto, + AIToolProviderDto, +} from '../types/tools'; + +import { useRequest } from '@abp/request'; + +export function useAIToolsApi() { + const { cancel, request } = useRequest(); + + /** + * 获取AI工具详情 + * @param id AI工具id + * @returns AI工具详情 + */ + function getApi(id: string): Promise { + return request( + `/api/ai-management/tools/${id}`, + { + method: 'GET', + }, + ); + } + + /** + * 获取AI工具分页列表 + * @param input 查询过滤参数 + * @returns AI工具分页列表 + */ + function getPagedListApi( + input?: AIToolDefinitionRecordGetPagedListInput, + ): Promise> { + return request>( + '/api/ai-management/tools', + { + method: 'GET', + params: input, + }, + ); + } + + /** + * 获取可用AI工具提供者列表 + * @returns AI工具提供者列表 + */ + function getAvailableProviderListApi(): Promise< + ListResultDto + > { + return request>( + '/api/ai-management/tools/available-providers', + { + method: 'GET', + }, + ); + } + + /** + * 删除AI工具 + * @param id AI工具id + */ + function deleteApi(id: string): Promise { + return request(`/api/ai-management/tools/${id}`, { + method: 'DELETE', + }); + } + + /** + * 新增AI工具 + * @param input 参数 + * @returns AI工具详情 + */ + function createApi( + input: AIToolDefinitionRecordCreateDto, + ): Promise { + return request(`/api/ai-management/tools`, { + data: input, + method: 'POST', + }); + } + + /** + * 编辑AI工具 + * @param id AI工具id + * @param input 参数 + * @returns AI工具详情 + */ + function updateApi( + id: string, + input: AIToolDefinitionRecordUpdateDto, + ): Promise { + return request( + `/api/ai-management/tools/${id}`, + { + data: input, + method: 'PUT', + }, + ); + } + + return { + cancel, + createApi, + deleteApi, + getApi, + getAvailableProviderListApi, + getPagedListApi, + updateApi, + }; +} diff --git a/apps/vben5/packages/@abp/ai-management/src/api/useWorkspaceDefinitionsApi.ts b/apps/vben5/packages/@abp/ai-management/src/api/useWorkspaceDefinitionsApi.ts index fec6e8d68..64695d656 100644 --- a/apps/vben5/packages/@abp/ai-management/src/api/useWorkspaceDefinitionsApi.ts +++ b/apps/vben5/packages/@abp/ai-management/src/api/useWorkspaceDefinitionsApi.ts @@ -87,7 +87,7 @@ export function useWorkspaceDefinitionsApi() { } /** - * 新增工作区 + * 编辑工作区 * @param id 工作区id * @param input 参数 * @returns 工作区详情 diff --git a/apps/vben5/packages/@abp/ai-management/src/components/index.ts b/apps/vben5/packages/@abp/ai-management/src/components/index.ts index 1e4ac33f0..cf5a5b9a3 100644 --- a/apps/vben5/packages/@abp/ai-management/src/components/index.ts +++ b/apps/vben5/packages/@abp/ai-management/src/components/index.ts @@ -1,2 +1,3 @@ export { default as Conversations } from './conversations/index.vue'; +export { default as AIToolDefinitionTable } from './tools/AIToolDefinitionTable.vue'; export { default as WorkspaceDefinitionTable } from './workspaces/WorkspaceDefinitionTable.vue'; diff --git a/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue b/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue new file mode 100644 index 000000000..c57c89f80 --- /dev/null +++ b/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue @@ -0,0 +1,321 @@ + + + + + diff --git a/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionTable.vue b/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionTable.vue new file mode 100644 index 000000000..fe687793c --- /dev/null +++ b/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionTable.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/apps/vben5/packages/@abp/ai-management/src/components/workspaces/WorkspaceDefinitionModal.vue b/apps/vben5/packages/@abp/ai-management/src/components/workspaces/WorkspaceDefinitionModal.vue index 9a0a344ea..2019f58d0 100644 --- a/apps/vben5/packages/@abp/ai-management/src/components/workspaces/WorkspaceDefinitionModal.vue +++ b/apps/vben5/packages/@abp/ai-management/src/components/workspaces/WorkspaceDefinitionModal.vue @@ -13,7 +13,11 @@ import { ref, useTemplateRef } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { $t } from '@vben/locales'; -import { useAuthorization } from '@abp/core'; +import { + useAuthorization, + useLocalization, + useLocalizationSerializer, +} from '@abp/core'; import { useMessage } from '@abp/ui'; import { AutoComplete, @@ -25,7 +29,9 @@ import { Tabs, Textarea, } from 'ant-design-vue'; +import debounce from 'lodash.debounce'; +import { useAIToolsApi } from '../../api/useAIToolsApi'; import { useWorkspaceDefinitionsApi } from '../../api/useWorkspaceDefinitionsApi'; import { WorkspaceDefinitionPermissions } from '../../constants/permissions'; @@ -53,6 +59,10 @@ const defaultModel: WorkspaceDefinitionVto = { const message = useMessage(); const { isGranted } = useAuthorization(); +const { Lr } = useLocalization(); +const { deserialize: deserializeLocalizableString } = + useLocalizationSerializer(); +const { getPagedListApi: getAIToolPagedListApi } = useAIToolsApi(); const { createApi, updateApi, getApi, getAvailableProviderListApi } = useWorkspaceDefinitionsApi(); // 表单允许编辑 @@ -65,6 +75,8 @@ const formModel = ref({ ...defaultModel }); const providerOptions = ref>([]); // 模型选项 const modelOptions = ref>([]); +// AI工具 +const aiToolOptions = ref>([]); // 工作区编辑模态框 const [Modal, modalApi] = useVbenModal({ @@ -85,7 +97,7 @@ async function onInit() { loading: true, title: $t('AIManagement.Workspaces:New'), }); - await onInitProviderOptions(); + await Promise.all([onSearchAIToolOptions(), onInitProviderOptions()]); const { id } = modalApi.getData(); if (!id) { isAllowUpdate.value = isGranted(WorkspaceDefinitionPermissions.Create); @@ -123,6 +135,27 @@ async function onInitProviderOptions() { }); } +/** 初始化AI工具选项 */ +const onSearchAIToolOptions = debounce((filter?: string) => { + getAIToolPagedListApi({ isEnabled: true, filter }).then((res) => { + aiToolOptions.value = res.items.map((item) => { + let description = item.description; + if (description) { + const localizableString = deserializeLocalizableString(description); + description = Lr( + localizableString.resourceName, + localizableString.name, + ); + } + return { + label: item.name, + value: item.name, + description, + }; + }); + }); +}, 500); + function onProviderChange(_: SelectValue, option: DefaultOptionType) { modelOptions.value = option.models; } @@ -188,6 +221,21 @@ async function onSubmit() { :auto-size="{ minRows: 3 }" /> + + +
@@ -254,7 +302,7 @@ async function onSubmit() { > , IHasConcurrencyStamp { + description?: string; + isEnabled: boolean; + isGlobal: boolean; + isSystem: boolean; + name: string; + provider: string; + stateCheckers?: string; +} + +interface AIToolDefinitionRecordCreateOrUpdateDto extends ExtensibleObject { + description?: string; + isEnabled: boolean; + isGlobal: boolean; + provider: string; + stateCheckers?: string; +} + +interface AIToolDefinitionRecordCreateDto extends AIToolDefinitionRecordCreateOrUpdateDto { + name: string; +} + +interface AIToolDefinitionRecordUpdateDto + extends AIToolDefinitionRecordCreateOrUpdateDto, IHasConcurrencyStamp {} + +interface AIToolDefinitionRecordGetPagedListInput extends PagedAndSortedResultRequestDto { + filter?: string; + isEnabled?: boolean; + provider?: string; +} + +interface AIToolPropertyDescriptorDto { + description?: string; + displayName: string; + name: string; + options: NameValue[]; + required: boolean; + valueType: string; +} + +interface AIToolProviderDto { + name: string; + properties: AIToolPropertyDescriptorDto[]; +} + +export type { + AIToolDefinitionRecordCreateDto, + AIToolDefinitionRecordDto, + AIToolDefinitionRecordGetPagedListInput, + AIToolDefinitionRecordUpdateDto, + AIToolProviderDto, +}; diff --git a/apps/vben5/packages/@abp/ai-management/src/types/workspaces.ts b/apps/vben5/packages/@abp/ai-management/src/types/workspaces.ts index acfedc2e4..f88093257 100644 --- a/apps/vben5/packages/@abp/ai-management/src/types/workspaces.ts +++ b/apps/vben5/packages/@abp/ai-management/src/types/workspaces.ts @@ -21,6 +21,7 @@ interface WorkspaceDefinitionRecordDto stateCheckers?: string; systemPrompt?: string; temperature?: number; + tools?: string[]; } interface WorkspaceDefinitionRecordCreateOrUpdateDto extends ExtensibleObject { @@ -38,6 +39,7 @@ interface WorkspaceDefinitionRecordCreateOrUpdateDto extends ExtensibleObject { stateCheckers?: string; systemPrompt?: string; temperature?: number; + tools?: string[]; } interface WorkspaceDefinitionRecordCreateDto extends WorkspaceDefinitionRecordCreateOrUpdateDto { diff --git a/apps/vben5/packages/@abp/ui/src/components/properties/PropertyTable.vue b/apps/vben5/packages/@abp/ui/src/components/properties/PropertyTable.vue index 1c10b5c35..6a377f570 100644 --- a/apps/vben5/packages/@abp/ui/src/components/properties/PropertyTable.vue +++ b/apps/vben5/packages/@abp/ui/src/components/properties/PropertyTable.vue @@ -8,6 +8,7 @@ import { computed, defineAsyncComponent } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { createIconifyIcon } from '@vben/icons'; import { $t } from '@vben/locales'; +import { isFunction } from '@vben/utils'; import { Button, Popconfirm, Table } from 'ant-design-vue'; @@ -46,8 +47,13 @@ const getTableColumns = computed((): TableColumnsType => { align: 'left', dataIndex: 'key', fixed: 'left', - minWidth: 100, + width: 100, title: $t('component.extra_property_dictionary.key'), + customRender: (opt) => { + if (props.renderKey && isFunction(props.renderKey)) { + return props.renderKey(opt.record.key); + } + }, }, { align: 'left', diff --git a/apps/vben5/packages/@abp/ui/src/components/properties/types.ts b/apps/vben5/packages/@abp/ui/src/components/properties/types.ts index 45dadb6d1..7fd5d9136 100644 --- a/apps/vben5/packages/@abp/ui/src/components/properties/types.ts +++ b/apps/vben5/packages/@abp/ui/src/components/properties/types.ts @@ -1,5 +1,7 @@ import type { Dictionary } from '@abp/core'; +import type { VNode } from 'vue'; + interface PropertyInfo { key: string; value: string; @@ -9,6 +11,7 @@ interface PropertyProps { allowEdit?: boolean; data?: Dictionary; disabled?: boolean; + renderKey?: (key: string) => string | VNode; } export type { PropertyInfo, PropertyProps }; diff --git a/apps/vben5/packages/@abp/webhooks/src/components/subscriptions/WebhookSubscriptionTable.vue b/apps/vben5/packages/@abp/webhooks/src/components/subscriptions/WebhookSubscriptionTable.vue index 554f68efa..0634ecaff 100644 --- a/apps/vben5/packages/@abp/webhooks/src/components/subscriptions/WebhookSubscriptionTable.vue +++ b/apps/vben5/packages/@abp/webhooks/src/components/subscriptions/WebhookSubscriptionTable.vue @@ -273,7 +273,7 @@ function onUpdate(row: WebhookSubscriptionDto) { function onDelete(row: WebhookSubscriptionDto) { Modal.confirm({ centered: true, - content: $t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.title]), + content: $t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.description]), onCancel: () => { cancel(); }, diff --git a/apps/vben5/packages/@abp/webhooks/src/types/subscriptions.ts b/apps/vben5/packages/@abp/webhooks/src/types/subscriptions.ts index e88459dfb..bd3203f56 100644 --- a/apps/vben5/packages/@abp/webhooks/src/types/subscriptions.ts +++ b/apps/vben5/packages/@abp/webhooks/src/types/subscriptions.ts @@ -5,8 +5,7 @@ import type { } from '@abp/core'; interface WebhookSubscriptionDto - extends CreationAuditedEntityDto, - IHasConcurrencyStamp { + extends CreationAuditedEntityDto, IHasConcurrencyStamp { description?: string; headers?: Record; isActive: boolean; @@ -31,15 +30,13 @@ interface WebhookSubscriptionCreateOrUpdateDto { type WebhookSubscriptionCreateDto = WebhookSubscriptionCreateOrUpdateDto; interface WebhookSubscriptionUpdateDto - extends IHasConcurrencyStamp, - WebhookSubscriptionCreateOrUpdateDto {} + extends IHasConcurrencyStamp, WebhookSubscriptionCreateOrUpdateDto {} interface WebhookSubscriptionDeleteManyInput { recordIds: string[]; } -interface WebhookSubscriptionGetListInput - extends PagedAndSortedResultRequestDto { +interface WebhookSubscriptionGetListInput extends PagedAndSortedResultRequestDto { beginCreationTime?: Date; endCreationTime?: Date; filter?: string; diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContext.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContext.cs index 35d068464..4dbec0f90 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContext.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/AIServiceMigrationsDbContext.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.AIManagement.Chats; using LINGYUN.Abp.AIManagement.EntityFrameworkCore; using LINGYUN.Abp.AIManagement.Tokens; +using LINGYUN.Abp.AIManagement.Tools; using LINGYUN.Abp.AIManagement.Workspaces; using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; @@ -14,6 +15,7 @@ public class AIServiceMigrationsDbContext : IAIManagementDbContext { public DbSet WorkspaceDefinitions { get; set; } + public DbSet AIToolDefinitions { get; set; } public DbSet TextChatMessageRecords { get; set; } public DbSet ConversationRecords { get; set; } public DbSet TokenUsageRecords { get; set; } diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327051206_Add-AI-Tool-Definition-Record.Designer.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327051206_Add-AI-Tool-Definition-Record.Designer.cs new file mode 100644 index 000000000..e2116f4aa --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327051206_Add-AI-Tool-Definition-Record.Designer.cs @@ -0,0 +1,376 @@ +// +using System; +using LINGYUN.Abp.MicroService.AIService; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + [DbContext(typeof(AIServiceMigrationsDbContext))] + [Migration("20260327051206_Add-AI-Tool-Definition-Record")] + partial class AddAIToolDefinitionRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.ConversationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UpdateAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.ToTable("AbpAIConversations", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.TextChatMessageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReplyAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReplyMessage") + .HasColumnType("text"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITextChatMessages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tokens.TokenUsageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CachedInputTokenCount") + .HasColumnType("bigint"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("InputTokenCount") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("OutputTokenCount") + .HasColumnType("bigint"); + + b.Property("ReasoningTokenCount") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalTokenCount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITokenUsages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tools.AIToolDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIAIToolDefinitionRecords", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Workspaces.WorkspaceDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiBaseUrl") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApiKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FrequencyPenalty") + .HasColumnType("real"); + + b.Property("Instructions") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MaxOutputTokens") + .HasColumnType("integer"); + + b.Property("ModelName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PresencePenalty") + .HasColumnType("real"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SystemPrompt") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Temperature") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIWorkspaceDefinitions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327051206_Add-AI-Tool-Definition-Record.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327051206_Add-AI-Tool-Definition-Record.cs new file mode 100644 index 000000000..77a10d504 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327051206_Add-AI-Tool-Definition-Record.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + /// + public partial class AddAIToolDefinitionRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpAIAIToolDefinitionRecords", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Provider = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Description = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + IsEnabled = table.Column(type: "boolean", nullable: false), + IsSystem = table.Column(type: "boolean", nullable: false), + StateCheckers = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "text", nullable: false), + ConcurrencyStamp = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: true), + LastModificationTime = table.Column(type: "timestamp with time zone", nullable: true), + LastModifierId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAIAIToolDefinitionRecords", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAIAIToolDefinitionRecords_Name", + table: "AbpAIAIToolDefinitionRecords", + column: "Name", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpAIAIToolDefinitionRecords"); + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327065840_Add-Tools-To-Workspace-Record.Designer.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327065840_Add-Tools-To-Workspace-Record.Designer.cs new file mode 100644 index 000000000..8c52d8546 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327065840_Add-Tools-To-Workspace-Record.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using LINGYUN.Abp.MicroService.AIService; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + [DbContext(typeof(AIServiceMigrationsDbContext))] + [Migration("20260327065840_Add-Tools-To-Workspace-Record")] + partial class AddToolsToWorkspaceRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.ConversationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UpdateAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.ToTable("AbpAIConversations", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.TextChatMessageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReplyAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReplyMessage") + .HasColumnType("text"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITextChatMessages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tokens.TokenUsageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CachedInputTokenCount") + .HasColumnType("bigint"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("InputTokenCount") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("OutputTokenCount") + .HasColumnType("bigint"); + + b.Property("ReasoningTokenCount") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalTokenCount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITokenUsages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tools.AIToolDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIAIToolDefinitionRecords", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Workspaces.WorkspaceDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiBaseUrl") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApiKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FrequencyPenalty") + .HasColumnType("real"); + + b.Property("Instructions") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MaxOutputTokens") + .HasColumnType("integer"); + + b.Property("ModelName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PresencePenalty") + .HasColumnType("real"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SystemPrompt") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Temperature") + .HasColumnType("real"); + + b.Property("Tools") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIWorkspaceDefinitions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327065840_Add-Tools-To-Workspace-Record.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327065840_Add-Tools-To-Workspace-Record.cs new file mode 100644 index 000000000..451b6fe46 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327065840_Add-Tools-To-Workspace-Record.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + /// + public partial class AddToolsToWorkspaceRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Tools", + table: "AbpAIWorkspaceDefinitions", + type: "character varying(128)", + maxLength: 128, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Tools", + table: "AbpAIWorkspaceDefinitions"); + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327071051_Add-IsGlobal-To-AITool-Record.Designer.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327071051_Add-IsGlobal-To-AITool-Record.Designer.cs new file mode 100644 index 000000000..c4ec5d5a7 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327071051_Add-IsGlobal-To-AITool-Record.Designer.cs @@ -0,0 +1,383 @@ +// +using System; +using LINGYUN.Abp.MicroService.AIService; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + [DbContext(typeof(AIServiceMigrationsDbContext))] + [Migration("20260327071051_Add-IsGlobal-To-AITool-Record")] + partial class AddIsGlobalToAIToolRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.ConversationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UpdateAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.ToTable("AbpAIConversations", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Chats.TextChatMessageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReplyAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReplyMessage") + .HasColumnType("text"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Workspace") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITextChatMessages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tokens.TokenUsageRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CachedInputTokenCount") + .HasColumnType("bigint"); + + b.Property("ConversationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("InputTokenCount") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("OutputTokenCount") + .HasColumnType("bigint"); + + b.Property("ReasoningTokenCount") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalTokenCount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ConversationId"); + + b.ToTable("AbpAITokenUsages", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tools.AIToolDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsGlobal") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIAIToolDefinitionRecords", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Workspaces.WorkspaceDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiBaseUrl") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApiKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FrequencyPenalty") + .HasColumnType("real"); + + b.Property("Instructions") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MaxOutputTokens") + .HasColumnType("integer"); + + b.Property("ModelName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PresencePenalty") + .HasColumnType("real"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SystemPrompt") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Temperature") + .HasColumnType("real"); + + b.Property("Tools") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIWorkspaceDefinitions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327071051_Add-IsGlobal-To-AITool-Record.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327071051_Add-IsGlobal-To-AITool-Record.cs new file mode 100644 index 000000000..81f736b78 --- /dev/null +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/20260327071051_Add-IsGlobal-To-AITool-Record.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LINGYUN.Abp.MicroService.AIService.Migrations +{ + /// + public partial class AddIsGlobalToAIToolRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsGlobal", + table: "AbpAIAIToolDefinitionRecords", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsGlobal", + table: "AbpAIAIToolDefinitionRecords"); + } + } +} diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/AIServiceMigrationsDbContextModelSnapshot.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/AIServiceMigrationsDbContextModelSnapshot.cs index 734232117..6146ee94a 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/AIServiceMigrationsDbContextModelSnapshot.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService.EntityFrameworkCore/Migrations/AIServiceMigrationsDbContextModelSnapshot.cs @@ -200,6 +200,74 @@ namespace LINGYUN.Abp.MicroService.AIService.Migrations b.ToTable("AbpAITokenUsages", (string)null); }); + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Tools.AIToolDefinitionRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsGlobal") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpAIAIToolDefinitionRecords", (string)null); + }); + modelBuilder.Entity("LINGYUN.Abp.AIManagement.Workspaces.WorkspaceDefinitionRecord", b => { b.Property("Id") @@ -295,6 +363,10 @@ namespace LINGYUN.Abp.MicroService.AIService.Migrations b.Property("Temperature") .HasColumnType("real"); + b.Property("Tools") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + b.HasKey("Id"); b.HasIndex("Name") diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs index 2f3288616..9b6d9c431 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs @@ -126,6 +126,9 @@ public class WorkspaceDefinition : IHasSimpleStateCheckers [NotNull] public Dictionary Properties { get; } + [NotNull] + public List Tools { get; } + public List> StateCheckers { get; } public WorkspaceDefinition( @@ -155,6 +158,7 @@ public class WorkspaceDefinition : IHasSimpleStateCheckers PresencePenalty = presencePenalty; IsEnabled = true; + Tools = new List(); Properties = new Dictionary(); StateCheckers = new List>(); } @@ -171,6 +175,12 @@ public class WorkspaceDefinition : IHasSimpleStateCheckers return this; } + public virtual WorkspaceDefinition WithTools(params string[] tools) + { + Tools.AddIfNotContains(tools); + return this; + } + public virtual WorkspaceDefinition WithProperty(string key, object value) { Properties[key] = value; diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs index 7e6af037b..24fcd4fde 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs @@ -5,15 +5,15 @@ using Volo.Abp; namespace LINGYUN.Abp.AI.Tools.Http; public static class HttpAIToolDefinitionExtenssions { - private const string RemoteService = "RemoteService"; - private const string RemoteController = "RemoteController"; - private const string RemoteMethod = "RemoteMethod"; + public const string RemoteService = "RemoteService"; + public const string RemoteController = "RemoteController"; + public const string RemoteMethod = "RemoteMethod"; - private const string Endpoint = "HttpEndpoint"; - private const string Method = "HttpMethod"; - private const string Headers = "HttpHeaders"; + public const string Endpoint = "HttpEndpoint"; + public const string Method = "HttpMethod"; + public const string Headers = "HttpHeaders"; - private const string CurrentAccessToken = "UseHttpCurrentAccessToken"; + public const string CurrentAccessToken = "UseHttpCurrentAccessToken"; public static AIToolDefinition UseHttpCurrentAccessToken(this AIToolDefinition definition, bool useCurrentAccessToken = true) { diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs index 32022ffa2..6eba86317 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs @@ -1,9 +1,12 @@ -using Microsoft.Extensions.AI; +using LINGYUN.Abp.AI.Localization; +using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using System; +using System.Net.Http; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace LINGYUN.Abp.AI.Tools.Http; public class HttpAIToolProvider : IAIToolProvider, ITransientDependency @@ -16,6 +19,28 @@ public class HttpAIToolProvider : IAIToolProvider, ITransientDependency ServiceProvider = serviceProvider; } + public virtual AIToolPropertyDescriptor[] GetPropertites() + { + return [ + AIToolPropertyDescriptor.CreateStringProperty( + HttpAIToolDefinitionExtenssions.Endpoint, + LocalizableString.Create("HttpAITool:Endpoint"), + required: true), + AIToolPropertyDescriptor.CreateSelectProperty( + HttpAIToolDefinitionExtenssions.Method, + LocalizableString.Create("HttpAITool:Method"), + [ + new(HttpMethod.Get.Method, HttpMethod.Get.Method), + new(HttpMethod.Post.Method, HttpMethod.Post.Method), + ]), + AIToolPropertyDescriptor.CreateDictionaryProperty( + HttpAIToolDefinitionExtenssions.Headers, + LocalizableString.Create("HttpAITool:Headers")), + AIToolPropertyDescriptor.CreateBoolProperty( + HttpAIToolDefinitionExtenssions.CurrentAccessToken, + LocalizableString.Create("HttpAITool:CurrentAccessToken"))]; + } + public virtual Task CreateToolsAsync(AIToolDefinition definition) { string? description = null; diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json index 088f61324..36672f8ec 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json @@ -1,6 +1,10 @@ { "culture": "en", "texts": { - "Tools:GetApplicationConfiguration": "Get Application Configuration" + "Tools:GetApplicationConfiguration": "Get Application Configuration", + "HttpAITool:Endpoint": "Endpoint", + "HttpAITool:Method": "Method", + "HttpAITool:Headers": "Headers", + "HttpAITool:CurrentAccessToken": "Use the current user token" } } \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json index d8439fa2d..4f62a73de 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json @@ -1,6 +1,10 @@ { "culture": "zh-Hans", "texts": { - "Tools:GetApplicationConfiguration": "获取应用程序配置" + "Tools:GetApplicationConfiguration": "获取应用程序配置", + "HttpAITool:Endpoint": "请求地址", + "HttpAITool:Method": "请求方法", + "HttpAITool:Headers": "请求头", + "HttpAITool:CurrentAccessToken": "使用当前用户Token" } } \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj index 838a8c793..487b3429f 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj @@ -14,6 +14,11 @@ + + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs index ceb89e623..0f7b11608 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs @@ -1,6 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using LINGYUN.Abp.AI.Localization; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Http.Client; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace LINGYUN.Abp.AI.Tools.Mcp; @@ -13,10 +16,22 @@ public class AbpAIToolsMcpModule : AbpModule { context.Services.AddMcpAIToolClient(); + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + Configure(options => { // Mcp工具 options.AIToolProviders.Add(); }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources"); + }); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json new file mode 100644 index 000000000..668fef84b --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json @@ -0,0 +1,14 @@ +{ + "culture": "en", + "texts": { + "McpAITool:Endpoint": "Endpoint", + "McpAITool:Headers": "Headers", + "McpAITool:TransportMode": "Transport Mode", + "McpAITool:TransportModeDesc": "The transport mode to use for the connection. The default is: Auto Detect.", + "McpAITool:ConnectionTimeout": "Connection Timeout", + "McpAITool:ConnectionTimeoutDesc": "The timeout used to establish the initial connection to the SSE server. The default is 30 seconds.", + "McpAITool:MaxReconnectionAttempts": "Max Reconnection Attempts", + "McpAITool:MaxReconnectionAttemptsDesc": "The maximum number of reconnection attempts. The default is 5.", + "McpAITool:CurrentAccessToken": "Use the current user token" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..401f0ad7b --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json @@ -0,0 +1,14 @@ +{ + "culture": "zh-Hans", + "texts": { + "McpAITool:Endpoint": "请求地址", + "McpAITool:Headers": "请求头", + "McpAITool:TransportMode": "传输方式", + "McpAITool:TransportModeDesc": "连接所采用的传输方式, 默认: Auto Detect.", + "McpAITool:ConnectionTimeout": "超时时间", + "McpAITool:ConnectionTimeoutDesc": "用于建立与 SSE 服务器初始连接的超时时间, 默认: 30秒.", + "McpAITool:MaxReconnectionAttempts": "最大重连次数", + "McpAITool:MaxReconnectionAttemptsDesc": "最大重连次数, 默认: 5次.", + "McpAITool:CurrentAccessToken": "使用当前用户Token" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs index 5854903d4..9914af69c 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs @@ -6,12 +6,12 @@ using Volo.Abp; namespace LINGYUN.Abp.AI.Tools.Mcp; public static class McpAIToolDefinitionExtenssions { - private const string Endpoint = "McpEndpoint"; - private const string Headers = "McpHeaders"; - private const string TransportMode = "McpTransportMode"; - private const string ConnectionTimeout = "McpConnectionTimeout"; - private const string MaxReconnectionAttempts = "McpMaxReconnectionAttempts"; - private const string CurrentAccessToken = "UseMcpCurrentAccessToken"; + public const string Endpoint = "McpEndpoint"; + public const string Headers = "McpHeaders"; + public const string TransportMode = "McpTransportMode"; + public const string ConnectionTimeout = "McpConnectionTimeout"; + public const string MaxReconnectionAttempts = "McpMaxReconnectionAttempts"; + public const string CurrentAccessToken = "UseMcpCurrentAccessToken"; public static AIToolDefinition UseMcpCurrentAccessToken(this AIToolDefinition definition, bool useCurrentAccessToken = true) { diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs index 1b32dc900..29b905a7d 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.AI; +using LINGYUN.Abp.AI.Localization; +using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Client; using System; @@ -8,6 +9,7 @@ using System.Net.Http; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Authentication; +using Volo.Abp.Localization; namespace LINGYUN.Abp.AI.Tools.Mcp; public class McpAIToolProvider : IAIToolProvider, ITransientDependency @@ -25,6 +27,38 @@ public class McpAIToolProvider : IAIToolProvider, ITransientDependency HttpClientFactory = httpClientFactory; } + public virtual AIToolPropertyDescriptor[] GetPropertites() + { + return [ + AIToolPropertyDescriptor.CreateStringProperty( + McpAIToolDefinitionExtenssions.Endpoint, + LocalizableString.Create("McpAITool:Endpoint"), + required: true), + AIToolPropertyDescriptor.CreateDictionaryProperty( + McpAIToolDefinitionExtenssions.Headers, + LocalizableString.Create("McpAITool:Headers")), + AIToolPropertyDescriptor.CreateSelectProperty( + McpAIToolDefinitionExtenssions.TransportMode, + LocalizableString.Create("McpAITool:TransportMode"), + [ + new("Auto Detect", HttpTransportMode.AutoDetect), + new("Streamable Http", HttpTransportMode.StreamableHttp), + new("Sse", HttpTransportMode.Sse), + ], + LocalizableString.Create("McpAITool:TransportModeDesc")), + AIToolPropertyDescriptor.CreateNumberProperty( + McpAIToolDefinitionExtenssions.ConnectionTimeout, + LocalizableString.Create("McpAITool:ConnectionTimeout"), + LocalizableString.Create("McpAITool:ConnectionTimeoutDesc")), + AIToolPropertyDescriptor.CreateNumberProperty( + McpAIToolDefinitionExtenssions.MaxReconnectionAttempts, + LocalizableString.Create("McpAITool:MaxReconnectionAttempts"), + LocalizableString.Create("McpAITool:MaxReconnectionAttemptsDesc")), + AIToolPropertyDescriptor.CreateBoolProperty( + McpAIToolDefinitionExtenssions.CurrentAccessToken, + LocalizableString.Create("McpAITool:CurrentAccessToken"))]; + } + public async virtual Task CreateToolsAsync(AIToolDefinition definition) { var httpClient = HttpClientFactory.CreateMcpAIToolClient(); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs index 5e4bc9a7e..38f84c93e 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs @@ -26,6 +26,14 @@ public class AIToolDefinition : IHasSimpleStateCheckers /// [NotNull] public Dictionary Properties { get; } + /// + /// 启用/禁用工具 + /// + public bool IsEnabled { get; set; } + /// + /// 全局工具 + /// + public bool IsGlobal { get; set; } public List> StateCheckers { get; } @@ -38,6 +46,7 @@ public class AIToolDefinition : IHasSimpleStateCheckers Provider = provider; Description = description; + IsEnabled = true; Properties = new Dictionary(); StateCheckers = new List>(); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs index beb0b1199..e2737c629 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs @@ -22,8 +22,12 @@ public class AIToolFactory : IAIToolFactory, IScopedDependency AIToolProviderManager = aIToolProviderManager; } - public virtual Task CreateTool(AIToolDefinition definition) + public async virtual Task CreateTool(AIToolDefinition definition) { + if (!definition.IsEnabled) + { + return []; + } foreach (var provider in AIToolProviderManager.Providers) { if (!string.Equals(provider.Name, definition.Provider)) @@ -31,7 +35,7 @@ public class AIToolFactory : IAIToolFactory, IScopedDependency continue; } - return provider.CreateToolsAsync(definition); + return await provider.CreateToolsAsync(definition); } throw new AbpException($"The AITool provider implementation named {definition.Provider} was not found!"); @@ -44,7 +48,7 @@ public class AIToolFactory : IAIToolFactory, IScopedDependency foreach (var toolDefine in toolDefines) { - if (await StateCheckerManager.IsEnabledAsync(toolDefine)) + if (toolDefine.IsEnabled && await StateCheckerManager.IsEnabledAsync(toolDefine)) { aiTools.AddRange(await CreateTool(toolDefine)); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs new file mode 100644 index 000000000..9d0194509 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.AI.Tools; + +public enum PropertyValueType +{ + String = 0, + Number = 1, + Boolean = 2, + Dictionary = 3, + Select = 4, +} + +public class AIToolPropertyDescriptor +{ + public string Name { get; } + public bool Required { get; } + public PropertyValueType ValueType { get; } + public List> Options { get; } + public ILocalizableString DisplayName { get; } + public ILocalizableString? Description { get; } + private AIToolPropertyDescriptor( + string name, + PropertyValueType valueType, + ILocalizableString displayName, + ILocalizableString? description = null, + bool required = false) + { + Name = name; + ValueType = valueType; + DisplayName = displayName; + Description = description; + Required = required; + + Options = new List>(); + } + + public static AIToolPropertyDescriptor CreateStringProperty( + string name, + ILocalizableString displayName, + ILocalizableString? description = null, + bool required = false) + { + return new AIToolPropertyDescriptor( + name, + PropertyValueType.String, + displayName, + description, + required); + } + + public static AIToolPropertyDescriptor CreateNumberProperty( + string name, + ILocalizableString displayName, + ILocalizableString? description = null, + bool required = false) + { + return new AIToolPropertyDescriptor( + name, + PropertyValueType.Number, + displayName, + description, + required); + } + + public static AIToolPropertyDescriptor CreateBoolProperty( + string name, + ILocalizableString displayName, + ILocalizableString? description = null, + bool required = false) + { + return new AIToolPropertyDescriptor( + name, + PropertyValueType.Boolean, + displayName, + description, + required); + } + + public static AIToolPropertyDescriptor CreateDictionaryProperty( + string name, + ILocalizableString displayName, + ILocalizableString? description = null) + { + return new AIToolPropertyDescriptor( + name, + PropertyValueType.Dictionary, + displayName, + description); + } + + public static AIToolPropertyDescriptor CreateSelectProperty( + string name, + ILocalizableString displayName, + List> options, + ILocalizableString? description = null, + bool required = false) + { + var propertyDescriptor = new AIToolPropertyDescriptor( + name, + PropertyValueType.Select, + displayName, + description, + required); + + foreach (var option in options) + { + propertyDescriptor.WithOption(option.Name, option.Value); + } + + return propertyDescriptor; + } + + public void WithOption(string name, object value) + { + Options.Add(new NameValue(name, value)); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs index 30029e5f5..1a10266eb 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.Linq; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -32,10 +33,35 @@ public class AbpAIToolsModule : AbpModule Configure(options => { - options.ChatClientBuildActions.Add(async (_, sp, builder) => + options.ChatClientBuildActions.Add(async (workspace, sp, builder) => { + var useAITools = new List(); + var useAIToolDefinitions = new List(); var aiToolFactory = sp.GetRequiredService(); - var aiTools = await aiToolFactory.CreateAllTools(); + var aiToolDefinitionManager = sp.GetRequiredService(); + var aiToolDefinitions = await aiToolDefinitionManager.GetAllAsync(); + + if (workspace.Tools.Count > 0) + { + useAIToolDefinitions.AddRange(aiToolDefinitions.Where(aiTool => workspace.Tools.Contains(aiTool.Name))); + } + + foreach (var globalAIToolDefinition in aiToolDefinitions.Where(aiTool => aiTool.IsGlobal)) + { + if (!useAIToolDefinitions.Any(tool => tool.Name == globalAIToolDefinition.Name)) + { + useAIToolDefinitions.Add(globalAIToolDefinition); + } + } + + foreach (var aiToolDefinition in useAIToolDefinitions) + { + var aiTools = await aiToolFactory.CreateTool(aiToolDefinition); + if (aiTools.Length > 0) + { + useAITools.AddRange(aiTools); + } + } builder.ConfigureOptions(ai => { @@ -44,7 +70,7 @@ public class AbpAIToolsModule : AbpModule ai.Tools ??= []; - foreach (var aiTool in aiTools) + foreach (var aiTool in useAITools) { ai.Tools.Add(aiTool); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs index c2d4d872c..fa552fa29 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs @@ -1,13 +1,16 @@ -using Volo.Abp.Collections; +using System.Collections.Generic; +using Volo.Abp.Collections; namespace LINGYUN.Abp.AI.Tools; public class AbpAIToolsOptions { public ITypeList DefinitionProviders { get; } public ITypeList AIToolProviders { get; } + public HashSet DeletedAITools { get; } public AbpAIToolsOptions() { DefinitionProviders = new TypeList(); AIToolProviders = new TypeList(); + DeletedAITools = new HashSet(); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs index 9379d79b2..09bb67f6c 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs @@ -5,17 +5,22 @@ using Volo.Abp; namespace LINGYUN.Abp.AI.Tools; public static class FunctionAIToolDefinitionExtenssions { - private const string FunctionType = "Function"; - public static AIToolDefinition WithFunction(this AIToolDefinition definition) + public const string FunctionType = "FunctionType"; + public const string FunctionName = "FunctionName"; + public static AIToolDefinition WithFunction(this AIToolDefinition definition, string? funcName = null) { - return definition.WithFunction(typeof(TFunc)); + return definition.WithFunction(typeof(TFunc), funcName); } - public static AIToolDefinition WithFunction(this AIToolDefinition definition, Type funcType) + public static AIToolDefinition WithFunction(this AIToolDefinition definition, Type funcType, string? funcName = null) { Check.NotNull(funcType, nameof(funcType)); definition.WithProperty(FunctionType, funcType.AssemblyQualifiedName); + if (!funcName.IsNullOrWhiteSpace()) + { + definition.WithProperty(FunctionName, funcName); + } return definition; } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs index 482228c22..af9b3335c 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs @@ -1,10 +1,12 @@ -using Microsoft.Extensions.AI; +using LINGYUN.Abp.AI.Localization; +using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using System; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace LINGYUN.Abp.AI.Tools; public class FunctionAIToolProvider : IAIToolProvider, ITransientDependency @@ -18,6 +20,20 @@ public class FunctionAIToolProvider : IAIToolProvider, ITransientDependency ServiceProvider = serviceProvider; } + public virtual AIToolPropertyDescriptor[] GetPropertites() + { + return [ + AIToolPropertyDescriptor.CreateStringProperty( + FunctionAIToolDefinitionExtenssions.FunctionType, + LocalizableString.Create("FunctionAITool:FunctionType"), + LocalizableString.Create("FunctionAITool:FunctionTypeDesc"), + required: true), + AIToolPropertyDescriptor.CreateStringProperty( + FunctionAIToolDefinitionExtenssions.FunctionName, + LocalizableString.Create("FunctionAITool:FunctionName"), + LocalizableString.Create("FunctionAITool:FunctionNameDesc"))]; + } + public virtual Task CreateToolsAsync(AIToolDefinition definition) { var aiToolType = definition.GetFunction(); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProvider.cs index b8027f85f..df0a95157 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProvider.cs @@ -6,5 +6,7 @@ public interface IAIToolProvider { string Name { get; } + AIToolPropertyDescriptor[] GetPropertites(); + Task CreateToolsAsync(AIToolDefinition definition); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/en.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/en.json index 7a1b2b9f1..d9e4cb283 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/en.json +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/en.json @@ -5,6 +5,10 @@ "Tools:GetNowTime": "Get now time", "Tools:GetUserInfo": "Get the current user information", "Tools:GetSetting": "Get setting value", - "Tools:GetSettings": "Get all settings" + "Tools:GetSettings": "Get all settings", + "FunctionAITool:FunctionType": "Custom function type", + "FunctionAITool:FunctionTypeDesc": "To use the full name of the function, use Type.AssemblyQualifiedName for retrieval.", + "FunctionAITool:FunctionName": "Custom function name", + "FunctionAITool:FunctionNameDesc": "The name of the called function, if not specified, will default to Invoke (synchronous) or InvokeAsync (asynchronous) function." } } \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/zh-Hans.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/zh-Hans.json index 0b5545756..a8a4b72fb 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/zh-Hans.json @@ -5,6 +5,10 @@ "Tools:GetNowTime": "获取当前时间", "Tools:GetUserInfo": "获取当前用户信息", "Tools:GetSetting": "获取设置值", - "Tools:GetSettings": "获取所有设置" + "Tools:GetSettings": "获取所有设置", + "FunctionAITool:FunctionType": "自定义函数类型", + "FunctionAITool:FunctionTypeDesc": "使用函数全名, 应使用Type.AssemblyQualifiedName获取.", + "FunctionAITool:FunctionName": "自定义函数名称", + "FunctionAITool:FunctionNameDesc": "调用的函数名称, 如未指定, 默认调用 Invoke(同步) 或 InvokeAsync(异步)函数." } } \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionDefinitionProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionDefinitionProvider.cs index 9b1c697f4..3226cf795 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionDefinitionProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionDefinitionProvider.cs @@ -11,23 +11,40 @@ public class AIManagementPermissionDefinitionProvider : PermissionDefinitionProv { var group = context.AddGroup(AIManagementPermissionNames.GroupName, L("Permission:AIManagement")); - var groupDefinition = group.AddPermission( + var workspaceDefinition = group.AddPermission( AIManagementPermissionNames.WorkspaceDefinition.Default, L("Permission:WorkspaceDefinition"), MultiTenancySides.Host); - groupDefinition.AddChild( + workspaceDefinition.AddChild( AIManagementPermissionNames.WorkspaceDefinition.Create, L("Permission:Create"), MultiTenancySides.Host); - groupDefinition.AddChild( + workspaceDefinition.AddChild( AIManagementPermissionNames.WorkspaceDefinition.Update, L("Permission:Update"), MultiTenancySides.Host); - groupDefinition.AddChild( + workspaceDefinition.AddChild( AIManagementPermissionNames.WorkspaceDefinition.Delete, L("Permission:Delete"), MultiTenancySides.Host); + var aiToolDefinition = group.AddPermission( + AIManagementPermissionNames.AIToolDefinition.Default, + L("Permission:AIToolDefinition"), + MultiTenancySides.Host); + aiToolDefinition.AddChild( + AIManagementPermissionNames.AIToolDefinition.Create, + L("Permission:Create"), + MultiTenancySides.Host); + aiToolDefinition.AddChild( + AIManagementPermissionNames.AIToolDefinition.Update, + L("Permission:Update"), + MultiTenancySides.Host); + aiToolDefinition.AddChild( + AIManagementPermissionNames.AIToolDefinition.Delete, + L("Permission:Delete"), + MultiTenancySides.Host); + var conversation = group.AddPermission( AIManagementPermissionNames.Conversation.Default, L("Permission:Conversation")); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionNames.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionNames.cs index 949d5539d..acde2ea56 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionNames.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Permissions/AIManagementPermissionNames.cs @@ -15,6 +15,15 @@ public static class AIManagementPermissionNames public const string Delete = Default + ".Delete"; } + public static class AIToolDefinition + { + public const string Default = GroupName + ".AIToolDefinitions"; + + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + } + public static class Conversation { public const string Default = GroupName + ".Conversations"; diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordCreateDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordCreateDto.cs new file mode 100644 index 000000000..c2b74f3fd --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordCreateDto.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.AIManagement.Tools.Dtos; +public class AIToolDefinitionRecordCreateDto : AIToolDefinitionRecordCreateOrUpdateDto +{ + [Required] + [DynamicStringLength(typeof(AIToolDefinitionRecordConsts), nameof(AIToolDefinitionRecordConsts.MaxNameLength))] + public string Name { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordCreateOrUpdateDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordCreateOrUpdateDto.cs new file mode 100644 index 000000000..1342491b5 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordCreateOrUpdateDto.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using Volo.Abp.ObjectExtending; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.AIManagement.Tools.Dtos; +public abstract class AIToolDefinitionRecordCreateOrUpdateDto : ExtensibleObject +{ + [Required] + [DynamicStringLength(typeof(AIToolDefinitionRecordConsts), nameof(AIToolDefinitionRecordConsts.MaxProviderLength))] + public string Provider { get; set; } + + [DynamicStringLength(typeof(AIToolDefinitionRecordConsts), nameof(AIToolDefinitionRecordConsts.MaxDescriptionLength))] + public string? Description { get; set; } + + public bool IsEnabled { get; set; } + + public bool IsGlobal { get; set; } + + [DynamicStringLength(typeof(AIToolDefinitionRecordConsts), nameof(AIToolDefinitionRecordConsts.MaxStateCheckersLength))] + public string? StateCheckers { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordDto.cs new file mode 100644 index 000000000..718c8e1a7 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordDto.cs @@ -0,0 +1,25 @@ +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Domain.Entities; + +namespace LINGYUN.Abp.AIManagement.Tools.Dtos; + +[Serializable] +public class AIToolDefinitionRecordDto : ExtensibleAuditedEntityDto, IHasConcurrencyStamp +{ + public string Name { get; set; } + + public string Provider { get; set; } + + public string? Description { get; set; } + + public bool IsEnabled { get; set; } + + public bool IsSystem { get; set; } + + public bool IsGlobal { get; set; } + + public string? StateCheckers { get; set; } + + public string ConcurrencyStamp { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordGetPagedListInput.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordGetPagedListInput.cs new file mode 100644 index 000000000..f19a6b3d6 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordGetPagedListInput.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.AIManagement.Tools.Dtos; + +[Serializable] +public class AIToolDefinitionRecordGetPagedListInput : PagedAndSortedResultRequestDto +{ + public string? Filter { get; set; } + + public bool? IsEnabled { get; set; } + + public string? Provider { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordUpdateDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordUpdateDto.cs new file mode 100644 index 000000000..c0bdc0c81 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolDefinitionRecordUpdateDto.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Domain.Entities; + +namespace LINGYUN.Abp.AIManagement.Tools.Dtos; +public class AIToolDefinitionRecordUpdateDto : AIToolDefinitionRecordCreateOrUpdateDto, IHasConcurrencyStamp +{ + [Required] + [StringLength(40)] + public string ConcurrencyStamp { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolPropertyDescriptorDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolPropertyDescriptorDto.cs new file mode 100644 index 000000000..4874482c3 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolPropertyDescriptorDto.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp; + +namespace LINGYUN.Abp.AIManagement.Tools.Dtos; +public class AIToolPropertyDescriptorDto +{ + public string Name { get; set; } + public bool Required { get; set; } + public string ValueType { get; set; } + public List> Options { get; set; } + public string DisplayName { get; set; } + public string? Description { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolProviderDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolProviderDto.cs new file mode 100644 index 000000000..0bf085539 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolProviderDto.cs @@ -0,0 +1,11 @@ +namespace LINGYUN.Abp.AIManagement.Tools.Dtos; +public class AIToolProviderDto +{ + public string Name { get; } + public AIToolPropertyDescriptorDto[] Properties { get; } + public AIToolProviderDto(string name, AIToolPropertyDescriptorDto[] properties) + { + Name = name; + Properties = properties; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionAppService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionAppService.cs new file mode 100644 index 000000000..eca31369d --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionAppService.cs @@ -0,0 +1,17 @@ +using LINGYUN.Abp.AIManagement.Tools.Dtos; +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.AIManagement.Tools; +public interface IAIToolDefinitionAppService : + ICrudAppService< + AIToolDefinitionRecordDto, + Guid, + AIToolDefinitionRecordGetPagedListInput, + AIToolDefinitionRecordCreateDto, + AIToolDefinitionRecordUpdateDto> +{ + Task> GetAvailableProvidersAsync(); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordCreateOrUpdateDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordCreateOrUpdateDto.cs index dcb866294..361ac7d50 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordCreateOrUpdateDto.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordCreateOrUpdateDto.cs @@ -42,6 +42,8 @@ public abstract class WorkspaceDefinitionRecordCreateOrUpdateDto : ExtensibleObj public bool IsEnabled { get; set; } + public string[]? Tools { get; set; } + [DynamicStringLength(typeof(WorkspaceDefinitionRecordConsts), nameof(WorkspaceDefinitionRecordConsts.MaxStateCheckersLength))] public string? StateCheckers { get; set; } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordDto.cs index 07cc111c7..140959dc7 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordDto.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordDto.cs @@ -35,6 +35,8 @@ public class WorkspaceDefinitionRecordDto : ExtensibleAuditedEntityDto, IH public bool IsSystem { get; set; } + public string[] Tools { get; set; } + public string? StateCheckers { get; set; } public string ConcurrencyStamp { get; set; } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordGetListInput.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordGetPagedListInput.cs similarity index 73% rename from aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordGetListInput.cs rename to aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordGetPagedListInput.cs index a4f99fb6c..ae4b74e42 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordGetListInput.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/Dtos/WorkspaceDefinitionRecordGetPagedListInput.cs @@ -4,7 +4,7 @@ using Volo.Abp.Application.Dtos; namespace LINGYUN.Abp.AIManagement.Workspaces.Dtos; [Serializable] -public class WorkspaceDefinitionRecordGetListInput : PagedAndSortedResultRequestDto +public class WorkspaceDefinitionRecordGetPagedListInput : PagedAndSortedResultRequestDto { public string? Filter { get; set; } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/IWorkspaceDefinitionAppService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/IWorkspaceDefinitionAppService.cs index 7f827538e..b546b796c 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/IWorkspaceDefinitionAppService.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Workspaces/IWorkspaceDefinitionAppService.cs @@ -9,7 +9,7 @@ public interface IWorkspaceDefinitionAppService : ICrudAppService< WorkspaceDefinitionRecordDto, Guid, - WorkspaceDefinitionRecordGetListInput, + WorkspaceDefinitionRecordGetPagedListInput, WorkspaceDefinitionRecordCreateDto, WorkspaceDefinitionRecordUpdateDto> { diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/AbpAIManagementApplicationMappers.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/AbpAIManagementApplicationMappers.cs index 6f6935e13..dc0e6377b 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/AbpAIManagementApplicationMappers.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/AbpAIManagementApplicationMappers.cs @@ -1,8 +1,11 @@ using LINGYUN.Abp.AIManagement.Chats; using LINGYUN.Abp.AIManagement.Chats.Dtos; +using LINGYUN.Abp.AIManagement.Tools; +using LINGYUN.Abp.AIManagement.Tools.Dtos; using LINGYUN.Abp.AIManagement.Workspaces; using LINGYUN.Abp.AIManagement.Workspaces.Dtos; using Riok.Mapperly.Abstractions; +using System; using Volo.Abp.Mapperly; using Volo.Abp.ObjectExtending; @@ -12,8 +15,17 @@ namespace LINGYUN.Abp.AIManagement; [MapExtraProperties(DefinitionChecks = MappingPropertyDefinitionChecks.None)] public partial class WorkspaceDefinitionRecordToWorkspaceDefinitionRecordDtoMapper : MapperBase { + [MapPropertyFromSource(nameof(WorkspaceDefinitionRecordDto.Tools), Use = nameof(ConvertTools))] public override partial WorkspaceDefinitionRecordDto Map(WorkspaceDefinitionRecord source); + + [MapPropertyFromSource(nameof(WorkspaceDefinitionRecordDto.Tools), Use = nameof(ConvertTools))] public override partial void Map(WorkspaceDefinitionRecord source, WorkspaceDefinitionRecordDto destination); + + [UserMapping(Default = false)] + private static string[] ConvertTools(WorkspaceDefinitionRecord record) + { + return !record.Tools.IsNullOrWhiteSpace() ? record.Tools.Split(",") : []; + } } [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] @@ -31,4 +43,12 @@ public partial class TextChatMessageRecordToTextChatMessageDtoMapper : MapperBas { return record.Role.Value; } -} \ No newline at end of file +} + +[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] +[MapExtraProperties(DefinitionChecks = MappingPropertyDefinitionChecks.None)] +public partial class AIToolDefinitionRecordToAIToolDefinitionRecordDtoMapper : MapperBase +{ + public override partial AIToolDefinitionRecordDto Map(AIToolDefinitionRecord source); + public override partial void Map(AIToolDefinitionRecord source, AIToolDefinitionRecordDto destination); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionAppService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionAppService.cs new file mode 100644 index 000000000..c4ebf5987 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionAppService.cs @@ -0,0 +1,168 @@ +using LINGYUN.Abp.AI.Tools; +using LINGYUN.Abp.AIManagement.Localization; +using LINGYUN.Abp.AIManagement.Permissions; +using LINGYUN.Abp.AIManagement.Tools.Dtos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class AIToolDefinitionAppService : + CrudAppService< + AIToolDefinitionRecord, + AIToolDefinitionRecordDto, + Guid, + AIToolDefinitionRecordGetPagedListInput, + AIToolDefinitionRecordCreateDto, + AIToolDefinitionRecordUpdateDto>, + IAIToolDefinitionAppService +{ + protected AbpAIToolsOptions AIToolsOptions { get; } + protected IAIToolDefinitionRecordRepository AIToolDefinitionRecordRepository { get; } + + public AIToolDefinitionAppService( + IOptions aiToolsOptions, + IAIToolDefinitionRecordRepository repository) + : base(repository) + { + AIToolsOptions = aiToolsOptions.Value; + AIToolDefinitionRecordRepository = repository; + + LocalizationResource = typeof(AIManagementResource); + ObjectMapperContext = typeof(AbpAIManagementApplicationModule); + + CreatePolicyName = AIManagementPermissionNames.AIToolDefinition.Create; + UpdatePolicyName = AIManagementPermissionNames.AIToolDefinition.Update; + DeletePolicyName = AIManagementPermissionNames.AIToolDefinition.Delete; + GetListPolicyName = AIManagementPermissionNames.AIToolDefinition.Default; + GetPolicyName = AIManagementPermissionNames.AIToolDefinition.Default; + } + + public virtual Task> GetAvailableProvidersAsync() + { + var providers = AIToolsOptions.AIToolProviders + .Select(LazyServiceProvider.GetRequiredService) + .OfType() + .Select(provider => + { + var properties = provider.GetPropertites(); + + return new AIToolProviderDto( + provider.Name, + properties.Select(prop => + { + var property = new AIToolPropertyDescriptorDto + { + Name = prop.Name, + Options = prop.Options, + Required = prop.Required, + ValueType = prop.ValueType.ToString(), + DisplayName = prop.DisplayName.Localize(StringLocalizerFactory), + }; + if (prop.Description != null) + { + property.Description = prop.Description.Localize(StringLocalizerFactory); + } + + return property; + }).ToArray()); + }); + + return Task.FromResult(new ListResultDto(providers.ToImmutableArray())); + } + + protected async override Task DeleteByIdAsync(Guid id) + { + var aiTool = await Repository.GetAsync(id); + + if (aiTool.IsSystem) + { + throw new BusinessException( + AIManagementErrorCodes.AITool.SystemAIToolNotAllowedToBeDeleted, + $"System AITool {aiTool.Name} is not allowed to be deleted!") + .WithData("AITool", aiTool.Name); + } + + await Repository.DeleteAsync(aiTool); + } + + protected async override Task> CreateFilteredQueryAsync(AIToolDefinitionRecordGetPagedListInput input) + { + var queryable = await base.CreateFilteredQueryAsync(input); + + return queryable + .WhereIf(input.IsEnabled.HasValue, x => x.IsEnabled == input.IsEnabled) + .WhereIf(!input.Provider.IsNullOrWhiteSpace(), x => x.Provider == input.Provider) + .WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.Provider.Contains(input.Filter!) || + x.Name.Contains(input.Filter!) || x.Description!.Contains(input.Filter!)); + } + + protected async override Task MapToEntityAsync(AIToolDefinitionRecordCreateDto createInput) + { + if (await AIToolDefinitionRecordRepository.FindByNameAsync(createInput.Name) != null) + { + throw new AIToolAlreadyExistsException(createInput.Name); + } + + var entity = new AIToolDefinitionRecord( + GuidGenerator.Create(), + createInput.Name, + createInput.Provider, + createInput.Description, + createInput.StateCheckers) + { + IsEnabled = createInput.IsEnabled, + IsGlobal = createInput.IsGlobal, + }; + + if (!entity.HasSameExtraProperties(createInput)) + { + entity.ExtraProperties.Clear(); + + foreach (var property in createInput.ExtraProperties) + { + entity.ExtraProperties.Add(property.Key, property.Value); + } + } + + return entity; + } + + protected override void MapToEntity(AIToolDefinitionRecordUpdateDto updateInput, AIToolDefinitionRecord entity) + { + if (entity.Description != updateInput.Description) + { + entity.Description = updateInput.Description; + } + + if (entity.IsEnabled != updateInput.IsEnabled) + { + entity.IsEnabled = updateInput.IsEnabled; + } + + if (entity.IsGlobal != updateInput.IsGlobal) + { + entity.IsGlobal = updateInput.IsGlobal; + } + + if (!entity.HasSameExtraProperties(updateInput)) + { + entity.ExtraProperties.Clear(); + + foreach (var property in updateInput.ExtraProperties) + { + entity.ExtraProperties.Add(property.Key, property.Value); + } + } + + entity.SetConcurrencyStampIfNotNull(updateInput.ConcurrencyStamp); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionAppService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionAppService.cs index 661fc697c..f4337fb02 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionAppService.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionAppService.cs @@ -5,6 +5,7 @@ using LINGYUN.Abp.AIManagement.Workspaces.Dtos; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -12,6 +13,7 @@ using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; using Volo.Abp.Security.Encryption; namespace LINGYUN.Abp.AIManagement.Workspaces; @@ -20,7 +22,7 @@ public class WorkspaceDefinitionAppService : WorkspaceDefinitionRecord, WorkspaceDefinitionRecordDto, Guid, - WorkspaceDefinitionRecordGetListInput, + WorkspaceDefinitionRecordGetPagedListInput, WorkspaceDefinitionRecordCreateDto, WorkspaceDefinitionRecordUpdateDto>, IWorkspaceDefinitionAppService @@ -79,7 +81,7 @@ public class WorkspaceDefinitionAppService : await Repository.DeleteAsync(workspace); } - protected async override Task> CreateFilteredQueryAsync(WorkspaceDefinitionRecordGetListInput input) + protected async override Task> CreateFilteredQueryAsync(WorkspaceDefinitionRecordGetPagedListInput input) { var queryable = await base.CreateFilteredQueryAsync(input); @@ -100,7 +102,7 @@ public class WorkspaceDefinitionAppService : throw new WorkspaceAlreadyExistsException(createInput.Name); } - var record = new WorkspaceDefinitionRecord( + var entity = new WorkspaceDefinitionRecord( GuidGenerator.Create(), createInput.Name, createInput.Provider, @@ -113,15 +115,29 @@ public class WorkspaceDefinitionAppService : createInput.MaxOutputTokens, createInput.FrequencyPenalty, createInput.PresencePenalty, - createInput.StateCheckers); + createInput.Tools?.JoinAsString(","), + createInput.StateCheckers) + { + IsEnabled = createInput.IsEnabled, + }; if (!createInput.ApiKey.IsNullOrWhiteSpace()) { var encryptApiKey = StringEncryptionService.Encrypt(createInput.ApiKey); - record.SetApiKey(encryptApiKey, createInput.ApiBaseUrl); + entity.SetApiKey(encryptApiKey, createInput.ApiBaseUrl); } - return record; + if (!entity.HasSameExtraProperties(createInput)) + { + entity.ExtraProperties.Clear(); + + foreach (var property in createInput.ExtraProperties) + { + entity.ExtraProperties.Add(property.Key, property.Value); + } + } + + return entity; } protected override void MapToEntity(WorkspaceDefinitionRecordUpdateDto updateInput, WorkspaceDefinitionRecord entity) @@ -181,6 +197,11 @@ public class WorkspaceDefinitionAppService : entity.StateCheckers = updateInput.StateCheckers; } + if (updateInput.Tools != null) + { + entity.Tools = updateInput.Tools.JoinAsString(","); + } + if (!updateInput.ApiKey.IsNullOrWhiteSpace()) { var encryptApiKey = StringEncryptionService.Encrypt(updateInput.ApiKey); @@ -196,5 +217,7 @@ public class WorkspaceDefinitionAppService : entity.ExtraProperties.Add(property.Key, property.Value); } } + + entity.SetConcurrencyStampIfNotNull(updateInput.ConcurrencyStamp); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/AIManagementErrorCodes.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/AIManagementErrorCodes.cs index bdd94b6cd..e0573ab5d 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/AIManagementErrorCodes.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/AIManagementErrorCodes.cs @@ -16,4 +16,18 @@ public static class AIManagementErrorCodes /// public const string SystemWorkspaceNotAllowedToBeDeleted = Prefix + "002"; } + + public static class AITool + { + public const string Prefix = Namespace + ":112"; + + /// + /// AITool {AITool} already exists! + /// + public const string NameAlreadyExists = Prefix + "001"; + /// + /// System AITool {AITool} is not allowed to be deleted! + /// + public const string SystemAIToolNotAllowedToBeDeleted = Prefix + "002"; + } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/en.json b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/en.json index b60960562..ee4925d27 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/en.json +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/en.json @@ -3,6 +3,7 @@ "texts": { "Permission:AIManagement": "Artificia Intelligence", "Permission:WorkspaceDefinition": "Workspaces", + "Permission:AIToolDefinition": "AI Tools", "Permission:Conversation": "Conversations", "Permission:Chats": "Chats", "Permission:SendMessage": "Send Message", @@ -11,6 +12,8 @@ "Permission:Delete": "Delete", "AIManagement:111001": "Workspace {Workspace} already exists!", "AIManagement:111002": "System workspace {Workspace} is not allowed to be deleted!", + "AIManagement:112001": "AITool {AITool} already exists!", + "AIManagement:112002": "System AITool {AITool} is not allowed to be deleted!", "DisplayName:MaxLatestHistoryMessagesToKeep": "Carry the recent conversation records", "Description:MaxLatestHistoryMessagesToKeep": "When a user initiates a conversation with a large model, the upper limit of the user's recent conversation history that is carried along.", "Conversations:Delete": "Delete Conversation", @@ -48,6 +51,14 @@ "DisplayName:PresencePenalty": "Presence Penalty", "Description:PresencePenalty": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.", "DisplayName:IsEnabled": "Is Enabled", - "DisplayName:Workspace": "Workspace" + "DisplayName:Workspace": "Workspace", + "DisplayName:Tools": "Tools", + "DisplayName:ToolProvider": "Tool Provider", + "DisplayName:IsGlobal": "Is Global", + "Description:IsGlobal": "The global tool is available in all workspaces and may result in a higher token value. Please exercise caution when choosing!", + "Tools": "Tools", + "Propertites": "Propertites", + "Tools:New": "New Tool", + "Tools:Edit": "Edit Tool" } } \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/zh-Hans.json b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/zh-Hans.json index a6825173b..f840feb14 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Localization/Resources/zh-Hans.json @@ -3,6 +3,7 @@ "texts": { "Permission:AIManagement": "人工智能", "Permission:WorkspaceDefinition": "工作区管理", + "Permission:AIToolDefinition": "AI工具管理", "Permission:Conversation": "对话管理", "Permission:Chats": "聊天管理", "Permission:SendMessage": "发送消息", @@ -11,6 +12,8 @@ "Permission:Delete": "删除", "AIManagement:111001": "工作区 {Workspace} 已存在!", "AIManagement:111002": "系统工作区 {Workspace} 不允许删除!", + "AIManagement:112001": "工具 {AITool} 已存在!", + "AIManagement:112002": "系统工具 {AITool} 不允许删除!", "DisplayName:MaxLatestHistoryMessagesToKeep": "携带最近对话记录", "Description:MaxLatestHistoryMessagesToKeep": "用户发起与大模型对话时,携带用户最近对话记录的上限.", "Conversations:Delete": "删除对话", @@ -48,6 +51,14 @@ "DisplayName:PresencePenalty": "存在惩罚", "Description:PresencePenalty": "介于 -2.0 和 2.0 之间的数字.如果该值为正,那么新 token 会根据其是否已在已有文本中出现受到相应的惩罚,从而增加模型谈论新主题的可能性.", "DisplayName:IsEnabled": "启用", - "DisplayName:Workspace": "工作区" + "DisplayName:Workspace": "工作区", + "DisplayName:Tools": "工具列表", + "DisplayName:ToolProvider": "工具提供者", + "DisplayName:IsGlobal": "全局工具", + "Description:IsGlobal": "全局工具在所有工作区均可用, 可能会造成Token偏高, 请谨慎选择!", + "Tools": "工具列表", + "Propertites": "属性", + "Tools:New": "新工具", + "Tools:Edit": "编辑工具" } } \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Tools/AIToolAlreadyExistsException.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Tools/AIToolAlreadyExistsException.cs new file mode 100644 index 000000000..5228cfd25 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Tools/AIToolAlreadyExistsException.cs @@ -0,0 +1,16 @@ +using Volo.Abp; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class AIToolAlreadyExistsException : BusinessException +{ + public string AITool { get; } + public AIToolAlreadyExistsException(string aiTool) + : base( + AIManagementErrorCodes.AITool.NameAlreadyExists, + $"A AITool named {aiTool} already exists!") + { + AITool = aiTool; + + WithData(nameof(AITool), aiTool); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecordConsts.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecordConsts.cs new file mode 100644 index 000000000..883809c66 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecordConsts.cs @@ -0,0 +1,8 @@ +namespace LINGYUN.Abp.AIManagement.Tools; +public static class AIToolDefinitionRecordConsts +{ + public static int MaxNameLength { get; set; } = 64; + public static int MaxProviderLength { get; set; } = 20; + public static int MaxDescriptionLength { get; set; } = 128; + public static int MaxStateCheckersLength { get; set; } = 256; +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecordConsts.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecordConsts.cs index f8a2e6bd1..e1e5bac0f 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecordConsts.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain.Shared/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecordConsts.cs @@ -10,5 +10,6 @@ public static class WorkspaceDefinitionRecordConsts public static int MaxApiBaseUrlLength { get; set; } = 128; public static int MaxSystemPromptLength { get; set; } = 512; public static int MaxInstructionsLength { get; set; } = 512; + public static int MaxToolsLength { get; set; } = 128; public static int MaxStateCheckersLength { get; set; } = 256; } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj index 813624bf7..390c80eb8 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj @@ -24,6 +24,7 @@ + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AIManagementOptions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AIManagementOptions.cs index 72ab61ffd..44505739a 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AIManagementOptions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AIManagementOptions.cs @@ -1,10 +1,10 @@ -using System; - -namespace LINGYUN.Abp.AIManagement; +namespace LINGYUN.Abp.AIManagement; public class AIManagementOptions { public bool IsDynamicWorkspaceStoreEnabled { get; set; } public bool SaveStaticWorkspacesToDatabase { get; set; } + public bool IsDynamicAIToolStoreEnabled { get; set; } + public bool SaveStaticAIToolsToDatabase { get; set; } public AIManagementOptions() { diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs index 75ba502f1..968dd7f2e 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs @@ -1,6 +1,8 @@ using LINGYUN.Abp.AI.Agent; using LINGYUN.Abp.AI.Localization; +using LINGYUN.Abp.AI.Tools; using LINGYUN.Abp.AIManagement.Localization; +using LINGYUN.Abp.AIManagement.Tools; using LINGYUN.Abp.AIManagement.Workspaces; using Microsoft.Extensions.DependencyInjection; using System.Threading; @@ -20,6 +22,7 @@ namespace LINGYUN.Abp.AIManagement; [DependsOn( typeof(AbpAIManagementDomainSharedModule), typeof(AbpAIAgentModule), + typeof(AbpAIToolsModule), typeof(AbpCachingModule), typeof(AbpMapperlyModule), typeof(AbpDddDomainModule))] @@ -57,8 +60,13 @@ public class AbpAIManagementDomainModule : AbpModule public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { var rootServiceProvider = context.ServiceProvider.GetRequiredService(); - var initializer = rootServiceProvider.GetRequiredService(); - await initializer.InitializeAsync(true, _cancellationTokenSource.Token); + var workspaceDynamicInitializer = rootServiceProvider.GetRequiredService(); + var aIToolDynamicInitializer = rootServiceProvider.GetRequiredService(); + + await Task.WhenAll([ + workspaceDynamicInitializer.InitializeAsync(true, _cancellationTokenSource.Token), + aIToolDynamicInitializer.InitializeAsync(true, _cancellationTokenSource.Token) + ]); } public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecord.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecord.cs new file mode 100644 index 000000000..6a3eacd23 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecord.cs @@ -0,0 +1,134 @@ +using JetBrains.Annotations; +using System; +using Volo.Abp; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities.Auditing; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class AIToolDefinitionRecord : AuditedAggregateRoot +{ + public string Name { get; private set; } + + public string Provider { get; private set; } + + public string? Description { get; set; } + + public bool IsEnabled { get; set; } + + public bool IsSystem { get; set; } + + public bool IsGlobal { get; set; } + + public string? StateCheckers { get; set; } + protected AIToolDefinitionRecord() + { + + } + + public AIToolDefinitionRecord( + [NotNull] Guid id, + [NotNull] string name, + [NotNull] string provider, + [CanBeNull] string? description = null, + [CanBeNull] string? stateCheckers = null) + : base(id) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name), AIToolDefinitionRecordConsts.MaxNameLength); + Provider = Check.NotNullOrWhiteSpace(provider, nameof(provider), AIToolDefinitionRecordConsts.MaxProviderLength); + Description = Check.Length(description, nameof(description), AIToolDefinitionRecordConsts.MaxDescriptionLength); + StateCheckers = Check.Length(stateCheckers, nameof(stateCheckers), AIToolDefinitionRecordConsts.MaxStateCheckersLength); + } + + public bool HasSameData(AIToolDefinitionRecord otherAITool) + { + if (Name != otherAITool.Name) + { + return false; + } + + if (Provider != otherAITool.Provider) + { + return false; + } + + if (Description != otherAITool.Description) + { + return false; + } + + if (IsEnabled != otherAITool.IsEnabled) + { + return false; + } + + if (IsSystem != otherAITool.IsSystem) + { + return false; + } + + if (IsGlobal != otherAITool.IsGlobal) + { + return false; + } + + if (StateCheckers != otherAITool.StateCheckers) + { + return false; + } + + if (!this.HasSameExtraProperties(otherAITool)) + { + return false; + } + + return true; + } + + public void Patch(AIToolDefinitionRecord otherAITool) + { + if (Name != otherAITool.Name) + { + Name = otherAITool.Name; + } + + if (Provider != otherAITool.Provider) + { + Provider = otherAITool.Provider; + } + + if (Description != otherAITool.Description) + { + Description = otherAITool.Description; + } + + if (IsEnabled != otherAITool.IsEnabled) + { + IsEnabled = otherAITool.IsEnabled; + } + + if (IsSystem != otherAITool.IsSystem) + { + IsSystem = otherAITool.IsSystem; + } + + if (IsGlobal != otherAITool.IsGlobal) + { + IsGlobal = otherAITool.IsGlobal; + } + + if (StateCheckers != otherAITool.StateCheckers) + { + StateCheckers = otherAITool.StateCheckers; + } + + if (!this.HasSameExtraProperties(otherAITool)) + { + ExtraProperties.Clear(); + + foreach (var property in otherAITool.ExtraProperties) + { + ExtraProperties.Add(property.Key, property.Value); + } + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionSerializer.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionSerializer.cs new file mode 100644 index 000000000..02fa35f8b --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionSerializer.cs @@ -0,0 +1,68 @@ +using LINGYUN.Abp.AI.Tools; +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.Localization; +using Volo.Abp.SimpleStateChecking; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class AIToolDefinitionSerializer : IAIToolDefinitionSerializer, ITransientDependency +{ + protected IGuidGenerator GuidGenerator { get; } + protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; } + protected ILocalizableStringSerializer LocalizableStringSerializer { get; } + + public AIToolDefinitionSerializer( + IGuidGenerator guidGenerator, + ISimpleStateCheckerSerializer stateCheckerSerializer, + ILocalizableStringSerializer localizableStringSerializer) + { + GuidGenerator = guidGenerator; + StateCheckerSerializer = stateCheckerSerializer; + LocalizableStringSerializer = localizableStringSerializer; + } + + public async virtual Task SerializeAsync(IEnumerable definitions) + { + var records = new List(); + foreach (var aiToolDef in definitions) + { + records.Add(await SerializeAsync(aiToolDef)); + } + + return records.ToArray(); + } + + public virtual Task SerializeAsync(AIToolDefinition definition) + { + using (CultureHelper.Use(CultureInfo.InvariantCulture)) + { + var aiToolRecord = new AIToolDefinitionRecord( + GuidGenerator.Create(), + definition.Name, + definition.Provider, + definition.Description != null ? LocalizableStringSerializer.Serialize(definition.Description) : null, + SerializeStateCheckers(definition.StateCheckers)) + { + IsGlobal = definition.IsGlobal, + IsEnabled = definition.IsEnabled, + IsSystem = true, + }; + + foreach (var property in definition.Properties) + { + aiToolRecord.SetProperty(property.Key, property.Value); + } + + return Task.FromResult(aiToolRecord); + } + } + + protected virtual string? SerializeStateCheckers(List> stateCheckers) + { + return StateCheckerSerializer.Serialize(stateCheckers); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDynamicInitializer.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDynamicInitializer.cs new file mode 100644 index 000000000..7367be7e1 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDynamicInitializer.cs @@ -0,0 +1,138 @@ +using LINGYUN.Abp.AI.Tools; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Polly; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class AIToolDynamicInitializer : ITransientDependency +{ + public ILogger Logger { get; set; } + + protected IServiceProvider ServiceProvider { get; } + + public AIToolDynamicInitializer(IServiceProvider serviceProvider) + { + Logger = NullLogger.Instance; + + ServiceProvider = serviceProvider; + } + + public virtual Task InitializeAsync(bool runInBackground, CancellationToken cancellationToken = default) + { + var options = ServiceProvider.GetRequiredService>().Value; + + if (!options.SaveStaticAIToolsToDatabase && !options.IsDynamicAIToolStoreEnabled) + { + return Task.CompletedTask; + } + + if (runInBackground) + { + var applicationLifetime = ServiceProvider.GetService(); + Task.Run(async () => + { + if (cancellationToken == default && applicationLifetime?.ApplicationStopping != null) + { + cancellationToken = applicationLifetime.ApplicationStopping; + } + await ExecuteInitializationAsync(options, cancellationToken); + }, cancellationToken); + + return Task.CompletedTask; + } + + return ExecuteInitializationAsync(options, cancellationToken); + } + + protected virtual async Task ExecuteInitializationAsync(AIManagementOptions options, CancellationToken cancellationToken) + { + try + { + var cancellationTokenProvider = ServiceProvider.GetRequiredService(); + using (cancellationTokenProvider.Use(cancellationToken)) + { + if (cancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await SaveStaticAIToolsToDatabaseAsync(options, cancellationToken); + + if (cancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await PreCacheDynamicAIToolsAsync(options); + } + } + catch + { + // No need to log here since inner calls log + } + } + + protected virtual async Task SaveStaticAIToolsToDatabaseAsync( + AIManagementOptions options, + CancellationToken cancellationToken) + { + if (!options.SaveStaticAIToolsToDatabase) + { + return; + } + + var staticAIToolSaver = ServiceProvider.GetRequiredService(); + + await Policy + .Handle(ex => ex is not OperationCanceledException) + .WaitAndRetryAsync( + 8, + retryAttempt => TimeSpan.FromSeconds( + Volo.Abp.RandomHelper.GetRandom( + (int)Math.Pow(2, retryAttempt) * 8, + (int)Math.Pow(2, retryAttempt) * 12) + ) + ) + .ExecuteAsync(async _ => + { + try + { + await staticAIToolSaver.SaveAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + throw; // Polly will catch it + } + }, cancellationToken); + } + + protected virtual async Task PreCacheDynamicAIToolsAsync(AIManagementOptions options) + { + if (!options.IsDynamicAIToolStoreEnabled) + { + return; + } + + var dynamicAIToolDefinitionStore = ServiceProvider.GetRequiredService(); + + try + { + // Pre-cache AITools, so first request doesn't wait + await dynamicAIToolDefinitionStore.GetAllAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + throw; // It will be cached in Initialize() + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStore.cs new file mode 100644 index 000000000..21b468b60 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStore.cs @@ -0,0 +1,160 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.AI.Tools; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Threading; + +namespace LINGYUN.Abp.AIManagement.Tools; + +[Dependency(ReplaceServices = true)] +public class DynamicAIToolDefinitionStore : IDynamicAIToolDefinitionStore, ITransientDependency +{ + protected IAIToolDefinitionRecordRepository AIToolDefinitionRecordRepository { get; } + protected IAIToolDefinitionSerializer AIToolDefinitionSerializer { get; } + protected IDynamicAIToolDefinitionStoreInMemoryCache StoreCache { get; } + protected IDistributedCache DistributedCache { get; } + protected IAbpDistributedLock DistributedLock { get; } + public AIManagementOptions AIManagementOptions { get; } + protected AbpDistributedCacheOptions CacheOptions { get; } + + public DynamicAIToolDefinitionStore( + IAIToolDefinitionRecordRepository aiToolDefinitionRecordRepository, + IAIToolDefinitionSerializer aiToolDefinitionSerializer, + IDynamicAIToolDefinitionStoreInMemoryCache storeCache, + IDistributedCache distributedCache, + IOptions cacheOptions, + IOptions aiManagementOptions, + IAbpDistributedLock distributedLock) + { + AIToolDefinitionRecordRepository = aiToolDefinitionRecordRepository; + AIToolDefinitionSerializer = aiToolDefinitionSerializer; + StoreCache = storeCache; + DistributedCache = distributedCache; + DistributedLock = distributedLock; + AIManagementOptions = aiManagementOptions.Value; + CacheOptions = cacheOptions.Value; + } + + public async virtual Task> GetAllAsync() + { + if (!AIManagementOptions.IsDynamicAIToolStoreEnabled) + { + return Array.Empty(); + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetAITools(); + } + } + + public async virtual Task GetAsync([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + + return await GetOrNullAsync(name) ?? throw new AbpException("Undefined AITool: " + name); + } + + public async virtual Task GetOrNullAsync([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + + if (!AIManagementOptions.IsDynamicAIToolStoreEnabled) + { + return null; + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetAIToolOrNull(name); + } + } + protected virtual async Task EnsureCacheIsUptoDateAsync() + { + if (StoreCache.LastCheckTime.HasValue && + DateTime.Now.Subtract(StoreCache.LastCheckTime.Value).TotalSeconds < 30) + { + return; + } + + var stampInDistributedCache = await GetOrSetStampInDistributedCache(); + + if (stampInDistributedCache == StoreCache.CacheStamp) + { + StoreCache.LastCheckTime = DateTime.Now; + return; + } + + await UpdateInMemoryStoreCache(); + + StoreCache.CacheStamp = stampInDistributedCache; + StoreCache.LastCheckTime = DateTime.Now; + } + + protected virtual async Task UpdateInMemoryStoreCache() + { + var workspaces = await AIToolDefinitionRecordRepository.GetListAsync(); + + await StoreCache.FillAsync(workspaces); + } + + protected virtual async Task GetOrSetStampInDistributedCache() + { + var cacheKey = GetCommonStampCacheKey(); + + var stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey); + if (stampInDistributedCache != null) + { + return stampInDistributedCache; + } + + await using (var commonLockHandle = await DistributedLock + .TryAcquireAsync(GetCommonDistributedLockKey(), TimeSpan.FromMinutes(2))) + { + if (commonLockHandle == null) + { + throw new AbpException( + "Could not acquire distributed lock for AITool definition common stamp check!" + ); + } + + stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey); + if (stampInDistributedCache != null) + { + return stampInDistributedCache; + } + + stampInDistributedCache = Guid.NewGuid().ToString(); + + await DistributedCache.SetStringAsync( + cacheKey, + stampInDistributedCache, + new DistributedCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromDays(30) + } + ); + } + + return stampInDistributedCache; + } + + protected virtual string GetCommonStampCacheKey() + { + return $"{CacheOptions.KeyPrefix}_AbpInMemoryAIToolCacheStamp"; + } + + protected virtual string GetCommonDistributedLockKey() + { + return $"{CacheOptions.KeyPrefix}_Common_AbpAIToolUpdateLock"; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreCacheInvalidator.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreCacheInvalidator.cs new file mode 100644 index 000000000..4f52cfe2e --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreCacheInvalidator.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; +using Volo.Abp.Threading; +using Volo.Abp.Timing; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class DynamicAIToolDefinitionStoreCacheInvalidator : + ILocalEventHandler>, + ITransientDependency +{ + private readonly IDynamicAIToolDefinitionStoreInMemoryCache _storeCache; + + private readonly IClock _clock; + private readonly IDistributedCache _distributedCache; + private readonly AbpDistributedCacheOptions _cacheOptions; + + public DynamicAIToolDefinitionStoreCacheInvalidator( + IClock clock, + IDistributedCache distributedCache, + IDynamicAIToolDefinitionStoreInMemoryCache storeCache, + IOptions cacheOptions) + { + _storeCache = storeCache; + _clock = clock; + _distributedCache = distributedCache; + _cacheOptions = cacheOptions.Value; + } + + public async virtual Task HandleEventAsync(EntityChangedEventData eventData) + { + await RemoveStampInDistributedCacheAsync(); + } + + protected async virtual Task RemoveStampInDistributedCacheAsync() + { + using (await _storeCache.SyncSemaphore.LockAsync()) + { + var cacheKey = GetCommonStampCacheKey(); + + await _distributedCache.RemoveAsync(cacheKey); + + _storeCache.CacheStamp = Guid.NewGuid().ToString(); + _storeCache.LastCheckTime = _clock.Now.AddMinutes(-5); + } + } + + protected virtual string GetCommonStampCacheKey() + { + return $"{_cacheOptions.KeyPrefix}_AbpInMemoryAIToolCacheStamp"; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreInMemoryCache.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreInMemoryCache.cs new file mode 100644 index 000000000..243cf4bfc --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreInMemoryCache.cs @@ -0,0 +1,81 @@ +using LINGYUN.Abp.AI.Tools; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; +using Volo.Abp.SimpleStateChecking; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class DynamicAIToolDefinitionStoreInMemoryCache : IDynamicAIToolDefinitionStoreInMemoryCache, ISingletonDependency +{ + public string CacheStamp { get; set; } + protected IDictionary AIToolDefinitions { get; } + protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; } + protected ILocalizableStringSerializer LocalizableStringSerializer { get; } + + public SemaphoreSlim SyncSemaphore { get; } = new(1, 1); + + public DateTime? LastCheckTime { get; set; } + + public DynamicAIToolDefinitionStoreInMemoryCache( + ISimpleStateCheckerSerializer stateCheckerSerializer, + ILocalizableStringSerializer localizableStringSerializer) + { + StateCheckerSerializer = stateCheckerSerializer; + LocalizableStringSerializer = localizableStringSerializer; + + AIToolDefinitions = new Dictionary(); + } + + public Task FillAsync(List tools) + { + AIToolDefinitions.Clear(); + + foreach (var tool in tools) + { + var toolDef = new AIToolDefinition( + tool.Name, + tool.Provider, + !tool.Description.IsNullOrWhiteSpace() ? LocalizableStringSerializer.Deserialize(tool.Description) : null) + { + IsEnabled = tool.IsEnabled, + IsGlobal = tool.IsGlobal, + }; + + if (!tool.StateCheckers.IsNullOrWhiteSpace()) + { + var checkers = StateCheckerSerializer + .DeserializeArray( + tool.StateCheckers, + toolDef + ); + toolDef.StateCheckers.AddRange(checkers); + } + + foreach (var property in tool.ExtraProperties) + { + if (property.Value != null) + { + toolDef.WithProperty(property.Key, property.Value); + } + } + + AIToolDefinitions[tool.Name] = toolDef; + } + + return Task.CompletedTask; + } + + public AIToolDefinition? GetAIToolOrNull(string name) + { + return AIToolDefinitions.GetOrDefault(name); + } + + public IReadOnlyList GetAITools() + { + return AIToolDefinitions.Values.ToList(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionRecordRepository.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionRecordRepository.cs new file mode 100644 index 000000000..71c3a1f3a --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionRecordRepository.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace LINGYUN.Abp.AIManagement.Tools; +public interface IAIToolDefinitionRecordRepository : IRepository +{ + Task FindByNameAsync( + string name, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionSerializer.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionSerializer.cs new file mode 100644 index 000000000..c7c322951 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionSerializer.cs @@ -0,0 +1,11 @@ +using LINGYUN.Abp.AI.Tools; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AIManagement.Tools; +public interface IAIToolDefinitionSerializer +{ + Task SerializeAsync(IEnumerable definitions); + + Task SerializeAsync(AIToolDefinition definition); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IDynamicAIToolDefinitionStoreInMemoryCache.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IDynamicAIToolDefinitionStoreInMemoryCache.cs new file mode 100644 index 000000000..108dd095b --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IDynamicAIToolDefinitionStoreInMemoryCache.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.AI.Tools; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AIManagement.Tools; +public interface IDynamicAIToolDefinitionStoreInMemoryCache +{ + string CacheStamp { get; set; } + + SemaphoreSlim SyncSemaphore { get; } + + DateTime? LastCheckTime { get; set; } + + Task FillAsync(List tools); + + AIToolDefinition? GetAIToolOrNull(string name); + + IReadOnlyList GetAITools(); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IStaticAIToolSaver.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IStaticAIToolSaver.cs new file mode 100644 index 000000000..afe42957a --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IStaticAIToolSaver.cs @@ -0,0 +1,7 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AIManagement.Tools; +public interface IStaticAIToolSaver +{ + Task SaveAsync(); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/StaticAIToolSaver.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/StaticAIToolSaver.cs new file mode 100644 index 000000000..f05988d9c --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/StaticAIToolSaver.cs @@ -0,0 +1,241 @@ +using LINGYUN.Abp.AI.Tools; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Guids; +using Volo.Abp.Json.SystemTextJson.Modifiers; +using Volo.Abp.Threading; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.AIManagement.Tools; +public class StaticAIToolSaver : IStaticAIToolSaver, ITransientDependency +{ + protected IStaticAIToolDefinitionStore StaticStore { get; } + protected IAIToolDefinitionRecordRepository AIToolDefinitionRecordRepository { get; } + protected IAIToolDefinitionSerializer AIToolDefinitionSerializer { get; } + protected IDistributedCache Cache { get; } + protected IApplicationInfoAccessor ApplicationInfoAccessor { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected AbpAIToolsOptions AIToolOptions { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected AbpDistributedCacheOptions CacheOptions { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected IGuidGenerator GuidGenerator { get; } + + public StaticAIToolSaver( + IStaticAIToolDefinitionStore staticStore, + IAIToolDefinitionRecordRepository aiToolDefinitionRecordRepository, + IAIToolDefinitionSerializer aiToolDefinitionSerializer, + IDistributedCache cache, + IOptions cacheOptions, + IApplicationInfoAccessor applicationInfoAccessor, + IAbpDistributedLock distributedLock, + IOptions aiToolOptions, + ICancellationTokenProvider cancellationTokenProvider, + IUnitOfWorkManager unitOfWorkManager, + IGuidGenerator guidGenerator) + { + StaticStore = staticStore; + AIToolDefinitionRecordRepository = aiToolDefinitionRecordRepository; + AIToolDefinitionSerializer = aiToolDefinitionSerializer; + Cache = cache; + ApplicationInfoAccessor = applicationInfoAccessor; + DistributedLock = distributedLock; + CancellationTokenProvider = cancellationTokenProvider; + AIToolOptions = aiToolOptions.Value; + CacheOptions = cacheOptions.Value; + UnitOfWorkManager = unitOfWorkManager; + GuidGenerator = guidGenerator; + } + + [UnitOfWork] + public async Task SaveAsync() + { + await using var applicationLockHandle = await DistributedLock.TryAcquireAsync( + GetApplicationDistributedLockKey() + ); + + if (applicationLockHandle == null) + { + return; + } + + var cacheKey = GetApplicationHashCacheKey(); + var cachedHash = await Cache.GetStringAsync(cacheKey, CancellationTokenProvider.Token); + + var aiTools = await AIToolDefinitionSerializer.SerializeAsync(await StaticStore.GetAllAsync()); + var currentHash = CalculateHash(aiTools, AIToolOptions.DeletedAITools); + + if (cachedHash == currentHash) + { + return; + } + + await using (var commonLockHandle = await DistributedLock.TryAcquireAsync( + GetCommonDistributedLockKey(), + TimeSpan.FromMinutes(5))) + { + if (commonLockHandle == null) + { + /* It will re-try */ + throw new AbpException("Could not acquire distributed lock for saving static AITool!"); + } + + using (var unitOfWork = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true)) + { + try + { + var hasChangesInAITools = await UpdateChangedAIToolsAsync(aiTools); + + if (hasChangesInAITools) + { + await Cache.SetStringAsync( + GetCommonStampCacheKey(), + Guid.NewGuid().ToString(), + new DistributedCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromDays(30) + }, + CancellationTokenProvider.Token + ); + } + } + catch + { + try + { + await unitOfWork.RollbackAsync(); + } + catch + { + /* ignored */ + } + + throw; + } + + await unitOfWork.CompleteAsync(); + } + } + + await Cache.SetStringAsync( + cacheKey, + currentHash, + new DistributedCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromDays(30) + }, + CancellationTokenProvider.Token + ); + } + + private async Task UpdateChangedAIToolsAsync(AIToolDefinitionRecord[] aiToolRecords) + { + var newRecords = new List(); + var changedRecords = new List(); + + var aiToolRecordsInDatabase = (await AIToolDefinitionRecordRepository.GetListAsync()).ToDictionary(x => x.Name); + + foreach (var record in aiToolRecords) + { + var aiToolRecordInDatabase = aiToolRecordsInDatabase.GetOrDefault(record.Name); + if (aiToolRecordInDatabase == null) + { + /* New group */ + newRecords.Add(record); + continue; + } + + if (record.HasSameData(aiToolRecordInDatabase)) + { + /* Not changed */ + continue; + } + + /* Changed */ + aiToolRecordInDatabase.Patch(record); + changedRecords.Add(aiToolRecordInDatabase); + } + + /* Deleted */ + var deletedRecords = new List(); + + if (AIToolOptions.DeletedAITools.Any()) + { + deletedRecords.AddRange(aiToolRecordsInDatabase.Values.Where(x => AIToolOptions.DeletedAITools.Contains(x.Name))); + } + + if (newRecords.Any()) + { + await AIToolDefinitionRecordRepository.InsertManyAsync(newRecords); + } + + if (changedRecords.Any()) + { + await AIToolDefinitionRecordRepository.UpdateManyAsync(changedRecords); + } + + if (deletedRecords.Any()) + { + await AIToolDefinitionRecordRepository.DeleteManyAsync(deletedRecords); + } + + return newRecords.Any() || changedRecords.Any() || deletedRecords.Any(); + } + + private string GetApplicationDistributedLockKey() + { + return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpAIToolUpdateLock"; + } + + private string GetCommonDistributedLockKey() + { + return $"{CacheOptions.KeyPrefix}_Common_AbpAIToolUpdateLock"; + } + + private string GetApplicationHashCacheKey() + { + return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpAIToolsHash"; + } + + private string GetCommonStampCacheKey() + { + return $"{CacheOptions.KeyPrefix}_AbpInMemoryAIToolCacheStamp"; + } + + private string CalculateHash(AIToolDefinitionRecord[] aiToolRecords, IEnumerable deletedAITool) + { + var jsonSerializerOptions = new JsonSerializerOptions + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + new AbpIgnorePropertiesModifiers().CreateModifyAction(x => x.Id), + } + } + }; + + var stringBuilder = new StringBuilder(); + + stringBuilder.Append("AITools:"); + stringBuilder.AppendLine(JsonSerializer.Serialize(aiToolRecords, jsonSerializerOptions)); + + stringBuilder.Append("DeletedAITool:"); + stringBuilder.Append(deletedAITool.JoinAsString(",")); + + return stringBuilder + .ToString() + .ToMd5(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStoreInMemoryCache.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStoreInMemoryCache.cs index 3d7d0d142..7baf9dfd5 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStoreInMemoryCache.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStoreInMemoryCache.cs @@ -62,6 +62,10 @@ public class DynamicWorkspaceDefinitionStoreInMemoryCache : IDynamicWorkspaceDef { workspaceDef.WithApiBaseUrl(workspace.ApiBaseUrl); } + if (!workspace.Tools.IsNullOrWhiteSpace()) + { + workspaceDef.WithTools(workspace.Tools.Split(",")); + } workspaceDef.IsEnabled = workspace.IsEnabled; if (!workspace.StateCheckers.IsNullOrWhiteSpace()) diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IDynamicWorkspaceDefinitionStoreInMemoryCache.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IDynamicWorkspaceDefinitionStoreInMemoryCache.cs index ec3c6b82b..3d3f1a1b9 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IDynamicWorkspaceDefinitionStoreInMemoryCache.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IDynamicWorkspaceDefinitionStoreInMemoryCache.cs @@ -14,7 +14,7 @@ public interface IDynamicWorkspaceDefinitionStoreInMemoryCache DateTime? LastCheckTime { get; set; } - Task FillAsync(List permissions); + Task FillAsync(List workspaces); WorkspaceDefinition? GetWorkspaceOrNull(string name); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecord.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecord.cs index 487256815..489adf94c 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecord.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionRecord.cs @@ -36,6 +36,8 @@ public class WorkspaceDefinitionRecord : AuditedAggregateRoot public bool IsSystem { get; set; } + public string? Tools { get; set; } + public string? StateCheckers { get; set; } protected WorkspaceDefinitionRecord() @@ -57,6 +59,7 @@ public class WorkspaceDefinitionRecord : AuditedAggregateRoot int? maxOutputTokens = null, float? frequencyPenalty = null, float? presencePenalty = null, + string? tools = null, string? stateCheckers = null) : base(id) { @@ -67,6 +70,7 @@ public class WorkspaceDefinitionRecord : AuditedAggregateRoot Description = Check.Length(description, nameof(description), WorkspaceDefinitionRecordConsts.MaxDescriptionLength); SystemPrompt = Check.Length(systemPrompt, nameof(systemPrompt), WorkspaceDefinitionRecordConsts.MaxSystemPromptLength); Instructions = Check.Length(instructions, nameof(instructions), WorkspaceDefinitionRecordConsts.MaxInstructionsLength); + Tools = Check.Length(tools, nameof(tools), WorkspaceDefinitionRecordConsts.MaxToolsLength); StateCheckers = Check.Length(stateCheckers, nameof(stateCheckers), WorkspaceDefinitionRecordConsts.MaxStateCheckersLength); Temperature = temperature; MaxOutputTokens = maxOutputTokens; @@ -147,6 +151,11 @@ public class WorkspaceDefinitionRecord : AuditedAggregateRoot return false; } + if (IsSystem != otherWorkspace.IsSystem) + { + return false; + } + if (Temperature != otherWorkspace.Temperature) { return false; @@ -232,6 +241,11 @@ public class WorkspaceDefinitionRecord : AuditedAggregateRoot IsEnabled = otherWorkspace.IsEnabled; } + if (IsSystem != otherWorkspace.IsSystem) + { + IsSystem = otherWorkspace.IsSystem; + } + if (Temperature != otherWorkspace.Temperature) { Temperature = otherWorkspace.Temperature; diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionSerializer.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionSerializer.cs index ec8ad7403..6202f058b 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionSerializer.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionSerializer.cs @@ -58,6 +58,7 @@ public class WorkspaceDefinitionSerializer : IWorkspaceDefinitionSerializer, ITr definition.MaxOutputTokens, definition.FrequencyPenalty, definition.PresencePenalty, + definition.Tools.JoinAsString(","), SerializeStateCheckers(definition.StateCheckers)); if (!definition.ApiKey.IsNullOrWhiteSpace()) diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContext.cs index fb3b6a9f2..35c634b94 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContext.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContext.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.AIManagement.Chats; using LINGYUN.Abp.AIManagement.Tokens; +using LINGYUN.Abp.AIManagement.Tools; using LINGYUN.Abp.AIManagement.Workspaces; using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; @@ -11,6 +12,7 @@ namespace LINGYUN.Abp.AIManagement.EntityFrameworkCore; public class AIManagementDbContext : AbpDbContext, IAIManagementDbContext { public DbSet WorkspaceDefinitions { get; set; } + public DbSet AIToolDefinitions { get; set; } public DbSet TextChatMessageRecords { get; set; } public DbSet ConversationRecords { get; set; } public DbSet TokenUsageRecords { get; set; } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContextModelBuilderExtensions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContextModelBuilderExtensions.cs index e62b67b95..ffe9e7f00 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContextModelBuilderExtensions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AIManagementDbContextModelBuilderExtensions.cs @@ -2,6 +2,7 @@ using LINGYUN.Abp.AIManagement.Chats; using LINGYUN.Abp.AIManagement.EntityFrameworkCore.ValueConversions; using LINGYUN.Abp.AIManagement.Tokens; +using LINGYUN.Abp.AIManagement.Tools; using LINGYUN.Abp.AIManagement.Workspaces; using Microsoft.EntityFrameworkCore; using Volo.Abp; @@ -90,6 +91,29 @@ public static class AIManagementDbContextModelBuilderExtensions .HasMaxLength(WorkspaceDefinitionRecordConsts.MaxInstructionsLength); b.Property(x => x.StateCheckers) .HasMaxLength(WorkspaceDefinitionRecordConsts.MaxStateCheckersLength); + b.Property(x => x.Tools) + .HasMaxLength(WorkspaceDefinitionRecordConsts.MaxToolsLength); + + b.HasIndex(x => new { x.Name }).IsUnique(); + + b.ApplyObjectExtensionMappings(); + }); + builder.Entity(b => + { + b.ToTable(AbpAIManagementDbProperties.DbTablePrefix + "AIToolDefinitionRecords", AbpAIManagementDbProperties.DbSchema); + + b.ConfigureByConvention(); + + b.Property(x => x.Name) + .HasMaxLength(AIToolDefinitionRecordConsts.MaxNameLength) + .IsRequired(); + b.Property(x => x.Provider) + .HasMaxLength(AIToolDefinitionRecordConsts.MaxProviderLength) + .IsRequired(); + b.Property(x => x.Description) + .HasMaxLength(AIToolDefinitionRecordConsts.MaxDescriptionLength); + b.Property(x => x.StateCheckers) + .HasMaxLength(AIToolDefinitionRecordConsts.MaxStateCheckersLength); b.HasIndex(x => new { x.Name }).IsUnique(); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AbpAIManagementEntityFrameworkCoreModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AbpAIManagementEntityFrameworkCoreModule.cs index bb4cf3964..051f6838b 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AbpAIManagementEntityFrameworkCoreModule.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/AbpAIManagementEntityFrameworkCoreModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.AIManagement.Chats; using LINGYUN.Abp.AIManagement.Tokens; +using LINGYUN.Abp.AIManagement.Tools; using LINGYUN.Abp.AIManagement.Workspaces; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.EntityFrameworkCore; @@ -23,6 +24,8 @@ public class AbpAIManagementEntityFrameworkCoreModule : AbpModule options.AddRepository(); options.AddRepository(); + + options.AddRepository(); }); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreAIToolDefinitionRecordRepository.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreAIToolDefinitionRecordRepository.cs new file mode 100644 index 000000000..326dde2aa --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreAIToolDefinitionRecordRepository.cs @@ -0,0 +1,27 @@ +using LINGYUN.Abp.AIManagement.Tools; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.AIManagement.EntityFrameworkCore; +public class EfCoreAIToolDefinitionRecordRepository : + EfCoreRepository, + IAIToolDefinitionRecordRepository +{ + public EfCoreAIToolDefinitionRecordRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async virtual Task FindByNameAsync(string name, CancellationToken cancellationToken = default) + { + return await (await GetQueryableAsync()) + .Where(x => x.Name == name) + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreWorkspaceDefinitionRecordRepository.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreWorkspaceDefinitionRecordRepository.cs index 7c0bcd3b5..2beccb310 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreWorkspaceDefinitionRecordRepository.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreWorkspaceDefinitionRecordRepository.cs @@ -21,6 +21,6 @@ public class EfCoreWorkspaceDefinitionRecordRepository : EfCoreRepository x.Name == name) - .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); ; + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/IAIManagementDbContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/IAIManagementDbContext.cs index edc46235d..abdb6ef53 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/IAIManagementDbContext.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/IAIManagementDbContext.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.AIManagement.Chats; using LINGYUN.Abp.AIManagement.Tokens; +using LINGYUN.Abp.AIManagement.Tools; using LINGYUN.Abp.AIManagement.Workspaces; using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; @@ -11,6 +12,7 @@ namespace LINGYUN.Abp.AIManagement.EntityFrameworkCore; public interface IAIManagementDbContext : IEfCoreDbContext { DbSet WorkspaceDefinitions { get; } + DbSet AIToolDefinitions { get; } DbSet TextChatMessageRecords { get; } DbSet ConversationRecords { get; } DbSet TokenUsageRecords { get; } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionController.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionController.cs new file mode 100644 index 000000000..7b14f7d1c --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionController.cs @@ -0,0 +1,64 @@ +using LINGYUN.Abp.AIManagement.Permissions; +using LINGYUN.Abp.AIManagement.Tools.Dtos; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.AIManagement.Tools; + +[Controller] +[RemoteService(Name = AIManagementRemoteServiceConsts.RemoteServiceName)] +[Area(AIManagementRemoteServiceConsts.ModuleName)] +[Route($"api/{AIManagementRemoteServiceConsts.ModuleName}/tools")] +[Authorize(AIManagementPermissionNames.AIToolDefinition.Default)] +public class AIToolDefinitionController : AbpControllerBase, IAIToolDefinitionAppService +{ + private readonly IAIToolDefinitionAppService _service; + public AIToolDefinitionController(IAIToolDefinitionAppService service) + { + _service = service; + } + + [HttpPost] + [Authorize(AIManagementPermissionNames.AIToolDefinition.Create)] + public virtual Task CreateAsync(AIToolDefinitionRecordCreateDto input) + { + return _service.CreateAsync(input); + } + + [HttpDelete("{id}")] + [Authorize(AIManagementPermissionNames.AIToolDefinition.Delete)] + public virtual Task DeleteAsync(Guid id) + { + return _service.DeleteAsync(id); + } + + [HttpGet("{id}")] + public virtual Task GetAsync(Guid id) + { + return _service.GetAsync(id); + } + + [HttpGet("available-providers")] + public virtual Task> GetAvailableProvidersAsync() + { + return _service.GetAvailableProvidersAsync(); + } + + [HttpGet] + public virtual Task> GetListAsync(AIToolDefinitionRecordGetPagedListInput input) + { + return _service.GetListAsync(input); + } + + [HttpPut("{id}")] + [Authorize(AIManagementPermissionNames.AIToolDefinition.Update)] + public virtual Task UpdateAsync(Guid id, AIToolDefinitionRecordUpdateDto input) + { + return _service.UpdateAsync(id, input); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionController.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionController.cs index 0a61a7bc2..c887d8056 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionController.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionController.cs @@ -50,7 +50,7 @@ public class WorkspaceDefinitionController : AbpControllerBase, IWorkspaceDefini } [HttpGet] - public virtual Task> GetListAsync(WorkspaceDefinitionRecordGetListInput input) + public virtual Task> GetListAsync(WorkspaceDefinitionRecordGetPagedListInput input) { return _service.GetListAsync(input); }