From 56caa3d598d6d639f5fcfabfe78e6fcd72ba16be Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 12 May 2024 19:43:41 +0800 Subject: [PATCH] 【优化】AI:调整 ai starter 的包封装 --- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java | 12 ++++++------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiImageConvert.java | 2 +- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java | 2 +- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java | 2 +- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java | 2 +- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java | 9 ++++++--- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalDallConfigVO.java | 2 +- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java | 12 ++++++------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiImageServiceImpl.java | 24 ++++++++++++------------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/midjourneyHandler/YuDaoMidjourneyMessageHandler.java | 6 +++--- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java | 3 +-- yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml | 2 ++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/AiPlatformEnum.java | 53 ----------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatClient.java | 36 ------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatException.java | 15 --------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatResponse.java | 119 ----------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/Generation.java | 86 -------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/StreamingChatClient.java | 29 ----------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AbstractMessage.java | 151 ------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AssistantMessage.java | 47 ----------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/ChatMessage.java | 39 --------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/FunctionMessage.java | 37 ------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MediaData.java | 46 ---------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/Message.java | 32 -------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MessageType.java | 52 ---------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/SystemMessage.java | 48 ------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/UserMessage.java | 51 --------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatGenerationMetadata.java | 75 --------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatResponseMetadata.java | 58 ---------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyRateLimit.java | 59 ----------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyUsage.java | 37 ------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/PromptMetadata.java | 136 ---------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/RateLimit.java | 84 ------------------------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/Usage.java | 66 ------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/package-info.java | 14 -------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/AssistantPromptTemplate.java | 55 ------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptions.java | 40 ---------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptionsBuilder.java | 89 ----------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatPromptTemplate.java | 87 --------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/FunctionPromptTemplate.java | 27 --------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/Prompt.java | 104 -------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplate.java | 218 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateActions.java | 34 ---------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateChatActions.java | 18 ------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateMessageActions.java | 24 ------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateStringActions.java | 14 -------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/SystemPromptTemplate.java | 55 ------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/TemplateFormat.java | 42 ------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java | 156 ------------------------------------------------------------------------------------------------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatModal.java | 47 ----------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenOptions.java | 122 -------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java | 62 -------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java | 9 --------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java | 8 -------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java | 16 ---------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java | 11 ----------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatClient.java | 135 --------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatModel.java | 53 ----------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoOptions.java | 76 ---------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java | 151 ------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletion.java | 48 ------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionMessage.java | 8 -------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionRequest.java | 107 ----------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatClient.java | 154 ---------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatModel.java | 40 ---------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanOptions.java | 142 ---------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java | 98 -------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanAuthRes.java | 48 ------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletion.java | 91 ------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionMessage.java | 8 -------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionRequest.java | 178 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/exception/YiYanApiException.java | 16 ---------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java | 38 +++++++++++++++++++------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java | 9 ++++----- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java | 10 +++++----- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/exception/AiException.java | 16 ++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/package-info.java | 4 ++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/exception/AiException.java | 15 --------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/Image.java | 77 ----------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageClient.java | 31 ------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGeneration.java | 53 ----------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGenerationMetadata.java | 23 ----------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageMessage.java | 63 --------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptions.java | 37 ------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptionsBuilder.java | 119 ----------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImagePrompt.java | 85 ------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponse.java | 75 --------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponseMetadata.java | 31 ------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java | 89 ----------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageClient.java | 101 ----------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageOptions.java | 95 ----------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageRequest.java | 58 ---------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageResponse.java | 39 --------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageModelEnum.java | 35 ----------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageStyleEnum.java | 36 ------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyConfig.java | 80 -------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java | 173 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java | 83 ----------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java | 149 ----------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/AttachmentsReq.java | 22 ---------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/DescribeReq.java | 24 ------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/ReRollReq.java | 22 ---------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/res/UploadAttachmentsRes.java | 36 ------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java | 49 ------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java | 31 ------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java | 28 ---------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java | 26 -------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java | 14 -------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java | 84 ------------------------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/FailureCallback.java | 6 ------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyMessageHandler.java | 15 --------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java | 224 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/SuccessCallback.java | 7 ------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WebSocketStarter.java | 8 -------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WssNotify.java | 13 ------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java | 281 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java | 111 --------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelClient.java | 40 ---------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptions.java | 31 ------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptionsUtils.java | 387 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelRequest.java | 51 --------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResponse.java | 62 -------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResult.java | 43 ------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResponseMetadata.java | 34 ---------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResultMetadata.java | 31 ------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/StreamingModelClient.java | 43 ------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallSupport.java | 163 ------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallback.java | 159 --------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallback.java | 60 ------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackContext.java | 126 ------------------------------------------------------------------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackWrapper.java | 140 -------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptions.java | 66 ------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptionsBuilder.java | 150 ------------------------------------------------------------------------------------------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/TypeResolverHelper.java | 87 --------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/package-info.java | 11 ----------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/package-info.java | 15 --------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractConversionServiceOutputParser.java | 42 ------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractMessageConverterOutputParser.java | 41 ----------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/BeanOutputParser.java | 171 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/FormatProvider.java | 33 --------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/ListOutputParser.java | 50 -------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/MapOutputParser.java | 60 ------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/OutputParser.java | 30 ------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/Parser.java | 24 ------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/README.md | 12 ------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatClient.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatException.java | 15 +++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatResponse.java | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/Generation.java | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/StreamingChatClient.java | 29 +++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AssistantMessage.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/ChatMessage.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/FunctionMessage.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MediaData.java | 46 ++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/Message.java | 32 ++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MessageType.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/SystemMessage.java | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/UserMessage.java | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatGenerationMetadata.java | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyRateLimit.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyUsage.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/PromptMetadata.java | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/RateLimit.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/Usage.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/package-info.java | 14 ++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/AssistantPromptTemplate.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatPromptTemplate.java | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/FunctionPromptTemplate.java | 27 +++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/Prompt.java | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateActions.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateChatActions.java | 18 ++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateMessageActions.java | 24 ++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateStringActions.java | 14 ++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/TemplateFormat.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/Image.java | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageClient.java | 31 +++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGeneration.java | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGenerationMetadata.java | 23 +++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageMessage.java | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptions.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptionsBuilder.java | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImagePrompt.java | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponse.java | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java | 31 +++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelClient.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptions.java | 31 +++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java | 387 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelRequest.java | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResponse.java | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResult.java | 43 +++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResponseMetadata.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResultMetadata.java | 31 +++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/StreamingModelClient.java | 43 +++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallSupport.java | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallback.java | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallback.java | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackContext.java | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptions.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptionsBuilder.java | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/package-info.java | 11 +++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyConfig.java | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyMessage.java | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractions.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractionsApi.java | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/AttachmentsReq.java | 22 ++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/DescribeReq.java | 24 ++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/ReRollReq.java | 22 ++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/res/UploadAttachmentsRes.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyConstants.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyGennerateStatusEnum.java | 31 +++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyInteractionsEnum.java | 28 ++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyMessageTypeEnum.java | 26 ++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyNotifyCode.java | 14 ++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/util/MidjourneyUtil.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/FailureCallback.java | 6 ++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyMessageHandler.java | 15 +++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyWebSocketStarter.java | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/SuccessCallback.java | 7 +++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WebSocketStarter.java | 8 ++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WssNotify.java | 13 +++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/listener/MidjourneyMessageListener.java | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageApi.java | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageClient.java | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageOptions.java | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageRequest.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageResponse.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageModelEnum.java | 35 +++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageStyleEnum.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatClient.java | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatModal.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenOptions.java | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenApi.java | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletion.java | 9 +++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionMessage.java | 8 ++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionRequest.java | 16 ++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/package-info.java | 11 +++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java | 8 ++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatClient.java | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatModel.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanOptions.java | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanApi.java | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanAuthRes.java | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletion.java | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionMessage.java | 8 ++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionRequest.java | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/exception/YiYanApiException.java | 16 ++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/package-info.java | 15 +++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractConversionServiceOutputParser.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractMessageConverterOutputParser.java | 41 +++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/BeanOutputParser.java | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/FormatProvider.java | 33 +++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/ListOutputParser.java | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/MapOutputParser.java | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/OutputParser.java | 30 ++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/Parser.java | 24 ++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/README.md | 12 ++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java | 19 ++++++++++--------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java | 4 ++-- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java | 17 +++++++++-------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java | 6 +++--- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java | 17 +++++++++-------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java | 11 ++++++----- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java | 2 +- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java | 7 ++++--- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/openAiImage/OpenAiImageClientTests.java | 10 +++++----- 281 files changed, 8788 insertions(+), 8781 deletions(-) delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/AiPlatformEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatException.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatResponse.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/Generation.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/StreamingChatClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AbstractMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AssistantMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/ChatMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/FunctionMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MediaData.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/Message.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MessageType.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/SystemMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/UserMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatGenerationMetadata.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatResponseMetadata.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyRateLimit.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyUsage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/PromptMetadata.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/RateLimit.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/Usage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/package-info.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/AssistantPromptTemplate.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptionsBuilder.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatPromptTemplate.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/FunctionPromptTemplate.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/Prompt.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplate.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateActions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateChatActions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateMessageActions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateStringActions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/SystemPromptTemplate.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/TemplateFormat.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatModal.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatModel.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletion.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionRequest.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatModel.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanAuthRes.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletion.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionRequest.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/exception/YiYanApiException.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/exception/AiException.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/package-info.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/exception/AiException.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/Image.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGeneration.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGenerationMetadata.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptionsBuilder.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImagePrompt.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponse.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponseMetadata.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageRequest.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageResponse.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageModelEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageStyleEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyConfig.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/AttachmentsReq.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/DescribeReq.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/ReRollReq.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/res/UploadAttachmentsRes.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/FailureCallback.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyMessageHandler.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/SuccessCallback.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WebSocketStarter.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WssNotify.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptionsUtils.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelRequest.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResponse.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResult.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResponseMetadata.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResultMetadata.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/StreamingModelClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallSupport.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallback.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallback.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackContext.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackWrapper.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptionsBuilder.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/TypeResolverHelper.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/package-info.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/package-info.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractConversionServiceOutputParser.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractMessageConverterOutputParser.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/BeanOutputParser.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/FormatProvider.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/ListOutputParser.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/MapOutputParser.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/OutputParser.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/Parser.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/README.md create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatException.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatResponse.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/Generation.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/StreamingChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AssistantMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/ChatMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/FunctionMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MediaData.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/Message.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MessageType.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/SystemMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/UserMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatGenerationMetadata.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyRateLimit.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyUsage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/PromptMetadata.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/RateLimit.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/Usage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/package-info.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/AssistantPromptTemplate.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatPromptTemplate.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/FunctionPromptTemplate.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/Prompt.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateActions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateChatActions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateMessageActions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateStringActions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/TemplateFormat.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/Image.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGeneration.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGenerationMetadata.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptionsBuilder.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImagePrompt.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponse.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelRequest.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResponse.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResult.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResponseMetadata.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResultMetadata.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/StreamingModelClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallSupport.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallback.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallback.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackContext.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptionsBuilder.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/package-info.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyConfig.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractionsApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/AttachmentsReq.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/DescribeReq.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/ReRollReq.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/res/UploadAttachmentsRes.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyConstants.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyGennerateStatusEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyInteractionsEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyMessageTypeEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyNotifyCode.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/util/MidjourneyUtil.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/FailureCallback.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyMessageHandler.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyWebSocketStarter.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/SuccessCallback.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WebSocketStarter.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WssNotify.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/listener/MidjourneyMessageListener.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageRequest.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageResponse.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageModelEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageStyleEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatModal.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletion.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionRequest.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/package-info.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatModel.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanAuthRes.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletion.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionRequest.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/exception/YiYanApiException.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/package-info.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractConversionServiceOutputParser.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractMessageConverterOutputParser.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/BeanOutputParser.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/FormatProvider.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/ListOutputParser.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/MapOutputParser.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/OutputParser.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/Parser.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/README.md diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java index 6ee9489..e15eff5 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java @@ -1,11 +1,11 @@ package cn.iocoder.yudao.module.ai.config; -import cn.iocoder.yudao.framework.ai.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.chat.ChatClient; -import cn.iocoder.yudao.framework.ai.chat.StreamingChatClient; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatClient; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatClient; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; +import org.springframework.ai.chat.ChatClient; +import org.springframework.ai.chat.StreamingChatClient; +import org.springframework.ai.models.tongyi.QianWenChatClient; +import org.springframework.ai.models.xinghuo.XingHuoChatClient; +import org.springframework.ai.models.yiyan.YiYanChatClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiImageConvert.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiImageConvert.java index 27bf111..b0f8abc 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiImageConvert.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiImageConvert.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.ai.convert; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; +import org.springframework.ai.models.midjourney.MidjourneyMessage; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingReqVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingRespVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListRespVO; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java index 7e57f7e..831b762 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.chat; -import cn.iocoder.yudao.framework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.messages.MessageType; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java index 306c1e0..e251d55 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.model; -import cn.iocoder.yudao.framework.ai.AiPlatformEnum; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java index 8c86127..7234280 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.model; -import cn.iocoder.yudao.framework.ai.AiPlatformEnum; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java index 1b6e957..ed5ecf2 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java @@ -1,7 +1,10 @@ package cn.iocoder.yudao.module.ai.dal.vo; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import lombok.Data; import lombok.experimental.Accessors; +import org.springframework.ai.models.xinghuo.XingHuoChatModel; +import org.springframework.ai.models.yiyan.YiYanChatModel; /** * modal config @@ -16,13 +19,13 @@ public class AiChatModalConfigVO { /** * 模型平台 (冗余,方便类型转换) - * 参考:{@link cn.iocoder.yudao.framework.ai.AiPlatformEnum} + * 参考:{@link AiPlatformEnum} */ private String platform; /** * 模型类型(冗余,方便类型转换) - * {@link cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatModel} - * {@link cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatModel} + * {@link YiYanChatModel} + * {@link XingHuoChatModel} */ private String type; } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalDallConfigVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalDallConfigVO.java index c7dce40..d77b4ec 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalDallConfigVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalDallConfigVO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.ai.dal.vo; -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageStyleEnum; +import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum; import lombok.Data; import lombok.experimental.Accessors; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java index a7dcd12..36316dc 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.ai.service.impl; import cn.hutool.core.exceptions.ExceptionUtil; -import cn.iocoder.yudao.framework.ai.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.chat.ChatClient; -import cn.iocoder.yudao.framework.ai.chat.ChatResponse; -import cn.iocoder.yudao.framework.ai.chat.StreamingChatClient; -import cn.iocoder.yudao.framework.ai.chat.messages.MessageType; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; +import org.springframework.ai.chat.ChatClient; +import org.springframework.ai.chat.ChatResponse; +import org.springframework.ai.chat.StreamingChatClient; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.prompt.Prompt; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.module.ai.ErrorCodeConstants; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiImageServiceImpl.java index d306b4b..4727127 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiImageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiImageServiceImpl.java @@ -2,18 +2,18 @@ package cn.iocoder.yudao.module.ai.service.impl; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.exception.AiException; -import cn.iocoder.yudao.framework.ai.image.ImageGeneration; -import cn.iocoder.yudao.framework.ai.image.ImagePrompt; -import cn.iocoder.yudao.framework.ai.image.ImageResponse; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageClient; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions; -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageModelEnum; -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageStyleEnum; -import cn.iocoder.yudao.framework.ai.midjourney.api.MidjourneyInteractionsApi; -import cn.iocoder.yudao.framework.ai.midjourney.api.req.ReRollReq; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyWebSocketStarter; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.WssNotify; +import cn.iocoder.yudao.framework.ai.core.exception.AiException; +import org.springframework.ai.image.ImageGeneration; +import org.springframework.ai.image.ImagePrompt; +import org.springframework.ai.image.ImageResponse; +import org.springframework.ai.models.openai.OpenAiImageClient; +import org.springframework.ai.models.openai.OpenAiImageOptions; +import org.springframework.ai.models.openai.enums.OpenAiImageModelEnum; +import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum; +import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi; +import org.springframework.ai.models.midjourney.api.req.ReRollReq; +import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter; +import org.springframework.ai.models.midjourney.webSocket.WssNotify; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/midjourneyHandler/YuDaoMidjourneyMessageHandler.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/midjourneyHandler/YuDaoMidjourneyMessageHandler.java index 0260a15..d1ccc08 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/midjourneyHandler/YuDaoMidjourneyMessageHandler.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/midjourneyHandler/YuDaoMidjourneyMessageHandler.java @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.ai.service.midjourneyHandler; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyGennerateStatusEnum; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyMessageHandler; +import org.springframework.ai.models.midjourney.MidjourneyMessage; +import org.springframework.ai.models.midjourney.constants.MidjourneyGennerateStatusEnum; +import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyOperationsVO; import cn.iocoder.yudao.module.ai.convert.AiImageConvert; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java index ed6e16c..c185ffe 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.ai.service.model; -import cn.iocoder.yudao.framework.ai.AiPlatformEnum; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -14,7 +14,6 @@ import org.springframework.validation.annotation.Validated; import java.util.List; -import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index 3a52489..2195b4b 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -10,6 +10,7 @@ yudao-spring-boot-starter-ai + 21 @@ -17,6 +18,7 @@ UTF-8 + org.springframework spring-core diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/AiPlatformEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/AiPlatformEnum.java deleted file mode 100644 index 3882e1a..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/AiPlatformEnum.java +++ /dev/null @@ -1,53 +0,0 @@ -package cn.iocoder.yudao.framework.ai; - -import com.google.common.collect.Lists; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.List; - -// TODO 芋艿:这块,看看要不要调整下; -/** - * ai 模型平台 - * - * author: fansili - * time: 2024/3/11 10:12 - */ -@Getter -@AllArgsConstructor -public enum AiPlatformEnum { - - YI_YAN("yiyan", "一言"), - QIAN_WEN("qianwen", "千问"), - XING_HUO("xinghuo", "星火"), - OPENAI("OpenAI", "OpenAI"), - OPEN_AI_DALL("dall", "dall"), - MIDJOURNEY("midjourney", "midjourney"), - - ; - - private String platform; - private String name; - - public static List CHAT_PLATFORM_LIST = Lists.newArrayList( - AiPlatformEnum.YI_YAN, - AiPlatformEnum.QIAN_WEN, - AiPlatformEnum.XING_HUO, - AiPlatformEnum.OPENAI - ); - - public static List IMAGE_PLATFORM_LIST = Lists.newArrayList( - AiPlatformEnum.OPEN_AI_DALL, - AiPlatformEnum.MIDJOURNEY - ); - - public static AiPlatformEnum validatePlatform(String platform) { - for (AiPlatformEnum platformEnum : AiPlatformEnum.values()) { - if (platformEnum.getPlatform().equals(platform)) { - return platformEnum; - } - } - throw new IllegalArgumentException("非法平台: " + platform); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatClient.java deleted file mode 100644 index 6fd8e89..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatClient.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat; - - -import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.model.ModelClient; - -@FunctionalInterface -public interface ChatClient extends ModelClient { - - default String call(String message) { - Prompt prompt = new Prompt(new UserMessage(message)); - Generation generation = call(prompt).getResult(); - return (generation != null) ? generation.getOutput().getContent() : ""; - } - - @Override - ChatResponse call(Prompt prompt); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatException.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatException.java deleted file mode 100644 index 2b96601..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatException.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chat; - -/** - * 聊天异常 - * - * author: fansili - * time: 2024/3/15 20:45 - */ -public class ChatException extends RuntimeException { - - public ChatException(String message) { - super(message); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatResponse.java deleted file mode 100644 index a6de72d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatResponse.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cn.iocoder.yudao.framework.ai.chat; - -import cn.iocoder.yudao.framework.ai.chat.metadata.ChatResponseMetadata; -import cn.iocoder.yudao.framework.ai.model.ModelResponse; -import org.springframework.util.CollectionUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * 人工智能提供商返回的聊天完成(例如生成)响应。 - * - * The chat completion (e.g. generation) response returned by an AI provider. - */ -public class ChatResponse implements ModelResponse { - - private final ChatResponseMetadata chatResponseMetadata; - - /** - * List of generated messages returned by the AI provider. - */ - private final List generations; - - /** - * Construct a new {@link ChatResponse} instance without metadata. - * @param generations the {@link List} of {@link Generation} returned by the AI - * provider. - */ - public ChatResponse(List generations) { - this(generations, ChatResponseMetadata.NULL); - } - - /** - * Construct a new {@link ChatResponse} instance. - * @param generations the {@link List} of {@link Generation} returned by the AI - * provider. - * @param chatResponseMetadata {@link ChatResponseMetadata} containing information - * about the use of the AI provider's API. - */ - public ChatResponse(List generations, ChatResponseMetadata chatResponseMetadata) { - this.chatResponseMetadata = chatResponseMetadata; -// this.generations = List.copyOf(generations); - this.generations = Collections.unmodifiableList(generations); - } - - /** - * The {@link List} of {@link Generation generated outputs}. - *

- * It is a {@link List} of {@link List lists} because the Prompt could request - * multiple output {@link Generation generations}. - * @return the {@link List} of {@link Generation generated outputs}. - */ - - @Override - public List getResults() { - return this.generations; - } - - /** - * @return Returns the first {@link Generation} in the generations list. - */ - public Generation getResult() { - if (CollectionUtils.isEmpty(this.generations)) { - return null; - } - return this.generations.get(0); - } - - /** - * @return Returns {@link ChatResponseMetadata} containing information about the use - * of the AI provider's API. - */ - @Override - public ChatResponseMetadata getMetadata() { - return this.chatResponseMetadata; - } - - @Override - public String toString() { - return "ChatResponse [metadata=" + chatResponseMetadata + ", generations=" + generations + "]"; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; -// if (!(o instanceof ChatResponse that)) -// return false; - if (!(o instanceof ChatResponse)) { - return false; - } - ChatResponse that = (ChatResponse) o; - return Objects.equals(chatResponseMetadata, that.chatResponseMetadata) - && Objects.equals(generations, that.generations); - } - - @Override - public int hashCode() { - return Objects.hash(chatResponseMetadata, generations); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/Generation.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/Generation.java deleted file mode 100644 index 1835d16..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/Generation.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat; - -import cn.iocoder.yudao.framework.ai.chat.messages.AssistantMessage; -import cn.iocoder.yudao.framework.ai.chat.metadata.ChatGenerationMetadata; -import cn.iocoder.yudao.framework.ai.model.ModelResult; -import org.springframework.lang.Nullable; - -import java.util.Map; -import java.util.Objects; - -/** - * 表示AI返回的响应。 - * - * Represents a response returned by the AI. - */ -public class Generation implements ModelResult { - - private AssistantMessage assistantMessage; - - private ChatGenerationMetadata chatGenerationMetadata; - - public Generation(String text) { - this.assistantMessage = new AssistantMessage(text); - } - - public Generation(String text, Map properties) { - this.assistantMessage = new AssistantMessage(text, properties); - } - - @Override - public AssistantMessage getOutput() { - return this.assistantMessage; - } - - @Override - public ChatGenerationMetadata getMetadata() { - ChatGenerationMetadata chatGenerationMetadata = this.chatGenerationMetadata; - return chatGenerationMetadata != null ? chatGenerationMetadata : ChatGenerationMetadata.NULL; - } - - public Generation withGenerationMetadata(@Nullable ChatGenerationMetadata chatGenerationMetadata) { - this.chatGenerationMetadata = chatGenerationMetadata; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - - if (!(o instanceof Generation)) { - return false; - } - Generation that = (Generation) o; - return Objects.equals(assistantMessage, that.assistantMessage) - && Objects.equals(chatGenerationMetadata, that.chatGenerationMetadata); - } - - @Override - public int hashCode() { - return Objects.hash(assistantMessage, chatGenerationMetadata); - } - - @Override - public String toString() { - return "Generation{" + "assistantMessage=" + assistantMessage + ", chatGenerationMetadata=" - + chatGenerationMetadata + '}'; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/StreamingChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/StreamingChatClient.java deleted file mode 100644 index fdd604f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/StreamingChatClient.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat; - -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.model.StreamingModelClient; -import reactor.core.publisher.Flux; - -@FunctionalInterface -public interface StreamingChatClient extends StreamingModelClient { - - @Override - Flux stream(Prompt prompt); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AbstractMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AbstractMessage.java deleted file mode 100644 index 8c8e60d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AbstractMessage.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import org.springframework.core.io.Resource; -import org.springframework.util.Assert; -import org.springframework.util.StreamUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public abstract class AbstractMessage implements Message { - - protected final MessageType messageType; - - protected final String textContent; - - protected final List mediaData; - - /** - * Additional options for the message to influence the response, not a generative map. - */ - protected final Map properties; - - protected AbstractMessage(MessageType messageType, String content) { - this(messageType, content, Map.of()); - } - - protected AbstractMessage(MessageType messageType, String content, Map messageProperties) { - Assert.notNull(messageType, "Message type must not be null"); - // Assert.notNull(content, "Content must not be null"); - this.messageType = messageType; - this.textContent = content; - this.mediaData = new ArrayList<>(); - this.properties = messageProperties; - } - - protected AbstractMessage(MessageType messageType, String textContent, List mediaData) { - this(messageType, textContent, mediaData, Map.of()); - } - - protected AbstractMessage(MessageType messageType, String textContent, List mediaData, - Map messageProperties) { - - Assert.notNull(messageType, "Message type must not be null"); - Assert.notNull(textContent, "Content must not be null"); - Assert.notNull(mediaData, "media data must not be null"); - - this.messageType = messageType; - this.textContent = textContent; - this.mediaData = new ArrayList<>(mediaData); - this.properties = messageProperties; - } - - protected AbstractMessage(MessageType messageType, Resource resource) { - this(messageType, resource, Collections.emptyMap()); - } - - @SuppressWarnings("null") - protected AbstractMessage(MessageType messageType, Resource resource, Map messageProperties) { - Assert.notNull(messageType, "Message type must not be null"); - Assert.notNull(resource, "Resource must not be null"); - - this.messageType = messageType; - this.properties = messageProperties; - this.mediaData = new ArrayList<>(); - - try (InputStream inputStream = resource.getInputStream()) { - this.textContent = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); - } - catch (IOException ex) { - throw new RuntimeException("Failed to read resource", ex); - } - } - - @Override - public String getContent() { - return this.textContent; - } - - @Override - public List getMediaData() { - return this.mediaData; - } - - @Override - public Map getProperties() { - return this.properties; - } - - @Override - public MessageType getMessageType() { - return this.messageType; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((mediaData == null) ? 0 : mediaData.hashCode()); - result = prime * result + ((properties == null) ? 0 : properties.hashCode()); - result = prime * result + ((messageType == null) ? 0 : messageType.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AbstractMessage other = (AbstractMessage) obj; - if (mediaData == null) { - if (other.mediaData != null) - return false; - } - else if (!mediaData.equals(other.mediaData)) - return false; - if (properties == null) { - if (other.properties != null) - return false; - } - else if (!properties.equals(other.properties)) - return false; - if (messageType != other.messageType) - return false; - return true; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AssistantMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AssistantMessage.java deleted file mode 100644 index 6a18eeb..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AssistantMessage.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import java.util.Map; - -/** - * 让生成人员知道内容是作为对用户的响应生成的。 - * 此角色指示生成者先前在会话中生成的消息。 - * 通过包括该系列中的辅助消息,您可以为生成的关于提供上下文之前在谈话中的交流。 - * - * Lets the generative know the content was generated as a response to the user. This role - * indicates messages that the generative has previously generated in the conversation. By - * including assistant messages in the series, you provide context to the generative about - * prior exchanges in the conversation. - */ -public class AssistantMessage extends AbstractMessage { - - public AssistantMessage(String content) { - super(MessageType.ASSISTANT, content); - } - - public AssistantMessage(String content, Map properties) { - super(MessageType.ASSISTANT, content, properties); - } - - @Override - public String toString() { - return "AssistantMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" - + messageType + '}'; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/ChatMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/ChatMessage.java deleted file mode 100644 index 1647013..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/ChatMessage.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import java.util.Map; - -public class ChatMessage extends AbstractMessage { - - public ChatMessage(String role, String content) { - super(MessageType.valueOf(role), content); - } - - public ChatMessage(String role, String content, Map properties) { - super(MessageType.valueOf(role), content, properties); - } - - public ChatMessage(MessageType messageType, String content) { - super(messageType, content); - } - - public ChatMessage(MessageType messageType, String content, Map properties) { - super(messageType, content, properties); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/FunctionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/FunctionMessage.java deleted file mode 100644 index d8b6fcc..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/FunctionMessage.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import java.util.Map; - -public class FunctionMessage extends AbstractMessage { - - public FunctionMessage(String content) { - super(MessageType.SYSTEM, content); - } - - public FunctionMessage(String content, Map properties) { - super(MessageType.SYSTEM, content, properties); - } - - @Override - public String toString() { - return "FunctionMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" - + messageType + '}'; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MediaData.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MediaData.java deleted file mode 100644 index fed6022..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MediaData.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import org.springframework.util.Assert; -import org.springframework.util.MimeType; - -/** - * @author Christian Tzolov - */ -public class MediaData { - - private final MimeType mimeType; - - private final Object data; - - public MediaData(MimeType mimeType, Object data) { - Assert.notNull(mimeType, "MimeType must not be null"); - // Assert.notNull(data, "Data must not be null"); - this.mimeType = mimeType; - this.data = data; - } - - public MimeType getMimeType() { - return this.mimeType; - } - - public Object getData() { - return this.data; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/Message.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/Message.java deleted file mode 100644 index 89c7b4f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/Message.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import java.util.List; -import java.util.Map; - -public interface Message { - - String getContent(); - - List getMediaData(); - - Map getProperties(); - - MessageType getMessageType(); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MessageType.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MessageType.java deleted file mode 100644 index 2c803a0..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/MessageType.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cn.iocoder.yudao.framework.ai.chat.messages; - -public enum MessageType { - - // 用户消息 - USER("user"), - - // 之前会话的消息 - ASSISTANT("assistant"), - - // 根据注释说明:您可以使用系统消息来指示具有生成性,表现得像某个角色或以特定的方式提供答案总体安排 - // 简单理解:在对话前,发送一条具有角色的信息让模型理解(如:你现在是一个10年拍摄经验的导演,拥有丰富的经验。 这样你就可以去问他,怎么拍一个短视频可以在抖音上火) - SYSTEM("system"), - - // 函数?根据引用现在不支持,会抛出一个异常 ---> throw new IllegalArgumentException("Tool execution results are not supported for Bedrock models"); - FUNCTION("function"); - - private final String value; - - MessageType(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public static MessageType fromValue(String value) { - for (MessageType messageType : MessageType.values()) { - if (messageType.getValue().equals(value)) { - return messageType; - } - } - throw new IllegalArgumentException("Invalid MessageType value: " + value); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/SystemMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/SystemMessage.java deleted file mode 100644 index c474f24..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/SystemMessage.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import org.springframework.core.io.Resource; - -/** - * 作为输入传递的“system”类型的消息。系统消息给出高级别对话说明。 - * 此角色通常提供高级说明对话。 - * 例如,您可以使用系统消息来指示具有生成性,表现得像某个角色或以特定的方式提供答案总体安排 - * - * A message of the type 'system' passed as input. The system message gives high level - * instructions for the conversation. This role typically provides high-level instructions - * for the conversation. For example, you might use a system message to instruct the - * generative to behave like a certain character or to provide answers in a specific - * format. - */ -public class SystemMessage extends AbstractMessage { - - public SystemMessage(String content) { - super(MessageType.SYSTEM, content); - } - - public SystemMessage(Resource resource) { - super(MessageType.SYSTEM, resource); - } - - @Override - public String toString() { - return "SystemMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" - + messageType + '}'; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/UserMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/UserMessage.java deleted file mode 100644 index 2b8f547..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/UserMessage.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.messages; - -import org.springframework.core.io.Resource; - -import java.util.List; - -/** - * 作为输入传递的“user”类型的消息具有用户角色的消息来自最终用户或开发者。 - * 它们表示问题、提示或您想要的任何输入产生反应的。 - * - * A message of the type 'user' passed as input Messages with the user role are from the - * end-user or developer. They represent questions, prompts, or any input that you want - * the generative to respond to. - */ -public class UserMessage extends AbstractMessage { - - public UserMessage(String message) { - super(MessageType.USER, message); - } - - public UserMessage(Resource resource) { - super(MessageType.USER, resource); - } - - public UserMessage(String textContent, List mediaDataList) { - super(MessageType.USER, textContent, mediaDataList); - } - - @Override - public String toString() { - return "UserMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" - + messageType + '}'; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatGenerationMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatGenerationMetadata.java deleted file mode 100644 index a90dda5..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatGenerationMetadata.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.metadata; - -import cn.iocoder.yudao.framework.ai.model.ResultMetadata; -import org.springframework.lang.Nullable; - -/** - * Abstract Data Type (ADT) encapsulating information on the completion choices in the AI - * response. - * - * @author John Blum - * @since 0.7.0 - */ -public interface ChatGenerationMetadata extends ResultMetadata { - - ChatGenerationMetadata NULL = ChatGenerationMetadata.from(null, null); - - /** - * Factory method used to construct a new {@link ChatGenerationMetadata} from the - * given {@link String finish reason} and content filter metadata. - * @param finishReason {@link String} contain the reason for the choice completion. - * @param contentFilterMetadata underlying AI provider metadata for filtering applied - * to generation content. - * @return a new {@link ChatGenerationMetadata} from the given {@link String finish - * reason} and content filter metadata. - */ - static ChatGenerationMetadata from(String finishReason, Object contentFilterMetadata) { - return new ChatGenerationMetadata() { - - @Override - @SuppressWarnings("unchecked") - public T getContentFilterMetadata() { - return (T) contentFilterMetadata; - } - - @Override - public String getFinishReason() { - return finishReason; - } - }; - } - - /** - * Returns the underlying AI provider metadata for filtering applied to generation - * content. - * @param {@link Class Type} used to cast the filtered content metadata into the - * AI provider-specific type. - * @return the underlying AI provider metadata for filtering applied to generation - * content. - */ - @Nullable - T getContentFilterMetadata(); - - /** - * Get the {@link String reason} this choice completed for the generation. - * @return the {@link String reason} this choice completed for the generation. - */ - String getFinishReason(); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatResponseMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatResponseMetadata.java deleted file mode 100644 index ebcb4ab..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/ChatResponseMetadata.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.metadata; - - -import cn.iocoder.yudao.framework.ai.model.ResponseMetadata; - -/** - * Abstract Data Type (ADT) modeling common AI provider metadata returned in an AI - * response. - * - * 抽象数据类型(ADT)建模AI响应中返回的常见AI提供者元数据。 - * - * @author John Blum - * @since 0.7.0 - */ -public interface ChatResponseMetadata extends ResponseMetadata { - - ChatResponseMetadata NULL = new ChatResponseMetadata() { - }; - - /** - * Returns AI provider specific metadata on rate limits. - * @return AI provider specific metadata on rate limits. - * @see RateLimit - */ - default RateLimit getRateLimit() { - return new EmptyRateLimit(); - } - - /** - * Returns AI provider specific metadata on API usage. - * @return AI provider specific metadata on API usage. - * @see Usage - */ - default Usage getUsage() { - return new EmptyUsage(); - } - - default PromptMetadata getPromptMetadata() { - return PromptMetadata.empty(); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyRateLimit.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyRateLimit.java deleted file mode 100644 index 62aeeb5..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyRateLimit.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.metadata; - -import java.time.Duration; - -/** - * A RateLimit implementation that returns zero for all property getters - * - * @author John Blum - * @since 0.7.0 - */ -public class EmptyRateLimit implements RateLimit { - - @Override - public Long getRequestsLimit() { - return 0L; - } - - @Override - public Long getRequestsRemaining() { - return 0L; - } - - @Override - public Duration getRequestsReset() { - return Duration.ZERO; - } - - @Override - public Long getTokensLimit() { - return 0L; - } - - @Override - public Long getTokensRemaining() { - return 0L; - } - - @Override - public Duration getTokensReset() { - return Duration.ZERO; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyUsage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyUsage.java deleted file mode 100644 index 11f0255..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/EmptyUsage.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.metadata; - -/** - * A EmpytUsage implementation that returns zero for all property getters - * - * @author John Blum - * @since 0.7.0 - */ -public class EmptyUsage implements Usage { - - @Override - public Long getPromptTokens() { - return 0L; - } - - @Override - public Long getGenerationTokens() { - return 0L; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/PromptMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/PromptMetadata.java deleted file mode 100644 index 94bad3a..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/PromptMetadata.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cn.iocoder.yudao.framework.ai.chat.metadata; - -import org.springframework.util.Assert; - -import java.util.Arrays; -import java.util.Optional; -import java.util.stream.StreamSupport; - -/** - * Abstract Data Type (ADT) modeling metadata gathered by the AI during request - * processing. - * - * @author John Blum - * @since 0.7.0 - */ -@FunctionalInterface -public interface PromptMetadata extends Iterable { - - /** - * Factory method used to create empty {@link PromptMetadata} when the information is - * not supplied by the AI provider. - * @return empty {@link PromptMetadata}. - */ - static PromptMetadata empty() { - return of(); - } - - /** - * Factory method used to create a new {@link PromptMetadata} composed of an array of - * {@link PromptFilterMetadata}. - * @param array array of {@link PromptFilterMetadata} used to compose the - * {@link PromptMetadata}. - * @return a new {@link PromptMetadata} composed of an array of - * {@link PromptFilterMetadata}. - */ - static PromptMetadata of(PromptFilterMetadata... array) { - return of(Arrays.asList(array)); - } - - /** - * Factory method used to create a new {@link PromptMetadata} composed of an - * {@link Iterable} of {@link PromptFilterMetadata}. - * @param iterable {@link Iterable} of {@link PromptFilterMetadata} used to compose - * the {@link PromptMetadata}. - * @return a new {@link PromptMetadata} composed of an {@link Iterable} of - * {@link PromptFilterMetadata}. - */ - static PromptMetadata of(Iterable iterable) { - Assert.notNull(iterable, "An Iterable of PromptFilterMetadata must not be null"); - return iterable::iterator; - } - - /** - * Returns an {@link Optional} {@link PromptFilterMetadata} at the given index. - * @param promptIndex index of the {@link PromptFilterMetadata} contained in this - * {@link PromptMetadata}. - * @return {@link Optional} {@link PromptFilterMetadata} at the given index. - * @throws IllegalArgumentException if the prompt index is less than 0. - */ - default Optional findByPromptIndex(int promptIndex) { - - Assert.isTrue(promptIndex > -1, "Prompt index [%d] must be greater than equal to 0".formatted(promptIndex)); - - return StreamSupport.stream(this.spliterator(), false) - .filter(promptFilterMetadata -> promptFilterMetadata.getPromptIndex() == promptIndex) - .findFirst(); - } - - /** - * Abstract Data Type (ADT) modeling filter metadata for all prompts sent during an AI - * request. - */ - interface PromptFilterMetadata { - - /** - * Factory method used to construct a new {@link PromptFilterMetadata} with the - * given prompt index and content filter metadata. - * @param promptIndex index of the prompt filter metadata contained in the AI - * response. - * @param contentFilterMetadata underlying AI provider metadata for filtering - * applied to prompt content. - * @return a new instance of {@link PromptFilterMetadata} with the given prompt - * index and content filter metadata. - */ - static PromptFilterMetadata from(int promptIndex, Object contentFilterMetadata) { - - return new PromptFilterMetadata() { - - @Override - public int getPromptIndex() { - return promptIndex; - } - - @Override - @SuppressWarnings("unchecked") - public T getContentFilterMetadata() { - return (T) contentFilterMetadata; - } - }; - } - - /** - * Index of the prompt filter metadata contained in the AI response. - * @return an {@link Integer index} fo the prompt filter metadata contained in the - * AI response. - */ - int getPromptIndex(); - - /** - * Returns the underlying AI provider metadata for filtering applied to prompt - * content. - * @param {@link Class Type} used to cast the filtered content metadata into - * the AI provider-specific type. - * @return the underlying AI provider metadata for filtering applied to prompt - * content. - */ - T getContentFilterMetadata(); - - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/RateLimit.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/RateLimit.java deleted file mode 100644 index 6842783..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/RateLimit.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.metadata; - -import java.time.Duration; - -/** - * Abstract Data Type (ADT) encapsulating metadata from an AI provider's API rate limits - * granted to the API key in use and the API key's current balance. - * - * @author John Blum - * @since 0.7.0 - */ -public interface RateLimit { - - /** - * Returns the maximum number of requests that are permitted before exhausting the - * rate limit. - * @return an {@link Long} with the maximum number of requests that are permitted - * before exhausting the rate limit. - * @see #getRequestsRemaining() - */ - Long getRequestsLimit(); - - /** - * Returns the remaining number of requests that are permitted before exhausting the - * {@link #getRequestsLimit() rate limit}. - * @return an {@link Long} with the remaining number of requests that are permitted - * before exhausting the {@link #getRequestsLimit() rate limit}. - * @see #getRequestsLimit() - */ - Long getRequestsRemaining(); - - /** - * Returns the {@link Duration time} until the rate limit (based on requests) resets - * to its {@link #getRequestsLimit() initial state}. - * @return a {@link Duration} representing the time until the rate limit (based on - * requests) resets to its {@link #getRequestsLimit() initial state}. - * @see #getRequestsLimit() - */ - Duration getRequestsReset(); - - /** - * Returns the maximum number of tokens that are permitted before exhausting the rate - * limit. - * @return an {@link Long} with the maximum number of tokens that are permitted before - * exhausting the rate limit. - * @see #getTokensRemaining() - */ - Long getTokensLimit(); - - /** - * Returns the remaining number of tokens that are permitted before exhausting the - * {@link #getTokensLimit() rate limit}. - * @return an {@link Long} with the remaining number of tokens that are permitted - * before exhausting the {@link #getTokensLimit() rate limit}. - * @see #getTokensLimit() - */ - Long getTokensRemaining(); - - /** - * Returns the {@link Duration time} until the rate limit (based on tokens) resets to - * its {@link #getTokensLimit() initial state}. - * @return a {@link Duration} with the time until the rate limit (based on tokens) - * resets to its {@link #getTokensLimit() initial state}. - * @see #getTokensLimit() - */ - Duration getTokensReset(); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/Usage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/Usage.java deleted file mode 100644 index cecbc82..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/metadata/Usage.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.metadata; - -/** - * 抽象数据类型(ADT)封装关于人工智能提供商API使用的元数据根据AI请求。 - * - * Abstract Data Type (ADT) encapsulating metadata on the usage of an AI provider's API - * per AI request. - * - * @author John Blum - * @since 0.7.0 - */ -public interface Usage { - - /** - * 返回AI请求的{@literal prompt}中使用的令牌数。 - * @返回一个{@link Long},其中包含在的{@literal提示符}中使用的令牌数AI请求。 - * - * Returns the number of tokens used in the {@literal prompt} of the AI request. - * @return an {@link Long} with the number of tokens used in the {@literal prompt} of - * the AI request. - * @see #getGenerationTokens() - */ - Long getPromptTokens(); - - /** - * Returns the number of tokens returned in the {@literal generation (aka completion)} - * of the AI's response. - * @return an {@link Long} with the number of tokens returned in the - * {@literal generation (aka completion)} of the AI's response. - * @see #getPromptTokens() - */ - Long getGenerationTokens(); - - /** - * Return the total number of tokens from both the {@literal prompt} of an AI request - * and {@literal generation} of the AI's response. - * @return the total number of tokens from both the {@literal prompt} of an AI request - * and {@literal generation} of the AI's response. - * @see #getPromptTokens() - * @see #getGenerationTokens() - */ - default Long getTotalTokens() { - Long promptTokens = getPromptTokens(); - promptTokens = promptTokens != null ? promptTokens : 0; - Long completionTokens = getGenerationTokens(); - completionTokens = completionTokens != null ? completionTokens : 0; - return promptTokens + completionTokens; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/package-info.java deleted file mode 100644 index 17c341f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/package-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * The org.sf.ai.chat package represents the bounded context for the Chat Model within the - * AI generative model domain. This package extends the core domain defined in - * org.sf.ai.generative, providing implementations specific to chat-based generative AI - * interactions. - * - * In line with Domain-Driven Design principles, this package includes implementations of - * entities and value objects specific to the chat context, such as ChatPrompt and - * ChatResponse, adhering to the ubiquitous language of chat interactions in AI models. - * - * This bounded context is designed to encapsulate all aspects of chat-based AI - * functionalities, maintaining a clear boundary from other contexts within the AI domain. - */ -package cn.iocoder.yudao.framework.ai.chat; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/AssistantPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/AssistantPromptTemplate.java deleted file mode 100644 index cfe9152..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/AssistantPromptTemplate.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.chat.messages.AssistantMessage; -import cn.iocoder.yudao.framework.ai.chat.messages.Message; -import org.springframework.core.io.Resource; - -import java.util.Map; - -public class AssistantPromptTemplate extends PromptTemplate { - - public AssistantPromptTemplate(String template) { - super(template); - } - - public AssistantPromptTemplate(Resource resource) { - super(resource); - } - - @Override - public Prompt create() { - return new Prompt(new AssistantMessage(render())); - } - - @Override - public Prompt create(Map model) { - return new Prompt(new AssistantMessage(render(model))); - } - - @Override - public Message createMessage() { - return new AssistantMessage(render()); - } - - @Override - public Message createMessage(Map model) { - return new AssistantMessage(render(model)); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptions.java deleted file mode 100644 index 979ca6b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptions.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.model.ModelOptions; - -/** - * 聊天选项代表了常见的选项,可在不同的聊天模式中移植。 - * - * The ChatOptions represent the common options, portable across different chat models. - */ -public interface ChatOptions extends ModelOptions { - - Float getTemperature(); - - void setTemperature(Float temperature); - - Float getTopP(); - - void setTopP(Float topP); - - Integer getTopK(); - - void setTopK(Integer topK); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptionsBuilder.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptionsBuilder.java deleted file mode 100644 index c3e236e..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatOptionsBuilder.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -public class ChatOptionsBuilder { - - private class ChatOptionsImpl implements ChatOptions { - - private Float temperature; - - private Float topP; - - private Integer topK; - - @Override - public Float getTemperature() { - return temperature; - } - - @Override - public void setTemperature(Float temperature) { - this.temperature = temperature; - } - - @Override - public Float getTopP() { - return topP; - } - - @Override - public void setTopP(Float topP) { - this.topP = topP; - } - - @Override - public Integer getTopK() { - return topK; - } - - @Override - public void setTopK(Integer topK) { - this.topK = topK; - } - - } - - private final ChatOptionsImpl options = new ChatOptionsImpl(); - - private ChatOptionsBuilder() { - } - - public static ChatOptionsBuilder builder() { - return new ChatOptionsBuilder(); - } - - public ChatOptionsBuilder withTemperature(Float temperature) { - options.setTemperature(temperature); - return this; - } - - public ChatOptionsBuilder withTopP(Float topP) { - options.setTopP(topP); - return this; - } - - public ChatOptionsBuilder withTopK(Integer topK) { - options.setTopK(topK); - return this; - } - - public ChatOptions build() { - return options; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatPromptTemplate.java deleted file mode 100644 index a06c981..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/ChatPromptTemplate.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.chat.messages.Message; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * PromptTemplate,用于将角色指定为字符串实现及其角色不足以满足您的需求。 - * - * A PromptTemplate that lets you specify the role as a string should the current - * implementations and their roles not suffice for your needs. - */ -public class ChatPromptTemplate implements PromptTemplateActions, PromptTemplateChatActions { - - private final List promptTemplates; - - public ChatPromptTemplate(List promptTemplates) { - this.promptTemplates = promptTemplates; - } - - @Override - public String render() { - StringBuilder sb = new StringBuilder(); - for (PromptTemplate promptTemplate : promptTemplates) { - sb.append(promptTemplate.render()); - } - return sb.toString(); - } - - @Override - public String render(Map model) { - StringBuilder sb = new StringBuilder(); - for (PromptTemplate promptTemplate : promptTemplates) { - sb.append(promptTemplate.render(model)); - } - return sb.toString(); - } - - @Override - public List createMessages() { - List messages = new ArrayList<>(); - for (PromptTemplate promptTemplate : promptTemplates) { - messages.add(promptTemplate.createMessage()); - } - return messages; - } - - @Override - public List createMessages(Map model) { - List messages = new ArrayList<>(); - for (PromptTemplate promptTemplate : promptTemplates) { - messages.add(promptTemplate.createMessage(model)); - } - return messages; - } - - @Override - public Prompt create() { - List messages = createMessages(); - return new Prompt(messages); - } - - @Override - public Prompt create(Map model) { - List messages = createMessages(model); - return new Prompt(messages); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/FunctionPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/FunctionPromptTemplate.java deleted file mode 100644 index e94545c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/FunctionPromptTemplate.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -public class FunctionPromptTemplate extends PromptTemplate { - - private String name; - - public FunctionPromptTemplate(String template) { - super(template); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/Prompt.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/Prompt.java deleted file mode 100644 index 95b5cd8..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/Prompt.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.chat.ChatResponse; -import cn.iocoder.yudao.framework.ai.chat.messages.Message; -import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage; -import cn.iocoder.yudao.framework.ai.model.ModelOptions; -import cn.iocoder.yudao.framework.ai.model.ModelRequest; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * 文字内容 - */ -public class Prompt implements ModelRequest> { - - private final List messages; - - private ChatOptions modelOptions; - - public Prompt(String contents) { - this(new UserMessage(contents)); - } - - public Prompt(Message message) { - this(Collections.singletonList(message)); - } - - public Prompt(List messages) { - this.messages = messages; - } - - public Prompt(String contents, ChatOptions modelOptions) { - this(new UserMessage(contents), modelOptions); - } - - public Prompt(Message message, ChatOptions modelOptions) { - this(Collections.singletonList(message), modelOptions); - } - - public Prompt(List messages, ChatOptions modelOptions) { - this.messages = messages; - this.modelOptions = modelOptions; - } - - public String getContents() { - StringBuilder sb = new StringBuilder(); - for (Message message : getInstructions()) { - sb.append(message.getContent()); - } - return sb.toString(); - } - - @Override - public ModelOptions getOptions() { - return this.modelOptions; - } - - @Override - public List getInstructions() { - return this.messages; - } - - @Override - public String toString() { - return "Prompt{" + "messages=" + this.messages + ", modelOptions=" + this.modelOptions + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; -// if (!(o instanceof Prompt prompt)) -// return false; - if (!(o instanceof Prompt)) { - return false; - } - Prompt prompt = (Prompt) o; - return Objects.equals(this.messages, prompt.messages) && Objects.equals(this.modelOptions, prompt.modelOptions); - } - - @Override - public int hashCode() { - return Objects.hash(this.messages, this.modelOptions); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplate.java deleted file mode 100644 index 7c0c0f7..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplate.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.chat.messages.Message; -import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage; -import cn.iocoder.yudao.framework.ai.parser.OutputParser; -import org.antlr.runtime.Token; -import org.antlr.runtime.TokenStream; -import org.springframework.core.io.Resource; -import org.springframework.util.StreamUtils; -import org.stringtemplate.v4.ST; -import org.stringtemplate.v4.compiler.STLexer; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * 用户输入的提示内容模板 - * - * 实现:提示词模板操作 提示词模板message相关操作 - */ -public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions { - - private ST st; - - private Map dynamicModel = new HashMap<>(); - - protected String template; - - protected TemplateFormat templateFormat = TemplateFormat.ST; - - private OutputParser outputParser; - - public PromptTemplate(Resource resource) { - try (InputStream inputStream = resource.getInputStream()) { - this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); - } - catch (IOException ex) { - throw new RuntimeException("Failed to read resource", ex); - } - try { - this.st = new ST(this.template, '{', '}'); - } - catch (Exception ex) { - throw new IllegalArgumentException("The template string is not valid.", ex); - } - } - - public PromptTemplate(String template) { - this.template = template; - // If the template string is not valid, an exception will be thrown - try { - this.st = new ST(this.template, '{', '}'); - } - catch (Exception ex) { - throw new IllegalArgumentException("The template string is not valid.", ex); - } - } - - public PromptTemplate(String template, Map model) { - this.template = template; - // If the template string is not valid, an exception will be thrown - try { - this.st = new ST(this.template, '{', '}'); - for (Entry entry : model.entrySet()) { - add(entry.getKey(), entry.getValue()); - dynamicModel.put(entry.getKey(), entry.getValue()); - } - } - catch (Exception ex) { - throw new IllegalArgumentException("The template string is not valid.", ex); - } - } - - public PromptTemplate(Resource resource, Map model) { - try (InputStream inputStream = resource.getInputStream()) { - this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); - } - catch (IOException ex) { - throw new RuntimeException("Failed to read resource", ex); - } - // If the template string is not valid, an exception will be thrown - try { - this.st = new ST(this.template, '{', '}'); - for (Entry entry : model.entrySet()) { - add(entry.getKey(), entry.getValue()); - dynamicModel.put(entry.getKey(), entry.getValue()); - } - } - catch (Exception ex) { - throw new IllegalArgumentException("The template string is not valid.", ex); - } - } - - public OutputParser getOutputParser() { - return outputParser; - } - - public void setOutputParser(OutputParser outputParser) { - Objects.requireNonNull(outputParser, "Output Parser can not be null"); - this.outputParser = outputParser; - } - - public void add(String name, Object value) { - this.st.add(name, value); - this.dynamicModel.put(name, value); - } - - public String getTemplate() { - return this.template; - } - - public TemplateFormat getTemplateFormat() { - return this.templateFormat; - } - - // Render Methods - @Override - public String render() { - validate(this.dynamicModel); - return st.render(); - } - - @Override - public String render(Map model) { - validate(model); - for (Entry entry : model.entrySet()) { - if (st.getAttribute(entry.getKey()) != null) { - st.remove(entry.getKey()); - } - if (entry.getValue() instanceof Resource) { - st.add(entry.getKey(), renderResource((Resource) entry.getValue())); - } - else { - st.add(entry.getKey(), entry.getValue()); - } - - } - return st.render(); - } - - private String renderResource(Resource resource) { - try { - return resource.getContentAsString(Charset.defaultCharset()); - } - catch (IOException e) { - throw new RuntimeException(e); - } - // try (InputStream inputStream = resource.getInputStream()) { - // return StreamUtils.copyToString(inputStream, Charset.defaultCharset()); - // } - // catch (IOException ex) { - // throw new RuntimeException(ex); - // } - } - - @Override - public Message createMessage() { - return new UserMessage(render()); - } - - @Override - public Message createMessage(Map model) { - return new UserMessage(render(model)); - } - - @Override - public Prompt create() { - return new Prompt(render(new HashMap<>())); - } - - @Override - public Prompt create(Map model) { - return new Prompt(render(model)); - } - - public Set getInputVariables() { - TokenStream tokens = this.st.impl.tokens; - return IntStream.range(0, tokens.range()) - .mapToObj(tokens::get) - .filter(token -> token.getType() == STLexer.ID) - .map(Token::getText) - .collect(Collectors.toSet()); - } - - protected void validate(Map model) { - Set dynamicVariableNames = new HashSet<>(this.dynamicModel.keySet()); - Set modelVariables = new HashSet<>(model.keySet()); - modelVariables.addAll(dynamicVariableNames); - Set missingEntries = new HashSet<>(getInputVariables()); - missingEntries.removeAll(modelVariables); - if (!missingEntries.isEmpty()) { - throw new IllegalStateException( - "All template variables were not replaced. Missing variable names are " + missingEntries); - } - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateActions.java deleted file mode 100644 index bdee00a..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateActions.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import java.util.Map; - -/** - * 提示词模板操作 - */ -public interface PromptTemplateActions extends PromptTemplateStringActions { - - /** - * 创建 Prompt - * @return - */ - Prompt create(); - - Prompt create(Map model); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateChatActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateChatActions.java deleted file mode 100644 index 922d57c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateChatActions.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.chat.messages.Message; - -import java.util.List; -import java.util.Map; - -/** - * 聊天操作 - * - */ -public interface PromptTemplateChatActions { - - List createMessages(); - - List createMessages(Map model); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateMessageActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateMessageActions.java deleted file mode 100644 index 8de851a..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateMessageActions.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.chat.messages.Message; - -import java.util.Map; - -/** - * 用户输入的提示内容 模板信息操作 - */ -public interface PromptTemplateMessageActions { - - /** - * 创建一个 message - * @return - */ - Message createMessage(); - - /** - * 创建一个 message - * @return - */ - Message createMessage(Map model); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateStringActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateStringActions.java deleted file mode 100644 index 58015b4..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/PromptTemplateStringActions.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import java.util.Map; - -/** - * 提示次模板字符串操作 - */ -public interface PromptTemplateStringActions { - - String render(); - - String render(Map model); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/SystemPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/SystemPromptTemplate.java deleted file mode 100644 index cafade4..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/SystemPromptTemplate.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -import cn.iocoder.yudao.framework.ai.chat.messages.Message; -import cn.iocoder.yudao.framework.ai.chat.messages.SystemMessage; -import org.springframework.core.io.Resource; - -import java.util.Map; - -public class SystemPromptTemplate extends PromptTemplate { - - public SystemPromptTemplate(String template) { - super(template); - } - - public SystemPromptTemplate(Resource resource) { - super(resource); - } - - @Override - public Message createMessage() { - return new SystemMessage(render()); - } - - @Override - public Message createMessage(Map model) { - return new SystemMessage(render(model)); - } - - @Override - public Prompt create() { - return new Prompt(new SystemMessage(render())); - } - - @Override - public Prompt create(Map model) { - return new Prompt(new SystemMessage(render(model))); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/TemplateFormat.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/TemplateFormat.java deleted file mode 100644 index c22a78d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/prompt/TemplateFormat.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.chat.prompt; - -public enum TemplateFormat { - - ST("ST"); - - private final String value; - - TemplateFormat(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public static TemplateFormat fromValue(String value) { - for (TemplateFormat templateFormat : TemplateFormat.values()) { - if (templateFormat.getValue().equals(value)) { - return templateFormat; - } - } - throw new IllegalArgumentException("Invalid TemplateFormat value: " + value); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java deleted file mode 100644 index e18ebe0..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java +++ /dev/null @@ -1,156 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen; - -import cn.iocoder.yudao.framework.ai.chat.*; -import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; -import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; -import com.alibaba.dashscope.aigc.generation.GenerationResult; -import com.alibaba.dashscope.aigc.generation.models.QwenParam; -import com.alibaba.dashscope.common.Message; -import com.google.common.collect.Lists; -import io.reactivex.Flowable; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; -import reactor.core.publisher.Flux; - -import java.time.Duration; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 阿里 通义千问 client - *

- * 文档地址:https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp - *

- * author: fansili - * time: 2024/3/13 21:06 - */ -@Slf4j -public class QianWenChatClient implements ChatClient, StreamingChatClient { - - private QianWenApi qianWenApi; - - private QianWenOptions qianWenOptions; - - - public QianWenChatClient() { - } - - public QianWenChatClient(QianWenApi qianWenApi) { - this.qianWenApi = qianWenApi; - } - - public QianWenChatClient(QianWenApi qianWenApi, QianWenOptions qianWenOptions) { - this.qianWenApi = qianWenApi; - this.qianWenOptions = qianWenOptions; - } - - // TODO @fansili:看看咋公用出来,允许传入类似异常之类的参数; - public final RetryTemplate retryTemplate = RetryTemplate.builder() - // 最大重试次数 10 - .maxAttempts(10) - .retryOn(YiYanApiException.class) - // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms - .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) - .withListener(new RetryListener() { - @Override - public void onError(RetryContext context, - RetryCallback callback, Throwable throwable) { - log.warn("重试异常:" + context.getRetryCount(), throwable); - } - }) - .build(); - - @Override - public ChatResponse call(Prompt prompt) { - return this.retryTemplate.execute(ctx -> { - // ctx 会有重试的信息 - // 创建 request 请求,stream模式需要供应商支持 - QwenParam request = this.createRequest(prompt, false); - // 调用 callWithFunctionSupport 发送请求 - ResponseEntity responseEntity = qianWenApi.chatCompletionEntity(request); - // 获取结果封装 chatCompletion - GenerationResult response = responseEntity.getBody(); -// if (!response.isSuccess()) { -// return new ChatResponse(List.of(new Generation(String.format("failed to create completion, requestId: %s, code: %s, message: %s\n", -// response.getRequestId(), response.getCode(), response.getMessage())))); -// } - // 转换为 Generation 返回 - return new ChatResponse(response.getOutput().getChoices().stream() - .map(choices -> new Generation(choices.getMessage().getContent())) - .collect(Collectors.toList())); - }); - } - - private QwenParam createRequest(Prompt prompt, boolean stream) { - // 获取 ChatOptions - QianWenOptions chatOptions = getChatOptions(prompt); - // - List messageList = Lists.newArrayList(); - prompt.getInstructions().stream().forEach(instruction -> { - Message message = new Message(); - message.setRole(instruction.getMessageType().getValue()); - message.setContent(instruction.getContent()); - messageList.add(message); - }); - return QwenParam.builder() - .model(qianWenApi.getQianWenChatModal().getModel()) - .prompt(prompt.getContents()) - .messages(messageList) - .maxTokens(chatOptions.getMaxTokens()) - .resultFormat(QwenParam.ResultFormat.MESSAGE) - .topP(Double.valueOf(chatOptions.getTopP())) - .topK(chatOptions.getTopK()) - .temperature(chatOptions.getTemperature()) - // 控制流式输出模式,即后面的内容会包含已经输出的内容;设置为True,将开启增量输出模式,后面的输出不会包含已经输出的内容,您需要自行拼接整体输出 - .incrementalOutput(true) - /* set the random seed, optional, default to 1234 if not set */ - .seed(100) - .apiKey(qianWenApi.getApiKey()) - .build(); - } - - private @NotNull QianWenOptions getChatOptions(Prompt prompt) { - // 两个都为null 则没有配置文件 - if (qianWenOptions == null && prompt.getOptions() == null) { - throw new ChatException("ChatOptions 未配置参数!"); - } - // 优先使用 Prompt 里面的 ChatOptions - ChatOptions options = qianWenOptions; - if (prompt.getOptions() != null) { - options = (ChatOptions) prompt.getOptions(); - } - // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 - if (!(options instanceof QianWenOptions)) { - throw new ChatException("Prompt 传入的不是 QianWenOptions!"); - } - return (QianWenOptions) options; - } - - @Override - public Flux stream(Prompt prompt) { - // ctx 会有重试的信息 - // 创建 request 请求,stream模式需要供应商支持 - QwenParam request = this.createRequest(prompt, true); - // 调用 callWithFunctionSupport 发送请求 - Flowable responseResult = this.qianWenApi.chatCompletionStream(request); - - return Flux.create(fluxSink -> - responseResult.subscribe( - value -> fluxSink.next( - new ChatResponse(value.getOutput().getChoices().stream() - .map(choices -> new Generation(choices.getMessage().getContent())) - .collect(Collectors.toList())) - ), - error -> fluxSink.error(error), - () -> fluxSink.complete() - ) - ); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatModal.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatModal.java deleted file mode 100644 index 7fd9411..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatModal.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 千问 chat 模型 - * - * 模型地址:https://help.aliyun.com/document_detail/2712576.html - * 模型介绍:https://help.aliyun.com/document_detail/2666503.html?spm=a2c4g.2701795.0.0.26eb34dfKzcWN4 - * - * @author fansili - * @time 2024/4/26 10:15 - * @since 1.0 - */ -@AllArgsConstructor -@Getter -public enum QianWenChatModal { - - // 千问付费模型 - QWEN_TURBO("通义千问超大规模语言模型", "qwen-turbo"), - QWEN_PLUS("通义千问超大规模语言模型增强版", "qwen-plus"), - QWEN_MAX("通义千问千亿级别超大规模语言模型", "qwen-max"), - QWEN_MAX_0403("通义千问千亿级别超大规模语言模型-0403", "qwen-max-0403"), - QWEN_MAX_0107("通义千问千亿级别超大规模语言模型-0107", "qwen-max-0107"), - QWEN_MAX_1201("通义千问千亿级别超大规模语言模型-1201", "qwen-max-1201"), - QWEN_MAX_LONGCONTEXT("通义千问千亿级别超大规模语言模型-28k tokens", "qwen-max-longcontext"), - - // 开源模型 - // https://help.aliyun.com/document_detail/2666503.html?spm=a2c4g.2701795.0.0.26eb34dfKzcWN4 - QWEN_72B_CHAT("通义千问1.5对外开源的72B规模参数量的经过人类指令对齐的chat模型", "qwen-72b-chat"), - - ; - - private String name; - - private String model; - - public static QianWenChatModal valueOfModel(String model) { - for (QianWenChatModal itemEnum : QianWenChatModal.values()) { - if (itemEnum.getModel().equals(model)) { - return itemEnum; - } - } - throw new IllegalArgumentException("Invalid MessageType value: " + model); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenOptions.java deleted file mode 100644 index 4cec864..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenOptions.java +++ /dev/null @@ -1,122 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen; - -import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.List; - -/** - * 阿里云 千问 属性 - * - * 地址:https://help.aliyun.com/document_detail/2684682.html?spm=a2c4g.2621347.0.0.195117e7Ytpkyo - * - * author: fansili - * time: 2024/3/15 19:57 - */ -@Data -@Accessors(chain = true) -public class QianWenOptions implements ChatOptions { - - /** - * 用户与模型的对话历史 - */ - private List messages; - /** - * 生成时,核采样方法的概率阈值。例如,取值为0.8时,仅保留累计概率之和大于等于0.8的概率分布中的token, - * 作为随机采样的候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的随机性越低。 - * 默认值为0.8。注意,取值不要大于等于1 - */ - private Float topP; - /** - * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量。其中qwen1.5-14b-chat、qwen1.5-7b-chat、qwen-14b-chat和qwen-7b-chat最大值和默认值均为1500,qwen-1.8b-chat、qwen-1.8b-longcontext-chat和qwen-72b-chat最大值和默认值均为2000 - */ - private Integer maxTokens = 1500; - - // - // 适配 ChatOptions - - @Override - public Float getTemperature() { - return null; - } - - @Override - public void setTemperature(Float temperature) { - - } - - @Override - public void setTopP(Float topP) { - this.topP = topP; - } - - @Override - public Integer getTopK() { - return null; - } - - @Override - public void setTopK(Integer topK) { - - } - - @Data - @Accessors - public static class Message { - /** - * 角色: system、user或assistant - */ - private String role; - /** - * 提示词或模型内容 - */ - private String content; - } - - @Data - @Accessors - public static class Parameters { - /** - * 输出格式, 默认为"text" - * "text"表示旧版本的text - * "message"表示兼容openai的message - */ - private String resultFormat; - /** - * 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。 - * 取值越大,生成的随机性越高;取值越小,生成的确定性越高。 - * 注意:如果top_k参数为空或者top_k的值大于100,表示不启用top_k策略,此时仅有top_p策略生效,默认是空。 - */ - private Integer topK; - /** - * 生成时使用的随机数种子,用户控制模型生成内容的随机性。 - * seed支持无符号64位整数,默认值为1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 - */ - private Integer seed; - /** - * 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。 - * 较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择, - * 生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 - * 取值范围: [0, 2),系统默认值1.0。不建议取值为0,无意义。 - */ - private Float temperature; - /** - * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量。 - * 其中qwen-turbo 最大值和默认值为1500, qwen-max、qwen-max-1201 、qwen-max-longcontext 和 qwen-plus最大值和默认值均为2000。 - */ - private Integer maxTokens; - /** - * stop参数用于实现内容生成过程的精确控制,在生成内容即将包含指定的字符串或token_ids时自动停止,生成内容不包含指定的内容。 - * 例如,如果指定stop为"你好",表示将要生成"你好"时停止;如果指定stop为[37763, 367],表示将要生成"Observation"时停止。 - */ - private List stop; - /** - * 用于控制流式输出模式,默认False,即后面内容会包含已经输出的内容;设置为True,将开启增量输出模式, - * 后面输出不会包含已经输出的内容,您需要自行拼接整体输出,参考流式输出示例代码。 - */ - private Boolean incrementalOutput; - } -} - - diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java deleted file mode 100644 index c5d2bb6..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java +++ /dev/null @@ -1,62 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen.api; - -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatModal; -import cn.iocoder.yudao.framework.ai.exception.AiException; -import com.alibaba.dashscope.aigc.generation.Generation; -import com.alibaba.dashscope.aigc.generation.GenerationResult; -import com.alibaba.dashscope.aigc.generation.models.QwenParam; -import com.alibaba.dashscope.common.Message; -import com.alibaba.dashscope.common.Role; -import com.alibaba.dashscope.exception.InputRequiredException; -import com.alibaba.dashscope.exception.NoApiKeyException; -import io.reactivex.Flowable; -import lombok.Getter; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; - -// TODO done @fansili:是不是挪到 api 包里?按照 spring ai 的结构;根目录只放 client 和 options - -/** - * 阿里 通义千问 - *

- * author: fansili - * time: 2024/3/13 21:09 - */ -@Getter -public class QianWenApi { - - // api key 获取地址:https://bailian.console.aliyun.com/?spm=5176.28197581.0.0.38db29a4G3GcVb&apiKey=1#/api-key - private String apiKey = "sk-Zsd81gZYg7"; - private Generation gen = new Generation(); - private QianWenChatModal qianWenChatModal; - - public QianWenApi(String apiKey, QianWenChatModal qianWenChatModal) { - this.apiKey = apiKey; - this.qianWenChatModal = qianWenChatModal; - } - - public ResponseEntity chatCompletionEntity(QwenParam request) { - GenerationResult call; - try { - call = gen.call(request); - } catch (NoApiKeyException e) { - throw new AiException("没有找到apiKey!" + e.getMessage()); - } catch (InputRequiredException e) { - throw new AiException("chat缺少必填字段!" + e.getMessage()); - } - // 阿里云的这个 http code 随便设置,外面判断是否成功用的 CompletionsResponse.isSuccess - return new ResponseEntity<>(call, HttpStatusCode.valueOf(200)); - } - - public Flowable chatCompletionStream(QwenParam request) { - Flowable resultFlowable; - try { - resultFlowable = gen.streamCall(request); - } catch (NoApiKeyException e) { - throw new AiException("没有找到apiKey!" + e.getMessage()); - } catch (InputRequiredException e) { - throw new AiException("chat缺少必填字段!" + e.getMessage()); - } - return resultFlowable; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java deleted file mode 100644 index 474a7d8..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java +++ /dev/null @@ -1,9 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen.api; - - -/** - * author: fansili - * time: 2024/3/13 21:07 - */ -public class QianWenChatCompletion { -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java deleted file mode 100644 index 07680cb..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen.api; - -/** - * author: fansili - * time: 2024/3/13 21:07 - */ -public class QianWenChatCompletionMessage { -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java deleted file mode 100644 index 5017ea4..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen.api; - -import com.alibaba.dashscope.aigc.generation.models.QwenParam; - -/** - * 千问 - * - * author: fansili - * time: 2024/3/13 21:07 - */ -public class QianWenChatCompletionRequest extends QwenParam { - - protected QianWenChatCompletionRequest(QwenParamBuilder b) { - super(b); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java deleted file mode 100644 index c22dbe0..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 阿里的 通义千问 - * - * 链接:https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all - * - * 千问所有模型:https://bailian.console.aliyun.com/?spm=5176.28515448.J_TC9GqcHi2edq9zUs9ZsDQ.1.417338b17zJTjy#/efm/my_model - * - * author: fansili - * time: 2024/3/13 21:05 - */ -package cn.iocoder.yudao.framework.ai.chatqianwen; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatClient.java deleted file mode 100644 index 90ae6df..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatClient.java +++ /dev/null @@ -1,135 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.iocoder.yudao.framework.ai.chat.*; -import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoApi; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; -import reactor.core.publisher.Flux; - -import java.time.Duration; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 讯飞星火 client - *

- * author: fansili - * time: 2024/3/11 10:19 - */ -@Slf4j -public class XingHuoChatClient implements ChatClient, StreamingChatClient { - - private XingHuoApi xingHuoApi; - - private XingHuoOptions xingHuoOptions; - - public final RetryTemplate retryTemplate = RetryTemplate.builder() - // 最大重试次数 10 - .maxAttempts(3) - .retryOn(ChatException.class) - // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms - .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) - .withListener(new RetryListener() { - @Override - public void onError(RetryContext context, - RetryCallback callback, Throwable throwable) { - System.err.println("正在重试... " + ExceptionUtil.getMessage(throwable)); - log.warn("重试异常:" + context.getRetryCount(), throwable); - } - - ; - }) - .build(); - - public XingHuoChatClient(XingHuoApi xingHuoApi) { - this.xingHuoApi = xingHuoApi; - } - - public XingHuoChatClient(XingHuoApi xingHuoApi, XingHuoOptions xingHuoOptions) { - this.xingHuoApi = xingHuoApi; - this.xingHuoOptions = xingHuoOptions; - } - - @Override - public ChatResponse call(Prompt prompt) { - - return this.retryTemplate.execute(ctx -> { - // ctx 会有重试的信息 - // 获取 chatOptions 属性 - XingHuoOptions chatOptions = this.getChatOptions(prompt); - // 创建 request 请求,stream模式需要供应商支持 - XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions); - // 调用 callWithFunctionSupport 发送请求 - ResponseEntity response = xingHuoApi.chatCompletionEntity(request, chatOptions.getChatModel()); - // 获取结果封装 ChatResponse - return new ChatResponse(List.of(new Generation(response.getBody().getPayload().getChoices().getText().get(0).getContent()))); - }); - } - - @Override - public Flux stream(Prompt prompt) { - // 获取 chatOptions 属性 - XingHuoOptions chatOptions = this.getChatOptions(prompt); - // 创建 request 请求,stream模式需要供应商支持 - XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions); - // 发送请求 - Flux response = this.xingHuoApi.chatCompletionStream(request, chatOptions.getChatModel()); - return response.map(res -> { - String content = res.getPayload().getChoices().getText().stream() - .map(item -> item.getContent()).collect(Collectors.joining()); - return new ChatResponse(List.of(new Generation(content))); - }); - } - - private XingHuoOptions getChatOptions(Prompt prompt) { - // 两个都为null 则没有配置文件 - if (xingHuoOptions == null && prompt.getOptions() == null) { - throw new ChatException("ChatOptions 未配置参数!"); - } - // 优先使用 Prompt 里面的 ChatOptions - ChatOptions options = xingHuoOptions; - if (prompt.getOptions() != null) { - options = (ChatOptions) prompt.getOptions(); - } - // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 - if (!(options instanceof XingHuoOptions)) { - throw new ChatException("Prompt 传入的不是 XingHuoOptions!"); - } - return (XingHuoOptions) options; - } - - private XingHuoChatCompletionRequest createRequest(Prompt prompt, XingHuoOptions xingHuoOptions) { - // 创建 header - XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id(xingHuoApi.getAppId()); - // 创建 params - XingHuoChatCompletionRequest.Parameter.Chat chatParameter = new XingHuoChatCompletionRequest.Parameter.Chat(); - BeanUtil.copyProperties(xingHuoOptions, chatParameter); - chatParameter.setDomain(xingHuoOptions.getChatModel().getModel()); - XingHuoChatCompletionRequest.Parameter parameter = new XingHuoChatCompletionRequest.Parameter().setChat(chatParameter); - // 创建 payload text 信息 - List texts = prompt.getInstructions().stream().map(message -> { - XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text(); - text.setContent(message.getContent()); - text.setRole(message.getMessageType().getValue()); - return text; - }).collect(Collectors.toList()); - // 创建 payload - XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload() - .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(texts)); - // 创建 request - return new XingHuoChatCompletionRequest() - .setHeader(header) - .setParameter(parameter) - .setPayload(payload); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatModel.java deleted file mode 100644 index 5be9f40..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatModel.java +++ /dev/null @@ -1,53 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo; - -import lombok.Getter; - -/** - * 讯飞星火 模型 - * - * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E - * - * 1tokens 约等于1.5个中文汉字 或者 0.8个英文单词 - * 星火V1.5支持[搜索]内置插件;星火V2.0、V3.0和V3.5支持[搜索]、[天气]、[日期]、[诗词]、[字词]、[股票]六个内置插件 - * 星火V3.5 现已支持system、Function Call 功能。 - * - * author: fansili - * time: 2024/3/11 10:12 - */ -@Getter -public enum XingHuoChatModel { - -// 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E -// general指向V1.5版本; -// generalv2指向V2版本; -// generalv3指向V3版本; -// generalv3.5指向V3.5版本; - - XING_HUO_1_5("星火大模型1.5", "general", "/v1.1/chat"), - XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"), - XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"), - XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"), - - ; - - XingHuoChatModel(String name, String model, String uri) { - this.name = name; - this.model = model; - this.uri = uri; - } - - private String name; - - private String model; - - private String uri; - - public static XingHuoChatModel valueOfModel(String model) { - for (XingHuoChatModel itemEnum : XingHuoChatModel.values()) { - if (itemEnum.getModel().equals(model)) { - return itemEnum; - } - } - throw new IllegalArgumentException("Invalid MessageType value: " + model); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoOptions.java deleted file mode 100644 index 7d56032..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoOptions.java +++ /dev/null @@ -1,76 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo; - -import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; -import lombok.Data; -import lombok.experimental.Accessors; - -/** - * 讯飞星火 - *

- * author: fansili - * time: 2024/3/16 20:29 - */ -@Data -@Accessors(chain = true) -public class XingHuoOptions implements ChatOptions { - - /** - * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E - *

- * 指定访问的领域: - * general指向V1.5版本; - * generalv2指向V2版本; - * generalv3指向V3版本; - * generalv3.5指向V3.5版本; - * 注意:不同的取值对应的url也不一样! - */ - private XingHuoChatModel chatModel = XingHuoChatModel.XING_HUO_3_5; - /** - * 取值范围 (0,1] ,默认值0.5 - */ - private Float temperature; - /** - * V1.5取值为[1,4096] - * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。 - */ - private Integer maxTokens; - /** - * 取值为[1,6],默认为4 - */ - private Integer topK; - /** - * 需要保障用户下的唯一性,用于关联用户会话 - */ - private String chatId; - - - @Override - public Float getTemperature() { - return this.temperature; - } - - @Override - public void setTemperature(Float temperature) { - this.temperature = temperature; - } - - @Override - public Float getTopP() { - return null; - } - - @Override - public void setTopP(Float topP) { - - } - - @Override - public Integer getTopK() { - return this.topK; - } - - @Override - public void setTopK(Integer topK) { - this.topK = topK; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java deleted file mode 100644 index e2a8577..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java +++ /dev/null @@ -1,151 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo.api; - -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONUtil; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatModel; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest; -import lombok.Data; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.net.URI; -import java.nio.charset.Charset; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.stream.Collectors; - -/** - * 讯飞星火 属性、api - *

- * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E - *

- * author: fansili - * time: 2024/3/11 10:12 - */ -@Data -public class XingHuoApi { - - private static final String DEFAULT_BASE_URL = "wss://spark-api.xf-yun.com"; - - private String appId; - private String appKey; - private String secretKey; - private WebClient webClient; - // 创建 WebSocketClient 实例 - private ReactorNettyWebSocketClient socketClient = new ReactorNettyWebSocketClient(); - - public XingHuoApi(String appId, String appKey, String secretKey) { - this.appId = appId; - this.appKey = appKey; - this.secretKey = secretKey; - } - - public ResponseEntity chatCompletionEntity(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) { - String authUrl; - try { -// XingHuoChatModel useChatModel; - authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri()); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new RuntimeException(e); - } - // wss 请求的 URI - URI uri = URI.create(authUrl); - // 发起 wss 请求并处理响应 - Flux messageFlux = Flux.create(sink -> { - socketClient.execute(uri, session -> - session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request)))) - .thenMany(session.receive() - .map(WebSocketMessage -> { - return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class); - }) - .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中 - .doOnError(sink::error) // 处理错误 - .doOnTerminate(sink::complete)) // 完成时关闭 sink - .then()) - .subscribe(); // 订阅以开始会话 - }); - // 阻塞获取所有结果 - List responseList = messageFlux.collectList().block(); - // 拼接 content - String responseContent = responseList.stream().map(item -> { - // 获取 content - return item.getPayload().getChoices().getText().stream().map(XingHuoChatCompletion.Text::getContent).collect(Collectors.joining()); - }).collect(Collectors.joining()); - // 将多个合并成一个 - XingHuoChatCompletion xingHuoChatCompletion = new XingHuoChatCompletion(); - xingHuoChatCompletion.setPayload(new XingHuoChatCompletion.Payload().setChoices(new XingHuoChatCompletion.Choices().setText(List.of(new XingHuoChatCompletion.Text().setContent(responseContent))))); - return new ResponseEntity<>(xingHuoChatCompletion, HttpStatusCode.valueOf(200)); - } - - - /** - * 获取验证请求url - * - * @return - */ - public String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException { - // 获取鉴权时间 date - SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); - format.setTimeZone(TimeZone.getTimeZone("GMT")); - String date = format.format(new Date()); - - // 获取signature_origin字段 - StringBuilder builder = new StringBuilder("host: ").append(host).append("\n"). - append("date: ").append(date).append("\n"). - append("GET ").append(path).append(" HTTP/1.1"); - - // 获得signatue - Charset charset = Charset.forName("UTF-8"); - Mac mac = Mac.getInstance("hmacsha256"); - SecretKeySpec sp = new SecretKeySpec(secretKey.getBytes(charset), "hmacsha256"); - mac.init(sp); - byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset)); - String signature = Base64.getEncoder().encodeToString(basebefore); - //获得 authorization_origin - String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", appKey, "hmac-sha256", "host date request-line", signature); - //获得authorization - String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset)); - // 获取httpUrl - Map param = new HashMap<>(); - param.put("authorization", authorization); - param.put("date", date); - param.put("host", host); - - String toParams = HttpUtil.toParams(param); - return "wss://" + host + path + "?" + toParams; - } - - public Flux chatCompletionStream(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) { - String authUrl; - try { - authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri()); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new RuntimeException(e); - } - // wss 请求的 URI - URI uri = URI.create(authUrl); - // 发起 wss 请求并处理响应 - // 创建一个 Flux 来处理接收到的消息 - Flux messageFlux = Flux.create(sink -> { - socketClient.execute(uri, session -> - session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request)))) - .thenMany(session.receive() - .map(WebSocketMessage -> JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class)) - .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中 - .doOnError(sink::error) // 处理错误 - .doOnTerminate(sink::complete)) // 完成时关闭 sink - .then()) - .subscribe(); // 订阅以开始会话 - }); - return messageFlux; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletion.java deleted file mode 100644 index a0f9653..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletion.java +++ /dev/null @@ -1,48 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo.api; - -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.List; - -/** - * author: fansili - * time: 2024/3/11 10:20 - */ -@Data -@Accessors(chain = true) -public class XingHuoChatCompletion { - private Header header; - private Payload payload; - - @Data - @Accessors(chain = true) - public static class Header { - private int code; - private String message; - private String sid; - private int status; - } - - @Data - @Accessors(chain = true) - public static class Payload { - private Choices choices; - } - - @Data - @Accessors(chain = true) - public static class Choices { - private int status; - private int seq; - private List text; - } - - @Data - @Accessors(chain = true) - public static class Text { - private String content; - private String role; - private int index; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionMessage.java deleted file mode 100644 index faa3c8c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionMessage.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo.api; - -/** - * author: fansili - * time: 2024/3/11 10:20 - */ -public class XingHuoChatCompletionMessage { -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionRequest.java deleted file mode 100644 index a90697b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionRequest.java +++ /dev/null @@ -1,107 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo.api; - -import lombok.Data; -import lombok.Getter; -import lombok.experimental.Accessors; - -import java.util.List; - -/** - * 讯飞星火 request - * - * author: fansili - * time: 2024/3/11 10:20 - */ -@Data -@Accessors(chain = true) -public class XingHuoChatCompletionRequest { - - private Header header; - private Parameter parameter; - private Payload payload; - - @Data - @Accessors(chain = true) - public static class Header { - private String app_id; - private String uid; - } - - @Data - @Accessors(chain = true) - public static class Parameter { - private Chat chat; - - @Data - @Accessors(chain = true) - public static class Chat { - /** - * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E - * - * 指定访问的领域: - * general指向V1.5版本; - * generalv2指向V2版本; - * generalv3指向V3版本; - * generalv3.5指向V3.5版本; - * 注意:不同的取值对应的url也不一样! - */ - private String domain = "generalv3.5"; - /** - * 取值范围 (0,1] ,默认值0.5 - */ - private Float temperature; - /** - * V1.5取值为[1,4096] - * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。 - */ - private Integer max_tokens; - /** - * 取值为[1,6],默认为4 - */ - private Integer top_k; - /** - * 需要保障用户下的唯一性,用于关联用户会话 - */ - private String chat_id; - } - } - - @Data - @Accessors(chain = true) - public static class Payload { - private Message message; - - @Data - @Accessors(chain = true) - public static class Message { - private List text; - - - @Data - @Accessors(chain = true) - public static class Text { - /** - * 角色 - */ - private String role; - /** - * 消息内容 - */ - private String content; - private Integer index; - - @Getter - public static enum Role { - SYSTEM("system"), - USER("user"), - ASSISTANT("assistant"); - private String name; - - private Role(String name) { - this.name = name; - } - } - } - } - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatClient.java deleted file mode 100644 index d95e9fb..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatClient.java +++ /dev/null @@ -1,154 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan; - -import cn.hutool.core.bean.BeanUtil; -import cn.iocoder.yudao.framework.ai.chat.*; -import cn.iocoder.yudao.framework.ai.chat.messages.Message; -import cn.iocoder.yudao.framework.ai.chat.messages.MessageType; -import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanApi; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletion; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletionRequest; -import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; -import reactor.core.publisher.Flux; - -import java.time.Duration; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 文心一言 - *

- * author: fansili - * time: 2024/3/8 19:11 - */ -@Slf4j -public class YiYanChatClient implements ChatClient, StreamingChatClient { - - private YiYanApi yiYanApi; - - private YiYanOptions yiYanOptions; - - public YiYanChatClient(YiYanApi yiYanApi) { - this.yiYanApi = yiYanApi; - } - - public YiYanChatClient(YiYanApi yiYanApi, YiYanOptions yiYanOptions) { - this.yiYanApi = yiYanApi; - this.yiYanOptions = yiYanOptions; - } - - public final RetryTemplate retryTemplate = RetryTemplate.builder() - // 最大重试次数 10 - .maxAttempts(10) - .retryOn(YiYanApiException.class) - // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms - .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) - .withListener(new RetryListener() { - @Override - public void onError(RetryContext context, - RetryCallback callback, Throwable throwable) { - log.warn("重试异常:" + context.getRetryCount(), throwable); - } - - ; - }) - .build(); - - @Override - public String call(String message) { - return ChatClient.super.call(message); - } - - @Override - public ChatResponse call(Prompt prompt) { - return this.retryTemplate.execute(ctx -> { - // ctx 会有重试的信息 - // 创建 request 请求,stream模式需要供应商支持 - YiYanChatCompletionRequest request = this.createRequest(prompt, false); - // 调用 callWithFunctionSupport 发送请求 - ResponseEntity response = yiYanApi.chatCompletionEntity(request); - // 获取结果封装 ChatResponse - YiYanChatCompletion chatCompletion = response.getBody(); - return new ChatResponse(List.of(new Generation(chatCompletion.getResult()))); - }); - } - - @Override - public Flux stream(Prompt prompt) { - // ctx 会有重试的信息 - // 创建 request 请求,stream模式需要供应商支持 - YiYanChatCompletionRequest request = this.createRequest(prompt, true); - // 调用 callWithFunctionSupport 发送请求 - Flux response = this.yiYanApi.chatCompletionStream(request); - response.doOnComplete(new Runnable() { - @Override - public void run() { - String a = ";"; - } - }); - return response.map(res -> new ChatResponse(List.of(new Generation(res.getResult())))); - } - - private YiYanChatCompletionRequest createRequest(Prompt prompt, boolean stream) { - // 获取配置 - YiYanOptions useOptions = getYiYanOptions(prompt); - // 创建 request - - // tip: 百度的 system 不在 message 里面 - // tip:百度的 message 只有 user 和 assistant - // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t - - // 获取 user 和 assistant - List messageList = prompt.getInstructions().stream() - // 过滤 system - .filter(msg -> MessageType.SYSTEM != msg.getMessageType()) - .map(msg -> new YiYanChatCompletionRequest.Message() - .setRole(msg.getMessageType().getValue()) - .setContent(msg.getContent()) - ).toList(); - // 获取 system - String systemPrompt = prompt.getInstructions().stream() - .filter(msg -> MessageType.SYSTEM == msg.getMessageType()) - .map(Message::getContent) - .collect(Collectors.joining()); - - YiYanChatCompletionRequest request = new YiYanChatCompletionRequest(messageList); - // 复制 qianWenOptions 属性取 request(这里 options 属性和 request 基本保持一致) - // top: 由于遵循 spring-ai规范,支持在构建client的时候传入默认的 chatOptions - BeanUtil.copyProperties(useOptions, request); - request.setTop_p(useOptions.getTopP()); - request.setMax_output_tokens(useOptions.getMaxOutputTokens()); - request.setTemperature(useOptions.getTemperature()); - request.setSystem(systemPrompt); - // 设置 stream - request.setStream(stream); - return request; - } - - private @NotNull YiYanOptions getYiYanOptions(Prompt prompt) { - // 两个都为null 则没有配置文件 - if (yiYanOptions == null && prompt.getOptions() == null) { - throw new ChatException("ChatOptions 未配置参数!"); - } - // 优先使用 Prompt 里面的 ChatOptions - ChatOptions options = yiYanOptions; - if (prompt.getOptions() != null) { - options = (ChatOptions) prompt.getOptions(); - } - // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 - if (!(options instanceof YiYanOptions)) { - throw new ChatException("Prompt 传入的不是 YiYanOptions!"); - } - // 转换 YiYanOptions - YiYanOptions useOptions = (YiYanOptions) options; - return useOptions; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatModel.java deleted file mode 100644 index 4c8b3aa..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanChatModel.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 一言模型 - * - * 可参考百度文档:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t - * - * author: fansili - * time: 2024/3/9 12:01 - */ -@Getter -@AllArgsConstructor -public enum YiYanChatModel { - - ERNIE4_0("ERNIE 4.0", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro"), - ERNIE4_3_5_8K("ERNIE-3.5-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"), - ERNIE4_3_5_8K_0205("ERNIE-3.5-8K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-0205"), - - ERNIE4_3_5_8K_1222("ERNIE-3.5-8K-1222", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-1222"), - ERNIE4_BOT_8K("ERNIE-Bot-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie_bot_8k"), - ERNIE4_3_5_4K_0205("ERNIE-3.5-4K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-4k-0205"), - - ; - - private String model; - - private String uri; - - public static YiYanChatModel valueOfModel(String model) { - for (YiYanChatModel itemEnum : YiYanChatModel.values()) { - if (itemEnum.getModel().equals(model)) { - return itemEnum; - } - } - throw new IllegalArgumentException("Invalid MessageType value: " + model); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanOptions.java deleted file mode 100644 index 84f0ced..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanOptions.java +++ /dev/null @@ -1,142 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan; - -import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletionRequest; -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.List; - -/** - * 百度 问心一言 - * - * 文档地址:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t - * - * author: fansili - * time: 2024/3/16 19:33 - */ -@Data -@Accessors(chain = true) -public class YiYanOptions implements ChatOptions { - - /** - * 一个可触发函数的描述列表,说明: - * (1)支持的function数量无限制 - * (2)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens - * 必填:否 - */ - private List functions; - /** - * 说明: - * (1)较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 - * (2)默认0.8,范围 (0, 1.0],不能为0 - * 必填:否 - */ - private Float temperature; - /** - * 说明: - * (1)影响输出文本的多样性,取值越大,生成文本的多样性越强 - * (2)默认0.8,取值范围 [0, 1.0] - * 必填:否 - */ - private Float topP; - /** - * 通过对已生成的token增加惩罚,减少重复生成的现象。说明: - * (1)值越大表示惩罚越大 - * (2)默认1.0,取值范围:[1.0, 2.0] - * - * 必填:否 - */ - private Float penalty_score; - /** - * 是否以流式接口的形式返回数据,默认false - * 必填:否 - */ - private Boolean stream; - /** - * 模型人设,主要用于人设设定,例如,你是xxx公司制作的AI助手,说明: - * (1)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens - * (2)如果同时使用system和functions,可能暂无法保证使用效果,持续进行优化 - * 必填:否 - */ - private String system; - /** - * 生成停止标识,当模型生成结果以stop中某个元素结尾时,停止文本生成。说明: - * (1)每个元素长度不超过20字符 - * (2)最多4个元素 - * 必填:否 - */ - private List stop; - /** - * 是否强制关闭实时搜索功能,默认false,表示不关闭 - * 必填:否 - */ - private Boolean disable_search; - /** - * 是否开启上角标返回,说明: - * (1)开启后,有概率触发搜索溯源信息search_info,search_info内容见响应参数介绍 - * (2)默认false,不开启 - * 必填:否 - */ - private Boolean enable_citation; - /** - * 指定模型最大输出token数,范围[2, 2048] - * 必填:否 - */ - private Integer maxOutputTokens; - /** - * 指定响应内容的格式,说明: - * (1)可选值: - * · json_object:以json格式返回,可能出现不满足效果情况 - * · text:以文本格式返回 - * (2)如果不填写参数response_format值,默认为text - * 必填:否 - */ - private String response_format; - /** - * 表示最终用户的唯一标识符 - * 必填:否 - */ - private String user_id; - /** - * 在函数调用场景下,提示大模型选择指定的函数(非强制),说明:指定的函数名必须在functions中存在 - * 必填:否 - * - * ERNIE-4.0-8K 模型没有这个字段 - */ - private String tool_choice; - - // - // 以下兼容 spring-ai ChatOptions 暂时没有其他地方用到 - - @Override - public Float getTemperature() { - return this.temperature; - } - - @Override - public void setTemperature(Float temperature) { - this.temperature = temperature; - } - - @Override - public Float getTopP() { - return topP; - } - - @Override - public void setTopP(Float topP) { - this.topP = topP; - } - - // 百度么有 topK - - @Override - public Integer getTopK() { - return null; - } - - @Override - public void setTopK(Integer topK) { - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java deleted file mode 100644 index ad1059b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java +++ /dev/null @@ -1,98 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan.api; - -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatModel; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanAuthRes; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletion; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletionRequest; -import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; -import lombok.Data; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * 文心一言 - *

- * author: fansili - * time: 2024/3/8 21:47 - */ -@Data -public class YiYanApi { - - private static final String DEFAULT_BASE_URL = "https://aip.baidubce.com"; - - private static final String AUTH_2_TOKEN_URI = "/oauth/2.0/token"; - - public static final String DEFAULT_CHAT_MODEL = "ERNIE 4.0"; - - // 获取access_token流程 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 - private String appKey; - private String secretKey; - private String token; - // token刷新时间(秒) - private int refreshTokenSecondTime; - // 发送请求 webClient - private final WebClient webClient; - // 使用的模型 - private YiYanChatModel useChatModel; - - public YiYanApi(String appKey, String secretKey, YiYanChatModel useChatModel, int refreshTokenSecondTime) { - this.appKey = appKey; - this.secretKey = secretKey; - this.useChatModel = useChatModel; - this.refreshTokenSecondTime = refreshTokenSecondTime; - - this.webClient = WebClient.builder() - .baseUrl(DEFAULT_BASE_URL) - .build(); - - token = getToken(); - } - - private String getToken() { - // 文档地址: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 - ResponseEntity response = this.webClient.post() - .uri(uriBuilder -> uriBuilder.path(AUTH_2_TOKEN_URI) - .queryParam("grant_type", "client_credentials") - .queryParam("client_id", appKey) - .queryParam("client_secret", secretKey) - .build() - ) - .retrieve() - .toEntity(YiYanAuthRes.class) - .block(); - // 检查请求状态 - if (HttpStatusCode.valueOf(200) != response.getStatusCode()) { - throw new YiYanApiException("一言认证失败! api:https://aip.baidubce.com/oauth/2.0/token 请检查 client_id、client_secret 是否正确!"); - } - YiYanAuthRes body = response.getBody(); - return body.getAccess_token(); - } - - public ResponseEntity chatCompletionEntity(YiYanChatCompletionRequest request) { - // TODO: 2024/3/10 小范 这里错误信息返回的结构不一样 -// {"error_code":17,"error_msg":"Open api daily request limit reached"} - return this.webClient.post() - .uri(uriBuilder - -> uriBuilder.path(useChatModel.getUri()) - .queryParam("access_token", token) - .build()) - .body(Mono.just(request), YiYanChatCompletionRequest.class) - .retrieve() - .toEntity(YiYanChatCompletion.class) - .block(); - } - - public Flux chatCompletionStream(YiYanChatCompletionRequest request) { - return this.webClient.post() - .uri(uriBuilder - -> uriBuilder.path(useChatModel.getUri()) - .queryParam("access_token", token) - .build()) - .body(Mono.just(request), YiYanChatCompletionRequest.class) - .retrieve() - .bodyToFlux(YiYanChatCompletion.class); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanAuthRes.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanAuthRes.java deleted file mode 100644 index e828858..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanAuthRes.java +++ /dev/null @@ -1,48 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan.api; - -import lombok.Data; - -/** - * 一言 获取access_token - * - * author: fansili - * time: 2024/3/10 08:51 - */ -@Data -public class YiYanAuthRes { - - /** - * 访问凭证 - */ - private String access_token; - /** - * 有效期,Access Token的有效期。 - * 说明:单位是秒,有效期30天 - */ - private int expires_in; - /** - * 错误码,说明:响应失败时返回该字段,成功时不返回 - */ - private String error; - /** - * 错误描述信息,帮助理解和解决发生的错误 - * 说明:响应失败时返回该字段,成功时不返回 - */ - private String error_description; - /** - * 暂时未使用,可忽略 - */ - private String session_key; - /** - * 暂时未使用,可忽略 - */ - private String refresh_token; - /** - * 暂时未使用,可忽略 - */ - private String scope; - /** - * 暂时未使用,可忽略 - */ - private String session_secret; -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletion.java deleted file mode 100644 index b995da8..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletion.java +++ /dev/null @@ -1,91 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan.api; - -import lombok.Data; - -/** - * 聊天返回 - * 百度链接: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t - * - * author: fansili - * time: 2024/3/9 10:34 - */ -@Data -public class YiYanChatCompletion { - - /** - * 本轮对话的id - */ - private String id; - /** - * 回包类型,chat.completion:多轮对话返回 - */ - private String object; - /** - * 时间戳 - */ - private int created; - /** - * 表示当前子句的序号。只有在流式接口模式下会返回该字段 - */ - private int sentence_id; - /** - * 表示当前子句是否是最后一句。只有在流式接口模式下会返回该字段 - */ - private boolean is_end; - /** - * 当前生成的结果是否被截断 - */ - private boolean is_truncated; - /** - * 输出内容标识,说明: - * · normal:输出内容完全由大模型生成,未触发截断、替换 - * · stop:输出结果命中入参stop中指定的字段后被截断 - * · length:达到了最大的token数,根据EB返回结果is_truncated来截断 - * · content_filter:输出内容被截断、兜底、替换为**等 - */ - private String finish_reason; - /** - * 搜索数据,当请求参数enable_citation为true并且触发搜索时,会返回该字段 - */ - private String search_info; - /** - * 对话返回结果 - */ - private String result; - /** - * 表示用户输入是否存在安全,是否关闭当前会话,清理历史会话信息 - * true:是,表示用户输入存在安全风险,建议关闭当前会话,清理历史会话信息 - * false:否,表示用户输入无安全风险 - */ - private boolean need_clear_history; - /** - * 说明: - * · 0:正常返回 - * · 其他:非正常 - */ - private int flag; - /** - * 当need_clear_history为true时,此字段会告知第几轮对话有敏感信息,如果是当前问题,ban_round=-1 - */ - private int ban_round; - /** - * token统计信息 - */ - private Usage usage; - - @Data - public static class Usage { - /** - * 问题tokens数 - */ - private int prompt_tokens; - /** - * 回答tokens数 - */ - private int completion_tokens; - /** - * tokens总数 - */ - private int total_tokens; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionMessage.java deleted file mode 100644 index ce6259c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionMessage.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan.api; - -/** - * author: fansili - * time: 2024/3/9 10:37 - */ -public class YiYanChatCompletionMessage { -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionRequest.java deleted file mode 100644 index de6c19e..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanChatCompletionRequest.java +++ /dev/null @@ -1,178 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan.api; - -import lombok.Data; - -import java.util.List; - -/** - * 一言 Completion req - * - * 百度千帆文档:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/jlil56u11 - * - * author: fansili - * time: 2024/3/9 10:34 - */ -@Data -public class YiYanChatCompletionRequest { - - public YiYanChatCompletionRequest(List messages) { - this.messages = messages; - } - - /** - * 聊天上下文信息。 - * 必填:是 - */ - private List messages; - /** - * 一个可触发函数的描述列表,说明: - * (1)支持的function数量无限制 - * (2)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens - * 必填:否 - */ - private List functions; - /** - * 说明: - * (1)较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 - * (2)默认0.8,范围 (0, 1.0],不能为0 - * 必填:否 - */ - private Float temperature; - /** - * 说明: - * (1)影响输出文本的多样性,取值越大,生成文本的多样性越强 - * (2)默认0.8,取值范围 [0, 1.0] - * 必填:否 - */ - private Float top_p; - /** - * 通过对已生成的token增加惩罚,减少重复生成的现象。说明: - * (1)值越大表示惩罚越大 - * (2)默认1.0,取值范围:[1.0, 2.0] - * - * 必填:否 - */ - private Float penalty_score; - /** - * 是否以流式接口的形式返回数据,默认false - * 必填:否 - */ - private Boolean stream; - /** - * 模型人设,主要用于人设设定,例如,你是xxx公司制作的AI助手,说明: - * (1)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens - * (2)如果同时使用system和functions,可能暂无法保证使用效果,持续进行优化 - * 必填:否 - */ - private String system; - /** - * 生成停止标识,当模型生成结果以stop中某个元素结尾时,停止文本生成。说明: - * (1)每个元素长度不超过20字符 - * (2)最多4个元素 - * 必填:否 - */ - private List stop; - /** - * 是否强制关闭实时搜索功能,默认false,表示不关闭 - * 必填:否 - */ - private Boolean disable_search; - /** - * 是否开启上角标返回,说明: - * (1)开启后,有概率触发搜索溯源信息search_info,search_info内容见响应参数介绍 - * (2)默认false,不开启 - * 必填:否 - */ - private Boolean enable_citation; - /** - * 指定模型最大输出token数,范围[2, 2048] - * 必填:否 - */ - private Integer max_output_tokens; - /** - * 指定响应内容的格式,说明: - * (1)可选值: - * · json_object:以json格式返回,可能出现不满足效果情况 - * · text:以文本格式返回 - * (2)如果不填写参数response_format值,默认为text - * 必填:否 - */ - private String response_format; - /** - * 表示最终用户的唯一标识符 - * 必填:否 - */ - private String user_id; - /** - * 在函数调用场景下,提示大模型选择指定的函数(非强制),说明:指定的函数名必须在functions中存在 - * 必填:否 - * - * ERNIE-4.0-8K 模型没有这个字段 - */ - private String tool_choice; - - - @Data - public static class Message { - private String role; - - private String content; - } - - @Data - public static class ToolChoice { - /** - * 指定工具类型,function - * 必填: 是 - */ - private String type; - /** - * 指定要使用的函数 - * 必填: 是 - */ - private Function function; - /** - * 指定要使用的函数名 - * 必填: 是 - */ - private String name; - } - - @Data - public static class Function { - /** - * 函数名 - * 必填: 是 - */ - private String name; - /** - * 函数描述 - * 必填: 是 - */ - private String description; - /** - * 函数请求参数,说明: - * (1)JSON Schema 格式,参考JSON Schema描述 - * (2)如果函数没有请求参数,parameters值格式如下: - * {"type": "object","properties": {}} - * 必填: 是 - */ - private String parameters; - /** - * 函数响应参数,JSON Schema 格式,参考JSON Schema描述 - * 必填: 否 - */ - private String responses; - /** - * function调用的一些历史示例,说明: - * (1)可以提供正例(正常触发)和反例(无需触发)的example - * ·正例:从历史请求数据中获取 - * ·反例: - * 当role = user,不会触发请求的query - * 当role = assistant,有固定的格式。function_call的name为空,arguments是空对象:"{}",thought可以填固定的:"我不需要调用任何工具" - * (2)兼容之前的 List(example) 格式 - */ - private String examples; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/exception/YiYanApiException.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/exception/YiYanApiException.java deleted file mode 100644 index dbb8ed9..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/exception/YiYanApiException.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan.exception; - -/** - * 一言 api 调用异常 - */ -public class YiYanApiException extends RuntimeException { - - public YiYanApiException(String message) { - super(message); - } - - public YiYanApiException(String message, Throwable cause) { - super(message, cause); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index 6eedb59..88581f8 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -1,25 +1,25 @@ package cn.iocoder.yudao.framework.ai.config; import cn.hutool.core.io.IoUtil; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatModal; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenOptions; -import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatClient; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoOptions; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoApi; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatClient; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanOptions; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanApi; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageApi; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageClient; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; -import cn.iocoder.yudao.framework.ai.midjourney.api.MidjourneyInteractionsApi; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyMessageHandler; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyWebSocketStarter; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MidjourneyMessageListener; +import org.springframework.ai.models.tongyi.QianWenChatClient; +import org.springframework.ai.models.tongyi.QianWenChatModal; +import org.springframework.ai.models.tongyi.QianWenOptions; +import org.springframework.ai.models.tongyi.api.QianWenApi; +import org.springframework.ai.models.xinghuo.XingHuoChatClient; +import org.springframework.ai.models.xinghuo.XingHuoOptions; +import org.springframework.ai.models.xinghuo.api.XingHuoApi; +import org.springframework.ai.models.yiyan.YiYanChatClient; +import org.springframework.ai.models.yiyan.YiYanOptions; +import org.springframework.ai.models.yiyan.api.YiYanApi; +import org.springframework.ai.models.openai.OpenAiImageApi; +import org.springframework.ai.models.openai.OpenAiImageClient; +import org.springframework.ai.models.openai.OpenAiImageOptions; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.MidjourneyMessage; +import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi; +import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler; +import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter; +import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.boot.autoconfigure.AutoConfiguration; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java index 11246df..1f17a12 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java @@ -1,12 +1,11 @@ package cn.iocoder.yudao.framework.ai.config; -import cn.iocoder.yudao.framework.ai.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatModel; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoOptions; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatModel; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; +import org.springframework.ai.models.xinghuo.XingHuoChatModel; +import org.springframework.ai.models.xinghuo.XingHuoOptions; +import org.springframework.ai.models.yiyan.YiYanChatModel; import lombok.Data; import lombok.experimental.Accessors; -import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.LinkedHashMap; import java.util.Map; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java index 57ef69e..a23b910 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.framework.ai.config; -import cn.iocoder.yudao.framework.ai.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatModel; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatModel; -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageModelEnum; -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageStyleEnum; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; +import org.springframework.ai.models.xinghuo.XingHuoChatModel; +import org.springframework.ai.models.yiyan.YiYanChatModel; +import org.springframework.ai.models.openai.enums.OpenAiImageModelEnum; +import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum; import lombok.Data; import lombok.experimental.Accessors; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java new file mode 100644 index 0000000..590797d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.framework.ai.core.enums; + +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +// TODO 芋艿:这块,看看要不要调整下; +/** + * ai 模型平台 + * + * author: fansili + * time: 2024/3/11 10:12 + */ +@Getter +@AllArgsConstructor +public enum AiPlatformEnum { + + YI_YAN("yiyan", "一言"), + QIAN_WEN("qianwen", "千问"), + XING_HUO("xinghuo", "星火"), + OPENAI("OpenAI", "OpenAI"), + OPEN_AI_DALL("dall", "dall"), + MIDJOURNEY("midjourney", "midjourney"), + + ; + + private String platform; + private String name; + + public static List CHAT_PLATFORM_LIST = Lists.newArrayList( + AiPlatformEnum.YI_YAN, + AiPlatformEnum.QIAN_WEN, + AiPlatformEnum.XING_HUO, + AiPlatformEnum.OPENAI + ); + + public static List IMAGE_PLATFORM_LIST = Lists.newArrayList( + AiPlatformEnum.OPEN_AI_DALL, + AiPlatformEnum.MIDJOURNEY + ); + + public static AiPlatformEnum validatePlatform(String platform) { + for (AiPlatformEnum platformEnum : AiPlatformEnum.values()) { + if (platformEnum.getPlatform().equals(platform)) { + return platformEnum; + } + } + throw new IllegalArgumentException("非法平台: " + platform); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/exception/AiException.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/exception/AiException.java new file mode 100644 index 0000000..bad13c6 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/exception/AiException.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.framework.ai.core.exception; + +// TODO @fan:这个有办法干掉么? +/** + * ai 异常 + * + * @author fansili + * @time 2024/4/13 17:05 + * @since 1.0 + */ +public class AiException extends RuntimeException { + + public AiException(String message) { + super(message); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/package-info.java new file mode 100644 index 0000000..b728517 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 芋道 AI Starter,整体参考 spring-ai 拓展 + */ +package cn.iocoder.yudao.framework.ai.core; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/exception/AiException.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/exception/AiException.java deleted file mode 100644 index d98b7af..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/exception/AiException.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.framework.ai.exception; - -/** - * ai 异常 - * - * @author fansili - * @time 2024/4/13 17:05 - * @since 1.0 - */ -public class AiException extends RuntimeException { - - public AiException(String message) { - super(message); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/Image.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/Image.java deleted file mode 100644 index d6fc44d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/Image.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - -import java.util.Objects; - -public class Image { - - /** - * 可以访问图像的URL。 - * - * The URL where the image can be accessed. - */ - private String url; - - /** - * Base64编码的图像字符串。 - * - * Base64 encoded image string. - */ - private String b64Json; - - public Image(String url, String b64Json) { - this.url = url; - this.b64Json = b64Json; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getB64Json() { - return b64Json; - } - - public void setB64Json(String b64Json) { - this.b64Json = b64Json; - } - - @Override - public String toString() { - return "Image{" + "url='" + url + '\'' + ", b64Json='" + b64Json + '\'' + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof Image image)) - return false; - return Objects.equals(url, image.url) && Objects.equals(b64Json, image.b64Json); - } - - @Override - public int hashCode() { - return Objects.hash(url, b64Json); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageClient.java deleted file mode 100644 index 98fc44f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageClient.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - - -import cn.iocoder.yudao.framework.ai.model.ModelClient; - -public interface ImageClient extends ModelClient { - - /** - * 跟 chat一样 - * @param imagePrompt the request object to be sent to the AI model - * @return - */ - ImageResponse call(ImagePrompt imagePrompt); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGeneration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGeneration.java deleted file mode 100644 index 5ec655d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGeneration.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - - -import cn.iocoder.yudao.framework.ai.model.ModelResult; - -public class ImageGeneration implements ModelResult { - - // metadata 信息为空现在 - private ImageGenerationMetadata imageGenerationMetadata; - - private Image image; - - public ImageGeneration(Image image) { - this.image = image; - } - - public ImageGeneration(Image image, ImageGenerationMetadata imageGenerationMetadata) { - this.image = image; - this.imageGenerationMetadata = imageGenerationMetadata; - } - - @Override - public Image getOutput() { - return image; - } - - @Override - public ImageGenerationMetadata getMetadata() { - return imageGenerationMetadata; - } - - @Override - public String toString() { - return "ImageGeneration{" + "imageGenerationMetadata=" + imageGenerationMetadata + ", image=" + image + '}'; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGenerationMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGenerationMetadata.java deleted file mode 100644 index 1d620d2..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageGenerationMetadata.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - -import cn.iocoder.yudao.framework.ai.model.ResultMetadata; - -public interface ImageGenerationMetadata extends ResultMetadata { - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageMessage.java deleted file mode 100644 index 6a01c6c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageMessage.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - -import java.util.Objects; - -public class ImageMessage { - - private String text; - - private Float weight; - - public ImageMessage(String text) { - this.text = text; - } - - public ImageMessage(String text, Float weight) { - this.text = text; - this.weight = weight; - } - - public String getText() { - return text; - } - - public Float getWeight() { - return weight; - } - - @Override - public String toString() { - return "mageMessage{" + "text='" + text + '\'' + ", weight=" + weight + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ImageMessage that)) - return false; - return Objects.equals(text, that.text) && Objects.equals(weight, that.weight); - } - - @Override - public int hashCode() { - return Objects.hash(text, weight); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptions.java deleted file mode 100644 index d9cfd0e..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptions.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - -import cn.iocoder.yudao.framework.ai.model.ModelOptions; - -/** - * ImageOptions represent the common options, portable across different image generation - * models. - */ -public interface ImageOptions extends ModelOptions { - - Integer getN(); - - String getModel(); - - Integer getWidth(); - - Integer getHeight(); - - String getResponseFormat(); // openai - url or base64 : stability ai byte[] or base64 - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptionsBuilder.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptionsBuilder.java deleted file mode 100644 index d1bc474..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageOptionsBuilder.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - -public class ImageOptionsBuilder { - - private class ImageModelOptionsImpl implements ImageOptions { - - private Integer n; - - private String model; - - private Integer width; - - private Integer height; - - private String responseFormat; - - @Override - public Integer getN() { - return n; - } - - public void setN(Integer n) { - this.n = n; - } - - @Override - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - @Override - public String getResponseFormat() { - return responseFormat; - } - - public void setResponseFormat(String responseFormat) { - this.responseFormat = responseFormat; - } - - @Override - public Integer getWidth() { - return width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - @Override - public Integer getHeight() { - return height; - } - - public void setHeight(Integer height) { - this.height = height; - } - - } - - private final ImageModelOptionsImpl options = new ImageModelOptionsImpl(); - - private ImageOptionsBuilder() { - - } - - public static ImageOptionsBuilder builder() { - return new ImageOptionsBuilder(); - } - - public ImageOptionsBuilder withN(Integer n) { - options.setN(n); - return this; - } - - public ImageOptionsBuilder withModel(String model) { - options.setModel(model); - return this; - } - - public ImageOptionsBuilder withResponseFormat(String responseFormat) { - options.setResponseFormat(responseFormat); - return this; - } - - public ImageOptionsBuilder withWidth(Integer width) { - options.setWidth(width); - return this; - } - - public ImageOptionsBuilder withHeight(Integer height) { - options.setHeight(height); - return this; - } - - public ImageOptions build() { - return options; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImagePrompt.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImagePrompt.java deleted file mode 100644 index 75ec519..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImagePrompt.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - -import cn.iocoder.yudao.framework.ai.model.ModelRequest; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * 图片内容 - */ -public class ImagePrompt implements ModelRequest> { - - private final List messages; - - private ImageOptions imageModelOptions; - - public ImagePrompt(List messages) { - this.messages = messages; - } - - public ImagePrompt(List messages, ImageOptions imageModelOptions) { - this.messages = messages; - this.imageModelOptions = imageModelOptions; - } - - public ImagePrompt(ImageMessage imageMessage, ImageOptions imageOptions) { - this(Collections.singletonList(imageMessage), imageOptions); - } - - public ImagePrompt(String instructions, ImageOptions imageOptions) { - this(new ImageMessage(instructions), imageOptions); - } - - public ImagePrompt(String instructions) { -// this(new ImageMessage(instructions), ImageOptionsBuilder.builder().build()); - this(new ImageMessage(instructions), null); - } - - @Override - public List getInstructions() { - return messages; - } - - @Override - public ImageOptions getOptions() { - return imageModelOptions; - } - - @Override - public String toString() { - return "NewImagePrompt{" + "messages=" + messages + ", imageModelOptions=" + imageModelOptions + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ImagePrompt that)) - return false; - return Objects.equals(messages, that.messages) && Objects.equals(imageModelOptions, that.imageModelOptions); - } - - @Override - public int hashCode() { - return Objects.hash(messages, imageModelOptions); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponse.java deleted file mode 100644 index ca91be5..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponse.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - -import cn.iocoder.yudao.framework.ai.model.ModelResponse; - -import java.util.List; -import java.util.Objects; - -public class ImageResponse implements ModelResponse { - - private final ImageResponseMetadata imageResponseMetadata; - - private final List imageGenerations; - - public ImageResponse(List generations) { - this(generations, ImageResponseMetadata.NULL); - } - - public ImageResponse(List generations, ImageResponseMetadata imageResponseMetadata) { - this.imageResponseMetadata = imageResponseMetadata; - this.imageGenerations = List.copyOf(generations); - } - - @Override - public ImageGeneration getResult() { - return imageGenerations.get(0); - } - - @Override - public List getResults() { - return imageGenerations; - } - - @Override - public ImageResponseMetadata getMetadata() { - return imageResponseMetadata; - } - - @Override - public String toString() { - return "ImageResponse{" + "imageResponseMetadata=" + imageResponseMetadata + ", imageGenerations=" - + imageGenerations + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ImageResponse that)) - return false; - return Objects.equals(imageResponseMetadata, that.imageResponseMetadata) - && Objects.equals(imageGenerations, that.imageGenerations); - } - - @Override - public int hashCode() { - return Objects.hash(imageResponseMetadata, imageGenerations); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponseMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponseMetadata.java deleted file mode 100644 index c4332c6..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/image/ImageResponseMetadata.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.image; - - -import cn.iocoder.yudao.framework.ai.model.ResponseMetadata; - -public interface ImageResponseMetadata extends ResponseMetadata { - - ImageResponseMetadata NULL = new ImageResponseMetadata() { - }; - - default Long created() { - return System.currentTimeMillis(); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java deleted file mode 100644 index efc15a3..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java +++ /dev/null @@ -1,89 +0,0 @@ -package cn.iocoder.yudao.framework.ai.imageopenai; - -import cn.hutool.json.JSONUtil; -import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest; -import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import io.netty.channel.ChannelOption; -import lombok.extern.slf4j.Slf4j; -import org.apache.http.HttpEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.netty.http.client.HttpClient; - -import java.io.IOException; -import java.net.URI; -import java.time.Duration; - -/** - * open ai image - *

- * author: fansili - * time: 2024/3/17 09:53 - */ -@Slf4j -public class OpenAiImageApi { - - private static final String DEFAULT_BASE_URL = "https://api.openai.com"; - private String apiKey = "your-api-key"; - // 发送请求 webClient - private final WebClient webClient; - - private CloseableHttpClient httpclient = HttpClients.createDefault(); - - public OpenAiImageApi(String apiKey) { - this.apiKey = apiKey; - // 创建一个HttpClient实例并设置超时 - HttpClient httpClient = HttpClient.create() - .responseTimeout(Duration.ofSeconds(300)) // 设置响应超时时间为30秒 - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 * 100); // 设置连接超时为5秒 - this.webClient = WebClient.builder() - .baseUrl(DEFAULT_BASE_URL) - .clientConnector(new ReactorClientHttpConnector(httpClient)) - .build(); - } - - public OpenAiImageResponse createImage(OpenAiImageRequest request) { - HttpPost httpPost = new HttpPost(); - httpPost.setURI(URI.create(DEFAULT_BASE_URL.concat("/v1/images/generations"))); - httpPost.setHeader("Content-Type", "application/json"); - httpPost.setHeader("Authorization", "Bearer " + apiKey); - httpPost.setEntity(new StringEntity(JsonUtils.toJsonString(request), "UTF-8")); - - CloseableHttpResponse response= null; - try { - response = httpclient.execute(httpPost); - HttpEntity entity = response.getEntity(); - String resultJson = EntityUtils.toString(entity); - log.info("openai 图片生成结果: {}", resultJson); - return JSONUtil.toBean(resultJson, OpenAiImageResponse.class); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (response != null) { - try { - response.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } -// String res = webClient.post() -// .uri(uriBuilder -> uriBuilder.path("/v1/images/generations").build()) -// .header("Content-Type", "application/json") -// .header("Authorization", "Bearer " + apiKey) -// // 设置请求体(这里假设jsonStr是一个JSON格式的字符串) -// .body(BodyInserters.fromValue(JacksonUtil.toJson(request))) -// // 发送请求并获取响应体 -// .retrieve() -// // 转换响应体为String类型 -// .bodyToMono(String.class) -// .block(); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageClient.java deleted file mode 100644 index 4dd536f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageClient.java +++ /dev/null @@ -1,101 +0,0 @@ -package cn.iocoder.yudao.framework.ai.imageopenai; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.codec.Base64; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpUtil; -import cn.iocoder.yudao.framework.ai.chat.ChatException; -import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; -import cn.iocoder.yudao.framework.ai.exception.AiException; -import cn.iocoder.yudao.framework.ai.image.*; -import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest; -import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; - -import java.time.Duration; - -/** - * open ai 生成 image - * - * author: fansili - * time: 2024/3/17 09:51 - */ -@Slf4j -public class OpenAiImageClient implements ImageClient { - - /** - * open image ai - */ - private OpenAiImageApi openAiImageApi; - /** - * 默认使用的 ImageOptions - */ - private OpenAiImageOptions defaultImageOptions; - - - public final RetryTemplate retryTemplate = RetryTemplate.builder() - // 最大重试次数 10 - .maxAttempts(10) - .retryOn(YiYanApiException.class) - // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms - .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) - .withListener(new RetryListener() { - @Override - public void onError(RetryContext context, - RetryCallback callback, Throwable throwable) { - log.warn("重试异常:" + context.getRetryCount(), throwable); - }; - }) - .build(); - - public OpenAiImageClient(OpenAiImageApi openAiImageApi, OpenAiImageOptions defaultImageOptions) { - this.openAiImageApi = openAiImageApi; - this.defaultImageOptions = defaultImageOptions; - } - - @Override - public ImageResponse call(ImagePrompt imagePrompt) { - return this.retryTemplate.execute(ctx -> { - OpenAiImageOptions openAiImageOptions = getOpenAiImageOptions(imagePrompt); - // 创建请求 - OpenAiImageRequest request = new OpenAiImageRequest(); - BeanUtil.copyProperties(openAiImageOptions, request); - request.setPrompt(imagePrompt.getInstructions().get(0).getText()); - request.setModel(openAiImageOptions.getModel()); - request.setStyle(openAiImageOptions.getStyle().getStyle()); - request.setSize(openAiImageOptions.getSize()); - // 发送请求 - OpenAiImageResponse response = openAiImageApi.createImage(request); - if (response.getError() != null && !StrUtil.isBlank(response.getError().getMessage())) { - // code 错误没有编码,就先根据 message 来进行判断 - throw new AiException("openAi 图片生成失败! " + response.getError().getMessage()); - } - return new ImageResponse(response.getData().stream().map(res -> { - byte[] bytes = HttpUtil.downloadBytes(res.getUrl()); - String base64 = Base64.encode(bytes); - return new ImageGeneration(new Image(res.getUrl(), base64)); - }).toList()); - }); - } - - private @NotNull OpenAiImageOptions getOpenAiImageOptions(ImagePrompt imagePrompt) { - // 检查是否配置了 OpenAiImageOptions - if (defaultImageOptions == null && imagePrompt.getOptions() == null) { - throw new ChatException("OpenAiImageOptions 未配置参数!"); - } - // 优先使用 request 中的 ImageOptions - ImageOptions useImageOptions = imagePrompt.getOptions() == null ? defaultImageOptions : imagePrompt.getOptions(); - if (!(useImageOptions instanceof OpenAiImageOptions)) { - throw new ChatException("配置信息不正确,传入的必须是 OpenAiImageOptions!"); - } - // 转换 OpenAiImageOptions - OpenAiImageOptions openAiImageOptions = (OpenAiImageOptions) useImageOptions; - return openAiImageOptions; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageOptions.java deleted file mode 100644 index c2fa748..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageOptions.java +++ /dev/null @@ -1,95 +0,0 @@ -package cn.iocoder.yudao.framework.ai.imageopenai; - -import cn.iocoder.yudao.framework.ai.image.ImageOptions; -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageModelEnum; -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageStyleEnum; -import lombok.Data; -import lombok.Getter; -import lombok.experimental.Accessors; - -/** - * open ai 配置文件 - * - * 文档地址:https://platform.openai.com/docs/api-reference/images/create - * - * author: fansili - * time: 2024/3/17 09:53 - */ -@Data -@Accessors(chain = true) -public class OpenAiImageOptions implements ImageOptions { - - // 必填字段,用于描述期望生成图像的文字说明。对于dall-e-2模型最大长度为1000个字符,对于dall-e-3模型最大长度为4000个字符。 - private String prompt; - - // 可选字段,默认为dall-e-2 - // 指定用于生成图像的模型名称。 - private OpenAiImageModelEnum model = OpenAiImageModelEnum.DALL_E_2; - - // 可选字段,默认为1 - // 生成图像的数量,必须在1到10之间。对于dall-e-3模型,目前仅支持n=1。 - private Integer n = 1; - - // 可选字段,默认为standard - // 设置生成图像的质量。hd质量将创建细节更丰富、图像整体一致性更高的图片。该参数仅对dall-e-3模型有效。 - private String quality = "standard"; - - // 可选字段,默认为url - // 返回生成图像的格式。必须是url或b64_json中的一种。URL链接的有效期是从生成图像后开始计算的60分钟内有效。 - private String responseFormat = "url"; - - // 可选字段,默认为1024x1024 - // 生成图像的尺寸大小。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。 - private String size = "1024x1024"; - - // 可选字段,默认为vivid - // 图像生成的风格。可为vivid(生动)或natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 - private OpenAiImageStyleEnum style = OpenAiImageStyleEnum.VIVID; - - // 可选字段 - // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。 - private String endUserId = "UID123456"; - - @Getter - public enum ResponseFormatEnum { - - URL("url"), - BASE64("b64_json"), - - ; - - ResponseFormatEnum(String value) { - this.value = value; - } - - private String value; - } - - // - // 适配 spring ai - - @Override - public Integer getN() { - return this.n; - } - - @Override - public String getModel() { - return this.model.getModel(); - } - - @Override - public Integer getWidth() { - return null; - } - - @Override - public Integer getHeight() { - return null; - } - - @Override - public String getResponseFormat() { - return this.responseFormat; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageRequest.java deleted file mode 100644 index 44ee174..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageRequest.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.yudao.framework.ai.imageopenai.api; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import lombok.experimental.Accessors; - -/** - * open ai 配置文件 - * - * 文档地址:https://platform.openai.com/docs/api-reference/images/create - * - * author: fansili - * time: 2024/3/17 09:53 - */ -@Data -@Accessors(chain = true) -public class OpenAiImageRequest { - - // 必填字段,用于描述期望生成图像的文字说明。对于dall-e-2模型最大长度为1000个字符,对于dall-e-3模型最大长度为4000个字符。 - @JsonProperty("prompt") - private String prompt; - - // 可选字段,默认为dall-e-2、dall-e-3 - // 指定用于生成图像的模型名称。 - @JsonProperty("model") - private String model = "dall-e-2"; - - // 可选字段,默认为1 - // 生成图像的数量,必须在1到10之间。对于dall-e-3模型,目前仅支持n=1。 - @JsonProperty("n") - private Integer n = 1; - - // 可选字段,默认为standard - // 设置生成图像的质量。hd质量将创建细节更丰富、图像整体一致性更高的图片。该参数仅对dall-e-3模型有效。 - @JsonProperty("quality") - private String quality = "standard"; - - // 可选字段,默认为url - // 返回生成图像的格式。必须是url或b64_json中的一种。URL链接的有效期是从生成图像后开始计算的60分钟内有效。 - @JsonProperty("response_format") - private String responseFormat = "url"; - - // 可选字段,默认为1024x1024 - // 生成图像的尺寸大小。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。 - @JsonProperty("size") - private String size = "1024x1024"; - - // 可选字段,默认为vivid - // 图像生成的风格。可为vivid(生动)或natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 - @JsonProperty("style") - private String style = "vivid"; - - // 可选字段 - // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。 - @JsonProperty("user") - private String endUserId = "UID123123"; - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageResponse.java deleted file mode 100644 index 0269896..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/api/OpenAiImageResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.framework.ai.imageopenai.api; - -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.List; - -/** - * image 返回 - * - * author: fansili - * time: 2024/3/17 10:27 - */ -@Data -@Accessors(chain = true) -public class OpenAiImageResponse { - - private long created; - private List data; - private Error error; - - @Data - @Accessors(chain = true) - public static class Item { - - private String url; - private String b64_json; - - } - - @Data - @Accessors(chain = true) - public static class Error { - private String code; - private String message; - private String param; - private String type; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageModelEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageModelEnum.java deleted file mode 100644 index 08cd2ae..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageModelEnum.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.framework.ai.imageopenai.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * open ai - * - * @author fansili - * @time 2024/4/28 14:21 - * @since 1.0 - */ -@AllArgsConstructor -@Getter -public enum OpenAiImageModelEnum { - - DALL_E_2("dall-e-2", "dall-e-2"), - - DALL_E_3("dall-e-3", "dall-e-3") - - ; - - private String model; - - private String name; - - public static OpenAiImageModelEnum valueOfModel(String model) { - for (OpenAiImageModelEnum itemEnum : OpenAiImageModelEnum.values()) { - if (itemEnum.getModel().equals(model)) { - return itemEnum; - } - } - throw new IllegalArgumentException("Invalid MessageType value: " + model); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageStyleEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageStyleEnum.java deleted file mode 100644 index 63feb85..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/enums/OpenAiImageStyleEnum.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.framework.ai.imageopenai.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * open ai image style - * - * @author fansili - * @time 2024/4/28 16:15 - * @since 1.0 - */ -@AllArgsConstructor -@Getter -public enum OpenAiImageStyleEnum { - - // 图像生成的风格。可为vivid(生动)或 natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 - - VIVID("vivid", "生动"), - NATURAL("natural", "自然"), - - ; - - private String style; - - private String name; - - public static OpenAiImageStyleEnum valueOfStyle(String style) { - for (OpenAiImageStyleEnum itemEnum : OpenAiImageStyleEnum.values()) { - if (itemEnum.getStyle().equals(style)) { - return itemEnum; - } - } - throw new IllegalArgumentException("Invalid MessageType value: " + style); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyConfig.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyConfig.java deleted file mode 100644 index 8f749ba..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyConfig.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney; - -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.Map; -import java.util.UUID; - -/** - * Midjourney 配置 - * - * author: fansili - * time: 2024/4/3 17:10 - */ -@Data -@Accessors(chain = true) -public class MidjourneyConfig { - - /** - * token信息 - * - * tip: 登录discard F12找 messages 接口 - */ - private String token; - /** - * 服务器id - */ - private String guildId; - /** - * 频道id - */ - private String channelId; - - // - // api 接口 - - /** - * 服务地址 - */ - private String serverUrl = "https://discord.com/"; - /** - * 发送命令 - */ - private String apiInteractions = "api/v9/interactions"; - /** - * 附件 - */ - private String apiAttachments = "/api/v9/channels/%s/attachments"; - /** - * 文件上传 - */ - private String apiAttachmentsUpload = "https://discord-attachments-uploads-prd.storage.googleapis.com/"; - - // - // 浏览器配置 - - private String userAage = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"; - - // - // 请求 json 文件 - - - private Map requestTemplates; - - // - // - - private String sessionId; - - public MidjourneyConfig(String token, String guildId, String channelId, Map requestTemplates) { - this.token = token; - this.guildId = guildId; - this.channelId = channelId; - this.requestTemplates = requestTemplates; - - // 生成 session id - sessionId = UUID.randomUUID().toString().replaceAll("-", ""); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java deleted file mode 100644 index 3ee83be..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java +++ /dev/null @@ -1,173 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney; - -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyGennerateStatusEnum; -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.List; - -@Data -@Accessors(chain = true) -public class MidjourneyMessage { - - /** - * id是一个重要的字段,在同时生成多个的时候,可以区分生成信息 - */ - private String id; - /** - * 提交id(nonce 可能会不存在,系统提示的时候,这个为空) - */ - private String nonce; - /** - * 现在已知: - * 0:我们发送的消息,和指令 - * 20: mj生成图片发送过程中 - * 19: 选择了某一张图片后的通知 - */ - private Integer type; - /** - * content - */ - private Content content; - /** - * 图片生成完成才有 - */ - private List components; - /** - * 生成过程中如果有,预展示图片,这里会有 - */ - private List attachments; - /** - * 原始数据(discard 返回的原始数据) - */ - private String rawData; - /** - * 生成状态(用于区分生成状态) - * 1、等待 - * 2、进行中 - * 3、完成 - * {@link MidjourneyGennerateStatusEnum} - */ - private String generateStatus; - /** - * 一般用于提示信息 - * - 错误 - * - 并发队列满了 - * - 账号违规了、敏感词 - * - 账号被封 - */ - private List embeds; - - @Data - @Accessors(chain = true) - public static class ComponentType { - - private int type; - - private List components; - } - - @Data - @Accessors(chain = true) - public static class Component { - /** - * 自定义ID,用于唯一标识特定交互动作及其上下文信息。 - */ - private String customId; - - /** - * 样式编号,用于确定按钮的样式外观。 - * 在某些应用中,例如Discord,2可能表示一种特定的颜色或形状的按钮。 - */ - private int style; - - /** - * 按钮的标签文本,用户可见的内容。 - */ - private String label; - - /** - * 组件类型,此处为2可能表示这是一种特定类型的交互组件, - * 如在Discord API中,类型2对应的是一个可点击的按钮组件。 - */ - private int type; - - } - - @Data - @Accessors(chain = true) - public static class Attachment { - // 文件名 - private String filename; - - // 附件大小(字节) - private int size; - - // 内容类型(例如:image/webp) - private String contentType; - - // 图像宽度(像素) - private int width; - - // 占位符版本号 - private int placeholderVersion; - - // 代理URL,用于访问附件资源 - private String proxyUrl; - - // 占位符标识符 - private String placeholder; - - // 附件ID - private String id; - - // 直接访问附件资源的URL - private String url; - - // 图像高度(像素) - private int height; - } - - @Data - @Accessors(chain = true) - public static class Content { - private String prompt; - private String progress; - private String status; - } - - /** - * embed 用于警告、提示、错误 - */ - @Data - @Accessors(chain = true) - public static class Embed { - - // 内容扫描版本号 - private int contentScanVersion; - - // 颜色值,这里用Java的Color类来表示,注意实际使用中可能需要自定义方法来从int转换为Color对象 - private String color; - - // 页脚信息,包含文本 - private Footer footer; - - // 描述信息 - private String description; - - // 消息类型,这里是富文本类型(这个区分不同提示类型) - private String type; - - // 标题 - private String title; - - // Footer类,作为嵌套类存在,用来表示footer部分的JSON对象 - @Data - @Accessors(chain = true) - public static class Footer { - // 页脚文本 - private String text; - } - - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java deleted file mode 100644 index 7654923..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java +++ /dev/null @@ -1,83 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.api; - -import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyConstants; -import com.google.common.collect.Maps; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -import java.util.HashMap; - -// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi - -/** - * 图片生成 - * - * author: fansili - * time: 2024/4/3 17:36 - */ -@Slf4j -public abstract class MidjourneyInteractions { - - // TODO done @fansili:静态变量,放在最前面哈; - /** - * header - referer 头信息 - */ - private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s"; - /** - * mj配置文件 - */ - protected final MidjourneyConfig midjourneyConfig; - - protected MidjourneyInteractions(MidjourneyConfig midjourneyConfig) { - this.midjourneyConfig = midjourneyConfig; - } - - /** - * 获取headers - application json - * - * @return - */ - protected HttpHeaders getHeadersOfAppJson() { - // 设置header值 - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - return httpHeaders; - } - - /** - * 获取headers - http form data - * - * @return - */ - protected HttpHeaders getHeadersOfFormData() { - // 设置header值 - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - return httpHeaders; - } - - /** - * 获取 - 默认参数 - * @return - */ - protected HashMap getDefaultParams() { - HashMap requestParams = Maps.newHashMap(); - // TODO done @fansili:感觉参数的组装,可以搞成一个公用的方法;就是 config + 入参的感觉; - requestParams.put("guild_id", midjourneyConfig.getGuildId()); - requestParams.put("channel_id", midjourneyConfig.getChannelId()); - requestParams.put("session_id", midjourneyConfig.getSessionId()); - requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); // TODO @fansili:建议用 uuid 之类的;nextId 跨进程未必合适哈; - return requestParams; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java deleted file mode 100644 index 7a578c8..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java +++ /dev/null @@ -1,149 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.api; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.api.req.AttachmentsReq; -import cn.iocoder.yudao.framework.ai.midjourney.api.req.DescribeReq; -import cn.iocoder.yudao.framework.ai.midjourney.api.req.ReRollReq; -import cn.iocoder.yudao.framework.ai.midjourney.api.res.UploadAttachmentsRes; -import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONObject; -import com.google.common.collect.Lists; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.FileSystemResource; -import org.springframework.http.*; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.util.HashMap; - -// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi -/** - * 图片生成 - * - * author: fansili - * time: 2024/4/3 17:36 - */ -@Slf4j -public class MidjourneyInteractionsApi extends MidjourneyInteractions { - - private final String url; - private final RestTemplate restTemplate = new RestTemplate(); // TODO @fansili:优先级低:后续搞到统一的管理 - - public MidjourneyInteractionsApi(MidjourneyConfig midjourneyConfig) { - super(midjourneyConfig); - this.url = midjourneyConfig.getServerUrl().concat(midjourneyConfig.getApiInteractions()); - } - - public Boolean imagine(String nonce, String prompt) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("imagine"); - // 设置参数 - HashMap requestParams = getDefaultParams(); - requestParams.put("nonce", nonce); - requestParams.put("prompt", prompt); - // 解析 template 参数占位符 - String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); - // 获取 header - HttpHeaders httpHeaders = getHeadersOfAppJson(); - // 发送请求 - HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); - String res = restTemplate.postForObject(url, requestEntity, String.class); - // 这个 res 只要不返回值,就是成功! - // TODO @fansili:可以直接 if (StrUtil.isBlank(res)) - if (StrUtil.isBlank(res)) { - return true; - } else { - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return false; - } - } - // TODO done @fansili:方法和方法之间,空一行哈; - - - public Boolean reRoll(ReRollReq reRoll) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("reroll"); - // 设置参数 - HashMap requestParams = getDefaultParams(); - requestParams.put("custom_id", reRoll.getCustomId()); - requestParams.put("message_id", reRoll.getMessageId()); - // 获取 header - HttpHeaders httpHeaders = getHeadersOfAppJson(); - // 设置参数 - String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); - // 发送请求 - HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); - String res = restTemplate.postForObject(url, requestEntity, String.class); - // 这个 res 只要不返回值,就是成功! - boolean isSuccess = StrUtil.isBlank(res); - if (isSuccess) { - return true; - } - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return isSuccess; - } - - // TODO @fansili:搞成私有方法,可能会好点; - public UploadAttachmentsRes uploadAttachments(AttachmentsReq attachments) { - // file - JSONObject fileObj = new JSONObject(); - fileObj.put("id", "0"); - fileObj.put("filename", attachments.getFileSystemResource().getFilename()); - // TODO @fansili:这块用 lombok 哪个异常处理,简化下代码; - try { - fileObj.put("file_size", attachments.getFileSystemResource().contentLength()); - } catch (IOException e) { - throw new RuntimeException(e); - } - // 创建用于存放表单数据的MultiValueMap - MultiValueMap multipartRequest = new LinkedMultiValueMap<>(); - multipartRequest.put("files", Lists.newArrayList(fileObj)); - // 设置header值 - HttpHeaders httpHeaders = getHeadersOfAppJson(); - // 创建HttpEntity对象,包含表单数据和头部信息 - HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(multipartRequest, httpHeaders); - // 发送POST请求并接收响应 - String uri = String.format(midjourneyConfig.getApiAttachments(), midjourneyConfig.getChannelId()); - String response = restTemplate.postForObject(midjourneyConfig.getServerUrl().concat(uri), multiValueMapHttpEntity, String.class); - UploadAttachmentsRes uploadAttachmentsRes = JSON.parseObject(response, UploadAttachmentsRes.class); - - // - // 上传文件 - String uploadUrl = uploadAttachmentsRes.getAttachments().getFirst().getUploadUrl(); - httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); - HttpEntity fileSystemResourceHttpEntity = new HttpEntity<>(attachments.getFileSystemResource(), httpHeaders); - ResponseEntity exchange = restTemplate.exchange(uploadUrl, HttpMethod.PUT, fileSystemResourceHttpEntity, String.class); - String uploadRes = exchange.getBody(); - return uploadAttachmentsRes; - } - - public Boolean describe(DescribeReq describe) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("describe"); - // 设置参数 - HashMap requestParams = getDefaultParams(); - requestParams.put("file_name", describe.getFileName()); - requestParams.put("final_file_name", describe.getFinalFileName()); - // 设置 header - HttpHeaders httpHeaders = getHeadersOfFormData(); - String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); - // 创建表单数据 - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("payload_json", requestBody); - // 发送请求 - HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(formData, httpHeaders); - String res = restTemplate.postForObject(url, multiValueMapHttpEntity, String.class); - // 这个 res 只要不返回值,就是成功! - boolean isSuccess = StrUtil.isBlank(res); - if (isSuccess) { - return true; - } - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return isSuccess; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/AttachmentsReq.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/AttachmentsReq.java deleted file mode 100644 index ed18a48..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/AttachmentsReq.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.api.req; - -import lombok.Data; -import lombok.experimental.Accessors; -import org.springframework.core.io.FileSystemResource; - -/** - * 附件 - *

- * author: fansili - * time: 2024/4/7 17:18 - */ -@Data -@Accessors(chain = true) -public class AttachmentsReq { - - /** - * 创建文件系统资源对象 - */ - private FileSystemResource fileSystemResource; - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/DescribeReq.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/DescribeReq.java deleted file mode 100644 index 49a4ab3..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/DescribeReq.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.api.req; - -import lombok.Data; -import lombok.experimental.Accessors; - -/** - * describe - * - * author: fansili - * time: 2024/4/7 12:30 - */ -@Data -@Accessors(chain = true) -public class DescribeReq { - - /** - * 文件名字 - */ - private String fileName; - /** - * UploadAttachmentsRes 里面的 finalFileName - */ - private String finalFileName; -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/ReRollReq.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/ReRollReq.java deleted file mode 100644 index 0d83689..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/req/ReRollReq.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.api.req; - -import lombok.Data; -import lombok.experimental.Accessors; - -/** - * author: fansili - * time: 2024/4/6 21:33 - */ -@Data -@Accessors(chain = true) -public class ReRollReq { - - /** - * socket 消息里面收到的 messageId - */ - private String messageId; - /** - * socket 消息里面的,操作按钮id(MJ::JOB::upsample::3::2aeefbef-43e2-4057-bcf1-43b5f39ab6f7) - */ - private String customId; -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/res/UploadAttachmentsRes.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/res/UploadAttachmentsRes.java deleted file mode 100644 index cba21c9..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/res/UploadAttachmentsRes.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.api.res; - -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.List; - -/** - * 上传附件 - res - * - * author: fansili - * time: 2024/4/8 13:32 - */ -@Data -@Accessors(chain = true) -public class UploadAttachmentsRes { - - private List attachments; - - @Data - @Accessors(chain = true) - public static class Attachment { - /** - * 附件的ID。 - */ - private int id; - /** - * 附件的上传URL。 - */ - private String uploadUrl; - /** - * 上传到服务器的文件名。 - */ - private String uploadFilename; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java deleted file mode 100644 index 29387a2..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -public final class MidjourneyConstants { - - /** - * 消息 - 编号 - */ - public static final String MSG_ID = "id"; - /** - * 用于区分操作唯一性 - */ - public static final String MSG_NONCE = "nonce"; - /** - * 消息 - 类型 - * 现在已知: - * 0:我们发送的消息,和指令 - * 20: mj生成图片发送过程中 - * 19: 选择了某一张图片后的通知 - */ - public static final String MSG_TYPE = "type"; - /** - * 平道id - */ - public static final String MSG_CHANNEL_ID = "channel_id"; - /** - * 内容 - * - * "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", - */ - public static final String MSG_CONTENT = "content"; - /** - * 组件(图片生成好之后才有) - */ - public static final String MSG_COMPONENTS = "components"; - /** - * 附件(生成中比较模糊的图片) - */ - public static final String MSG_ATTACHMENTS = "attachments"; - /** - * 一般用于提示 - */ - public static final String MSG_EMBEDS = "embeds"; - - - // - // - - public static final String HTTP_COOKIE = "__dcfduid=6ca536c0e3fa11eeb7cbe34c31b49caf; __sdcfduid=6ca536c1e3fa11eeb7cbe34c31b49caf52cce5ffd8983d2a052cf6aba75fe5fe566f2c265902e283ce30dbf98b8c9c93; _gcl_au=1.1.245923998.1710853617; _ga=GA1.1.111061823.1710853617; __cfruid=6385bb3f48345a006b25992db7dcf984e395736d-1712124666; _cfuvid=O09la5ms0ypNptiG0iD8A6BKWlTxz1LG0WR7qRStD7o-1712124666575-0.0.1.1-604800000; locale=zh-CN; cf_clearance=l_YGod1_SUtYxpDVeZXiX7DLLPl1DYrquZe8WVltvYs-1712124668-1.0.1.1-Hl2.fToel23EpF2HCu9J20rB4D7OhhCzoajPSdo.9Up.wPxhvq22DP9RHzEBKuIUlKyH.kJLxXJfAt2N.LD5WQ; OptanonConsent=isIABGlobal=false&datestamp=Wed+Apr+03+2024+14%3A11%3A15+GMT%2B0800+(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)&version=6.33.0&hosts=&landingPath=https%3A%2F%2Fdiscord.com%2F&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1; _ga_Q149DFWHT7=GS1.1.1712124668.4.1.1712124679.0.0.0"; -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java deleted file mode 100644 index ef7147b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -// TODO done @fansili:1)Mj 缩写,还是搞成全称。。虽然长一点,但是感觉会相对清晰一些哈;2)lombok 相关的注解,可以用用哈;3)value 改 status; -/** - * mj 生成状态 - * - * author: fansili - * time: 2024/4/6 21:07 - */ -@Getter -@AllArgsConstructor -public enum MidjourneyGennerateStatusEnum { - - WAITING("waiting", "等待..."), - IN_PROGRESS("in_progress", "进行中"), - COMPLETED("completed", "完成"), - - ; - - /** - * 状态 - */ - private String status; - /** - * 状态信息 - */ - private String message; -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java deleted file mode 100644 index 08832e7..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -import lombok.Getter; - -/** - * MJ 命令 - */ -@Getter -public enum MidjourneyInteractionsEnum { - - IMAGINE("imagine", "生成图片"), - DESCRIBE("describe", "生成描述"), - FAST("fast", "快速生成"), - SETTINGS("settings", "设置"), - ASK("ask", "提问"), - BLEND("blend", "融合"), - - ; - - MidjourneyInteractionsEnum(String value, String message) { - this.value =value; - this.message =message; - } - - private String value; - private String message; - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java deleted file mode 100644 index 9c8eb9e..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - - -public enum MidjourneyMessageTypeEnum { - /** - * 创建. - */ - CREATE, - /** - * 修改. - */ - UPDATE, - /** - * 删除. - */ - DELETE; - - public static MidjourneyMessageTypeEnum of(String type) { - return switch (type) { - case "MESSAGE_CREATE" -> CREATE; - case "MESSAGE_UPDATE" -> UPDATE; - case "MESSAGE_DELETE" -> DELETE; - default -> null; - }; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java deleted file mode 100644 index 1dccb8a..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public final class MidjourneyNotifyCode { - /** - * 成功. - */ - public static final int SUCCESS = 1; - - - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java deleted file mode 100644 index 7e4c94e..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.util; - -import cn.hutool.core.text.CharSequenceUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; - -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * mj util - * - * author: fansili - * time: 2024/4/6 19:00 - */ -public class MidjourneyUtil { - /** - * content正则匹配prompt和进度. - */ - public static final String CONTENT_REGEX = ".*?\\*\\*(.*?)\\*\\*.+<@\\d+> \\((.*?)\\)"; - public static final String CONTENT_PROGRESS_REGEX = "\\(([^)]*)\\)"; - - /** - * 解析 content 参数 - * - * @param content - * @return - */ - public static MidjourneyMessage.Content parseContent(String content) { - // 有三种格式。 - // 南极应该是什么样子? - // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", - // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (fast, stealth)" - // Upscaling image #3 with **A girl from China, creator of ancient Chinese clothing, dances on the square** - <@975372485971312700> (Waiting to start) - MidjourneyMessage.Content mjContent = new MidjourneyMessage.Content(); - if (CharSequenceUtil.isBlank(content)) { - return null; - } - if (!content.contains("<@")) { - return mjContent.setPrompt(content); - } - int rawIndex = content.indexOf("<@") - 3; - String prompt = content.substring(0, rawIndex).trim(); - String contentTail = content.substring(rawIndex).trim(); - // 检查是否存在进度条 - Pattern pattern = Pattern.compile(CONTENT_PROGRESS_REGEX); - Matcher matcher = pattern.matcher(contentTail); - - if (contentTail.contains("%")) { - if (matcher.find()) { - // 获取第一个(也是此处唯一的)捕获组的内容 - String progress = matcher.group(1); - mjContent.setProgress(progress); - } - if (matcher.find()) { - String status = matcher.group(1); - mjContent.setStatus(status); - } - } else { - if (matcher.find()) { - // 获取第一个(也是此处唯一的)捕获组的内容 - String status = matcher.group(1); - mjContent.setStatus(status); - } - } - mjContent.setPrompt(prompt); - // tip:contentArray - return mjContent; - } - - /** - * 设置 params - * - * @param requestTemplate - * @param requestParams - * @return - */ - public static String parseTemplate(String requestTemplate, Map requestParams) { - for (Map.Entry entry : requestParams.entrySet()) { - requestTemplate = requestTemplate.replace("$".concat(entry.getKey()), entry.getValue()); - } - return requestTemplate; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/FailureCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/FailureCallback.java deleted file mode 100644 index 29bdb06..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/FailureCallback.java +++ /dev/null @@ -1,6 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket; - - -public interface FailureCallback { - void onFailure(int code, String reason); -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyMessageHandler.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyMessageHandler.java deleted file mode 100644 index d5522fc..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyMessageHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket; - -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; - -/** - * message handler - * - * @author fansili - * @time 2024/4/29 14:29 - * @since 1.0 - */ -public interface MidjourneyMessageHandler { - - void messageHandler(MidjourneyMessage midjourneyMessage); -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java deleted file mode 100644 index ddf7f3f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java +++ /dev/null @@ -1,224 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket; - - -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyNotifyCode; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.handler.MidjourneyWebSocketHandler; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MidjourneyMessageListener; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.apache.tomcat.websocket.Constants; -import org.jetbrains.annotations.NotNull; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.WebSocketHttpHeaders; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.client.standard.StandardWebSocketClient; - -import java.io.IOException; -import java.net.URI; - -// TODO @fansili:mj 这块 websocket 有点小复杂,虽然代码量 400 多行;感觉可以考虑,有没第三方 sdk,通过它透明接入 mj -@Slf4j -public class MidjourneyWebSocketStarter implements WebSocketStarter { - /** - * 链接重试次数 - */ - private static final int CONNECT_RETRY_LIMIT = 5; - /** - * mj 配置文件 - */ - private final MidjourneyConfig midjourneyConfig; - /** - * mj 监听(所有message 都会 callback到这里) - */ - private final MidjourneyMessageListener userMessageListener; - /** - * wss 服务器 - */ - private final String wssServer; - /** - * - */ - private final String resumeWss; - /** - * - */ - private ResumeData resumeData = null; - /** - * 是否运行成功 - */ - private boolean running = false; - /** - * 链接成功的 session - */ - private WebSocketSession webSocketSession = null; - private WssNotify wssNotify = null; - - public MidjourneyWebSocketStarter(String wssServer, - String resumeWss, - MidjourneyConfig midjourneyConfig, - MidjourneyMessageListener userMessageListener) { - this.wssServer = wssServer; - this.resumeWss = resumeWss; - this.midjourneyConfig = midjourneyConfig; - this.userMessageListener = userMessageListener; - } - - @Override - public void start(WssNotify wssNotify) { - this.wssNotify = wssNotify; - start(false); - } - - private void start(boolean reconnect) { - // 设置header - WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); - headers.add("Accept-Encoding", "gzip, deflate, br"); - headers.add("Accept-Language", "zh-CN,zh;q=0.9"); - headers.add("Cache-Control", "no-cache"); - headers.add("Pragma", "no-cache"); - headers.add("Sec-Websocket-Extensions", "permessage-deflate; client_max_window_bits"); - headers.add("User-Agent", this.midjourneyConfig.getUserAage()); - // 创建 mjHeader - MidjourneyWebSocketHandler mjWebSocketHandler = new MidjourneyWebSocketHandler( - this.midjourneyConfig, this.userMessageListener, this::onSocketSuccess, this::onSocketFailure); - // - String gatewayUrl; - if (reconnect) { - gatewayUrl = getGatewayServer(this.resumeData.getResumeGatewayUrl()) + "/?encoding=json&v=9&compress=zlib-stream"; - mjWebSocketHandler.setSessionId(this.resumeData.getSessionId()); - mjWebSocketHandler.setSequence(this.resumeData.getSequence()); - mjWebSocketHandler.setResumeGatewayUrl(this.resumeData.getResumeGatewayUrl()); - } else { - gatewayUrl = getGatewayServer(null) + "/?encoding=json&v=9&compress=zlib-stream"; - } - // 创建 StandardWebSocketClient - StandardWebSocketClient webSocketClient = new StandardWebSocketClient(); - // 设置 io timeout 时间 - webSocketClient.getUserProperties().put(Constants.IO_TIMEOUT_MS_PROPERTY, "10000"); - // - ListenableFuture socketSessionFuture = webSocketClient.doHandshake(mjWebSocketHandler, headers, URI.create(gatewayUrl)); - // 添加 callback 进行回调 - socketSessionFuture.addCallback(new ListenableFutureCallback<>() { - @Override - public void onFailure(@NotNull Throwable e) { - onSocketFailure(MidjourneyWebSocketHandler.CLOSE_CODE_EXCEPTION, e.getMessage()); - } - - @Override - public void onSuccess(WebSocketSession session) { - MidjourneyWebSocketStarter.this.webSocketSession = session; - } - }); - } - - private void onSocketSuccess(String sessionId, Object sequence, String resumeGatewayUrl) { - this.resumeData = new ResumeData(sessionId, sequence, resumeGatewayUrl); - this.running = true; - notifyWssLock(MidjourneyNotifyCode.SUCCESS, ""); - } - - private void onSocketFailure(int code, String reason) { - // 1001异常可以忽略 - if (code == 1001) { - return; - } - // 关闭 socket - closeSocketSessionWhenIsOpen(); - // 没有运行通知 - if (!this.running) { - notifyWssLock(code, reason); - return; - } - // 已经运行先设置为false,发起 - this.running = false; - if (code >= 4000) { - log.warn("[wss-{}] Can't reconnect! Account disabled. Closed by {}({}).", this.midjourneyConfig.getChannelId(), code, reason); - } else if (code == 2001) { - log.warn("[wss-{}] Closed by {}({}). Try reconnect...", this.midjourneyConfig.getChannelId(), code, reason); - tryReconnect(); - } else { - log.warn("[wss-{}] Closed by {}({}). Try new connection...", this.midjourneyConfig.getChannelId(), code, reason); - tryNewConnect(); - } - } - - /** - * 重连 - */ - private void tryReconnect() { - try { - tryStart(true); - } catch (Exception e) { - log.warn("[wss-{}] Reconnect fail: {}, Try new connection...", this.midjourneyConfig.getChannelId(), e.getMessage()); - ThreadUtil.sleep(1000); - tryNewConnect(); - } - } - - private void tryNewConnect() { - // 链接重试次数5 - for (int i = 1; i <= CONNECT_RETRY_LIMIT; i++) { - try { - tryStart(false); - return; - } catch (Exception e) { - log.warn("[wss-{}] New connect fail ({}): {}", this.midjourneyConfig.getChannelId(), i, e.getMessage()); - ThreadUtil.sleep(5000); - } - } - log.error("[wss-{}] Account disabled", this.midjourneyConfig.getChannelId()); - } - - public void tryStart(boolean reconnect) { - start(reconnect); - } - - private void notifyWssLock(int code, String reason) { - System.err.println("notifyWssLock: " + code + " - " + reason); - if (wssNotify != null) { - wssNotify.notify(code, reason); - } - } - - /** - * 关闭 socket session - */ - private void closeSocketSessionWhenIsOpen() { - try { - if (this.webSocketSession != null && this.webSocketSession.isOpen()) { - this.webSocketSession.close(CloseStatus.GOING_AWAY); - } - } catch (IOException e) { - // do nothing - } - } - - private String getGatewayServer(String resumeGatewayUrl) { - if (CharSequenceUtil.isNotBlank(resumeGatewayUrl)) { - return CharSequenceUtil.isBlank(this.resumeWss) ? resumeGatewayUrl : this.resumeWss; - } - return this.wssServer; - } - - @Getter - public static class ResumeData { - - public ResumeData(String sessionId, Object sequence, String resumeGatewayUrl) { - this.sessionId = sessionId; - this.sequence = sequence; - this.resumeGatewayUrl = resumeGatewayUrl; - } - - /** - * socket session - */ - private final String sessionId; - private final Object sequence; - private final String resumeGatewayUrl; - } -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/SuccessCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/SuccessCallback.java deleted file mode 100644 index 729498f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/SuccessCallback.java +++ /dev/null @@ -1,7 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket; - - -public interface SuccessCallback { - - void onSuccess(String sessionId, Object sequence, String resumeGatewayUrl); -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WebSocketStarter.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WebSocketStarter.java deleted file mode 100644 index 5a438aa..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WebSocketStarter.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket; - - -public interface WebSocketStarter { - - void start(WssNotify wssNotify) throws Exception; - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WssNotify.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WssNotify.java deleted file mode 100644 index 33c8a29..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/WssNotify.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket; - -/** - * 通知信息 - * - * @author fansili - * @time 2024/4/29 14:21 - * @since 1.0 - */ -public interface WssNotify { - - void notify(int code, String message); -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java deleted file mode 100644 index b6fbf5c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java +++ /dev/null @@ -1,281 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket.handler; - -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.http.useragent.UserAgent; -import cn.hutool.http.useragent.UserAgentUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.FailureCallback; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.SuccessCallback; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MidjourneyMessageListener; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import net.dv8tion.jda.api.utils.data.DataArray; -import net.dv8tion.jda.api.utils.data.DataObject; -import net.dv8tion.jda.api.utils.data.DataType; -import net.dv8tion.jda.internal.requests.WebSocketCode; -import net.dv8tion.jda.internal.utils.compress.Decompressor; -import net.dv8tion.jda.internal.utils.compress.ZlibDecompressor; -import org.jetbrains.annotations.NotNull; -import org.springframework.web.socket.*; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -@Slf4j -public class MidjourneyWebSocketHandler implements WebSocketHandler { - /** - * close 错误码:重连 - */ - public static final int CLOSE_CODE_RECONNECT = 2001; - /** - * close 错误码:无效、作废 - */ - public static final int CLOSE_CODE_INVALIDATE = 1009; - /** - * close 错误码:异常 - */ - public static final int CLOSE_CODE_EXCEPTION = 1011; - /** - * mj配置文件 - */ - private final MidjourneyConfig midjourneyConfig; - /** - * mj 消息监听 - */ - private final MidjourneyMessageListener userMessageListener; - /** - * 成功回调 - */ - private final SuccessCallback successCallback; - /** - * 失败回调 - */ - private final FailureCallback failureCallback; - /** - * 心跳执行器 - */ - private final ScheduledExecutorService heartExecutor; - /** - * auth数据 - */ - private final DataObject authData; - - @Setter - private String sessionId = null; - @Setter - private Object sequence = null; - @Setter - private String resumeGatewayUrl = null; - - private long interval = 41250; - private boolean heartbeatAck = false; - - private Future heartbeatInterval; - private Future heartbeatTimeout; - - /** - * 处理 message 消息的 Decompressor - */ - private final Decompressor decompressor = new ZlibDecompressor(2048); - - public MidjourneyWebSocketHandler(MidjourneyConfig account, - MidjourneyMessageListener userMessageListener, - SuccessCallback successCallback, - FailureCallback failureCallback) { - this.midjourneyConfig = account; - this.userMessageListener = userMessageListener; - this.successCallback = successCallback; - this.failureCallback = failureCallback; - this.heartExecutor = Executors.newSingleThreadScheduledExecutor(); - this.authData = createAuthData(); - } - - @Override - public void afterConnectionEstablished(@NotNull WebSocketSession session) throws Exception { - // do nothing - } - - @Override - public void handleTransportError(@NotNull WebSocketSession session, @NotNull Throwable e) throws Exception { - log.error("[wss-{}] Transport error", this.midjourneyConfig.getChannelId(), e); - // 通知链接异常 - onFailure(CLOSE_CODE_EXCEPTION, "transport error"); - } - - @Override - public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus closeStatus) throws Exception { - // 链接关闭 - onFailure(closeStatus.getCode(), closeStatus.getReason()); - } - - @Override - public boolean supportsPartialMessages() { - return true; - } - - @Override - public void handleMessage(@NotNull WebSocketSession session, WebSocketMessage message) throws Exception { - // 获取 message 消息 - ByteBuffer buffer = (ByteBuffer) message.getPayload(); - // 解析 message - byte[] decompressed = decompressor.decompress(buffer.array()); - if (decompressed == null) { - return; - } - // 转换 json - String json = new String(decompressed, StandardCharsets.UTF_8); - // 转换 jda 自带的 dataObject(和json object 差不多) - DataObject data = DataObject.fromJson(json); - // 获取消息类型 - int opCode = data.getInt("op"); - switch (opCode) { - case WebSocketCode.HEARTBEAT -> handleHeartbeat(session); - case WebSocketCode.HEARTBEAT_ACK -> { - this.heartbeatAck = true; - clearHeartbeatTimeout(); - } - case WebSocketCode.HELLO -> { - handleHello(session, data); - doResumeOrIdentify(session); - } - case WebSocketCode.RESUME -> onSuccess(); - case WebSocketCode.RECONNECT -> onFailure(CLOSE_CODE_RECONNECT, "receive server reconnect"); - case WebSocketCode.INVALIDATE_SESSION -> onFailure(CLOSE_CODE_INVALIDATE, "receive session invalid"); - case WebSocketCode.DISPATCH -> handleDispatch(data); - default -> log.debug("[wss-{}] Receive unknown code: {}.", midjourneyConfig.getChannelId(), data); - } - } - - private void handleDispatch(DataObject raw) { - this.sequence = raw.opt("s").orElse(null); - if (!raw.isType("d", DataType.OBJECT)) { - return; - } - DataObject content = raw.getObject("d"); - String t = raw.getString("t", null); - if ("READY".equals(t)) { - this.sessionId = content.getString("session_id"); - this.resumeGatewayUrl = content.getString("resume_gateway_url"); - onSuccess(); - } else if ("RESUMED".equals(t)) { - onSuccess(); - } else { - try { - this.userMessageListener.onMessage(raw); - } catch (Exception e) { - log.error("[wss-{}] Handle message error", this.midjourneyConfig.getChannelId(), e); - } - } - } - - private void handleHeartbeat(WebSocketSession session) { - sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); - this.heartbeatTimeout = ThreadUtil.execAsync(() -> { - ThreadUtil.sleep(this.interval); - onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack"); - }); - } - - private void handleHello(WebSocketSession session, DataObject data) { - clearHeartbeatInterval(); - this.interval = data.getObject("d").getLong("heartbeat_interval"); - this.heartbeatAck = true; - this.heartbeatInterval = this.heartExecutor.scheduleAtFixedRate(() -> { - if (this.heartbeatAck) { - this.heartbeatAck = false; - sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); - } else { - onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack interval"); - } - }, (long) Math.floor(RandomUtil.randomDouble(0, 1) * this.interval), this.interval, TimeUnit.MILLISECONDS); - } - - private void doResumeOrIdentify(WebSocketSession session) { - if (CharSequenceUtil.isBlank(this.sessionId)) { - sendMessage(session, WebSocketCode.IDENTIFY, this.authData); - } else { - var data = DataObject.empty().put("token", this.midjourneyConfig.getToken()) - .put("session_id", this.sessionId).put("seq", this.sequence); - sendMessage(session, WebSocketCode.RESUME, data); - } - } - - private void sendMessage(WebSocketSession session, int op, Object d) { - var data = DataObject.empty().put("op", op).put("d", d); - try { - session.sendMessage(new TextMessage(data.toString())); - } catch (IOException e) { - log.error("[wss-{}] Send message error", this.midjourneyConfig.getChannelId(), e); - onFailure(CLOSE_CODE_EXCEPTION, "send message error"); - } - } - - private void onSuccess() { - ThreadUtil.execute(() -> this.successCallback.onSuccess(this.sessionId, this.sequence, this.resumeGatewayUrl)); - } - - private void onFailure(int code, String reason) { - clearHeartbeatTimeout(); - clearHeartbeatInterval(); - ThreadUtil.execute(() -> this.failureCallback.onFailure(code, reason)); - } - - private void clearHeartbeatTimeout() { - if (this.heartbeatTimeout != null) { - this.heartbeatTimeout.cancel(true); - this.heartbeatTimeout = null; - } - } - - private void clearHeartbeatInterval() { - if (this.heartbeatInterval != null) { - this.heartbeatInterval.cancel(true); - this.heartbeatInterval = null; - } - } - - private DataObject createAuthData() { - UserAgent userAgent = UserAgentUtil.parse(this.midjourneyConfig.getUserAage()); - DataObject connectionProperties = DataObject.empty() - .put("browser", userAgent.getBrowser().getName()) - .put("browser_user_agent", this.midjourneyConfig.getUserAage()) - .put("browser_version", userAgent.getVersion()) - .put("client_build_number", 222963) - .put("client_event_source", null) - .put("device", "") - .put("os", userAgent.getOs().getName()) - .put("referer", "https://www.midjourney.com") - .put("referrer_current", "") - .put("referring_domain", "www.midjourney.com") - .put("referring_domain_current", "") - .put("release_channel", "stable") - .put("system_locale", "zh-CN"); - DataObject presence = DataObject.empty() - .put("activities", DataArray.empty()) - .put("afk", false) - .put("since", 0) - .put("status", "online"); - DataObject clientState = DataObject.empty() - .put("api_code_version", 0) - .put("guild_versions", DataObject.empty()) - .put("highest_last_message_id", "0") - .put("private_channels_version", "0") - .put("read_state_version", 0) - .put("user_guild_settings_version", -1) - .put("user_settings_version", -1); - return DataObject.empty() - .put("capabilities", 16381) - .put("client_state", clientState) - .put("compress", false) - .put("presence", presence) - .put("properties", connectionProperties) - .put("token", this.midjourneyConfig.getToken()); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java deleted file mode 100644 index 0d196fa..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java +++ /dev/null @@ -1,111 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener; - - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyConstants; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyGennerateStatusEnum; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyMessageTypeEnum; -import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyMessageHandler; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import lombok.extern.slf4j.Slf4j; -import net.dv8tion.jda.api.utils.data.DataObject; - -import java.util.List; - -@Slf4j -public class MidjourneyMessageListener { - - private MidjourneyConfig midjourneyConfig; - private MidjourneyMessageHandler midjourneyMessageHandler = null; - - public MidjourneyMessageListener(MidjourneyConfig midjourneyConfig) { - this.midjourneyConfig = midjourneyConfig; - } - - public MidjourneyMessageListener(MidjourneyConfig midjourneyConfig, - MidjourneyMessageHandler midjourneyMessageHandler) { - this.midjourneyConfig = midjourneyConfig; - this.midjourneyMessageHandler = midjourneyMessageHandler; - } - - public void onMessage(DataObject raw) { - MidjourneyMessageTypeEnum messageType = MidjourneyMessageTypeEnum.of(raw.getString("t")); - if (messageType == null || MidjourneyMessageTypeEnum.DELETE == messageType) { - return; - } - DataObject data = raw.getObject("d"); - if (ignoreAndLogMessage(data, messageType)) { - return; - } - log.info("socket message: {}", raw); - // 转换几个重要的信息 - MidjourneyMessage mjMessage = new MidjourneyMessage(); - mjMessage.setId(getString(data, MidjourneyConstants.MSG_ID, "")); - mjMessage.setNonce(getString(data, MidjourneyConstants.MSG_NONCE, "")); - mjMessage.setType(data.getInt(MidjourneyConstants.MSG_TYPE)); - mjMessage.setRawData(StrUtil.str(raw.toJson(), "UTF-8")); - mjMessage.setContent(MidjourneyUtil.parseContent(data.getString(MidjourneyConstants.MSG_CONTENT))); - // 转换 components - if (!data.getArray(MidjourneyConstants.MSG_COMPONENTS).isEmpty()) { - String componentsJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_COMPONENTS).toJson(), "UTF-8"); - List components = JsonUtils.parseArray(componentsJson, MidjourneyMessage.ComponentType.class); - mjMessage.setComponents(components); - } - // 转换附件 - if (!data.getArray(MidjourneyConstants.MSG_ATTACHMENTS).isEmpty()) { - String attachmentsJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_ATTACHMENTS).toJson(), "UTF-8"); - List attachments = JsonUtils.parseArray(attachmentsJson, MidjourneyMessage.Attachment.class); - mjMessage.setAttachments(attachments); - } - // 转换 embeds 提示信息 - if (!data.getArray(MidjourneyConstants.MSG_EMBEDS).isEmpty()) { - String embedJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_EMBEDS).toJson(), "UTF-8"); - List embeds = JsonUtils.parseArray(embedJson, MidjourneyMessage.Embed.class); - mjMessage.setEmbeds(embeds); - } - // 转换状态 - convertGenerateStatus(mjMessage); - // message handler 调用 - if (midjourneyMessageHandler != null) { - midjourneyMessageHandler.messageHandler(mjMessage); - } - } - - private String getString(DataObject data, String key, String defaultValue) { - if (!data.hasKey(key)) { - return defaultValue; - } - return data.getString(key); - } - - private void convertGenerateStatus(MidjourneyMessage mjMessage) { - // - // tip:提示、警告、异常 content是没有内容的 - // tip: 一般错误信息在 Embeds 只要 Embeds有值,content就没信息。 - if (CollUtil.isNotEmpty(mjMessage.getEmbeds())) { - return; - } - if (mjMessage.getType() == 20 && mjMessage.getContent().getStatus().contains("Waiting")) { - mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.WAITING.getStatus()); - } else if (mjMessage.getType() == 20 && !StrUtil.isBlank(mjMessage.getContent().getProgress())) { - mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.IN_PROGRESS.getStatus()); - } else if (mjMessage.getType() == 0 && !CollUtil.isEmpty(mjMessage.getComponents())) { - mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.COMPLETED.getStatus()); - } - } - - private boolean ignoreAndLogMessage(DataObject data, MidjourneyMessageTypeEnum messageType) { - String channelId = data.getString(MidjourneyConstants.MSG_CHANNEL_ID); - if (!CharSequenceUtil.equals(channelId, midjourneyConfig.getChannelId())) { - return true; - } - String authorName = data.optObject("author").map(a -> a.getString("username")).orElse("System"); - log.debug("{} - {} - {}: {}", midjourneyConfig.getChannelId(), messageType.name(), authorName, data.opt("content").orElse("")); - return false; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelClient.java deleted file mode 100644 index 705a7a9..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelClient.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -/** - * The ModelClient interface provides a generic API for invoking AI models. It is designed - * to handle the interaction with various types of AI models by abstracting the process of - * sending requests and receiving responses. The interface uses Java generics to - * accommodate different types of requests and responses, enhancing flexibility and - * adaptability across different AI model implementations. - * - * @param the generic type of the request to the AI model - * @param the generic type of the response from the AI model - * @author Mark Pollack - * @since 0.8.0 - */ -public interface ModelClient, TRes extends ModelResponse> { - - /** - * Executes a method call to the AI model. - * @param request the request object to be sent to the AI model - * @return the response from the AI model - */ - TRes call(TReq request) throws Throwable; - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptions.java deleted file mode 100644 index 6a5a738..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptions.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -/** - * Interface representing the customizable options for AI model interactions. This marker - * interface allows for the specification of various settings and parameters that can - * influence the behavior and output of AI models. It is designed to provide flexibility - * and adaptability in different AI scenarios, ensuring that the AI models can be - * fine-tuned according to specific requirements. - * - * @author Mark Pollack - * @since 0.8.0 - */ -public interface ModelOptions { - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptionsUtils.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptionsUtils.java deleted file mode 100644 index 0d6f740..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelOptionsUtils.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.victools.jsonschema.generator.*; -import com.github.victools.jsonschema.module.jackson.JacksonModule; -import com.github.victools.jsonschema.module.jackson.JacksonOption; -import com.github.victools.jsonschema.module.swagger2.Swagger2Module; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; - -/** - * Utility class for manipulating {@link ModelOptions} objects. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -public final class ModelOptionsUtils { - - private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper() - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - - private final static List BEAN_MERGE_FIELD_EXCISIONS = List.of("class"); - - private static ConcurrentHashMap, List> REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap, List>(); - - private static AtomicReference SCHEMA_GENERATOR_CACHE = new AtomicReference<>(); - - private ModelOptionsUtils() { - - } - - /** - * Converts the given JSON string to a Map of String and Object. - * @param json the JSON string to convert to a Map. - * @return the converted Map. - */ - public static Map jsonToMap(String json) { - try { - return OBJECT_MAPPER.readValue(json, MAP_TYPE_REF); - } - catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static TypeReference> MAP_TYPE_REF = new TypeReference>() { - }; - - /** - * Converts the given JSON string to an Object of the given type. - * @param the type of the object to return. - * @param json the JSON string to convert to an object. - * @param type the type of the object to return. - * @return Object instance of the given type. - */ - public static T jsonToObject(String json, Class type) { - try { - return OBJECT_MAPPER.readValue(json, type); - } - catch (Exception e) { - throw new RuntimeException("Failed to json: " + json, e); - } - } - - /** - * Converts the given object to a JSON string. - * @param object the object to convert to a JSON string. - * @return the JSON string. - */ - public static String toJsonString(Object object) { - try { - return OBJECT_MAPPER.writeValueAsString(object); - } - catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - /** - * Merges the source object into the target object and returns an object represented - * by the given class. The JSON property names are used to match the fields to merge. - * The source non-null values override the target values with the same field name. The - * source null values are ignored. If the acceptedFieldNames is not empty, only the - * fields with the given names are merged and returned. If the acceptedFieldNames is - * empty, use the {@code @JsonProperty} names, inferred from the provided clazz. - * @param they type of the class to return. - * @param source the source object to merge. - * @param target the target object to merge into. - * @param clazz the class to return. - * @param acceptedFieldNames the list of field names accepted for the target object. - * @return the merged object represented by the given class. - */ - public static T merge(Object source, Object target, Class clazz, List acceptedFieldNames) { - - if (source == null) { - source = Map.of(); - } - - List requestFieldNames = CollectionUtils.isEmpty(acceptedFieldNames) - ? REQUEST_FIELD_NAMES_PER_CLASS.computeIfAbsent(clazz, ModelOptionsUtils::getJsonPropertyValues) - : acceptedFieldNames; - - if (CollectionUtils.isEmpty(requestFieldNames)) { - throw new IllegalArgumentException("No @JsonProperty fields found in the " + clazz.getName()); - } - - Map sourceMap = ModelOptionsUtils.objectToMap(source); - Map targetMap = ModelOptionsUtils.objectToMap(target); - - targetMap.putAll(sourceMap.entrySet() - .stream() - .filter(e -> e.getValue() != null) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()))); - - targetMap = targetMap.entrySet() - .stream() - .filter(e -> requestFieldNames.contains(e.getKey())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - - return ModelOptionsUtils.mapToClass(targetMap, clazz); - } - - /** - * Merges the source object into the target object and returns an object represented - * by the given class. The JSON property names are used to match the fields to merge. - * The source non-null values override the target values with the same field name. The - * source null values are ignored. Returns the only field names that match the - * {@code @JsonProperty} names, inferred from the provided clazz. - * @param they type of the class to return. - * @param source the source object to merge. - * @param target the target object to merge into. - * @param clazz the class to return. - * @return the merged object represented by the given class. - */ - public static T merge(Object source, Object target, Class clazz) { - return ModelOptionsUtils.merge(source, target, clazz, null); - } - - /** - * Converts the given object to a Map. - * @param source the object to convert to a Map. - * @return the converted Map. - */ - public static Map objectToMap(Object source) { - if (source == null) { - return new HashMap<>(); - } - try { - String json = OBJECT_MAPPER.writeValueAsString(source); - return OBJECT_MAPPER.readValue(json, new TypeReference>() { - }) - .entrySet() - .stream() - .filter(e -> e.getValue() != null) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - } - catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - /** - * Converts the given Map to the given class. - * @param the type of the class to return. - * @param source the Map to convert to the given class. - * @param clazz the class to convert the Map to. - * @return the converted class. - */ - public static T mapToClass(Map source, Class clazz) { - try { - String json = OBJECT_MAPPER.writeValueAsString(source); - return OBJECT_MAPPER.readValue(json, clazz); - } - catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - /** - * Returns the list of name values of the {@link JsonProperty} annotations. - * @param clazz the class that contains fields annotated with {@link JsonProperty}. - * @return the list of values of the {@link JsonProperty} annotations. - */ - public static List getJsonPropertyValues(Class clazz) { - List values = new ArrayList<>(); - Field[] fields = clazz.getDeclaredFields(); - for (Field field : fields) { - JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class); - if (jsonPropertyAnnotation != null) { - values.add(jsonPropertyAnnotation.value()); - } - } - return values; - } - - /** - * Returns a new instance of the targetBeanClazz that copies the bean values from the - * sourceBean instance. - * @param sourceBean the source bean to copy the values from. - * @param sourceInterfaceClazz the source interface class. Only the fields with the - * same name as the interface methods are copied. This allow the source object to be a - * subclass of the source interface with additional, non-interface fields. - * @param targetBeanClazz the target class, a subclass of the ChatOptions, to convert - * into. - * @param the target class type. - * @return a new instance of the targetBeanClazz with the values from the sourceBean - * instance. - */ - public static T copyToTarget(S sourceBean, Class sourceInterfaceClazz, - Class targetBeanClazz) { - - Assert.notNull(sourceInterfaceClazz, "SourceOptionsClazz must not be null"); - Assert.notNull(targetBeanClazz, "TargetOptionsClazz must not be null"); - - if (sourceBean == null) { - return null; - } - - if (sourceBean.getClass().isAssignableFrom(targetBeanClazz)) { - return (T) sourceBean; - } - - try { - T targetOptions = targetBeanClazz.getConstructor().newInstance(); - - ModelOptionsUtils.mergeBeans(sourceBean, targetOptions, sourceInterfaceClazz, true); - - return targetOptions; - } - catch (Exception e) { - throw new RuntimeException( - "Failed to convert the " + sourceInterfaceClazz.getName() + " into " + targetBeanClazz.getName(), - e); - } - } - - /** - * Merges the source object into the target object. The source null values are - * ignored. Only objects with Getter and Setter methods are supported. - * @param the type of the source and target object. - * @param source the source object to merge. - * @param target the target object to merge into. - * @param sourceInterfaceClazz the source interface class. Only the fields with the - * same name as the interface methods are merged. This allow the source object to be a - * subclass of the source interface with additional, non-interface fields. - * @param overrideNonNullTargetValues if true, the source non-null values override the - * target values with the same field name. If false, the source non-null values are - * ignored. - * @return the merged target object. - */ - public static T mergeBeans(S source, T target, Class sourceInterfaceClazz, - boolean overrideNonNullTargetValues) { - Assert.notNull(source, "Source object must not be null"); - Assert.notNull(target, "Target object must not be null"); - - BeanWrapper sourceBeanWrap = new BeanWrapperImpl(source); - BeanWrapper targetBeanWrap = new BeanWrapperImpl(target); - - List interfaceNames = Arrays.stream(sourceInterfaceClazz.getMethods()).map(m -> m.getName()).toList(); - - for (PropertyDescriptor descriptor : sourceBeanWrap.getPropertyDescriptors()) { - - if (!BEAN_MERGE_FIELD_EXCISIONS.contains(descriptor.getName()) - && interfaceNames.contains(toGetName(descriptor.getName()))) { - - String propertyName = descriptor.getName(); - Object value = sourceBeanWrap.getPropertyValue(propertyName); - - // Copy value to the target object - if (value != null) { - var targetValue = targetBeanWrap.getPropertyValue(propertyName); - - if (targetValue == null || overrideNonNullTargetValues) { - targetBeanWrap.setPropertyValue(propertyName, value); - } - } - } - } - - return target; - } - - private static String toGetName(String name) { - return "get" + name.substring(0, 1).toUpperCase() + name.substring(1); - } - - /** - * Generates JSON Schema (version 2020_12) for the given class. - * @param clazz the class to generate JSON Schema for. - * @param toUpperCaseTypeValues if true, the type values are converted to upper case. - * @return the generated JSON Schema as a String. - */ - public static String getJsonSchema(Class clazz, boolean toUpperCaseTypeValues) { - - if (SCHEMA_GENERATOR_CACHE.get() == null) { - - JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED); - Swagger2Module swaggerModule = new Swagger2Module(); - - SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, - OptionPreset.PLAIN_JSON) - .with(Option.EXTRA_OPEN_API_FORMAT_VALUES) - .with(Option.PLAIN_DEFINITION_KEYS) - .with(swaggerModule) - .with(jacksonModule); - - SchemaGeneratorConfig config = configBuilder.build(); - SchemaGenerator generator = new SchemaGenerator(config); - SCHEMA_GENERATOR_CACHE.compareAndSet(null, generator); - } - - ObjectNode node = SCHEMA_GENERATOR_CACHE.get().generateSchema(clazz); - if (toUpperCaseTypeValues) { // Required for OpenAPI 3.0 (at least Vertex AI - // version of it). - toUpperCaseTypeValues(node); - } - - return node.toPrettyString(); - } - - public static void toUpperCaseTypeValues(ObjectNode node) { - if (node == null) { - return; - } - if (node.isObject()) { - node.fields().forEachRemaining(entry -> { - JsonNode value = entry.getValue(); - if (value.isObject()) { - toUpperCaseTypeValues((ObjectNode) value); - } - else if (value.isArray()) { - ((ArrayNode) value).elements().forEachRemaining(element -> { - if (element.isObject() || element.isArray()) { - toUpperCaseTypeValues((ObjectNode) element); - } - }); - } - else if (value.isTextual() && entry.getKey().equals("type")) { - String oldValue = ((ObjectNode) node).get("type").asText(); - ((ObjectNode) node).put("type", oldValue.toUpperCase()); - } - }); - } - else if (node.isArray()) { - node.elements().forEachRemaining(element -> { - if (element.isObject() || element.isArray()) { - toUpperCaseTypeValues((ObjectNode) element); - } - }); - } - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelRequest.java deleted file mode 100644 index 915aba2..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -/** - * 表示对AI模型的请求的接口。此接口封装了 与人工智能模型交互所需的必要信息,包括指令或 输入(通用类型T)和附加模型选项。它提供了一种标准化的方式 - * 向人工智能模型发送请求,确保包括所有必要的细节,并且可以易于管理。 - * - * Interface representing a request to an AI model. This interface encapsulates the - * necessary information required to interact with an AI model, including instructions or - * inputs (of generic type T) and additional model options. It provides a standardized way - * to send requests to AI models, ensuring that all necessary details are included and can - * be easily managed. - * - * @param the type of instructions or input required by the AI model - * @author Mark Pollack - * @since 0.8.0 - */ -public interface ModelRequest { - - /** - * 检索AI模型所需的指令或输入。 返回AI模型所需的指令或输入 - * - * Retrieves the instructions or input required by the AI model. - * @return the instructions or input required by the AI model - */ - T getInstructions(); // required input - - /** - * 检索人工智能模型交互的可自定义选项。 返回AI模型交互的自定义选项 - * - * Retrieves the customizable options for AI model interactions. - * @return the customizable options for AI model interactions - */ - ModelOptions getOptions(); - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResponse.java deleted file mode 100644 index 0bc0cc5..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResponse.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -import java.util.List; - -/** - * - * 表示从AI模型接收到的响应的接口。此接口提供 访问AI模型生成的主要结果或结果列表的方法,以及 以及响应元数据。它是封装和管理的标准化方式 - * 人工智能模型的输出,确保轻松检索和处理生成的信息 - * - * Interface representing the response received from an AI model. This interface provides - * methods to access the main result or a list of results generated by the AI model, along - * with the response metadata. It serves as a standardized way to encapsulate and manage - * the output from AI models, ensuring easy retrieval and processing of the generated - * information. - * - * @param the type of the result(s) provided by the AI model - * @author Mark Pollack - * @since 0.8.0 - */ -public interface ModelResponse> { - - /** - * 检索AI模型的结果。 - * - * Retrieves the result of the AI model. - * @return the result generated by the AI model - */ - T getResult(); - - /** - * 检索AI模型生成的输出列表。 - * - * Retrieves the list of generated outputs by the AI model. - * @return the list of generated outputs - */ - List getResults(); - - /** - * 检索与AI模型的响应相关联的响应元数据。 - * - * Retrieves the response metadata associated with the AI model's response. - * @return the response metadata - */ - ResponseMetadata getMetadata(); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResult.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResult.java deleted file mode 100644 index 616be18..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ModelResult.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -/** - * This interface provides methods to access the main output of the AI model and the - * metadata associated with this result. It is designed to offer a standardized and - * comprehensive way to handle and interpret the outputs generated by AI models, catering - * to diverse AI applications and use cases. - * - * @param the type of the output generated by the AI model - * @author Mark Pollack - * @since 0.8.0 - */ -public interface ModelResult { - - /** - * Retrieves the output generated by the AI model. - * @return the output generated by the AI model - */ - T getOutput(); - - /** - * Retrieves the metadata associated with the result of an AI model. - * @return the metadata associated with the result - */ - ResultMetadata getMetadata(); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResponseMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResponseMetadata.java deleted file mode 100644 index 2a85cdf..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResponseMetadata.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -/** - * 表示与AI模型的响应相关联的元数据的接口。此接口 旨在提供有关人工智能生成反应的附加信息 模型,包括处理细节和模型特定数据。它是一种价值 - * 核心领域内的对象,增强对人工智能模型的理解和管理 在各种应用中的响应。 - * - * Interface representing metadata associated with an AI model's response. This interface - * is designed to provide additional information about the generative response from an AI - * model, including processing details and model-specific data. It serves as a value - * object within the core domain, enhancing the understanding and management of AI model - * responses in various applications. - * - * @author Mark Pollack - * @since 0.8.0 - */ -public interface ResponseMetadata { - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResultMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResultMetadata.java deleted file mode 100644 index 528b764..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/ResultMetadata.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -/** - * Interface representing metadata associated with the results of an AI model. This - * interface focuses on providing additional context and insights into the results - * generated by AI models. It could include information like computation time, model - * version, or other relevant details that enhance understanding and management of AI - * model outputs in various applications. - * - * @author Mark Pollack - * @since 0.8.0 - */ -public interface ResultMetadata { - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/StreamingModelClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/StreamingModelClient.java deleted file mode 100644 index abbe859..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/StreamingModelClient.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model; - -import reactor.core.publisher.Flux; - -/** - * The StreamingModelClient interface provides a generic API for invoking a AI models with - * streaming response. It abstracts the process of sending requests and receiving a - * streaming responses. The interface uses Java generics to accommodate different types of - * requests and responses, enhancing flexibility and adaptability across different AI - * model implementations. - * - * @param the generic type of the request to the AI model - * @param the generic type of a single item in the streaming response from the - * AI model - * @author Christian Tzolov - * @since 0.8.0 - */ -public interface StreamingModelClient, TResChunk extends ModelResponse> { - - /** - * Executes a method call to the AI model. - * @param request the request object to be sent to the AI model - * @return the streaming response from the AI model - */ - Flux stream(TReq request); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallSupport.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallSupport.java deleted file mode 100644 index 33409f7..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallSupport.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model.function; - -import org.springframework.util.CollectionUtils; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author Christian Tzolov - */ -public abstract class AbstractFunctionCallSupport { - - protected final static boolean IS_RUNTIME_CALL = true; - - /** - * 函数回调寄存器用于按名称解析函数回调。 - * - * The function callback register is used to resolve the function callbacks by name. - */ - protected final Map functionCallbackRegister = new ConcurrentHashMap<>(); - - /** - * 函数回调上下文用于按名称解析函数回调来自Spring上下文。 - * 它是可选的,通常与Spring一起使用自动配置。 - * - * The function callback context is used to resolve the function callbacks by name - * from the Spring context. It is optional and usually used with Spring - * auto-configuration. - */ - protected final FunctionCallbackContext functionCallbackContext; - - public AbstractFunctionCallSupport(FunctionCallbackContext functionCallbackContext) { - this.functionCallbackContext = functionCallbackContext; - } - - public Map getFunctionCallbackRegister() { - return this.functionCallbackRegister; - } - - protected Set handleFunctionCallbackConfigurations(FunctionCallingOptions options, boolean isRuntimeCall) { - - Set functionToCall = new HashSet<>(); - - if (options != null) { - if (!CollectionUtils.isEmpty(options.getFunctionCallbacks())) { - options.getFunctionCallbacks().stream().forEach(functionCallback -> { - - // Register the tool callback. - if (isRuntimeCall) { - this.functionCallbackRegister.put(functionCallback.getName(), functionCallback); - } - else { - this.functionCallbackRegister.putIfAbsent(functionCallback.getName(), functionCallback); - } - - // Automatically enable the function, usually from prompt callback. - if (isRuntimeCall) { - functionToCall.add(functionCallback.getName()); - } - }); - } - - // Add the explicitly enabled functions. - if (!CollectionUtils.isEmpty(options.getFunctions())) { - functionToCall.addAll(options.getFunctions()); - } - } - - return functionToCall; - } - - /** - * Resolve the function callbacks by name. Retrieve them from the registry or try to - * resolve them from the Application Context. - * @param functionNames Name of function callbacks to retrieve. - * @return list of resolved FunctionCallbacks. - */ - protected List resolveFunctionCallbacks(Set functionNames) { - - List retrievedFunctionCallbacks = new ArrayList<>(); - - for (String functionName : functionNames) { - if (!this.functionCallbackRegister.containsKey(functionName)) { - - if (this.functionCallbackContext != null) { - FunctionCallback functionCallback = this.functionCallbackContext.getFunctionCallback(functionName, - null); - if (functionCallback != null) { - this.functionCallbackRegister.put(functionName, functionCallback); - } - else { - throw new IllegalStateException( - "No function callback [" + functionName + "] fund in tht FunctionCallbackContext"); - } - } - else { - throw new IllegalStateException("No function callback found for name: " + functionName); - } - } - FunctionCallback functionCallback = this.functionCallbackRegister.get(functionName); - - retrievedFunctionCallbacks.add(functionCallback); - } - - return retrievedFunctionCallbacks; - } - - /// - protected Resp callWithFunctionSupport(Req request) { - Resp response = this.doChatCompletion(request); - return this.handleFunctionCallOrReturn(request, response); - } - - protected Resp handleFunctionCallOrReturn(Req request, Resp response) { - - if (!this.isToolFunctionCall(response)) { - return response; - } - - // The chat completion tool call requires the complete conversation - // history. Including the initial user message. - List conversationHistory = new ArrayList<>(); - - conversationHistory.addAll(this.doGetUserMessages(request)); - - Msg responseMessage = this.doGetToolResponseMessage(response); - - // Add the assistant response to the message conversation history. - conversationHistory.add(responseMessage); - - Req newRequest = this.doCreateToolResponseRequest(request, responseMessage, conversationHistory); - - return this.callWithFunctionSupport(newRequest); - } - - abstract protected Req doCreateToolResponseRequest(Req previousRequest, Msg responseMessage, - List conversationHistory); - - abstract protected List doGetUserMessages(Req request); - - abstract protected Msg doGetToolResponseMessage(Resp response); - - abstract protected Resp doChatCompletion(Req request); - - abstract protected boolean isToolFunctionCall(Resp response); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallback.java deleted file mode 100644 index 044c181..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/AbstractFunctionCallback.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model.function; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.util.Assert; - -import java.util.function.Function; - -/** - * Abstract implementation of the {@link FunctionCallback} for interacting with the - * Model's function calling protocol and a {@link Function} wrapping the interaction with - * the 3rd party service/function. - * - * Implement the {@code O apply(I request) } method to implement the interaction with the - * 3rd party service/function. - * - * The {@link #responseConverter} function is responsible to convert the 3rd party - * function's output type into a string expected by the LLM model. - * - * @param the 3rd party service input type. - * @param the 3rd party service output type. - * @author Christian Tzolov - */ -abstract class AbstractFunctionCallback implements Function, FunctionCallback { - - private final String name; - - private final String description; - - private final Class inputType; - - private final String inputTypeSchema; - - private final ObjectMapper objectMapper; - - private final Function responseConverter; - - /** - * Constructs a new {@link AbstractFunctionCallback} with the given name, description, - * input type and default object mapper. - * @param name Function name. Should be unique within the ChatClient's function - * registry. - * @param description Function description. Used as a "system prompt" by the model to - * decide if the function should be called. - * @param inputTypeSchema Used to compute, the argument's Schema (such as JSON Schema - * or OpenAPI Schema)required by the Model's function calling protocol. - * @param inputType Used to compute, the argument's JSON schema required by the - * Model's function calling protocol. - * @param responseConverter Used to convert the function's output type to a string. - * @param objectMapper Used to convert the function's input and output types to and - * from JSON. - */ - protected AbstractFunctionCallback(String name, String description, String inputTypeSchema, Class inputType, - Function responseConverter, ObjectMapper objectMapper) { - Assert.notNull(name, "Name must not be null"); - Assert.notNull(description, "Description must not be null"); - Assert.notNull(inputType, "InputType must not be null"); - Assert.notNull(inputTypeSchema, "InputTypeSchema must not be null"); - Assert.notNull(responseConverter, "ResponseConverter must not be null"); - Assert.notNull(objectMapper, "ObjectMapper must not be null"); - this.name = name; - this.description = description; - this.inputType = inputType; - this.inputTypeSchema = inputTypeSchema; - this.responseConverter = responseConverter; - this.objectMapper = objectMapper; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getDescription() { - return this.description; - } - - @Override - public String getInputTypeSchema() { - return this.inputTypeSchema; - } - - @Override - public String call(String functionArguments) { - - // Convert the tool calls JSON arguments into a Java function request object. - I request = fromJson(functionArguments, inputType); - - // extend conversation with function response. - return this.andThen(this.responseConverter).apply(request); - } - - private T fromJson(String json, Class targetClass) { - try { - return this.objectMapper.readValue(json, targetClass); - } - catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((description == null) ? 0 : description.hashCode()); - result = prime * result + ((inputType == null) ? 0 : inputType.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AbstractFunctionCallback other = (AbstractFunctionCallback) obj; - if (name == null) { - if (other.name != null) - return false; - } - else if (!name.equals(other.name)) - return false; - if (description == null) { - if (other.description != null) - return false; - } - else if (!description.equals(other.description)) - return false; - if (inputType == null) { - if (other.inputType != null) - return false; - } - else if (!inputType.equals(other.inputType)) - return false; - return true; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallback.java deleted file mode 100644 index a14a59b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallback.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model.function; - -/** - * - * 表示模型函数调用处理程序。实现已向注册对触发函数调用的提示进行建模和调用。 - * - * https://blog.csdn.net/weixin_37546425/article/details/136402740 - * - * https://www.163.com/dy/article/ICE2S20P05119NPR.html - * - * Represents a model function call handler. Implementations are registered with the - * Models and called on prompts that trigger the function call. - * - * @author Christian Tzolov - */ -public interface FunctionCallback { - - /** - * @return Returns the Function name. Unique within the model. - */ - public String getName(); - - /** - * @return Returns the function description. This description is used by the model do - * decide if the function should be called or not. - */ - public String getDescription(); - - /** - * @return Returns the JSON schema of the function input type. - */ - public String getInputTypeSchema(); - - /** - * Called when a model detects and triggers a function call. The model is responsible - * to pass the function arguments in the pre-configured JSON schema format. - * @param functionInput JSON string with the function arguments to be passed to the - * function. The arguments are defined as JSON schema usually registered with the the - * model. - * @return String containing the function call response. - */ - public String call(String functionInput); - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackContext.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackContext.java deleted file mode 100644 index ca24e26..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackContext.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cn.iocoder.yudao.framework.ai.model.function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import org.springframework.beans.BeansException; -import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; -import org.springframework.cloud.function.context.config.FunctionContextUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.annotation.Description; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; - -import java.lang.reflect.Type; -import java.util.function.Function; - -/** - * A Spring {@link ApplicationContextAware} implementation that provides a way to retrieve - * a {@link Function} from the Spring context and wrap it into a {@link FunctionCallback}. - * - * The name of the function is determined by the bean name. - * - * The description of the function is determined by the following rules: - *

    - *
  • Provided as a default description
  • - *
  • Provided as a {@code @Description} annotation on the bean
  • - *
  • Provided as a {@code @JsonClassDescription} annotation on the input class
  • - *
- * - * @author Christian Tzolov - * @author Christopher Smith - */ -public class FunctionCallbackContext implements ApplicationContextAware { - - private GenericApplicationContext applicationContext; - - private FunctionCallbackWrapper.Builder.SchemaType schemaType = FunctionCallbackWrapper.Builder.SchemaType.JSON_SCHEMA; - - public void setSchemaType(FunctionCallbackWrapper.Builder.SchemaType schemaType) { - this.schemaType = schemaType; - } - - @Override - public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { - this.applicationContext = (GenericApplicationContext) applicationContext; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public FunctionCallback getFunctionCallback(@NonNull String beanName, @Nullable String defaultDescription) { - - Type beanType = FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), beanName); - - if (beanType == null) { - throw new IllegalArgumentException( - "Functional bean with name: " + beanName + " does not exist in the context."); - } - - if (!Function.class.isAssignableFrom(FunctionTypeUtils.getRawType(beanType))) { - throw new IllegalArgumentException( - "Function call Bean must be of type Function. Found: " + beanType.getTypeName()); - } - - Type functionInputType = TypeResolverHelper.getFunctionArgumentType(beanType, 0); - - Class functionInputClass = FunctionTypeUtils.getRawType(functionInputType); - String functionName = beanName; - String functionDescription = defaultDescription; - - if (!StringUtils.hasText(functionDescription)) { - // Look for a Description annotation on the bean - Description descriptionAnnotation = applicationContext.findAnnotationOnBean(beanName, Description.class); - - if (descriptionAnnotation != null) { - functionDescription = descriptionAnnotation.value(); - } - - if (!StringUtils.hasText(functionDescription)) { - // Look for a JsonClassDescription annotation on the input class - JsonClassDescription jsonClassDescriptionAnnotation = functionInputClass - .getAnnotation(JsonClassDescription.class); - if (jsonClassDescriptionAnnotation != null) { - functionDescription = jsonClassDescriptionAnnotation.value(); - } - } - - if (!StringUtils.hasText(functionDescription)) { - throw new IllegalStateException("Could not determine function description." - + "Please provide a description either as a default parameter, via @Description annotation on the bean " - + "or @JsonClassDescription annotation on the input class."); - } - } - - Object bean = this.applicationContext.getBean(beanName); - - // TODO: 2024/3/16 fansili 适配jdk8 - return null; -// if (bean instanceof Function function) { -// return FunctionCallbackWrapper.builder(function) -// .withName(functionName) -// .withSchemaType(this.schemaType) -// .withDescription(functionDescription) -// .withInputType(functionInputClass) -// .build(); -// } -// else { -// throw new IllegalArgumentException("Bean must be of type Function"); -// } - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackWrapper.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackWrapper.java deleted file mode 100644 index d824121..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallbackWrapper.java +++ /dev/null @@ -1,140 +0,0 @@ -package cn.iocoder.yudao.framework.ai.model.function; - -import cn.iocoder.yudao.framework.ai.model.ModelOptionsUtils; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.util.Assert; - -import java.util.function.Function; - -/** - * Note that the underlying function is responsible for converting the output into format - * that can be consumed by the Model. The default implementation converts the output into - * String before sending it to the Model. Provide a custom function responseConverter - * implementation to override this. - * - */ -public class FunctionCallbackWrapper extends AbstractFunctionCallback { - - private final Function function; - - private FunctionCallbackWrapper(String name, String description, String inputTypeSchema, Class inputType, - Function responseConverter, Function function) { - super(name, description, inputTypeSchema, inputType, responseConverter, - new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)); - Assert.notNull(function, "Function must not be null"); - this.function = function; - } - - @SuppressWarnings("unchecked") - private static Class resolveInputType(Function function) { - return (Class) TypeResolverHelper.getFunctionInputClass((Class>) function.getClass()); - } - - @Override - public O apply(I input) { - return this.function.apply(input); - } - - public static Builder builder(Function function) { - return new Builder<>(function); - } - - public static class Builder { - - public enum SchemaType { - - JSON_SCHEMA, OPEN_API_SCHEMA - - } - - private String name; - - private String description; - - private Class inputType; - - private final Function function; - - private SchemaType schemaType = SchemaType.JSON_SCHEMA; - - public Builder(Function function) { - Assert.notNull(function, "Function must not be null"); - this.function = function; - } - - // By default the response is converted to a JSON string. - private Function responseConverter = (response) -> ModelOptionsUtils.toJsonString(response); - - private String inputTypeSchema; - - private ObjectMapper objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - public Builder withName(String name) { - Assert.hasText(name, "Name must not be empty"); - this.name = name; - return this; - } - - public Builder withDescription(String description) { - Assert.hasText(description, "Description must not be empty"); - this.description = description; - return this; - } - - @SuppressWarnings("unchecked") - public Builder withInputType(Class inputType) { - this.inputType = (Class) inputType; - return this; - } - - public Builder withResponseConverter(Function responseConverter) { - Assert.notNull(responseConverter, "ResponseConverter must not be null"); - this.responseConverter = responseConverter; - return this; - } - - public Builder withInputTypeSchema(String inputTypeSchema) { - Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty"); - this.inputTypeSchema = inputTypeSchema; - return this; - } - - public Builder withObjectMapper(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "ObjectMapper must not be null"); - this.objectMapper = objectMapper; - return this; - } - - public Builder withSchemaType(SchemaType schemaType) { - Assert.notNull(schemaType, "SchemaType must not be null"); - this.schemaType = schemaType; - return this; - } - - public FunctionCallbackWrapper build() { - - Assert.hasText(this.name, "Name must not be empty"); - Assert.hasText(this.description, "Description must not be empty"); - // Assert.notNull(this.inputType, "InputType must not be null"); - Assert.notNull(this.function, "Function must not be null"); - Assert.notNull(this.responseConverter, "ResponseConverter must not be null"); - Assert.notNull(this.objectMapper, "ObjectMapper must not be null"); - - if (this.inputType == null) { - this.inputType = resolveInputType(this.function); - } - - if (this.inputTypeSchema == null) { - boolean upperCaseTypeValues = this.schemaType == SchemaType.OPEN_API_SCHEMA; - this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues); - } - - return new FunctionCallbackWrapper<>(this.name, this.description, this.inputTypeSchema, this.inputType, - this.responseConverter, this.function); - } - - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptions.java deleted file mode 100644 index fdb42d6..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptions.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model.function; - -import java.util.List; -import java.util.Set; - -/** - * @author Christian Tzolov - */ -public interface FunctionCallingOptions { - - /** - * Function Callbacks to be registered with the ChatClient. For Prompt Options the - * functionCallbacks are automatically enabled for the duration of the prompt - * execution. For Default Options the FunctionCallbacks are registered but disabled by - * default. You have to use "functions" property to list the function names from the - * ChatClient registry to be used in the chat completion requests. - * @return Return the Function Callbacks to be registered with the ChatClient. - */ - List getFunctionCallbacks(); - - /** - * Set the Function Callbacks to be registered with the ChatClient. - * @param functionCallbacks the Function Callbacks to be registered with the - * ChatClient. - */ - void setFunctionCallbacks(List functionCallbacks); - - /** - * @return List of function names from the ChatClient registry to be used in the next - * chat completion requests. - */ - Set getFunctions(); - - /** - * Set the list of function names from the ChatClient registry to be used in the next - * chat completion requests. - * @param functions the list of function names from the ChatClient registry to be used - * in the next chat completion requests. - */ - void setFunctions(Set functions); - - /** - * @return Returns FunctionCallingOptionsBuilder to create a new instance of - * FunctionCallingOptions. - */ - public static FunctionCallingOptionsBuilder builder() { - return new FunctionCallingOptionsBuilder(); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptionsBuilder.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptionsBuilder.java deleted file mode 100644 index 4fc258b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/FunctionCallingOptionsBuilder.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model.function; - -import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; -import org.springframework.util.Assert; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Builder for {@link FunctionCallingOptions}. Using the {@link FunctionCallingOptions} - * permits options portability between different AI providers that support - * function-calling. - * - * @author Christian Tzolov - * @since 0.8.1 - */ -public class FunctionCallingOptionsBuilder { - - private final PortableFunctionCallingOptions options; - - public FunctionCallingOptionsBuilder() { - this.options = new PortableFunctionCallingOptions(); - } - - public FunctionCallingOptionsBuilder withFunctionCallbacks(List functionCallbacks) { - this.options.setFunctionCallbacks(functionCallbacks); - return this; - } - - public FunctionCallingOptionsBuilder withFunctionCallback(FunctionCallback functionCallback) { - Assert.notNull(functionCallback, "FunctionCallback must not be null"); - this.options.getFunctionCallbacks().add(functionCallback); - return this; - } - - public FunctionCallingOptionsBuilder withFunctions(Set functions) { - this.options.setFunctions(functions); - return this; - } - - public FunctionCallingOptionsBuilder withFunction(String function) { - Assert.notNull(function, "Function must not be null"); - this.options.getFunctions().add(function); - return this; - } - - public FunctionCallingOptionsBuilder withTemperature(Float temperature) { - this.options.setTemperature(temperature); - return this; - } - - public FunctionCallingOptionsBuilder withTopP(Float topP) { - this.options.setTopP(topP); - return this; - } - - public FunctionCallingOptionsBuilder withTopK(Integer topK) { - this.options.setTopK(topK); - return this; - } - - public PortableFunctionCallingOptions build() { - return this.options; - } - - public static class PortableFunctionCallingOptions implements FunctionCallingOptions, ChatOptions { - - private List functionCallbacks = new ArrayList<>(); - - private Set functions = new HashSet<>(); - - private Float temperature; - - private Float topP; - - private Integer topK; - - @Override - public List getFunctionCallbacks() { - return this.functionCallbacks; - } - - @Override - public void setFunctionCallbacks(List functionCallbacks) { - Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null"); - this.functionCallbacks = functionCallbacks; - } - - @Override - public Set getFunctions() { - return this.functions; - } - - @Override - public void setFunctions(Set functions) { - Assert.notNull(functions, "Functions must not be null"); - this.functions = functions; - } - - @Override - public Float getTemperature() { - return this.temperature; - } - - @Override - public void setTemperature(Float temperature) { - this.temperature = temperature; - } - - @Override - public Float getTopP() { - return this.topP; - } - - @Override - public void setTopP(Float topP) { - this.topP = topP; - } - - @Override - public Integer getTopK() { - return this.topK; - } - - @Override - public void setTopK(Integer topK) { - this.topK = topK; - } - - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/TypeResolverHelper.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/TypeResolverHelper.java deleted file mode 100644 index e1c8c49..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/function/TypeResolverHelper.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.model.function; - -import net.jodah.typetools.TypeResolver; - -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.function.Function; - -/** - * @author Christian Tzolov - */ -public class TypeResolverHelper { - - public static Class getFunctionInputClass(Class> functionClass) { - return getFunctionArgumentClass(functionClass, 0); - } - - public static Class getFunctionOutputClass(Class> functionClass) { - return getFunctionArgumentClass(functionClass, 1); - } - - public static Class getFunctionArgumentClass(Class> functionClass, int argumentIndex) { - Type type = TypeResolver.reify(Function.class, functionClass); - - var argumentType = type instanceof ParameterizedType - ? ((ParameterizedType) type).getActualTypeArguments()[argumentIndex] : Object.class; - - return toRawClass(argumentType); - } - - public static Type getFunctionInputType(Class> functionClass) { - return getFunctionArgumentType(functionClass, 0); - } - - public static Type getFunctionOutputType(Class> functionClass) { - return getFunctionArgumentType(functionClass, 1); - } - - public static Type getFunctionArgumentType(Class> functionClass, int argumentIndex) { - Type functionType = TypeResolver.reify(Function.class, functionClass); - return getFunctionArgumentType(functionType, argumentIndex); - } - - public static Type getFunctionArgumentType(Type functionType, int argumentIndex) { - var argumentType = functionType instanceof ParameterizedType - ? ((ParameterizedType) functionType).getActualTypeArguments()[argumentIndex] : Object.class; - - return argumentType; - } - - /** - * Effectively converts {@link Type} which could be {@link ParameterizedType} to raw - * Class (no generics). - * @param type actual {@link Type} instance - * @return instance of {@link Class} as raw representation of the provided - * {@link Type} - */ - public static Class toRawClass(Type type) { - return type != null - ? TypeResolver.resolveRawClass(type instanceof GenericArrayType ? type : TypeResolver.reify(type), null) - : null; - } - - // public static void main(String[] args) { - // Class> clazz = MockWeatherService.class; - // System.out.println(getFunctionInputType(clazz)); - - // } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/package-info.java deleted file mode 100644 index d2cf8e3..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/model/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Provides a set of interfaces and classes for a generic API designed to interact with - * various AI models. This package includes interfaces for handling AI model calls, - * requests, responses, results, and associated metadata. It is designed to offer a - * flexible and adaptable framework for interacting with different types of AI models, - * abstracting the complexities involved in model invocation and result processing. The - * use of generics enhances the API's capability to work with a wide range of models, - * ensuring a broad applicability across diverse AI scenarios. - * - */ -package cn.iocoder.yudao.framework.ai.model; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/package-info.java deleted file mode 100644 index ffd2ac9..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/package-info.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * author: fansili - * time: 2024/3/12 20:29 - * - * TODO @fansili:包的想法,需要重点看看 - * - * 1. org.springframework.ai:包括 chat、image、model、parser、util 部分 - * - * 2. yudao.framework.models - * \qianwen 通义千问 - * \yiyan 文心一言 - * \xinghuo 星火 - * \midjourney - */ -package cn.iocoder.yudao.framework.ai; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractConversionServiceOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractConversionServiceOutputParser.java deleted file mode 100644 index cee9fff..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractConversionServiceOutputParser.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.parser; - -import org.springframework.core.convert.support.DefaultConversionService; - -/** - * Abstract {@link OutputParser} implementation that uses a pre-configured - * {@link DefaultConversionService} to convert the LLM output into the desired type - * format. - * - * @param Specifies the desired response type. - * @author Mark Pollack - * @author Christian Tzolov - */ -public abstract class AbstractConversionServiceOutputParser implements OutputParser { - - private final DefaultConversionService conversionService; - - public AbstractConversionServiceOutputParser(DefaultConversionService conversionService) { - this.conversionService = conversionService; - } - - public DefaultConversionService getConversionService() { - return conversionService; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractMessageConverterOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractMessageConverterOutputParser.java deleted file mode 100644 index abe5005..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/AbstractMessageConverterOutputParser.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.parser; - -import org.springframework.messaging.converter.MessageConverter; - -/** - * Abstract {@link OutputParser} implementation that uses a pre-configured - * {@link MessageConverter} to convert the LLM output into the desired type format. - * - * @param Specifies the desired response type. - * @author Mark Pollack - * @author Christian Tzolov - */ -public abstract class AbstractMessageConverterOutputParser implements OutputParser { - - private MessageConverter messageConverter; - - public AbstractMessageConverterOutputParser(MessageConverter messageConverter) { - this.messageConverter = messageConverter; - } - - public MessageConverter getMessageConverter() { - return this.messageConverter; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/BeanOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/BeanOutputParser.java deleted file mode 100644 index 109e75f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/BeanOutputParser.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.parser; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.util.DefaultIndenter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.github.victools.jsonschema.generator.SchemaGenerator; -import com.github.victools.jsonschema.generator.SchemaGeneratorConfig; -import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; -import com.github.victools.jsonschema.module.jackson.JacksonModule; - -import java.util.Map; -import java.util.Objects; - -import static com.github.victools.jsonschema.generator.OptionPreset.PLAIN_JSON; -import static com.github.victools.jsonschema.generator.SchemaVersion.DRAFT_2020_12; - -/** - * An implementation of {@link OutputParser} that transforms the LLM output to a specific - * object type using JSON schema. This parser works by generating a JSON schema based on a - * given Java class, which is then used to validate and transform the LLM output into the - * desired type. - * - * @param The target type to which the output will be converted. - * @author Mark Pollack - * @author Christian Tzolov - * @author Sebastian Ullrich - * @author Kirk Lund - */ -public class BeanOutputParser implements OutputParser { - - /** Holds the generated JSON schema for the target type. */ - private String jsonSchema; - - /** The Java class representing the target type. */ - @SuppressWarnings({ "FieldMayBeFinal", "rawtypes" }) - private Class clazz; - - /** The object mapper used for deserialization and other JSON operations. */ - @SuppressWarnings("FieldMayBeFinal") - private ObjectMapper objectMapper; - - /** - * Constructor to initialize with the target type's class. - * @param clazz The target type's class. - */ - public BeanOutputParser(Class clazz) { - this(clazz, null); - } - - /** - * Constructor to initialize with the target type's class, a custom object mapper, and - * a line endings normalizer to ensure consistent line endings on any platform. - * @param clazz The target type's class. - * @param objectMapper Custom object mapper for JSON operations. endings. - */ - public BeanOutputParser(Class clazz, ObjectMapper objectMapper) { - Objects.requireNonNull(clazz, "Java Class cannot be null;"); - this.clazz = clazz; - this.objectMapper = objectMapper != null ? objectMapper : getObjectMapper(); - generateSchema(); - } - - /** - * Generates the JSON schema for the target type. - */ - private void generateSchema() { - JacksonModule jacksonModule = new JacksonModule(); - SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(DRAFT_2020_12, PLAIN_JSON) - .with(jacksonModule); - SchemaGeneratorConfig config = configBuilder.build(); - SchemaGenerator generator = new SchemaGenerator(config); - JsonNode jsonNode = generator.generateSchema(this.clazz); - ObjectWriter objectWriter = new ObjectMapper() - .writer(new DefaultPrettyPrinter().withObjectIndenter(new DefaultIndenter().withLinefeed("\n"))); - try { - this.jsonSchema = objectWriter.writeValueAsString(jsonNode); - } - catch (JsonProcessingException e) { - throw new RuntimeException("Could not pretty print json schema for " + this.clazz, e); - } - } - - @Override - /** - * Parses the given text to transform it to the desired target type. - * @param text The LLM output in string format. - * @return The parsed output in the desired target type. - */ - public T parse(String text) { - try { - // If the response is a JSON Schema, extract the properties and use them as - // the response. - text = this.jsonSchemaToInstance(text); - return (T) this.objectMapper.readValue(text, this.clazz); - } - catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - /** - * Converts a JSON Schema to an instance based on a given text. - * @param text The JSON Schema in string format. - * @return The JSON instance generated from the JSON Schema, or the original text if - * the input is not a JSON Schema. - */ - private String jsonSchemaToInstance(String text) { - try { - Map map = this.objectMapper.readValue(text, Map.class); - if (map.containsKey("$schema")) { - return this.objectMapper.writeValueAsString(map.get("properties")); - } - } - catch (Exception e) { - } - return text; - } - - /** - * Configures and returns an object mapper for JSON operations. - * @return Configured object mapper. - */ - protected ObjectMapper getObjectMapper() { - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper; - } - - /** - * Provides the expected format of the response, instructing that it should adhere to - * the generated JSON schema. - * @return The instruction format string. - */ - @Override - public String getFormat() { -// String template = """ -// Your response should be in JSON format. -// Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. -// Do not include markdown code blocks in your response. -// Here is the JSON Schema instance your output must adhere to: -// ```%s``` -// """; - String template = "Your response should be in JSON format.\n" + - "\t\t\t\tDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\n" + - "\t\t\t\tDo not include markdown code blocks in your response.\n" + - "\t\t\t\tHere is the JSON Schema instance your output must adhere to:\n" + - "\t\t\t\t```%s```"; - return String.format(template, this.jsonSchema); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/FormatProvider.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/FormatProvider.java deleted file mode 100644 index 53d0017..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/FormatProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.parser; - -/** - * Implementations of this interface provides instructions for how the output of a - * language generative should be formatted. - * - * @author Mark Pollack - */ -public interface FormatProvider { - - /** - * @return Returns a string containing instructions for how the output of a language - * generative should be formatted. - */ - String getFormat(); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/ListOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/ListOutputParser.java deleted file mode 100644 index 37fd49b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/ListOutputParser.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cn.iocoder.yudao.framework.ai.parser; - -import org.springframework.core.convert.support.DefaultConversionService; - -import java.util.List; - -/** - * {@link OutputParser} implementation that uses a {@link DefaultConversionService} to - * convert the LLM output into a {@link List} instance. - * - * @author Mark Pollack - * @author Christian Tzolov - */ -public class ListOutputParser extends AbstractConversionServiceOutputParser> { - - public ListOutputParser(DefaultConversionService defaultConversionService) { - super(defaultConversionService); - } - - @Override - public String getFormat() { -// return """ -// Your response should be a list of comma separated values -// eg: `foo, bar, baz` -// """; - return "Your response should be a list of comma separated values\n" + - "\t\t\t\teg: `foo, bar, baz`"; - } - - @Override - public List parse(String text) { - return getConversionService().convert(text, List.class); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/MapOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/MapOutputParser.java deleted file mode 100644 index bd57b3b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/MapOutputParser.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.parser; - -import org.springframework.messaging.Message; -import org.springframework.messaging.converter.MappingJackson2MessageConverter; -import org.springframework.messaging.support.MessageBuilder; - -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -/** - * {@link OutputParser} implementation that uses a pre-configured - * {@link MappingJackson2MessageConverter} to convert the LLM output into a - * java.util.Map<String, Object> instance. - * - * @author Mark Pollack - * @author Christian Tzolov - */ -public class MapOutputParser extends AbstractMessageConverterOutputParser> { - - public MapOutputParser() { - super(new MappingJackson2MessageConverter()); - } - - @Override - public Map parse(String text) { - Message message = MessageBuilder.withPayload(text.getBytes(StandardCharsets.UTF_8)).build(); - return (Map) getMessageConverter().fromMessage(message, HashMap.class); - } - - @Override - public String getFormat() { -// String raw = """ -// Your response should be in JSON format. -// The data structure for the JSON should match this Java class: %s -// Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. -// """; - String raw = "Your response should be in JSON format.\n" + - "\t\t\t\tThe data structure for the JSON should match this Java class: %s\n" + - "\t\t\t\tDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation."; - return String.format(raw, "java.util.HashMap"); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/OutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/OutputParser.java deleted file mode 100644 index 1543532..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/OutputParser.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.parser; - -/** - * Converts the (raw) LLM output into a structured responses of type. The - * {@link FormatProvider#getFormat()} method should provide the LLM prompt description of - * the desired format. - * - * @param Specifies the desired response type. - * @author Mark Pollack - * @author Christian Tzolov - */ -public interface OutputParser extends Parser, FormatProvider { - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/Parser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/Parser.java deleted file mode 100644 index 6016ee5..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/Parser.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.parser; - -@FunctionalInterface -public interface Parser { - - T parse(String text); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/README.md b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/README.md deleted file mode 100644 index 740e330..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/parser/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Output Parsing - -* [Documentation](https://docs.spring.io/spring-ai/reference/concepts.html#_output_parsing) -* [Usage examples](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIT.java) - -The output of AI models traditionally arrives as a java.util.String, even if you ask for the reply to be in JSON. It may be the correct JSON, but it isn’t a JSON data structure. It is just a string. Also, asking "for JSON" as part of the prompt isn’t 100% accurate. - -This intricacy has led to the emergence of a specialized field involving the creation of prompts to yield the intended output, followed by parsing the resulting simple string into a usable data structure for application integration. - -Output parsing employs meticulously crafted prompts, often necessitating multiple interactions with the model to achieve the desired formatting. - -This challenge has prompted OpenAI to introduce 'OpenAI Functions' as a means to specify the desired output format from the model precisely. diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatClient.java new file mode 100644 index 0000000..bd6aa3e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatClient.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat; + + +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ModelClient; + +@FunctionalInterface +public interface ChatClient extends ModelClient { + + default String call(String message) { + Prompt prompt = new Prompt(new UserMessage(message)); + Generation generation = call(prompt).getResult(); + return (generation != null) ? generation.getOutput().getContent() : ""; + } + + @Override + ChatResponse call(Prompt prompt); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatException.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatException.java new file mode 100644 index 0000000..bc92226 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatException.java @@ -0,0 +1,15 @@ +package org.springframework.ai.chat; + +/** + * 聊天异常 + * + * author: fansili + * time: 2024/3/15 20:45 + */ +public class ChatException extends RuntimeException { + + public ChatException(String message) { + super(message); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatResponse.java new file mode 100644 index 0000000..641212b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/ChatResponse.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.chat; + +import org.springframework.ai.chat.metadata.ChatResponseMetadata; +import org.springframework.ai.model.ModelResponse; +import org.springframework.util.CollectionUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * 人工智能提供商返回的聊天完成(例如生成)响应。 + * + * The chat completion (e.g. generation) response returned by an AI provider. + */ +public class ChatResponse implements ModelResponse { + + private final ChatResponseMetadata chatResponseMetadata; + + /** + * List of generated messages returned by the AI provider. + */ + private final List generations; + + /** + * Construct a new {@link ChatResponse} instance without metadata. + * @param generations the {@link List} of {@link Generation} returned by the AI + * provider. + */ + public ChatResponse(List generations) { + this(generations, ChatResponseMetadata.NULL); + } + + /** + * Construct a new {@link ChatResponse} instance. + * @param generations the {@link List} of {@link Generation} returned by the AI + * provider. + * @param chatResponseMetadata {@link ChatResponseMetadata} containing information + * about the use of the AI provider's API. + */ + public ChatResponse(List generations, ChatResponseMetadata chatResponseMetadata) { + this.chatResponseMetadata = chatResponseMetadata; +// this.generations = List.copyOf(generations); + this.generations = Collections.unmodifiableList(generations); + } + + /** + * The {@link List} of {@link Generation generated outputs}. + *

+ * It is a {@link List} of {@link List lists} because the Prompt could request + * multiple output {@link Generation generations}. + * @return the {@link List} of {@link Generation generated outputs}. + */ + + @Override + public List getResults() { + return this.generations; + } + + /** + * @return Returns the first {@link Generation} in the generations list. + */ + public Generation getResult() { + if (CollectionUtils.isEmpty(this.generations)) { + return null; + } + return this.generations.get(0); + } + + /** + * @return Returns {@link ChatResponseMetadata} containing information about the use + * of the AI provider's API. + */ + @Override + public ChatResponseMetadata getMetadata() { + return this.chatResponseMetadata; + } + + @Override + public String toString() { + return "ChatResponse [metadata=" + chatResponseMetadata + ", generations=" + generations + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; +// if (!(o instanceof ChatResponse that)) +// return false; + if (!(o instanceof ChatResponse)) { + return false; + } + ChatResponse that = (ChatResponse) o; + return Objects.equals(chatResponseMetadata, that.chatResponseMetadata) + && Objects.equals(generations, that.generations); + } + + @Override + public int hashCode() { + return Objects.hash(chatResponseMetadata, generations); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/Generation.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/Generation.java new file mode 100644 index 0000000..ca17848 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/Generation.java @@ -0,0 +1,86 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; +import org.springframework.ai.model.ModelResult; +import org.springframework.lang.Nullable; + +import java.util.Map; +import java.util.Objects; + +/** + * 表示AI返回的响应。 + * + * Represents a response returned by the AI. + */ +public class Generation implements ModelResult { + + private AssistantMessage assistantMessage; + + private ChatGenerationMetadata chatGenerationMetadata; + + public Generation(String text) { + this.assistantMessage = new AssistantMessage(text); + } + + public Generation(String text, Map properties) { + this.assistantMessage = new AssistantMessage(text, properties); + } + + @Override + public AssistantMessage getOutput() { + return this.assistantMessage; + } + + @Override + public ChatGenerationMetadata getMetadata() { + ChatGenerationMetadata chatGenerationMetadata = this.chatGenerationMetadata; + return chatGenerationMetadata != null ? chatGenerationMetadata : ChatGenerationMetadata.NULL; + } + + public Generation withGenerationMetadata(@Nullable ChatGenerationMetadata chatGenerationMetadata) { + this.chatGenerationMetadata = chatGenerationMetadata; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + + if (!(o instanceof Generation)) { + return false; + } + Generation that = (Generation) o; + return Objects.equals(assistantMessage, that.assistantMessage) + && Objects.equals(chatGenerationMetadata, that.chatGenerationMetadata); + } + + @Override + public int hashCode() { + return Objects.hash(assistantMessage, chatGenerationMetadata); + } + + @Override + public String toString() { + return "Generation{" + "assistantMessage=" + assistantMessage + ", chatGenerationMetadata=" + + chatGenerationMetadata + '}'; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/StreamingChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/StreamingChatClient.java new file mode 100644 index 0000000..079989d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/StreamingChatClient.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat; + +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.StreamingModelClient; +import reactor.core.publisher.Flux; + +@FunctionalInterface +public interface StreamingChatClient extends StreamingModelClient { + + @Override + Flux stream(Prompt prompt); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java new file mode 100644 index 0000000..7cc9360 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java @@ -0,0 +1,151 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.StreamUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public abstract class AbstractMessage implements Message { + + protected final MessageType messageType; + + protected final String textContent; + + protected final List mediaData; + + /** + * Additional options for the message to influence the response, not a generative map. + */ + protected final Map properties; + + protected AbstractMessage(MessageType messageType, String content) { + this(messageType, content, Map.of()); + } + + protected AbstractMessage(MessageType messageType, String content, Map messageProperties) { + Assert.notNull(messageType, "Message type must not be null"); + // Assert.notNull(content, "Content must not be null"); + this.messageType = messageType; + this.textContent = content; + this.mediaData = new ArrayList<>(); + this.properties = messageProperties; + } + + protected AbstractMessage(MessageType messageType, String textContent, List mediaData) { + this(messageType, textContent, mediaData, Map.of()); + } + + protected AbstractMessage(MessageType messageType, String textContent, List mediaData, + Map messageProperties) { + + Assert.notNull(messageType, "Message type must not be null"); + Assert.notNull(textContent, "Content must not be null"); + Assert.notNull(mediaData, "media data must not be null"); + + this.messageType = messageType; + this.textContent = textContent; + this.mediaData = new ArrayList<>(mediaData); + this.properties = messageProperties; + } + + protected AbstractMessage(MessageType messageType, Resource resource) { + this(messageType, resource, Collections.emptyMap()); + } + + @SuppressWarnings("null") + protected AbstractMessage(MessageType messageType, Resource resource, Map messageProperties) { + Assert.notNull(messageType, "Message type must not be null"); + Assert.notNull(resource, "Resource must not be null"); + + this.messageType = messageType; + this.properties = messageProperties; + this.mediaData = new ArrayList<>(); + + try (InputStream inputStream = resource.getInputStream()) { + this.textContent = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); + } + catch (IOException ex) { + throw new RuntimeException("Failed to read resource", ex); + } + } + + @Override + public String getContent() { + return this.textContent; + } + + @Override + public List getMediaData() { + return this.mediaData; + } + + @Override + public Map getProperties() { + return this.properties; + } + + @Override + public MessageType getMessageType() { + return this.messageType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mediaData == null) ? 0 : mediaData.hashCode()); + result = prime * result + ((properties == null) ? 0 : properties.hashCode()); + result = prime * result + ((messageType == null) ? 0 : messageType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractMessage other = (AbstractMessage) obj; + if (mediaData == null) { + if (other.mediaData != null) + return false; + } + else if (!mediaData.equals(other.mediaData)) + return false; + if (properties == null) { + if (other.properties != null) + return false; + } + else if (!properties.equals(other.properties)) + return false; + if (messageType != other.messageType) + return false; + return true; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AssistantMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AssistantMessage.java new file mode 100644 index 0000000..28710db --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/AssistantMessage.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import java.util.Map; + +/** + * 让生成人员知道内容是作为对用户的响应生成的。 + * 此角色指示生成者先前在会话中生成的消息。 + * 通过包括该系列中的辅助消息,您可以为生成的关于提供上下文之前在谈话中的交流。 + * + * Lets the generative know the content was generated as a response to the user. This role + * indicates messages that the generative has previously generated in the conversation. By + * including assistant messages in the series, you provide context to the generative about + * prior exchanges in the conversation. + */ +public class AssistantMessage extends AbstractMessage { + + public AssistantMessage(String content) { + super(MessageType.ASSISTANT, content); + } + + public AssistantMessage(String content, Map properties) { + super(MessageType.ASSISTANT, content, properties); + } + + @Override + public String toString() { + return "AssistantMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" + + messageType + '}'; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/ChatMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/ChatMessage.java new file mode 100644 index 0000000..194aa54 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/ChatMessage.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import java.util.Map; + +public class ChatMessage extends AbstractMessage { + + public ChatMessage(String role, String content) { + super(MessageType.valueOf(role), content); + } + + public ChatMessage(String role, String content, Map properties) { + super(MessageType.valueOf(role), content, properties); + } + + public ChatMessage(MessageType messageType, String content) { + super(messageType, content); + } + + public ChatMessage(MessageType messageType, String content, Map properties) { + super(messageType, content, properties); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/FunctionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/FunctionMessage.java new file mode 100644 index 0000000..1faba6a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/FunctionMessage.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import java.util.Map; + +public class FunctionMessage extends AbstractMessage { + + public FunctionMessage(String content) { + super(MessageType.SYSTEM, content); + } + + public FunctionMessage(String content, Map properties) { + super(MessageType.SYSTEM, content, properties); + } + + @Override + public String toString() { + return "FunctionMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" + + messageType + '}'; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MediaData.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MediaData.java new file mode 100644 index 0000000..9acd9b1 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MediaData.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import org.springframework.util.Assert; +import org.springframework.util.MimeType; + +/** + * @author Christian Tzolov + */ +public class MediaData { + + private final MimeType mimeType; + + private final Object data; + + public MediaData(MimeType mimeType, Object data) { + Assert.notNull(mimeType, "MimeType must not be null"); + // Assert.notNull(data, "Data must not be null"); + this.mimeType = mimeType; + this.data = data; + } + + public MimeType getMimeType() { + return this.mimeType; + } + + public Object getData() { + return this.data; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/Message.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/Message.java new file mode 100644 index 0000000..10c893a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/Message.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import java.util.List; +import java.util.Map; + +public interface Message { + + String getContent(); + + List getMediaData(); + + Map getProperties(); + + MessageType getMessageType(); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MessageType.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MessageType.java new file mode 100644 index 0000000..a227c45 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/MessageType.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.chat.messages; + +public enum MessageType { + + // 用户消息 + USER("user"), + + // 之前会话的消息 + ASSISTANT("assistant"), + + // 根据注释说明:您可以使用系统消息来指示具有生成性,表现得像某个角色或以特定的方式提供答案总体安排 + // 简单理解:在对话前,发送一条具有角色的信息让模型理解(如:你现在是一个10年拍摄经验的导演,拥有丰富的经验。 这样你就可以去问他,怎么拍一个短视频可以在抖音上火) + SYSTEM("system"), + + // 函数?根据引用现在不支持,会抛出一个异常 ---> throw new IllegalArgumentException("Tool execution results are not supported for Bedrock models"); + FUNCTION("function"); + + private final String value; + + MessageType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static MessageType fromValue(String value) { + for (MessageType messageType : MessageType.values()) { + if (messageType.getValue().equals(value)) { + return messageType; + } + } + throw new IllegalArgumentException("Invalid MessageType value: " + value); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/SystemMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/SystemMessage.java new file mode 100644 index 0000000..f8c67f2 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/SystemMessage.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import org.springframework.core.io.Resource; + +/** + * 作为输入传递的“system”类型的消息。系统消息给出高级别对话说明。 + * 此角色通常提供高级说明对话。 + * 例如,您可以使用系统消息来指示具有生成性,表现得像某个角色或以特定的方式提供答案总体安排 + * + * A message of the type 'system' passed as input. The system message gives high level + * instructions for the conversation. This role typically provides high-level instructions + * for the conversation. For example, you might use a system message to instruct the + * generative to behave like a certain character or to provide answers in a specific + * format. + */ +public class SystemMessage extends AbstractMessage { + + public SystemMessage(String content) { + super(MessageType.SYSTEM, content); + } + + public SystemMessage(Resource resource) { + super(MessageType.SYSTEM, resource); + } + + @Override + public String toString() { + return "SystemMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" + + messageType + '}'; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/UserMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/UserMessage.java new file mode 100644 index 0000000..ca7f360 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/messages/UserMessage.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.messages; + +import org.springframework.core.io.Resource; + +import java.util.List; + +/** + * 作为输入传递的“user”类型的消息具有用户角色的消息来自最终用户或开发者。 + * 它们表示问题、提示或您想要的任何输入产生反应的。 + * + * A message of the type 'user' passed as input Messages with the user role are from the + * end-user or developer. They represent questions, prompts, or any input that you want + * the generative to respond to. + */ +public class UserMessage extends AbstractMessage { + + public UserMessage(String message) { + super(MessageType.USER, message); + } + + public UserMessage(Resource resource) { + super(MessageType.USER, resource); + } + + public UserMessage(String textContent, List mediaDataList) { + super(MessageType.USER, textContent, mediaDataList); + } + + @Override + public String toString() { + return "UserMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType=" + + messageType + '}'; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatGenerationMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatGenerationMetadata.java new file mode 100644 index 0000000..d9f5fc5 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatGenerationMetadata.java @@ -0,0 +1,75 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.metadata; + +import org.springframework.ai.model.ResultMetadata; +import org.springframework.lang.Nullable; + +/** + * Abstract Data Type (ADT) encapsulating information on the completion choices in the AI + * response. + * + * @author John Blum + * @since 0.7.0 + */ +public interface ChatGenerationMetadata extends ResultMetadata { + + ChatGenerationMetadata NULL = ChatGenerationMetadata.from(null, null); + + /** + * Factory method used to construct a new {@link ChatGenerationMetadata} from the + * given {@link String finish reason} and content filter metadata. + * @param finishReason {@link String} contain the reason for the choice completion. + * @param contentFilterMetadata underlying AI provider metadata for filtering applied + * to generation content. + * @return a new {@link ChatGenerationMetadata} from the given {@link String finish + * reason} and content filter metadata. + */ + static ChatGenerationMetadata from(String finishReason, Object contentFilterMetadata) { + return new ChatGenerationMetadata() { + + @Override + @SuppressWarnings("unchecked") + public T getContentFilterMetadata() { + return (T) contentFilterMetadata; + } + + @Override + public String getFinishReason() { + return finishReason; + } + }; + } + + /** + * Returns the underlying AI provider metadata for filtering applied to generation + * content. + * @param {@link Class Type} used to cast the filtered content metadata into the + * AI provider-specific type. + * @return the underlying AI provider metadata for filtering applied to generation + * content. + */ + @Nullable + T getContentFilterMetadata(); + + /** + * Get the {@link String reason} this choice completed for the generation. + * @return the {@link String reason} this choice completed for the generation. + */ + String getFinishReason(); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java new file mode 100644 index 0000000..38eb1fd --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.metadata; + + +import org.springframework.ai.model.ResponseMetadata; + +/** + * Abstract Data Type (ADT) modeling common AI provider metadata returned in an AI + * response. + * + * 抽象数据类型(ADT)建模AI响应中返回的常见AI提供者元数据。 + * + * @author John Blum + * @since 0.7.0 + */ +public interface ChatResponseMetadata extends ResponseMetadata { + + ChatResponseMetadata NULL = new ChatResponseMetadata() { + }; + + /** + * Returns AI provider specific metadata on rate limits. + * @return AI provider specific metadata on rate limits. + * @see RateLimit + */ + default RateLimit getRateLimit() { + return new EmptyRateLimit(); + } + + /** + * Returns AI provider specific metadata on API usage. + * @return AI provider specific metadata on API usage. + * @see Usage + */ + default Usage getUsage() { + return new EmptyUsage(); + } + + default PromptMetadata getPromptMetadata() { + return PromptMetadata.empty(); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyRateLimit.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyRateLimit.java new file mode 100644 index 0000000..480a94d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyRateLimit.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.metadata; + +import java.time.Duration; + +/** + * A RateLimit implementation that returns zero for all property getters + * + * @author John Blum + * @since 0.7.0 + */ +public class EmptyRateLimit implements RateLimit { + + @Override + public Long getRequestsLimit() { + return 0L; + } + + @Override + public Long getRequestsRemaining() { + return 0L; + } + + @Override + public Duration getRequestsReset() { + return Duration.ZERO; + } + + @Override + public Long getTokensLimit() { + return 0L; + } + + @Override + public Long getTokensRemaining() { + return 0L; + } + + @Override + public Duration getTokensReset() { + return Duration.ZERO; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyUsage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyUsage.java new file mode 100644 index 0000000..027e302 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/EmptyUsage.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.metadata; + +/** + * A EmpytUsage implementation that returns zero for all property getters + * + * @author John Blum + * @since 0.7.0 + */ +public class EmptyUsage implements Usage { + + @Override + public Long getPromptTokens() { + return 0L; + } + + @Override + public Long getGenerationTokens() { + return 0L; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/PromptMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/PromptMetadata.java new file mode 100644 index 0000000..becb12d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/PromptMetadata.java @@ -0,0 +1,136 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.chat.metadata; + +import org.springframework.util.Assert; + +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.StreamSupport; + +/** + * Abstract Data Type (ADT) modeling metadata gathered by the AI during request + * processing. + * + * @author John Blum + * @since 0.7.0 + */ +@FunctionalInterface +public interface PromptMetadata extends Iterable { + + /** + * Factory method used to create empty {@link PromptMetadata} when the information is + * not supplied by the AI provider. + * @return empty {@link PromptMetadata}. + */ + static PromptMetadata empty() { + return of(); + } + + /** + * Factory method used to create a new {@link PromptMetadata} composed of an array of + * {@link PromptFilterMetadata}. + * @param array array of {@link PromptFilterMetadata} used to compose the + * {@link PromptMetadata}. + * @return a new {@link PromptMetadata} composed of an array of + * {@link PromptFilterMetadata}. + */ + static PromptMetadata of(PromptFilterMetadata... array) { + return of(Arrays.asList(array)); + } + + /** + * Factory method used to create a new {@link PromptMetadata} composed of an + * {@link Iterable} of {@link PromptFilterMetadata}. + * @param iterable {@link Iterable} of {@link PromptFilterMetadata} used to compose + * the {@link PromptMetadata}. + * @return a new {@link PromptMetadata} composed of an {@link Iterable} of + * {@link PromptFilterMetadata}. + */ + static PromptMetadata of(Iterable iterable) { + Assert.notNull(iterable, "An Iterable of PromptFilterMetadata must not be null"); + return iterable::iterator; + } + + /** + * Returns an {@link Optional} {@link PromptFilterMetadata} at the given index. + * @param promptIndex index of the {@link PromptFilterMetadata} contained in this + * {@link PromptMetadata}. + * @return {@link Optional} {@link PromptFilterMetadata} at the given index. + * @throws IllegalArgumentException if the prompt index is less than 0. + */ + default Optional findByPromptIndex(int promptIndex) { + + Assert.isTrue(promptIndex > -1, "Prompt index [%d] must be greater than equal to 0".formatted(promptIndex)); + + return StreamSupport.stream(this.spliterator(), false) + .filter(promptFilterMetadata -> promptFilterMetadata.getPromptIndex() == promptIndex) + .findFirst(); + } + + /** + * Abstract Data Type (ADT) modeling filter metadata for all prompts sent during an AI + * request. + */ + interface PromptFilterMetadata { + + /** + * Factory method used to construct a new {@link PromptFilterMetadata} with the + * given prompt index and content filter metadata. + * @param promptIndex index of the prompt filter metadata contained in the AI + * response. + * @param contentFilterMetadata underlying AI provider metadata for filtering + * applied to prompt content. + * @return a new instance of {@link PromptFilterMetadata} with the given prompt + * index and content filter metadata. + */ + static PromptFilterMetadata from(int promptIndex, Object contentFilterMetadata) { + + return new PromptFilterMetadata() { + + @Override + public int getPromptIndex() { + return promptIndex; + } + + @Override + @SuppressWarnings("unchecked") + public T getContentFilterMetadata() { + return (T) contentFilterMetadata; + } + }; + } + + /** + * Index of the prompt filter metadata contained in the AI response. + * @return an {@link Integer index} fo the prompt filter metadata contained in the + * AI response. + */ + int getPromptIndex(); + + /** + * Returns the underlying AI provider metadata for filtering applied to prompt + * content. + * @param {@link Class Type} used to cast the filtered content metadata into + * the AI provider-specific type. + * @return the underlying AI provider metadata for filtering applied to prompt + * content. + */ + T getContentFilterMetadata(); + + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/RateLimit.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/RateLimit.java new file mode 100644 index 0000000..22dd64b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/RateLimit.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.metadata; + +import java.time.Duration; + +/** + * Abstract Data Type (ADT) encapsulating metadata from an AI provider's API rate limits + * granted to the API key in use and the API key's current balance. + * + * @author John Blum + * @since 0.7.0 + */ +public interface RateLimit { + + /** + * Returns the maximum number of requests that are permitted before exhausting the + * rate limit. + * @return an {@link Long} with the maximum number of requests that are permitted + * before exhausting the rate limit. + * @see #getRequestsRemaining() + */ + Long getRequestsLimit(); + + /** + * Returns the remaining number of requests that are permitted before exhausting the + * {@link #getRequestsLimit() rate limit}. + * @return an {@link Long} with the remaining number of requests that are permitted + * before exhausting the {@link #getRequestsLimit() rate limit}. + * @see #getRequestsLimit() + */ + Long getRequestsRemaining(); + + /** + * Returns the {@link Duration time} until the rate limit (based on requests) resets + * to its {@link #getRequestsLimit() initial state}. + * @return a {@link Duration} representing the time until the rate limit (based on + * requests) resets to its {@link #getRequestsLimit() initial state}. + * @see #getRequestsLimit() + */ + Duration getRequestsReset(); + + /** + * Returns the maximum number of tokens that are permitted before exhausting the rate + * limit. + * @return an {@link Long} with the maximum number of tokens that are permitted before + * exhausting the rate limit. + * @see #getTokensRemaining() + */ + Long getTokensLimit(); + + /** + * Returns the remaining number of tokens that are permitted before exhausting the + * {@link #getTokensLimit() rate limit}. + * @return an {@link Long} with the remaining number of tokens that are permitted + * before exhausting the {@link #getTokensLimit() rate limit}. + * @see #getTokensLimit() + */ + Long getTokensRemaining(); + + /** + * Returns the {@link Duration time} until the rate limit (based on tokens) resets to + * its {@link #getTokensLimit() initial state}. + * @return a {@link Duration} with the time until the rate limit (based on tokens) + * resets to its {@link #getTokensLimit() initial state}. + * @see #getTokensLimit() + */ + Duration getTokensReset(); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/Usage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/Usage.java new file mode 100644 index 0000000..de4fb9a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/metadata/Usage.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.metadata; + +/** + * 抽象数据类型(ADT)封装关于人工智能提供商API使用的元数据根据AI请求。 + * + * Abstract Data Type (ADT) encapsulating metadata on the usage of an AI provider's API + * per AI request. + * + * @author John Blum + * @since 0.7.0 + */ +public interface Usage { + + /** + * 返回AI请求的{@literal prompt}中使用的令牌数。 + * @返回一个{@link Long},其中包含在的{@literal提示符}中使用的令牌数AI请求。 + * + * Returns the number of tokens used in the {@literal prompt} of the AI request. + * @return an {@link Long} with the number of tokens used in the {@literal prompt} of + * the AI request. + * @see #getGenerationTokens() + */ + Long getPromptTokens(); + + /** + * Returns the number of tokens returned in the {@literal generation (aka completion)} + * of the AI's response. + * @return an {@link Long} with the number of tokens returned in the + * {@literal generation (aka completion)} of the AI's response. + * @see #getPromptTokens() + */ + Long getGenerationTokens(); + + /** + * Return the total number of tokens from both the {@literal prompt} of an AI request + * and {@literal generation} of the AI's response. + * @return the total number of tokens from both the {@literal prompt} of an AI request + * and {@literal generation} of the AI's response. + * @see #getPromptTokens() + * @see #getGenerationTokens() + */ + default Long getTotalTokens() { + Long promptTokens = getPromptTokens(); + promptTokens = promptTokens != null ? promptTokens : 0; + Long completionTokens = getGenerationTokens(); + completionTokens = completionTokens != null ? completionTokens : 0; + return promptTokens + completionTokens; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/package-info.java new file mode 100644 index 0000000..98d92eb --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/package-info.java @@ -0,0 +1,14 @@ +/** + * The org.sf.ai.chat package represents the bounded context for the Chat Model within the + * AI generative model domain. This package extends the core domain defined in + * org.sf.ai.generative, providing implementations specific to chat-based generative AI + * interactions. + * + * In line with Domain-Driven Design principles, this package includes implementations of + * entities and value objects specific to the chat context, such as ChatPrompt and + * ChatResponse, adhering to the ubiquitous language of chat interactions in AI models. + * + * This bounded context is designed to encapsulate all aspects of chat-based AI + * functionalities, maintaining a clear boundary from other contexts within the AI domain. + */ +package org.springframework.ai.chat; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/AssistantPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/AssistantPromptTemplate.java new file mode 100644 index 0000000..ae6373c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/AssistantPromptTemplate.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.core.io.Resource; + +import java.util.Map; + +public class AssistantPromptTemplate extends PromptTemplate { + + public AssistantPromptTemplate(String template) { + super(template); + } + + public AssistantPromptTemplate(Resource resource) { + super(resource); + } + + @Override + public Prompt create() { + return new Prompt(new AssistantMessage(render())); + } + + @Override + public Prompt create(Map model) { + return new Prompt(new AssistantMessage(render(model))); + } + + @Override + public Message createMessage() { + return new AssistantMessage(render()); + } + + @Override + public Message createMessage(Map model) { + return new AssistantMessage(render(model)); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java new file mode 100644 index 0000000..5c45a37 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.model.ModelOptions; + +/** + * 聊天选项代表了常见的选项,可在不同的聊天模式中移植。 + * + * The ChatOptions represent the common options, portable across different chat models. + */ +public interface ChatOptions extends ModelOptions { + + Float getTemperature(); + + void setTemperature(Float temperature); + + Float getTopP(); + + void setTopP(Float topP); + + Integer getTopK(); + + void setTopK(Integer topK); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java new file mode 100644 index 0000000..f702f63 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +public class ChatOptionsBuilder { + + private class ChatOptionsImpl implements ChatOptions { + + private Float temperature; + + private Float topP; + + private Integer topK; + + @Override + public Float getTemperature() { + return temperature; + } + + @Override + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + @Override + public Float getTopP() { + return topP; + } + + @Override + public void setTopP(Float topP) { + this.topP = topP; + } + + @Override + public Integer getTopK() { + return topK; + } + + @Override + public void setTopK(Integer topK) { + this.topK = topK; + } + + } + + private final ChatOptionsImpl options = new ChatOptionsImpl(); + + private ChatOptionsBuilder() { + } + + public static ChatOptionsBuilder builder() { + return new ChatOptionsBuilder(); + } + + public ChatOptionsBuilder withTemperature(Float temperature) { + options.setTemperature(temperature); + return this; + } + + public ChatOptionsBuilder withTopP(Float topP) { + options.setTopP(topP); + return this; + } + + public ChatOptionsBuilder withTopK(Integer topK) { + options.setTopK(topK); + return this; + } + + public ChatOptions build() { + return options; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatPromptTemplate.java new file mode 100644 index 0000000..15f9ca5 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/ChatPromptTemplate.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.chat.messages.Message; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * PromptTemplate,用于将角色指定为字符串实现及其角色不足以满足您的需求。 + * + * A PromptTemplate that lets you specify the role as a string should the current + * implementations and their roles not suffice for your needs. + */ +public class ChatPromptTemplate implements PromptTemplateActions, PromptTemplateChatActions { + + private final List promptTemplates; + + public ChatPromptTemplate(List promptTemplates) { + this.promptTemplates = promptTemplates; + } + + @Override + public String render() { + StringBuilder sb = new StringBuilder(); + for (PromptTemplate promptTemplate : promptTemplates) { + sb.append(promptTemplate.render()); + } + return sb.toString(); + } + + @Override + public String render(Map model) { + StringBuilder sb = new StringBuilder(); + for (PromptTemplate promptTemplate : promptTemplates) { + sb.append(promptTemplate.render(model)); + } + return sb.toString(); + } + + @Override + public List createMessages() { + List messages = new ArrayList<>(); + for (PromptTemplate promptTemplate : promptTemplates) { + messages.add(promptTemplate.createMessage()); + } + return messages; + } + + @Override + public List createMessages(Map model) { + List messages = new ArrayList<>(); + for (PromptTemplate promptTemplate : promptTemplates) { + messages.add(promptTemplate.createMessage(model)); + } + return messages; + } + + @Override + public Prompt create() { + List messages = createMessages(); + return new Prompt(messages); + } + + @Override + public Prompt create(Map model) { + List messages = createMessages(model); + return new Prompt(messages); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/FunctionPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/FunctionPromptTemplate.java new file mode 100644 index 0000000..4c7ce98 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/FunctionPromptTemplate.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +public class FunctionPromptTemplate extends PromptTemplate { + + private String name; + + public FunctionPromptTemplate(String template) { + super(template); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/Prompt.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/Prompt.java new file mode 100644 index 0000000..a598b3c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/Prompt.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.model.ModelOptions; +import org.springframework.ai.model.ModelRequest; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * 文字内容 + */ +public class Prompt implements ModelRequest> { + + private final List messages; + + private ChatOptions modelOptions; + + public Prompt(String contents) { + this(new UserMessage(contents)); + } + + public Prompt(Message message) { + this(Collections.singletonList(message)); + } + + public Prompt(List messages) { + this.messages = messages; + } + + public Prompt(String contents, ChatOptions modelOptions) { + this(new UserMessage(contents), modelOptions); + } + + public Prompt(Message message, ChatOptions modelOptions) { + this(Collections.singletonList(message), modelOptions); + } + + public Prompt(List messages, ChatOptions modelOptions) { + this.messages = messages; + this.modelOptions = modelOptions; + } + + public String getContents() { + StringBuilder sb = new StringBuilder(); + for (Message message : getInstructions()) { + sb.append(message.getContent()); + } + return sb.toString(); + } + + @Override + public ModelOptions getOptions() { + return this.modelOptions; + } + + @Override + public List getInstructions() { + return this.messages; + } + + @Override + public String toString() { + return "Prompt{" + "messages=" + this.messages + ", modelOptions=" + this.modelOptions + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; +// if (!(o instanceof Prompt prompt)) +// return false; + if (!(o instanceof Prompt)) { + return false; + } + Prompt prompt = (Prompt) o; + return Objects.equals(this.messages, prompt.messages) && Objects.equals(this.modelOptions, prompt.modelOptions); + } + + @Override + public int hashCode() { + return Objects.hash(this.messages, this.modelOptions); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java new file mode 100644 index 0000000..f1e910d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java @@ -0,0 +1,218 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.parser.OutputParser; +import org.antlr.runtime.Token; +import org.antlr.runtime.TokenStream; +import org.springframework.core.io.Resource; +import org.springframework.util.StreamUtils; +import org.stringtemplate.v4.ST; +import org.stringtemplate.v4.compiler.STLexer; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 用户输入的提示内容模板 + * + * 实现:提示词模板操作 提示词模板message相关操作 + */ +public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions { + + private ST st; + + private Map dynamicModel = new HashMap<>(); + + protected String template; + + protected TemplateFormat templateFormat = TemplateFormat.ST; + + private OutputParser outputParser; + + public PromptTemplate(Resource resource) { + try (InputStream inputStream = resource.getInputStream()) { + this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); + } + catch (IOException ex) { + throw new RuntimeException("Failed to read resource", ex); + } + try { + this.st = new ST(this.template, '{', '}'); + } + catch (Exception ex) { + throw new IllegalArgumentException("The template string is not valid.", ex); + } + } + + public PromptTemplate(String template) { + this.template = template; + // If the template string is not valid, an exception will be thrown + try { + this.st = new ST(this.template, '{', '}'); + } + catch (Exception ex) { + throw new IllegalArgumentException("The template string is not valid.", ex); + } + } + + public PromptTemplate(String template, Map model) { + this.template = template; + // If the template string is not valid, an exception will be thrown + try { + this.st = new ST(this.template, '{', '}'); + for (Entry entry : model.entrySet()) { + add(entry.getKey(), entry.getValue()); + dynamicModel.put(entry.getKey(), entry.getValue()); + } + } + catch (Exception ex) { + throw new IllegalArgumentException("The template string is not valid.", ex); + } + } + + public PromptTemplate(Resource resource, Map model) { + try (InputStream inputStream = resource.getInputStream()) { + this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); + } + catch (IOException ex) { + throw new RuntimeException("Failed to read resource", ex); + } + // If the template string is not valid, an exception will be thrown + try { + this.st = new ST(this.template, '{', '}'); + for (Entry entry : model.entrySet()) { + add(entry.getKey(), entry.getValue()); + dynamicModel.put(entry.getKey(), entry.getValue()); + } + } + catch (Exception ex) { + throw new IllegalArgumentException("The template string is not valid.", ex); + } + } + + public OutputParser getOutputParser() { + return outputParser; + } + + public void setOutputParser(OutputParser outputParser) { + Objects.requireNonNull(outputParser, "Output Parser can not be null"); + this.outputParser = outputParser; + } + + public void add(String name, Object value) { + this.st.add(name, value); + this.dynamicModel.put(name, value); + } + + public String getTemplate() { + return this.template; + } + + public TemplateFormat getTemplateFormat() { + return this.templateFormat; + } + + // Render Methods + @Override + public String render() { + validate(this.dynamicModel); + return st.render(); + } + + @Override + public String render(Map model) { + validate(model); + for (Entry entry : model.entrySet()) { + if (st.getAttribute(entry.getKey()) != null) { + st.remove(entry.getKey()); + } + if (entry.getValue() instanceof Resource) { + st.add(entry.getKey(), renderResource((Resource) entry.getValue())); + } + else { + st.add(entry.getKey(), entry.getValue()); + } + + } + return st.render(); + } + + private String renderResource(Resource resource) { + try { + return resource.getContentAsString(Charset.defaultCharset()); + } + catch (IOException e) { + throw new RuntimeException(e); + } + // try (InputStream inputStream = resource.getInputStream()) { + // return StreamUtils.copyToString(inputStream, Charset.defaultCharset()); + // } + // catch (IOException ex) { + // throw new RuntimeException(ex); + // } + } + + @Override + public Message createMessage() { + return new UserMessage(render()); + } + + @Override + public Message createMessage(Map model) { + return new UserMessage(render(model)); + } + + @Override + public Prompt create() { + return new Prompt(render(new HashMap<>())); + } + + @Override + public Prompt create(Map model) { + return new Prompt(render(model)); + } + + public Set getInputVariables() { + TokenStream tokens = this.st.impl.tokens; + return IntStream.range(0, tokens.range()) + .mapToObj(tokens::get) + .filter(token -> token.getType() == STLexer.ID) + .map(Token::getText) + .collect(Collectors.toSet()); + } + + protected void validate(Map model) { + Set dynamicVariableNames = new HashSet<>(this.dynamicModel.keySet()); + Set modelVariables = new HashSet<>(model.keySet()); + modelVariables.addAll(dynamicVariableNames); + Set missingEntries = new HashSet<>(getInputVariables()); + missingEntries.removeAll(modelVariables); + if (!missingEntries.isEmpty()) { + throw new IllegalStateException( + "All template variables were not replaced. Missing variable names are " + missingEntries); + } + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateActions.java new file mode 100644 index 0000000..8097e3f --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateActions.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +import java.util.Map; + +/** + * 提示词模板操作 + */ +public interface PromptTemplateActions extends PromptTemplateStringActions { + + /** + * 创建 Prompt + * @return + */ + Prompt create(); + + Prompt create(Map model); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateChatActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateChatActions.java new file mode 100644 index 0000000..24d9f90 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateChatActions.java @@ -0,0 +1,18 @@ +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.chat.messages.Message; + +import java.util.List; +import java.util.Map; + +/** + * 聊天操作 + * + */ +public interface PromptTemplateChatActions { + + List createMessages(); + + List createMessages(Map model); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateMessageActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateMessageActions.java new file mode 100644 index 0000000..17ff492 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateMessageActions.java @@ -0,0 +1,24 @@ +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.chat.messages.Message; + +import java.util.Map; + +/** + * 用户输入的提示内容 模板信息操作 + */ +public interface PromptTemplateMessageActions { + + /** + * 创建一个 message + * @return + */ + Message createMessage(); + + /** + * 创建一个 message + * @return + */ + Message createMessage(Map model); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateStringActions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateStringActions.java new file mode 100644 index 0000000..0cd9df3 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/PromptTemplateStringActions.java @@ -0,0 +1,14 @@ +package org.springframework.ai.chat.prompt; + +import java.util.Map; + +/** + * 提示次模板字符串操作 + */ +public interface PromptTemplateStringActions { + + String render(); + + String render(Map model); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java new file mode 100644 index 0000000..539287d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.core.io.Resource; + +import java.util.Map; + +public class SystemPromptTemplate extends PromptTemplate { + + public SystemPromptTemplate(String template) { + super(template); + } + + public SystemPromptTemplate(Resource resource) { + super(resource); + } + + @Override + public Message createMessage() { + return new SystemMessage(render()); + } + + @Override + public Message createMessage(Map model) { + return new SystemMessage(render(model)); + } + + @Override + public Prompt create() { + return new Prompt(new SystemMessage(render())); + } + + @Override + public Prompt create(Map model) { + return new Prompt(new SystemMessage(render(model))); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/TemplateFormat.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/TemplateFormat.java new file mode 100644 index 0000000..1001ee2 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/chat/prompt/TemplateFormat.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.chat.prompt; + +public enum TemplateFormat { + + ST("ST"); + + private final String value; + + TemplateFormat(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static TemplateFormat fromValue(String value) { + for (TemplateFormat templateFormat : TemplateFormat.values()) { + if (templateFormat.getValue().equals(value)) { + return templateFormat; + } + } + throw new IllegalArgumentException("Invalid TemplateFormat value: " + value); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/Image.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/Image.java new file mode 100644 index 0000000..3c6ae1e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/Image.java @@ -0,0 +1,77 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + +import java.util.Objects; + +public class Image { + + /** + * 可以访问图像的URL。 + * + * The URL where the image can be accessed. + */ + private String url; + + /** + * Base64编码的图像字符串。 + * + * Base64 encoded image string. + */ + private String b64Json; + + public Image(String url, String b64Json) { + this.url = url; + this.b64Json = b64Json; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getB64Json() { + return b64Json; + } + + public void setB64Json(String b64Json) { + this.b64Json = b64Json; + } + + @Override + public String toString() { + return "Image{" + "url='" + url + '\'' + ", b64Json='" + b64Json + '\'' + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Image image)) + return false; + return Objects.equals(url, image.url) && Objects.equals(b64Json, image.b64Json); + } + + @Override + public int hashCode() { + return Objects.hash(url, b64Json); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageClient.java new file mode 100644 index 0000000..1993fbe --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageClient.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + + +import org.springframework.ai.model.ModelClient; + +public interface ImageClient extends ModelClient { + + /** + * 跟 chat一样 + * @param imagePrompt the request object to be sent to the AI model + * @return + */ + ImageResponse call(ImagePrompt imagePrompt); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGeneration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGeneration.java new file mode 100644 index 0000000..924f587 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGeneration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + + +import org.springframework.ai.model.ModelResult; + +public class ImageGeneration implements ModelResult { + + // metadata 信息为空现在 + private ImageGenerationMetadata imageGenerationMetadata; + + private Image image; + + public ImageGeneration(Image image) { + this.image = image; + } + + public ImageGeneration(Image image, ImageGenerationMetadata imageGenerationMetadata) { + this.image = image; + this.imageGenerationMetadata = imageGenerationMetadata; + } + + @Override + public Image getOutput() { + return image; + } + + @Override + public ImageGenerationMetadata getMetadata() { + return imageGenerationMetadata; + } + + @Override + public String toString() { + return "ImageGeneration{" + "imageGenerationMetadata=" + imageGenerationMetadata + ", image=" + image + '}'; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGenerationMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGenerationMetadata.java new file mode 100644 index 0000000..e140aa8 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageGenerationMetadata.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + +import org.springframework.ai.model.ResultMetadata; + +public interface ImageGenerationMetadata extends ResultMetadata { + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageMessage.java new file mode 100644 index 0000000..51d378b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageMessage.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + +import java.util.Objects; + +public class ImageMessage { + + private String text; + + private Float weight; + + public ImageMessage(String text) { + this.text = text; + } + + public ImageMessage(String text, Float weight) { + this.text = text; + this.weight = weight; + } + + public String getText() { + return text; + } + + public Float getWeight() { + return weight; + } + + @Override + public String toString() { + return "mageMessage{" + "text='" + text + '\'' + ", weight=" + weight + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ImageMessage that)) + return false; + return Objects.equals(text, that.text) && Objects.equals(weight, that.weight); + } + + @Override + public int hashCode() { + return Objects.hash(text, weight); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptions.java new file mode 100644 index 0000000..4283763 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptions.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + +import org.springframework.ai.model.ModelOptions; + +/** + * ImageOptions represent the common options, portable across different image generation + * models. + */ +public interface ImageOptions extends ModelOptions { + + Integer getN(); + + String getModel(); + + Integer getWidth(); + + Integer getHeight(); + + String getResponseFormat(); // openai - url or base64 : stability ai byte[] or base64 + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptionsBuilder.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptionsBuilder.java new file mode 100644 index 0000000..49dc349 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageOptionsBuilder.java @@ -0,0 +1,119 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + +public class ImageOptionsBuilder { + + private class ImageModelOptionsImpl implements ImageOptions { + + private Integer n; + + private String model; + + private Integer width; + + private Integer height; + + private String responseFormat; + + @Override + public Integer getN() { + return n; + } + + public void setN(Integer n) { + this.n = n; + } + + @Override + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + @Override + public String getResponseFormat() { + return responseFormat; + } + + public void setResponseFormat(String responseFormat) { + this.responseFormat = responseFormat; + } + + @Override + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + @Override + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + } + + private final ImageModelOptionsImpl options = new ImageModelOptionsImpl(); + + private ImageOptionsBuilder() { + + } + + public static ImageOptionsBuilder builder() { + return new ImageOptionsBuilder(); + } + + public ImageOptionsBuilder withN(Integer n) { + options.setN(n); + return this; + } + + public ImageOptionsBuilder withModel(String model) { + options.setModel(model); + return this; + } + + public ImageOptionsBuilder withResponseFormat(String responseFormat) { + options.setResponseFormat(responseFormat); + return this; + } + + public ImageOptionsBuilder withWidth(Integer width) { + options.setWidth(width); + return this; + } + + public ImageOptionsBuilder withHeight(Integer height) { + options.setHeight(height); + return this; + } + + public ImageOptions build() { + return options; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImagePrompt.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImagePrompt.java new file mode 100644 index 0000000..4f9dcdc --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImagePrompt.java @@ -0,0 +1,85 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + +import org.springframework.ai.model.ModelRequest; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * 图片内容 + */ +public class ImagePrompt implements ModelRequest> { + + private final List messages; + + private ImageOptions imageModelOptions; + + public ImagePrompt(List messages) { + this.messages = messages; + } + + public ImagePrompt(List messages, ImageOptions imageModelOptions) { + this.messages = messages; + this.imageModelOptions = imageModelOptions; + } + + public ImagePrompt(ImageMessage imageMessage, ImageOptions imageOptions) { + this(Collections.singletonList(imageMessage), imageOptions); + } + + public ImagePrompt(String instructions, ImageOptions imageOptions) { + this(new ImageMessage(instructions), imageOptions); + } + + public ImagePrompt(String instructions) { +// this(new ImageMessage(instructions), ImageOptionsBuilder.builder().build()); + this(new ImageMessage(instructions), null); + } + + @Override + public List getInstructions() { + return messages; + } + + @Override + public ImageOptions getOptions() { + return imageModelOptions; + } + + @Override + public String toString() { + return "NewImagePrompt{" + "messages=" + messages + ", imageModelOptions=" + imageModelOptions + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ImagePrompt that)) + return false; + return Objects.equals(messages, that.messages) && Objects.equals(imageModelOptions, that.imageModelOptions); + } + + @Override + public int hashCode() { + return Objects.hash(messages, imageModelOptions); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponse.java new file mode 100644 index 0000000..d3e3145 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponse.java @@ -0,0 +1,75 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + +import org.springframework.ai.model.ModelResponse; + +import java.util.List; +import java.util.Objects; + +public class ImageResponse implements ModelResponse { + + private final ImageResponseMetadata imageResponseMetadata; + + private final List imageGenerations; + + public ImageResponse(List generations) { + this(generations, ImageResponseMetadata.NULL); + } + + public ImageResponse(List generations, ImageResponseMetadata imageResponseMetadata) { + this.imageResponseMetadata = imageResponseMetadata; + this.imageGenerations = List.copyOf(generations); + } + + @Override + public ImageGeneration getResult() { + return imageGenerations.get(0); + } + + @Override + public List getResults() { + return imageGenerations; + } + + @Override + public ImageResponseMetadata getMetadata() { + return imageResponseMetadata; + } + + @Override + public String toString() { + return "ImageResponse{" + "imageResponseMetadata=" + imageResponseMetadata + ", imageGenerations=" + + imageGenerations + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ImageResponse that)) + return false; + return Objects.equals(imageResponseMetadata, that.imageResponseMetadata) + && Objects.equals(imageGenerations, that.imageGenerations); + } + + @Override + public int hashCode() { + return Objects.hash(imageResponseMetadata, imageGenerations); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java new file mode 100644 index 0000000..78fa5e7 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.image; + + +import org.springframework.ai.model.ResponseMetadata; + +public interface ImageResponseMetadata extends ResponseMetadata { + + ImageResponseMetadata NULL = new ImageResponseMetadata() { + }; + + default Long created() { + return System.currentTimeMillis(); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelClient.java new file mode 100644 index 0000000..03bec4c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelClient.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +/** + * The ModelClient interface provides a generic API for invoking AI models. It is designed + * to handle the interaction with various types of AI models by abstracting the process of + * sending requests and receiving responses. The interface uses Java generics to + * accommodate different types of requests and responses, enhancing flexibility and + * adaptability across different AI model implementations. + * + * @param the generic type of the request to the AI model + * @param the generic type of the response from the AI model + * @author Mark Pollack + * @since 0.8.0 + */ +public interface ModelClient, TRes extends ModelResponse> { + + /** + * Executes a method call to the AI model. + * @param request the request object to be sent to the AI model + * @return the response from the AI model + */ + TRes call(TReq request) throws Throwable; + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptions.java new file mode 100644 index 0000000..96b301b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptions.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +/** + * Interface representing the customizable options for AI model interactions. This marker + * interface allows for the specification of various settings and parameters that can + * influence the behavior and output of AI models. It is designed to provide flexibility + * and adaptability in different AI scenarios, ensuring that the AI models can be + * fine-tuned according to specific requirements. + * + * @author Mark Pollack + * @since 0.8.0 + */ +public interface ModelOptions { + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java new file mode 100644 index 0000000..9360399 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java @@ -0,0 +1,387 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.victools.jsonschema.generator.*; +import com.github.victools.jsonschema.module.jackson.JacksonModule; +import com.github.victools.jsonschema.module.jackson.JacksonOption; +import com.github.victools.jsonschema.module.swagger2.Swagger2Module; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +/** + * Utility class for manipulating {@link ModelOptions} objects. + * + * @author Christian Tzolov + * @since 0.8.0 + */ +public final class ModelOptionsUtils { + + private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + + private final static List BEAN_MERGE_FIELD_EXCISIONS = List.of("class"); + + private static ConcurrentHashMap, List> REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap, List>(); + + private static AtomicReference SCHEMA_GENERATOR_CACHE = new AtomicReference<>(); + + private ModelOptionsUtils() { + + } + + /** + * Converts the given JSON string to a Map of String and Object. + * @param json the JSON string to convert to a Map. + * @return the converted Map. + */ + public static Map jsonToMap(String json) { + try { + return OBJECT_MAPPER.readValue(json, MAP_TYPE_REF); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static TypeReference> MAP_TYPE_REF = new TypeReference>() { + }; + + /** + * Converts the given JSON string to an Object of the given type. + * @param the type of the object to return. + * @param json the JSON string to convert to an object. + * @param type the type of the object to return. + * @return Object instance of the given type. + */ + public static T jsonToObject(String json, Class type) { + try { + return OBJECT_MAPPER.readValue(json, type); + } + catch (Exception e) { + throw new RuntimeException("Failed to json: " + json, e); + } + } + + /** + * Converts the given object to a JSON string. + * @param object the object to convert to a JSON string. + * @return the JSON string. + */ + public static String toJsonString(Object object) { + try { + return OBJECT_MAPPER.writeValueAsString(object); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * Merges the source object into the target object and returns an object represented + * by the given class. The JSON property names are used to match the fields to merge. + * The source non-null values override the target values with the same field name. The + * source null values are ignored. If the acceptedFieldNames is not empty, only the + * fields with the given names are merged and returned. If the acceptedFieldNames is + * empty, use the {@code @JsonProperty} names, inferred from the provided clazz. + * @param they type of the class to return. + * @param source the source object to merge. + * @param target the target object to merge into. + * @param clazz the class to return. + * @param acceptedFieldNames the list of field names accepted for the target object. + * @return the merged object represented by the given class. + */ + public static T merge(Object source, Object target, Class clazz, List acceptedFieldNames) { + + if (source == null) { + source = Map.of(); + } + + List requestFieldNames = CollectionUtils.isEmpty(acceptedFieldNames) + ? REQUEST_FIELD_NAMES_PER_CLASS.computeIfAbsent(clazz, ModelOptionsUtils::getJsonPropertyValues) + : acceptedFieldNames; + + if (CollectionUtils.isEmpty(requestFieldNames)) { + throw new IllegalArgumentException("No @JsonProperty fields found in the " + clazz.getName()); + } + + Map sourceMap = ModelOptionsUtils.objectToMap(source); + Map targetMap = ModelOptionsUtils.objectToMap(target); + + targetMap.putAll(sourceMap.entrySet() + .stream() + .filter(e -> e.getValue() != null) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()))); + + targetMap = targetMap.entrySet() + .stream() + .filter(e -> requestFieldNames.contains(e.getKey())) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + + return ModelOptionsUtils.mapToClass(targetMap, clazz); + } + + /** + * Merges the source object into the target object and returns an object represented + * by the given class. The JSON property names are used to match the fields to merge. + * The source non-null values override the target values with the same field name. The + * source null values are ignored. Returns the only field names that match the + * {@code @JsonProperty} names, inferred from the provided clazz. + * @param they type of the class to return. + * @param source the source object to merge. + * @param target the target object to merge into. + * @param clazz the class to return. + * @return the merged object represented by the given class. + */ + public static T merge(Object source, Object target, Class clazz) { + return ModelOptionsUtils.merge(source, target, clazz, null); + } + + /** + * Converts the given object to a Map. + * @param source the object to convert to a Map. + * @return the converted Map. + */ + public static Map objectToMap(Object source) { + if (source == null) { + return new HashMap<>(); + } + try { + String json = OBJECT_MAPPER.writeValueAsString(source); + return OBJECT_MAPPER.readValue(json, new TypeReference>() { + }) + .entrySet() + .stream() + .filter(e -> e.getValue() != null) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * Converts the given Map to the given class. + * @param the type of the class to return. + * @param source the Map to convert to the given class. + * @param clazz the class to convert the Map to. + * @return the converted class. + */ + public static T mapToClass(Map source, Class clazz) { + try { + String json = OBJECT_MAPPER.writeValueAsString(source); + return OBJECT_MAPPER.readValue(json, clazz); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns the list of name values of the {@link JsonProperty} annotations. + * @param clazz the class that contains fields annotated with {@link JsonProperty}. + * @return the list of values of the {@link JsonProperty} annotations. + */ + public static List getJsonPropertyValues(Class clazz) { + List values = new ArrayList<>(); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class); + if (jsonPropertyAnnotation != null) { + values.add(jsonPropertyAnnotation.value()); + } + } + return values; + } + + /** + * Returns a new instance of the targetBeanClazz that copies the bean values from the + * sourceBean instance. + * @param sourceBean the source bean to copy the values from. + * @param sourceInterfaceClazz the source interface class. Only the fields with the + * same name as the interface methods are copied. This allow the source object to be a + * subclass of the source interface with additional, non-interface fields. + * @param targetBeanClazz the target class, a subclass of the ChatOptions, to convert + * into. + * @param the target class type. + * @return a new instance of the targetBeanClazz with the values from the sourceBean + * instance. + */ + public static T copyToTarget(S sourceBean, Class sourceInterfaceClazz, + Class targetBeanClazz) { + + Assert.notNull(sourceInterfaceClazz, "SourceOptionsClazz must not be null"); + Assert.notNull(targetBeanClazz, "TargetOptionsClazz must not be null"); + + if (sourceBean == null) { + return null; + } + + if (sourceBean.getClass().isAssignableFrom(targetBeanClazz)) { + return (T) sourceBean; + } + + try { + T targetOptions = targetBeanClazz.getConstructor().newInstance(); + + ModelOptionsUtils.mergeBeans(sourceBean, targetOptions, sourceInterfaceClazz, true); + + return targetOptions; + } + catch (Exception e) { + throw new RuntimeException( + "Failed to convert the " + sourceInterfaceClazz.getName() + " into " + targetBeanClazz.getName(), + e); + } + } + + /** + * Merges the source object into the target object. The source null values are + * ignored. Only objects with Getter and Setter methods are supported. + * @param the type of the source and target object. + * @param source the source object to merge. + * @param target the target object to merge into. + * @param sourceInterfaceClazz the source interface class. Only the fields with the + * same name as the interface methods are merged. This allow the source object to be a + * subclass of the source interface with additional, non-interface fields. + * @param overrideNonNullTargetValues if true, the source non-null values override the + * target values with the same field name. If false, the source non-null values are + * ignored. + * @return the merged target object. + */ + public static T mergeBeans(S source, T target, Class sourceInterfaceClazz, + boolean overrideNonNullTargetValues) { + Assert.notNull(source, "Source object must not be null"); + Assert.notNull(target, "Target object must not be null"); + + BeanWrapper sourceBeanWrap = new BeanWrapperImpl(source); + BeanWrapper targetBeanWrap = new BeanWrapperImpl(target); + + List interfaceNames = Arrays.stream(sourceInterfaceClazz.getMethods()).map(m -> m.getName()).toList(); + + for (PropertyDescriptor descriptor : sourceBeanWrap.getPropertyDescriptors()) { + + if (!BEAN_MERGE_FIELD_EXCISIONS.contains(descriptor.getName()) + && interfaceNames.contains(toGetName(descriptor.getName()))) { + + String propertyName = descriptor.getName(); + Object value = sourceBeanWrap.getPropertyValue(propertyName); + + // Copy value to the target object + if (value != null) { + var targetValue = targetBeanWrap.getPropertyValue(propertyName); + + if (targetValue == null || overrideNonNullTargetValues) { + targetBeanWrap.setPropertyValue(propertyName, value); + } + } + } + } + + return target; + } + + private static String toGetName(String name) { + return "get" + name.substring(0, 1).toUpperCase() + name.substring(1); + } + + /** + * Generates JSON Schema (version 2020_12) for the given class. + * @param clazz the class to generate JSON Schema for. + * @param toUpperCaseTypeValues if true, the type values are converted to upper case. + * @return the generated JSON Schema as a String. + */ + public static String getJsonSchema(Class clazz, boolean toUpperCaseTypeValues) { + + if (SCHEMA_GENERATOR_CACHE.get() == null) { + + JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED); + Swagger2Module swaggerModule = new Swagger2Module(); + + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, + OptionPreset.PLAIN_JSON) + .with(Option.EXTRA_OPEN_API_FORMAT_VALUES) + .with(Option.PLAIN_DEFINITION_KEYS) + .with(swaggerModule) + .with(jacksonModule); + + SchemaGeneratorConfig config = configBuilder.build(); + SchemaGenerator generator = new SchemaGenerator(config); + SCHEMA_GENERATOR_CACHE.compareAndSet(null, generator); + } + + ObjectNode node = SCHEMA_GENERATOR_CACHE.get().generateSchema(clazz); + if (toUpperCaseTypeValues) { // Required for OpenAPI 3.0 (at least Vertex AI + // version of it). + toUpperCaseTypeValues(node); + } + + return node.toPrettyString(); + } + + public static void toUpperCaseTypeValues(ObjectNode node) { + if (node == null) { + return; + } + if (node.isObject()) { + node.fields().forEachRemaining(entry -> { + JsonNode value = entry.getValue(); + if (value.isObject()) { + toUpperCaseTypeValues((ObjectNode) value); + } + else if (value.isArray()) { + ((ArrayNode) value).elements().forEachRemaining(element -> { + if (element.isObject() || element.isArray()) { + toUpperCaseTypeValues((ObjectNode) element); + } + }); + } + else if (value.isTextual() && entry.getKey().equals("type")) { + String oldValue = ((ObjectNode) node).get("type").asText(); + ((ObjectNode) node).put("type", oldValue.toUpperCase()); + } + }); + } + else if (node.isArray()) { + node.elements().forEachRemaining(element -> { + if (element.isObject() || element.isArray()) { + toUpperCaseTypeValues((ObjectNode) element); + } + }); + } + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelRequest.java new file mode 100644 index 0000000..b3c4969 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelRequest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +/** + * 表示对AI模型的请求的接口。此接口封装了 与人工智能模型交互所需的必要信息,包括指令或 输入(通用类型T)和附加模型选项。它提供了一种标准化的方式 + * 向人工智能模型发送请求,确保包括所有必要的细节,并且可以易于管理。 + * + * Interface representing a request to an AI model. This interface encapsulates the + * necessary information required to interact with an AI model, including instructions or + * inputs (of generic type T) and additional model options. It provides a standardized way + * to send requests to AI models, ensuring that all necessary details are included and can + * be easily managed. + * + * @param the type of instructions or input required by the AI model + * @author Mark Pollack + * @since 0.8.0 + */ +public interface ModelRequest { + + /** + * 检索AI模型所需的指令或输入。 返回AI模型所需的指令或输入 + * + * Retrieves the instructions or input required by the AI model. + * @return the instructions or input required by the AI model + */ + T getInstructions(); // required input + + /** + * 检索人工智能模型交互的可自定义选项。 返回AI模型交互的自定义选项 + * + * Retrieves the customizable options for AI model interactions. + * @return the customizable options for AI model interactions + */ + ModelOptions getOptions(); + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResponse.java new file mode 100644 index 0000000..b3f40ff --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResponse.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +import java.util.List; + +/** + * + * 表示从AI模型接收到的响应的接口。此接口提供 访问AI模型生成的主要结果或结果列表的方法,以及 以及响应元数据。它是封装和管理的标准化方式 + * 人工智能模型的输出,确保轻松检索和处理生成的信息 + * + * Interface representing the response received from an AI model. This interface provides + * methods to access the main result or a list of results generated by the AI model, along + * with the response metadata. It serves as a standardized way to encapsulate and manage + * the output from AI models, ensuring easy retrieval and processing of the generated + * information. + * + * @param the type of the result(s) provided by the AI model + * @author Mark Pollack + * @since 0.8.0 + */ +public interface ModelResponse> { + + /** + * 检索AI模型的结果。 + * + * Retrieves the result of the AI model. + * @return the result generated by the AI model + */ + T getResult(); + + /** + * 检索AI模型生成的输出列表。 + * + * Retrieves the list of generated outputs by the AI model. + * @return the list of generated outputs + */ + List getResults(); + + /** + * 检索与AI模型的响应相关联的响应元数据。 + * + * Retrieves the response metadata associated with the AI model's response. + * @return the response metadata + */ + ResponseMetadata getMetadata(); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResult.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResult.java new file mode 100644 index 0000000..5a5613a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ModelResult.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +/** + * This interface provides methods to access the main output of the AI model and the + * metadata associated with this result. It is designed to offer a standardized and + * comprehensive way to handle and interpret the outputs generated by AI models, catering + * to diverse AI applications and use cases. + * + * @param the type of the output generated by the AI model + * @author Mark Pollack + * @since 0.8.0 + */ +public interface ModelResult { + + /** + * Retrieves the output generated by the AI model. + * @return the output generated by the AI model + */ + T getOutput(); + + /** + * Retrieves the metadata associated with the result of an AI model. + * @return the metadata associated with the result + */ + ResultMetadata getMetadata(); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResponseMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResponseMetadata.java new file mode 100644 index 0000000..15e685d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResponseMetadata.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +/** + * 表示与AI模型的响应相关联的元数据的接口。此接口 旨在提供有关人工智能生成反应的附加信息 模型,包括处理细节和模型特定数据。它是一种价值 + * 核心领域内的对象,增强对人工智能模型的理解和管理 在各种应用中的响应。 + * + * Interface representing metadata associated with an AI model's response. This interface + * is designed to provide additional information about the generative response from an AI + * model, including processing details and model-specific data. It serves as a value + * object within the core domain, enhancing the understanding and management of AI model + * responses in various applications. + * + * @author Mark Pollack + * @since 0.8.0 + */ +public interface ResponseMetadata { + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResultMetadata.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResultMetadata.java new file mode 100644 index 0000000..78d5f7f --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/ResultMetadata.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +/** + * Interface representing metadata associated with the results of an AI model. This + * interface focuses on providing additional context and insights into the results + * generated by AI models. It could include information like computation time, model + * version, or other relevant details that enhance understanding and management of AI + * model outputs in various applications. + * + * @author Mark Pollack + * @since 0.8.0 + */ +public interface ResultMetadata { + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/StreamingModelClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/StreamingModelClient.java new file mode 100644 index 0000000..da1db15 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/StreamingModelClient.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model; + +import reactor.core.publisher.Flux; + +/** + * The StreamingModelClient interface provides a generic API for invoking a AI models with + * streaming response. It abstracts the process of sending requests and receiving a + * streaming responses. The interface uses Java generics to accommodate different types of + * requests and responses, enhancing flexibility and adaptability across different AI + * model implementations. + * + * @param the generic type of the request to the AI model + * @param the generic type of a single item in the streaming response from the + * AI model + * @author Christian Tzolov + * @since 0.8.0 + */ +public interface StreamingModelClient, TResChunk extends ModelResponse> { + + /** + * Executes a method call to the AI model. + * @param request the request object to be sent to the AI model + * @return the streaming response from the AI model + */ + Flux stream(TReq request); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallSupport.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallSupport.java new file mode 100644 index 0000000..67a34b1 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallSupport.java @@ -0,0 +1,163 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model.function; + +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Christian Tzolov + */ +public abstract class AbstractFunctionCallSupport { + + protected final static boolean IS_RUNTIME_CALL = true; + + /** + * 函数回调寄存器用于按名称解析函数回调。 + * + * The function callback register is used to resolve the function callbacks by name. + */ + protected final Map functionCallbackRegister = new ConcurrentHashMap<>(); + + /** + * 函数回调上下文用于按名称解析函数回调来自Spring上下文。 + * 它是可选的,通常与Spring一起使用自动配置。 + * + * The function callback context is used to resolve the function callbacks by name + * from the Spring context. It is optional and usually used with Spring + * auto-configuration. + */ + protected final FunctionCallbackContext functionCallbackContext; + + public AbstractFunctionCallSupport(FunctionCallbackContext functionCallbackContext) { + this.functionCallbackContext = functionCallbackContext; + } + + public Map getFunctionCallbackRegister() { + return this.functionCallbackRegister; + } + + protected Set handleFunctionCallbackConfigurations(FunctionCallingOptions options, boolean isRuntimeCall) { + + Set functionToCall = new HashSet<>(); + + if (options != null) { + if (!CollectionUtils.isEmpty(options.getFunctionCallbacks())) { + options.getFunctionCallbacks().stream().forEach(functionCallback -> { + + // Register the tool callback. + if (isRuntimeCall) { + this.functionCallbackRegister.put(functionCallback.getName(), functionCallback); + } + else { + this.functionCallbackRegister.putIfAbsent(functionCallback.getName(), functionCallback); + } + + // Automatically enable the function, usually from prompt callback. + if (isRuntimeCall) { + functionToCall.add(functionCallback.getName()); + } + }); + } + + // Add the explicitly enabled functions. + if (!CollectionUtils.isEmpty(options.getFunctions())) { + functionToCall.addAll(options.getFunctions()); + } + } + + return functionToCall; + } + + /** + * Resolve the function callbacks by name. Retrieve them from the registry or try to + * resolve them from the Application Context. + * @param functionNames Name of function callbacks to retrieve. + * @return list of resolved FunctionCallbacks. + */ + protected List resolveFunctionCallbacks(Set functionNames) { + + List retrievedFunctionCallbacks = new ArrayList<>(); + + for (String functionName : functionNames) { + if (!this.functionCallbackRegister.containsKey(functionName)) { + + if (this.functionCallbackContext != null) { + FunctionCallback functionCallback = this.functionCallbackContext.getFunctionCallback(functionName, + null); + if (functionCallback != null) { + this.functionCallbackRegister.put(functionName, functionCallback); + } + else { + throw new IllegalStateException( + "No function callback [" + functionName + "] fund in tht FunctionCallbackContext"); + } + } + else { + throw new IllegalStateException("No function callback found for name: " + functionName); + } + } + FunctionCallback functionCallback = this.functionCallbackRegister.get(functionName); + + retrievedFunctionCallbacks.add(functionCallback); + } + + return retrievedFunctionCallbacks; + } + + /// + protected Resp callWithFunctionSupport(Req request) { + Resp response = this.doChatCompletion(request); + return this.handleFunctionCallOrReturn(request, response); + } + + protected Resp handleFunctionCallOrReturn(Req request, Resp response) { + + if (!this.isToolFunctionCall(response)) { + return response; + } + + // The chat completion tool call requires the complete conversation + // history. Including the initial user message. + List conversationHistory = new ArrayList<>(); + + conversationHistory.addAll(this.doGetUserMessages(request)); + + Msg responseMessage = this.doGetToolResponseMessage(response); + + // Add the assistant response to the message conversation history. + conversationHistory.add(responseMessage); + + Req newRequest = this.doCreateToolResponseRequest(request, responseMessage, conversationHistory); + + return this.callWithFunctionSupport(newRequest); + } + + abstract protected Req doCreateToolResponseRequest(Req previousRequest, Msg responseMessage, + List conversationHistory); + + abstract protected List doGetUserMessages(Req request); + + abstract protected Msg doGetToolResponseMessage(Resp response); + + abstract protected Resp doChatCompletion(Req request); + + abstract protected boolean isToolFunctionCall(Resp response); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallback.java new file mode 100644 index 0000000..801ea3c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/AbstractFunctionCallback.java @@ -0,0 +1,159 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model.function; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.util.Assert; + +import java.util.function.Function; + +/** + * Abstract implementation of the {@link FunctionCallback} for interacting with the + * Model's function calling protocol and a {@link Function} wrapping the interaction with + * the 3rd party service/function. + * + * Implement the {@code O apply(I request) } method to implement the interaction with the + * 3rd party service/function. + * + * The {@link #responseConverter} function is responsible to convert the 3rd party + * function's output type into a string expected by the LLM model. + * + * @param the 3rd party service input type. + * @param the 3rd party service output type. + * @author Christian Tzolov + */ +abstract class AbstractFunctionCallback implements Function, FunctionCallback { + + private final String name; + + private final String description; + + private final Class inputType; + + private final String inputTypeSchema; + + private final ObjectMapper objectMapper; + + private final Function responseConverter; + + /** + * Constructs a new {@link AbstractFunctionCallback} with the given name, description, + * input type and default object mapper. + * @param name Function name. Should be unique within the ChatClient's function + * registry. + * @param description Function description. Used as a "system prompt" by the model to + * decide if the function should be called. + * @param inputTypeSchema Used to compute, the argument's Schema (such as JSON Schema + * or OpenAPI Schema)required by the Model's function calling protocol. + * @param inputType Used to compute, the argument's JSON schema required by the + * Model's function calling protocol. + * @param responseConverter Used to convert the function's output type to a string. + * @param objectMapper Used to convert the function's input and output types to and + * from JSON. + */ + protected AbstractFunctionCallback(String name, String description, String inputTypeSchema, Class inputType, + Function responseConverter, ObjectMapper objectMapper) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(description, "Description must not be null"); + Assert.notNull(inputType, "InputType must not be null"); + Assert.notNull(inputTypeSchema, "InputTypeSchema must not be null"); + Assert.notNull(responseConverter, "ResponseConverter must not be null"); + Assert.notNull(objectMapper, "ObjectMapper must not be null"); + this.name = name; + this.description = description; + this.inputType = inputType; + this.inputTypeSchema = inputTypeSchema; + this.responseConverter = responseConverter; + this.objectMapper = objectMapper; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getDescription() { + return this.description; + } + + @Override + public String getInputTypeSchema() { + return this.inputTypeSchema; + } + + @Override + public String call(String functionArguments) { + + // Convert the tool calls JSON arguments into a Java function request object. + I request = fromJson(functionArguments, inputType); + + // extend conversation with function response. + return this.andThen(this.responseConverter).apply(request); + } + + private T fromJson(String json, Class targetClass) { + try { + return this.objectMapper.readValue(json, targetClass); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + ((inputType == null) ? 0 : inputType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractFunctionCallback other = (AbstractFunctionCallback) obj; + if (name == null) { + if (other.name != null) + return false; + } + else if (!name.equals(other.name)) + return false; + if (description == null) { + if (other.description != null) + return false; + } + else if (!description.equals(other.description)) + return false; + if (inputType == null) { + if (other.inputType != null) + return false; + } + else if (!inputType.equals(other.inputType)) + return false; + return true; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallback.java new file mode 100644 index 0000000..91fc3ba --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallback.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model.function; + +/** + * + * 表示模型函数调用处理程序。实现已向注册对触发函数调用的提示进行建模和调用。 + * + * https://blog.csdn.net/weixin_37546425/article/details/136402740 + * + * https://www.163.com/dy/article/ICE2S20P05119NPR.html + * + * Represents a model function call handler. Implementations are registered with the + * Models and called on prompts that trigger the function call. + * + * @author Christian Tzolov + */ +public interface FunctionCallback { + + /** + * @return Returns the Function name. Unique within the model. + */ + public String getName(); + + /** + * @return Returns the function description. This description is used by the model do + * decide if the function should be called or not. + */ + public String getDescription(); + + /** + * @return Returns the JSON schema of the function input type. + */ + public String getInputTypeSchema(); + + /** + * Called when a model detects and triggers a function call. The model is responsible + * to pass the function arguments in the pre-configured JSON schema format. + * @param functionInput JSON string with the function arguments to be passed to the + * function. The arguments are defined as JSON schema usually registered with the the + * model. + * @return String containing the function call response. + */ + public String call(String functionInput); + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackContext.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackContext.java new file mode 100644 index 0000000..5524182 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackContext.java @@ -0,0 +1,126 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.model.function; + +import com.fasterxml.jackson.annotation.JsonClassDescription; +import org.springframework.beans.BeansException; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; +import org.springframework.cloud.function.context.config.FunctionContextUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Description; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Type; +import java.util.function.Function; + +/** + * A Spring {@link ApplicationContextAware} implementation that provides a way to retrieve + * a {@link Function} from the Spring context and wrap it into a {@link FunctionCallback}. + * + * The name of the function is determined by the bean name. + * + * The description of the function is determined by the following rules: + *

    + *
  • Provided as a default description
  • + *
  • Provided as a {@code @Description} annotation on the bean
  • + *
  • Provided as a {@code @JsonClassDescription} annotation on the input class
  • + *
+ * + * @author Christian Tzolov + * @author Christopher Smith + */ +public class FunctionCallbackContext implements ApplicationContextAware { + + private GenericApplicationContext applicationContext; + + private FunctionCallbackWrapper.Builder.SchemaType schemaType = FunctionCallbackWrapper.Builder.SchemaType.JSON_SCHEMA; + + public void setSchemaType(FunctionCallbackWrapper.Builder.SchemaType schemaType) { + this.schemaType = schemaType; + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + this.applicationContext = (GenericApplicationContext) applicationContext; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public FunctionCallback getFunctionCallback(@NonNull String beanName, @Nullable String defaultDescription) { + + Type beanType = FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), beanName); + + if (beanType == null) { + throw new IllegalArgumentException( + "Functional bean with name: " + beanName + " does not exist in the context."); + } + + if (!Function.class.isAssignableFrom(FunctionTypeUtils.getRawType(beanType))) { + throw new IllegalArgumentException( + "Function call Bean must be of type Function. Found: " + beanType.getTypeName()); + } + + Type functionInputType = TypeResolverHelper.getFunctionArgumentType(beanType, 0); + + Class functionInputClass = FunctionTypeUtils.getRawType(functionInputType); + String functionName = beanName; + String functionDescription = defaultDescription; + + if (!StringUtils.hasText(functionDescription)) { + // Look for a Description annotation on the bean + Description descriptionAnnotation = applicationContext.findAnnotationOnBean(beanName, Description.class); + + if (descriptionAnnotation != null) { + functionDescription = descriptionAnnotation.value(); + } + + if (!StringUtils.hasText(functionDescription)) { + // Look for a JsonClassDescription annotation on the input class + JsonClassDescription jsonClassDescriptionAnnotation = functionInputClass + .getAnnotation(JsonClassDescription.class); + if (jsonClassDescriptionAnnotation != null) { + functionDescription = jsonClassDescriptionAnnotation.value(); + } + } + + if (!StringUtils.hasText(functionDescription)) { + throw new IllegalStateException("Could not determine function description." + + "Please provide a description either as a default parameter, via @Description annotation on the bean " + + "or @JsonClassDescription annotation on the input class."); + } + } + + Object bean = this.applicationContext.getBean(beanName); + + // TODO: 2024/3/16 fansili 适配jdk8 + return null; +// if (bean instanceof Function function) { +// return FunctionCallbackWrapper.builder(function) +// .withName(functionName) +// .withSchemaType(this.schemaType) +// .withDescription(functionDescription) +// .withInputType(functionInputClass) +// .build(); +// } +// else { +// throw new IllegalArgumentException("Bean must be of type Function"); +// } + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java new file mode 100644 index 0000000..23c981b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java @@ -0,0 +1,140 @@ +package org.springframework.ai.model.function; + +import org.springframework.ai.model.ModelOptionsUtils; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.util.Assert; + +import java.util.function.Function; + +/** + * Note that the underlying function is responsible for converting the output into format + * that can be consumed by the Model. The default implementation converts the output into + * String before sending it to the Model. Provide a custom function responseConverter + * implementation to override this. + * + */ +public class FunctionCallbackWrapper extends AbstractFunctionCallback { + + private final Function function; + + private FunctionCallbackWrapper(String name, String description, String inputTypeSchema, Class inputType, + Function responseConverter, Function function) { + super(name, description, inputTypeSchema, inputType, responseConverter, + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)); + Assert.notNull(function, "Function must not be null"); + this.function = function; + } + + @SuppressWarnings("unchecked") + private static Class resolveInputType(Function function) { + return (Class) TypeResolverHelper.getFunctionInputClass((Class>) function.getClass()); + } + + @Override + public O apply(I input) { + return this.function.apply(input); + } + + public static Builder builder(Function function) { + return new Builder<>(function); + } + + public static class Builder { + + public enum SchemaType { + + JSON_SCHEMA, OPEN_API_SCHEMA + + } + + private String name; + + private String description; + + private Class inputType; + + private final Function function; + + private SchemaType schemaType = SchemaType.JSON_SCHEMA; + + public Builder(Function function) { + Assert.notNull(function, "Function must not be null"); + this.function = function; + } + + // By default the response is converted to a JSON string. + private Function responseConverter = (response) -> ModelOptionsUtils.toJsonString(response); + + private String inputTypeSchema; + + private ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + public Builder withName(String name) { + Assert.hasText(name, "Name must not be empty"); + this.name = name; + return this; + } + + public Builder withDescription(String description) { + Assert.hasText(description, "Description must not be empty"); + this.description = description; + return this; + } + + @SuppressWarnings("unchecked") + public Builder withInputType(Class inputType) { + this.inputType = (Class) inputType; + return this; + } + + public Builder withResponseConverter(Function responseConverter) { + Assert.notNull(responseConverter, "ResponseConverter must not be null"); + this.responseConverter = responseConverter; + return this; + } + + public Builder withInputTypeSchema(String inputTypeSchema) { + Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty"); + this.inputTypeSchema = inputTypeSchema; + return this; + } + + public Builder withObjectMapper(ObjectMapper objectMapper) { + Assert.notNull(objectMapper, "ObjectMapper must not be null"); + this.objectMapper = objectMapper; + return this; + } + + public Builder withSchemaType(SchemaType schemaType) { + Assert.notNull(schemaType, "SchemaType must not be null"); + this.schemaType = schemaType; + return this; + } + + public FunctionCallbackWrapper build() { + + Assert.hasText(this.name, "Name must not be empty"); + Assert.hasText(this.description, "Description must not be empty"); + // Assert.notNull(this.inputType, "InputType must not be null"); + Assert.notNull(this.function, "Function must not be null"); + Assert.notNull(this.responseConverter, "ResponseConverter must not be null"); + Assert.notNull(this.objectMapper, "ObjectMapper must not be null"); + + if (this.inputType == null) { + this.inputType = resolveInputType(this.function); + } + + if (this.inputTypeSchema == null) { + boolean upperCaseTypeValues = this.schemaType == SchemaType.OPEN_API_SCHEMA; + this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues); + } + + return new FunctionCallbackWrapper<>(this.name, this.description, this.inputTypeSchema, this.inputType, + this.responseConverter, this.function); + } + + } + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptions.java new file mode 100644 index 0000000..c66a4f5 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptions.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model.function; + +import java.util.List; +import java.util.Set; + +/** + * @author Christian Tzolov + */ +public interface FunctionCallingOptions { + + /** + * Function Callbacks to be registered with the ChatClient. For Prompt Options the + * functionCallbacks are automatically enabled for the duration of the prompt + * execution. For Default Options the FunctionCallbacks are registered but disabled by + * default. You have to use "functions" property to list the function names from the + * ChatClient registry to be used in the chat completion requests. + * @return Return the Function Callbacks to be registered with the ChatClient. + */ + List getFunctionCallbacks(); + + /** + * Set the Function Callbacks to be registered with the ChatClient. + * @param functionCallbacks the Function Callbacks to be registered with the + * ChatClient. + */ + void setFunctionCallbacks(List functionCallbacks); + + /** + * @return List of function names from the ChatClient registry to be used in the next + * chat completion requests. + */ + Set getFunctions(); + + /** + * Set the list of function names from the ChatClient registry to be used in the next + * chat completion requests. + * @param functions the list of function names from the ChatClient registry to be used + * in the next chat completion requests. + */ + void setFunctions(Set functions); + + /** + * @return Returns FunctionCallingOptionsBuilder to create a new instance of + * FunctionCallingOptions. + */ + public static FunctionCallingOptionsBuilder builder() { + return new FunctionCallingOptionsBuilder(); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptionsBuilder.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptionsBuilder.java new file mode 100644 index 0000000..4404440 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/FunctionCallingOptionsBuilder.java @@ -0,0 +1,150 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model.function; + +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Builder for {@link FunctionCallingOptions}. Using the {@link FunctionCallingOptions} + * permits options portability between different AI providers that support + * function-calling. + * + * @author Christian Tzolov + * @since 0.8.1 + */ +public class FunctionCallingOptionsBuilder { + + private final PortableFunctionCallingOptions options; + + public FunctionCallingOptionsBuilder() { + this.options = new PortableFunctionCallingOptions(); + } + + public FunctionCallingOptionsBuilder withFunctionCallbacks(List functionCallbacks) { + this.options.setFunctionCallbacks(functionCallbacks); + return this; + } + + public FunctionCallingOptionsBuilder withFunctionCallback(FunctionCallback functionCallback) { + Assert.notNull(functionCallback, "FunctionCallback must not be null"); + this.options.getFunctionCallbacks().add(functionCallback); + return this; + } + + public FunctionCallingOptionsBuilder withFunctions(Set functions) { + this.options.setFunctions(functions); + return this; + } + + public FunctionCallingOptionsBuilder withFunction(String function) { + Assert.notNull(function, "Function must not be null"); + this.options.getFunctions().add(function); + return this; + } + + public FunctionCallingOptionsBuilder withTemperature(Float temperature) { + this.options.setTemperature(temperature); + return this; + } + + public FunctionCallingOptionsBuilder withTopP(Float topP) { + this.options.setTopP(topP); + return this; + } + + public FunctionCallingOptionsBuilder withTopK(Integer topK) { + this.options.setTopK(topK); + return this; + } + + public PortableFunctionCallingOptions build() { + return this.options; + } + + public static class PortableFunctionCallingOptions implements FunctionCallingOptions, ChatOptions { + + private List functionCallbacks = new ArrayList<>(); + + private Set functions = new HashSet<>(); + + private Float temperature; + + private Float topP; + + private Integer topK; + + @Override + public List getFunctionCallbacks() { + return this.functionCallbacks; + } + + @Override + public void setFunctionCallbacks(List functionCallbacks) { + Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null"); + this.functionCallbacks = functionCallbacks; + } + + @Override + public Set getFunctions() { + return this.functions; + } + + @Override + public void setFunctions(Set functions) { + Assert.notNull(functions, "Functions must not be null"); + this.functions = functions; + } + + @Override + public Float getTemperature() { + return this.temperature; + } + + @Override + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + @Override + public Float getTopP() { + return this.topP; + } + + @Override + public void setTopP(Float topP) { + this.topP = topP; + } + + @Override + public Integer getTopK() { + return this.topK; + } + + @Override + public void setTopK(Integer topK) { + this.topK = topK; + } + + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java new file mode 100644 index 0000000..604ce0a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model.function; + +import net.jodah.typetools.TypeResolver; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.function.Function; + +/** + * @author Christian Tzolov + */ +public class TypeResolverHelper { + + public static Class getFunctionInputClass(Class> functionClass) { + return getFunctionArgumentClass(functionClass, 0); + } + + public static Class getFunctionOutputClass(Class> functionClass) { + return getFunctionArgumentClass(functionClass, 1); + } + + public static Class getFunctionArgumentClass(Class> functionClass, int argumentIndex) { + Type type = TypeResolver.reify(Function.class, functionClass); + + var argumentType = type instanceof ParameterizedType + ? ((ParameterizedType) type).getActualTypeArguments()[argumentIndex] : Object.class; + + return toRawClass(argumentType); + } + + public static Type getFunctionInputType(Class> functionClass) { + return getFunctionArgumentType(functionClass, 0); + } + + public static Type getFunctionOutputType(Class> functionClass) { + return getFunctionArgumentType(functionClass, 1); + } + + public static Type getFunctionArgumentType(Class> functionClass, int argumentIndex) { + Type functionType = TypeResolver.reify(Function.class, functionClass); + return getFunctionArgumentType(functionType, argumentIndex); + } + + public static Type getFunctionArgumentType(Type functionType, int argumentIndex) { + var argumentType = functionType instanceof ParameterizedType + ? ((ParameterizedType) functionType).getActualTypeArguments()[argumentIndex] : Object.class; + + return argumentType; + } + + /** + * Effectively converts {@link Type} which could be {@link ParameterizedType} to raw + * Class (no generics). + * @param type actual {@link Type} instance + * @return instance of {@link Class} as raw representation of the provided + * {@link Type} + */ + public static Class toRawClass(Type type) { + return type != null + ? TypeResolver.resolveRawClass(type instanceof GenericArrayType ? type : TypeResolver.reify(type), null) + : null; + } + + // public static void main(String[] args) { + // Class> clazz = MockWeatherService.class; + // System.out.println(getFunctionInputType(clazz)); + + // } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/package-info.java new file mode 100644 index 0000000..12eaa53 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/model/package-info.java @@ -0,0 +1,11 @@ +/** + * Provides a set of interfaces and classes for a generic API designed to interact with + * various AI models. This package includes interfaces for handling AI model calls, + * requests, responses, results, and associated metadata. It is designed to offer a + * flexible and adaptable framework for interacting with different types of AI models, + * abstracting the complexities involved in model invocation and result processing. The + * use of generics enhances the API's capability to work with a wide range of models, + * ensuring a broad applicability across diverse AI scenarios. + * + */ +package org.springframework.ai.model; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyConfig.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyConfig.java new file mode 100644 index 0000000..ea35c77 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyConfig.java @@ -0,0 +1,80 @@ +package org.springframework.ai.models.midjourney; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Map; +import java.util.UUID; + +/** + * Midjourney 配置 + * + * author: fansili + * time: 2024/4/3 17:10 + */ +@Data +@Accessors(chain = true) +public class MidjourneyConfig { + + /** + * token信息 + * + * tip: 登录discard F12找 messages 接口 + */ + private String token; + /** + * 服务器id + */ + private String guildId; + /** + * 频道id + */ + private String channelId; + + // + // api 接口 + + /** + * 服务地址 + */ + private String serverUrl = "https://discord.com/"; + /** + * 发送命令 + */ + private String apiInteractions = "api/v9/interactions"; + /** + * 附件 + */ + private String apiAttachments = "/api/v9/channels/%s/attachments"; + /** + * 文件上传 + */ + private String apiAttachmentsUpload = "https://discord-attachments-uploads-prd.storage.googleapis.com/"; + + // + // 浏览器配置 + + private String userAage = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"; + + // + // 请求 json 文件 + + + private Map requestTemplates; + + // + // + + private String sessionId; + + public MidjourneyConfig(String token, String guildId, String channelId, Map requestTemplates) { + this.token = token; + this.guildId = guildId; + this.channelId = channelId; + this.requestTemplates = requestTemplates; + + // 生成 session id + sessionId = UUID.randomUUID().toString().replaceAll("-", ""); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyMessage.java new file mode 100644 index 0000000..30ce8ce --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/MidjourneyMessage.java @@ -0,0 +1,173 @@ +package org.springframework.ai.models.midjourney; + +import org.springframework.ai.models.midjourney.constants.MidjourneyGennerateStatusEnum; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +@Data +@Accessors(chain = true) +public class MidjourneyMessage { + + /** + * id是一个重要的字段,在同时生成多个的时候,可以区分生成信息 + */ + private String id; + /** + * 提交id(nonce 可能会不存在,系统提示的时候,这个为空) + */ + private String nonce; + /** + * 现在已知: + * 0:我们发送的消息,和指令 + * 20: mj生成图片发送过程中 + * 19: 选择了某一张图片后的通知 + */ + private Integer type; + /** + * content + */ + private Content content; + /** + * 图片生成完成才有 + */ + private List components; + /** + * 生成过程中如果有,预展示图片,这里会有 + */ + private List attachments; + /** + * 原始数据(discard 返回的原始数据) + */ + private String rawData; + /** + * 生成状态(用于区分生成状态) + * 1、等待 + * 2、进行中 + * 3、完成 + * {@link MidjourneyGennerateStatusEnum} + */ + private String generateStatus; + /** + * 一般用于提示信息 + * - 错误 + * - 并发队列满了 + * - 账号违规了、敏感词 + * - 账号被封 + */ + private List embeds; + + @Data + @Accessors(chain = true) + public static class ComponentType { + + private int type; + + private List components; + } + + @Data + @Accessors(chain = true) + public static class Component { + /** + * 自定义ID,用于唯一标识特定交互动作及其上下文信息。 + */ + private String customId; + + /** + * 样式编号,用于确定按钮的样式外观。 + * 在某些应用中,例如Discord,2可能表示一种特定的颜色或形状的按钮。 + */ + private int style; + + /** + * 按钮的标签文本,用户可见的内容。 + */ + private String label; + + /** + * 组件类型,此处为2可能表示这是一种特定类型的交互组件, + * 如在Discord API中,类型2对应的是一个可点击的按钮组件。 + */ + private int type; + + } + + @Data + @Accessors(chain = true) + public static class Attachment { + // 文件名 + private String filename; + + // 附件大小(字节) + private int size; + + // 内容类型(例如:image/webp) + private String contentType; + + // 图像宽度(像素) + private int width; + + // 占位符版本号 + private int placeholderVersion; + + // 代理URL,用于访问附件资源 + private String proxyUrl; + + // 占位符标识符 + private String placeholder; + + // 附件ID + private String id; + + // 直接访问附件资源的URL + private String url; + + // 图像高度(像素) + private int height; + } + + @Data + @Accessors(chain = true) + public static class Content { + private String prompt; + private String progress; + private String status; + } + + /** + * embed 用于警告、提示、错误 + */ + @Data + @Accessors(chain = true) + public static class Embed { + + // 内容扫描版本号 + private int contentScanVersion; + + // 颜色值,这里用Java的Color类来表示,注意实际使用中可能需要自定义方法来从int转换为Color对象 + private String color; + + // 页脚信息,包含文本 + private Footer footer; + + // 描述信息 + private String description; + + // 消息类型,这里是富文本类型(这个区分不同提示类型) + private String type; + + // 标题 + private String title; + + // Footer类,作为嵌套类存在,用来表示footer部分的JSON对象 + @Data + @Accessors(chain = true) + public static class Footer { + // 页脚文本 + private String text; + } + + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractions.java new file mode 100644 index 0000000..6e64b2c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractions.java @@ -0,0 +1,83 @@ +package org.springframework.ai.models.midjourney.api; + +import cn.hutool.core.util.IdUtil; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.constants.MidjourneyConstants; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import java.util.HashMap; + +// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi + +/** + * 图片生成 + * + * author: fansili + * time: 2024/4/3 17:36 + */ +@Slf4j +public abstract class MidjourneyInteractions { + + // TODO done @fansili:静态变量,放在最前面哈; + /** + * header - referer 头信息 + */ + private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s"; + /** + * mj配置文件 + */ + protected final MidjourneyConfig midjourneyConfig; + + protected MidjourneyInteractions(MidjourneyConfig midjourneyConfig) { + this.midjourneyConfig = midjourneyConfig; + } + + /** + * 获取headers - application json + * + * @return + */ + protected HttpHeaders getHeadersOfAppJson() { + // 设置header值 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.set("Authorization", midjourneyConfig.getToken()); + httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); + httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); + httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); + return httpHeaders; + } + + /** + * 获取headers - http form data + * + * @return + */ + protected HttpHeaders getHeadersOfFormData() { + // 设置header值 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); + httpHeaders.set("Authorization", midjourneyConfig.getToken()); + httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); + httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); + httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); + return httpHeaders; + } + + /** + * 获取 - 默认参数 + * @return + */ + protected HashMap getDefaultParams() { + HashMap requestParams = Maps.newHashMap(); + // TODO done @fansili:感觉参数的组装,可以搞成一个公用的方法;就是 config + 入参的感觉; + requestParams.put("guild_id", midjourneyConfig.getGuildId()); + requestParams.put("channel_id", midjourneyConfig.getChannelId()); + requestParams.put("session_id", midjourneyConfig.getSessionId()); + requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); // TODO @fansili:建议用 uuid 之类的;nextId 跨进程未必合适哈; + return requestParams; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractionsApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractionsApi.java new file mode 100644 index 0000000..3771113 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/MidjourneyInteractionsApi.java @@ -0,0 +1,149 @@ +package org.springframework.ai.models.midjourney.api; + +import cn.hutool.core.util.StrUtil; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.api.req.AttachmentsReq; +import org.springframework.ai.models.midjourney.api.req.DescribeReq; +import org.springframework.ai.models.midjourney.api.req.ReRollReq; +import org.springframework.ai.models.midjourney.api.res.UploadAttachmentsRes; +import org.springframework.ai.models.midjourney.util.MidjourneyUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.HashMap; + +// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi +/** + * 图片生成 + * + * author: fansili + * time: 2024/4/3 17:36 + */ +@Slf4j +public class MidjourneyInteractionsApi extends MidjourneyInteractions { + + private final String url; + private final RestTemplate restTemplate = new RestTemplate(); // TODO @fansili:优先级低:后续搞到统一的管理 + + public MidjourneyInteractionsApi(MidjourneyConfig midjourneyConfig) { + super(midjourneyConfig); + this.url = midjourneyConfig.getServerUrl().concat(midjourneyConfig.getApiInteractions()); + } + + public Boolean imagine(String nonce, String prompt) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("imagine"); + // 设置参数 + HashMap requestParams = getDefaultParams(); + requestParams.put("nonce", nonce); + requestParams.put("prompt", prompt); + // 解析 template 参数占位符 + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 获取 header + HttpHeaders httpHeaders = getHeadersOfAppJson(); + // 发送请求 + HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); + String res = restTemplate.postForObject(url, requestEntity, String.class); + // 这个 res 只要不返回值,就是成功! + // TODO @fansili:可以直接 if (StrUtil.isBlank(res)) + if (StrUtil.isBlank(res)) { + return true; + } else { + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return false; + } + } + // TODO done @fansili:方法和方法之间,空一行哈; + + + public Boolean reRoll(ReRollReq reRoll) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("reroll"); + // 设置参数 + HashMap requestParams = getDefaultParams(); + requestParams.put("custom_id", reRoll.getCustomId()); + requestParams.put("message_id", reRoll.getMessageId()); + // 获取 header + HttpHeaders httpHeaders = getHeadersOfAppJson(); + // 设置参数 + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 发送请求 + HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); + String res = restTemplate.postForObject(url, requestEntity, String.class); + // 这个 res 只要不返回值,就是成功! + boolean isSuccess = StrUtil.isBlank(res); + if (isSuccess) { + return true; + } + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return isSuccess; + } + + // TODO @fansili:搞成私有方法,可能会好点; + public UploadAttachmentsRes uploadAttachments(AttachmentsReq attachments) { + // file + JSONObject fileObj = new JSONObject(); + fileObj.put("id", "0"); + fileObj.put("filename", attachments.getFileSystemResource().getFilename()); + // TODO @fansili:这块用 lombok 哪个异常处理,简化下代码; + try { + fileObj.put("file_size", attachments.getFileSystemResource().contentLength()); + } catch (IOException e) { + throw new RuntimeException(e); + } + // 创建用于存放表单数据的MultiValueMap + MultiValueMap multipartRequest = new LinkedMultiValueMap<>(); + multipartRequest.put("files", Lists.newArrayList(fileObj)); + // 设置header值 + HttpHeaders httpHeaders = getHeadersOfAppJson(); + // 创建HttpEntity对象,包含表单数据和头部信息 + HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(multipartRequest, httpHeaders); + // 发送POST请求并接收响应 + String uri = String.format(midjourneyConfig.getApiAttachments(), midjourneyConfig.getChannelId()); + String response = restTemplate.postForObject(midjourneyConfig.getServerUrl().concat(uri), multiValueMapHttpEntity, String.class); + UploadAttachmentsRes uploadAttachmentsRes = JSON.parseObject(response, UploadAttachmentsRes.class); + + // + // 上传文件 + String uploadUrl = uploadAttachmentsRes.getAttachments().getFirst().getUploadUrl(); + httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); + HttpEntity fileSystemResourceHttpEntity = new HttpEntity<>(attachments.getFileSystemResource(), httpHeaders); + ResponseEntity exchange = restTemplate.exchange(uploadUrl, HttpMethod.PUT, fileSystemResourceHttpEntity, String.class); + String uploadRes = exchange.getBody(); + return uploadAttachmentsRes; + } + + public Boolean describe(DescribeReq describe) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("describe"); + // 设置参数 + HashMap requestParams = getDefaultParams(); + requestParams.put("file_name", describe.getFileName()); + requestParams.put("final_file_name", describe.getFinalFileName()); + // 设置 header + HttpHeaders httpHeaders = getHeadersOfFormData(); + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 创建表单数据 + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("payload_json", requestBody); + // 发送请求 + HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(formData, httpHeaders); + String res = restTemplate.postForObject(url, multiValueMapHttpEntity, String.class); + // 这个 res 只要不返回值,就是成功! + boolean isSuccess = StrUtil.isBlank(res); + if (isSuccess) { + return true; + } + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return isSuccess; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/AttachmentsReq.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/AttachmentsReq.java new file mode 100644 index 0000000..97de61b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/AttachmentsReq.java @@ -0,0 +1,22 @@ +package org.springframework.ai.models.midjourney.api.req; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.core.io.FileSystemResource; + +/** + * 附件 + *

+ * author: fansili + * time: 2024/4/7 17:18 + */ +@Data +@Accessors(chain = true) +public class AttachmentsReq { + + /** + * 创建文件系统资源对象 + */ + private FileSystemResource fileSystemResource; + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/DescribeReq.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/DescribeReq.java new file mode 100644 index 0000000..62e13c8 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/DescribeReq.java @@ -0,0 +1,24 @@ +package org.springframework.ai.models.midjourney.api.req; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * describe + * + * author: fansili + * time: 2024/4/7 12:30 + */ +@Data +@Accessors(chain = true) +public class DescribeReq { + + /** + * 文件名字 + */ + private String fileName; + /** + * UploadAttachmentsRes 里面的 finalFileName + */ + private String finalFileName; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/ReRollReq.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/ReRollReq.java new file mode 100644 index 0000000..990b60e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/req/ReRollReq.java @@ -0,0 +1,22 @@ +package org.springframework.ai.models.midjourney.api.req; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * author: fansili + * time: 2024/4/6 21:33 + */ +@Data +@Accessors(chain = true) +public class ReRollReq { + + /** + * socket 消息里面收到的 messageId + */ + private String messageId; + /** + * socket 消息里面的,操作按钮id(MJ::JOB::upsample::3::2aeefbef-43e2-4057-bcf1-43b5f39ab6f7) + */ + private String customId; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/res/UploadAttachmentsRes.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/res/UploadAttachmentsRes.java new file mode 100644 index 0000000..6d8c0e9 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/api/res/UploadAttachmentsRes.java @@ -0,0 +1,36 @@ +package org.springframework.ai.models.midjourney.api.res; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 上传附件 - res + * + * author: fansili + * time: 2024/4/8 13:32 + */ +@Data +@Accessors(chain = true) +public class UploadAttachmentsRes { + + private List attachments; + + @Data + @Accessors(chain = true) + public static class Attachment { + /** + * 附件的ID。 + */ + private int id; + /** + * 附件的上传URL。 + */ + private String uploadUrl; + /** + * 上传到服务器的文件名。 + */ + private String uploadFilename; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyConstants.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyConstants.java new file mode 100644 index 0000000..b80ae6e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyConstants.java @@ -0,0 +1,49 @@ +package org.springframework.ai.models.midjourney.constants; + +public final class MidjourneyConstants { + + /** + * 消息 - 编号 + */ + public static final String MSG_ID = "id"; + /** + * 用于区分操作唯一性 + */ + public static final String MSG_NONCE = "nonce"; + /** + * 消息 - 类型 + * 现在已知: + * 0:我们发送的消息,和指令 + * 20: mj生成图片发送过程中 + * 19: 选择了某一张图片后的通知 + */ + public static final String MSG_TYPE = "type"; + /** + * 平道id + */ + public static final String MSG_CHANNEL_ID = "channel_id"; + /** + * 内容 + * + * "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", + */ + public static final String MSG_CONTENT = "content"; + /** + * 组件(图片生成好之后才有) + */ + public static final String MSG_COMPONENTS = "components"; + /** + * 附件(生成中比较模糊的图片) + */ + public static final String MSG_ATTACHMENTS = "attachments"; + /** + * 一般用于提示 + */ + public static final String MSG_EMBEDS = "embeds"; + + + // + // + + public static final String HTTP_COOKIE = "__dcfduid=6ca536c0e3fa11eeb7cbe34c31b49caf; __sdcfduid=6ca536c1e3fa11eeb7cbe34c31b49caf52cce5ffd8983d2a052cf6aba75fe5fe566f2c265902e283ce30dbf98b8c9c93; _gcl_au=1.1.245923998.1710853617; _ga=GA1.1.111061823.1710853617; __cfruid=6385bb3f48345a006b25992db7dcf984e395736d-1712124666; _cfuvid=O09la5ms0ypNptiG0iD8A6BKWlTxz1LG0WR7qRStD7o-1712124666575-0.0.1.1-604800000; locale=zh-CN; cf_clearance=l_YGod1_SUtYxpDVeZXiX7DLLPl1DYrquZe8WVltvYs-1712124668-1.0.1.1-Hl2.fToel23EpF2HCu9J20rB4D7OhhCzoajPSdo.9Up.wPxhvq22DP9RHzEBKuIUlKyH.kJLxXJfAt2N.LD5WQ; OptanonConsent=isIABGlobal=false&datestamp=Wed+Apr+03+2024+14%3A11%3A15+GMT%2B0800+(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)&version=6.33.0&hosts=&landingPath=https%3A%2F%2Fdiscord.com%2F&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1; _ga_Q149DFWHT7=GS1.1.1712124668.4.1.1712124679.0.0.0"; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyGennerateStatusEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyGennerateStatusEnum.java new file mode 100644 index 0000000..1d2d36b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyGennerateStatusEnum.java @@ -0,0 +1,31 @@ +package org.springframework.ai.models.midjourney.constants; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +// TODO done @fansili:1)Mj 缩写,还是搞成全称。。虽然长一点,但是感觉会相对清晰一些哈;2)lombok 相关的注解,可以用用哈;3)value 改 status; +/** + * mj 生成状态 + * + * author: fansili + * time: 2024/4/6 21:07 + */ +@Getter +@AllArgsConstructor +public enum MidjourneyGennerateStatusEnum { + + WAITING("waiting", "等待..."), + IN_PROGRESS("in_progress", "进行中"), + COMPLETED("completed", "完成"), + + ; + + /** + * 状态 + */ + private String status; + /** + * 状态信息 + */ + private String message; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyInteractionsEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyInteractionsEnum.java new file mode 100644 index 0000000..1f5bbc4 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyInteractionsEnum.java @@ -0,0 +1,28 @@ +package org.springframework.ai.models.midjourney.constants; + +import lombok.Getter; + +/** + * MJ 命令 + */ +@Getter +public enum MidjourneyInteractionsEnum { + + IMAGINE("imagine", "生成图片"), + DESCRIBE("describe", "生成描述"), + FAST("fast", "快速生成"), + SETTINGS("settings", "设置"), + ASK("ask", "提问"), + BLEND("blend", "融合"), + + ; + + MidjourneyInteractionsEnum(String value, String message) { + this.value =value; + this.message =message; + } + + private String value; + private String message; + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyMessageTypeEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyMessageTypeEnum.java new file mode 100644 index 0000000..b13a4ef --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyMessageTypeEnum.java @@ -0,0 +1,26 @@ +package org.springframework.ai.models.midjourney.constants; + + +public enum MidjourneyMessageTypeEnum { + /** + * 创建. + */ + CREATE, + /** + * 修改. + */ + UPDATE, + /** + * 删除. + */ + DELETE; + + public static MidjourneyMessageTypeEnum of(String type) { + return switch (type) { + case "MESSAGE_CREATE" -> CREATE; + case "MESSAGE_UPDATE" -> UPDATE; + case "MESSAGE_DELETE" -> DELETE; + default -> null; + }; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyNotifyCode.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyNotifyCode.java new file mode 100644 index 0000000..b2cc65a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/constants/MidjourneyNotifyCode.java @@ -0,0 +1,14 @@ +package org.springframework.ai.models.midjourney.constants; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public final class MidjourneyNotifyCode { + /** + * 成功. + */ + public static final int SUCCESS = 1; + + + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/util/MidjourneyUtil.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/util/MidjourneyUtil.java new file mode 100644 index 0000000..50376b3 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/util/MidjourneyUtil.java @@ -0,0 +1,84 @@ +package org.springframework.ai.models.midjourney.util; + +import cn.hutool.core.text.CharSequenceUtil; +import org.springframework.ai.models.midjourney.MidjourneyMessage; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * mj util + * + * author: fansili + * time: 2024/4/6 19:00 + */ +public class MidjourneyUtil { + /** + * content正则匹配prompt和进度. + */ + public static final String CONTENT_REGEX = ".*?\\*\\*(.*?)\\*\\*.+<@\\d+> \\((.*?)\\)"; + public static final String CONTENT_PROGRESS_REGEX = "\\(([^)]*)\\)"; + + /** + * 解析 content 参数 + * + * @param content + * @return + */ + public static MidjourneyMessage.Content parseContent(String content) { + // 有三种格式。 + // 南极应该是什么样子? + // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", + // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (fast, stealth)" + // Upscaling image #3 with **A girl from China, creator of ancient Chinese clothing, dances on the square** - <@975372485971312700> (Waiting to start) + MidjourneyMessage.Content mjContent = new MidjourneyMessage.Content(); + if (CharSequenceUtil.isBlank(content)) { + return null; + } + if (!content.contains("<@")) { + return mjContent.setPrompt(content); + } + int rawIndex = content.indexOf("<@") - 3; + String prompt = content.substring(0, rawIndex).trim(); + String contentTail = content.substring(rawIndex).trim(); + // 检查是否存在进度条 + Pattern pattern = Pattern.compile(CONTENT_PROGRESS_REGEX); + Matcher matcher = pattern.matcher(contentTail); + + if (contentTail.contains("%")) { + if (matcher.find()) { + // 获取第一个(也是此处唯一的)捕获组的内容 + String progress = matcher.group(1); + mjContent.setProgress(progress); + } + if (matcher.find()) { + String status = matcher.group(1); + mjContent.setStatus(status); + } + } else { + if (matcher.find()) { + // 获取第一个(也是此处唯一的)捕获组的内容 + String status = matcher.group(1); + mjContent.setStatus(status); + } + } + mjContent.setPrompt(prompt); + // tip:contentArray + return mjContent; + } + + /** + * 设置 params + * + * @param requestTemplate + * @param requestParams + * @return + */ + public static String parseTemplate(String requestTemplate, Map requestParams) { + for (Map.Entry entry : requestParams.entrySet()) { + requestTemplate = requestTemplate.replace("$".concat(entry.getKey()), entry.getValue()); + } + return requestTemplate; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/FailureCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/FailureCallback.java new file mode 100644 index 0000000..9e03c07 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/FailureCallback.java @@ -0,0 +1,6 @@ +package org.springframework.ai.models.midjourney.webSocket; + + +public interface FailureCallback { + void onFailure(int code, String reason); +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyMessageHandler.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyMessageHandler.java new file mode 100644 index 0000000..efb7948 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyMessageHandler.java @@ -0,0 +1,15 @@ +package org.springframework.ai.models.midjourney.webSocket; + +import org.springframework.ai.models.midjourney.MidjourneyMessage; + +/** + * message handler + * + * @author fansili + * @time 2024/4/29 14:29 + * @since 1.0 + */ +public interface MidjourneyMessageHandler { + + void messageHandler(MidjourneyMessage midjourneyMessage); +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyWebSocketStarter.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyWebSocketStarter.java new file mode 100644 index 0000000..d4c81f3 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/MidjourneyWebSocketStarter.java @@ -0,0 +1,224 @@ +package org.springframework.ai.models.midjourney.webSocket; + + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.thread.ThreadUtil; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.constants.MidjourneyNotifyCode; +import org.springframework.ai.models.midjourney.webSocket.handler.MidjourneyWebSocketHandler; +import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.websocket.Constants; +import org.jetbrains.annotations.NotNull; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; + +import java.io.IOException; +import java.net.URI; + +// TODO @fansili:mj 这块 websocket 有点小复杂,虽然代码量 400 多行;感觉可以考虑,有没第三方 sdk,通过它透明接入 mj +@Slf4j +public class MidjourneyWebSocketStarter implements WebSocketStarter { + /** + * 链接重试次数 + */ + private static final int CONNECT_RETRY_LIMIT = 5; + /** + * mj 配置文件 + */ + private final MidjourneyConfig midjourneyConfig; + /** + * mj 监听(所有message 都会 callback到这里) + */ + private final MidjourneyMessageListener userMessageListener; + /** + * wss 服务器 + */ + private final String wssServer; + /** + * + */ + private final String resumeWss; + /** + * + */ + private ResumeData resumeData = null; + /** + * 是否运行成功 + */ + private boolean running = false; + /** + * 链接成功的 session + */ + private WebSocketSession webSocketSession = null; + private WssNotify wssNotify = null; + + public MidjourneyWebSocketStarter(String wssServer, + String resumeWss, + MidjourneyConfig midjourneyConfig, + MidjourneyMessageListener userMessageListener) { + this.wssServer = wssServer; + this.resumeWss = resumeWss; + this.midjourneyConfig = midjourneyConfig; + this.userMessageListener = userMessageListener; + } + + @Override + public void start(WssNotify wssNotify) { + this.wssNotify = wssNotify; + start(false); + } + + private void start(boolean reconnect) { + // 设置header + WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + headers.add("Accept-Encoding", "gzip, deflate, br"); + headers.add("Accept-Language", "zh-CN,zh;q=0.9"); + headers.add("Cache-Control", "no-cache"); + headers.add("Pragma", "no-cache"); + headers.add("Sec-Websocket-Extensions", "permessage-deflate; client_max_window_bits"); + headers.add("User-Agent", this.midjourneyConfig.getUserAage()); + // 创建 mjHeader + MidjourneyWebSocketHandler mjWebSocketHandler = new MidjourneyWebSocketHandler( + this.midjourneyConfig, this.userMessageListener, this::onSocketSuccess, this::onSocketFailure); + // + String gatewayUrl; + if (reconnect) { + gatewayUrl = getGatewayServer(this.resumeData.getResumeGatewayUrl()) + "/?encoding=json&v=9&compress=zlib-stream"; + mjWebSocketHandler.setSessionId(this.resumeData.getSessionId()); + mjWebSocketHandler.setSequence(this.resumeData.getSequence()); + mjWebSocketHandler.setResumeGatewayUrl(this.resumeData.getResumeGatewayUrl()); + } else { + gatewayUrl = getGatewayServer(null) + "/?encoding=json&v=9&compress=zlib-stream"; + } + // 创建 StandardWebSocketClient + StandardWebSocketClient webSocketClient = new StandardWebSocketClient(); + // 设置 io timeout 时间 + webSocketClient.getUserProperties().put(Constants.IO_TIMEOUT_MS_PROPERTY, "10000"); + // + ListenableFuture socketSessionFuture = webSocketClient.doHandshake(mjWebSocketHandler, headers, URI.create(gatewayUrl)); + // 添加 callback 进行回调 + socketSessionFuture.addCallback(new ListenableFutureCallback<>() { + @Override + public void onFailure(@NotNull Throwable e) { + onSocketFailure(MidjourneyWebSocketHandler.CLOSE_CODE_EXCEPTION, e.getMessage()); + } + + @Override + public void onSuccess(WebSocketSession session) { + MidjourneyWebSocketStarter.this.webSocketSession = session; + } + }); + } + + private void onSocketSuccess(String sessionId, Object sequence, String resumeGatewayUrl) { + this.resumeData = new ResumeData(sessionId, sequence, resumeGatewayUrl); + this.running = true; + notifyWssLock(MidjourneyNotifyCode.SUCCESS, ""); + } + + private void onSocketFailure(int code, String reason) { + // 1001异常可以忽略 + if (code == 1001) { + return; + } + // 关闭 socket + closeSocketSessionWhenIsOpen(); + // 没有运行通知 + if (!this.running) { + notifyWssLock(code, reason); + return; + } + // 已经运行先设置为false,发起 + this.running = false; + if (code >= 4000) { + log.warn("[wss-{}] Can't reconnect! Account disabled. Closed by {}({}).", this.midjourneyConfig.getChannelId(), code, reason); + } else if (code == 2001) { + log.warn("[wss-{}] Closed by {}({}). Try reconnect...", this.midjourneyConfig.getChannelId(), code, reason); + tryReconnect(); + } else { + log.warn("[wss-{}] Closed by {}({}). Try new connection...", this.midjourneyConfig.getChannelId(), code, reason); + tryNewConnect(); + } + } + + /** + * 重连 + */ + private void tryReconnect() { + try { + tryStart(true); + } catch (Exception e) { + log.warn("[wss-{}] Reconnect fail: {}, Try new connection...", this.midjourneyConfig.getChannelId(), e.getMessage()); + ThreadUtil.sleep(1000); + tryNewConnect(); + } + } + + private void tryNewConnect() { + // 链接重试次数5 + for (int i = 1; i <= CONNECT_RETRY_LIMIT; i++) { + try { + tryStart(false); + return; + } catch (Exception e) { + log.warn("[wss-{}] New connect fail ({}): {}", this.midjourneyConfig.getChannelId(), i, e.getMessage()); + ThreadUtil.sleep(5000); + } + } + log.error("[wss-{}] Account disabled", this.midjourneyConfig.getChannelId()); + } + + public void tryStart(boolean reconnect) { + start(reconnect); + } + + private void notifyWssLock(int code, String reason) { + System.err.println("notifyWssLock: " + code + " - " + reason); + if (wssNotify != null) { + wssNotify.notify(code, reason); + } + } + + /** + * 关闭 socket session + */ + private void closeSocketSessionWhenIsOpen() { + try { + if (this.webSocketSession != null && this.webSocketSession.isOpen()) { + this.webSocketSession.close(CloseStatus.GOING_AWAY); + } + } catch (IOException e) { + // do nothing + } + } + + private String getGatewayServer(String resumeGatewayUrl) { + if (CharSequenceUtil.isNotBlank(resumeGatewayUrl)) { + return CharSequenceUtil.isBlank(this.resumeWss) ? resumeGatewayUrl : this.resumeWss; + } + return this.wssServer; + } + + @Getter + public static class ResumeData { + + public ResumeData(String sessionId, Object sequence, String resumeGatewayUrl) { + this.sessionId = sessionId; + this.sequence = sequence; + this.resumeGatewayUrl = resumeGatewayUrl; + } + + /** + * socket session + */ + private final String sessionId; + private final Object sequence; + private final String resumeGatewayUrl; + } +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/SuccessCallback.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/SuccessCallback.java new file mode 100644 index 0000000..3b0426d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/SuccessCallback.java @@ -0,0 +1,7 @@ +package org.springframework.ai.models.midjourney.webSocket; + + +public interface SuccessCallback { + + void onSuccess(String sessionId, Object sequence, String resumeGatewayUrl); +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WebSocketStarter.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WebSocketStarter.java new file mode 100644 index 0000000..0b48eaa --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WebSocketStarter.java @@ -0,0 +1,8 @@ +package org.springframework.ai.models.midjourney.webSocket; + + +public interface WebSocketStarter { + + void start(WssNotify wssNotify) throws Exception; + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WssNotify.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WssNotify.java new file mode 100644 index 0000000..8b85c39 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/WssNotify.java @@ -0,0 +1,13 @@ +package org.springframework.ai.models.midjourney.webSocket; + +/** + * 通知信息 + * + * @author fansili + * @time 2024/4/29 14:21 + * @since 1.0 + */ +public interface WssNotify { + + void notify(int code, String message); +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java new file mode 100644 index 0000000..8f985c4 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java @@ -0,0 +1,281 @@ +package org.springframework.ai.models.midjourney.webSocket.handler; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.webSocket.FailureCallback; +import org.springframework.ai.models.midjourney.webSocket.SuccessCallback; +import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.utils.data.DataArray; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.api.utils.data.DataType; +import net.dv8tion.jda.internal.requests.WebSocketCode; +import net.dv8tion.jda.internal.utils.compress.Decompressor; +import net.dv8tion.jda.internal.utils.compress.ZlibDecompressor; +import org.jetbrains.annotations.NotNull; +import org.springframework.web.socket.*; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class MidjourneyWebSocketHandler implements WebSocketHandler { + /** + * close 错误码:重连 + */ + public static final int CLOSE_CODE_RECONNECT = 2001; + /** + * close 错误码:无效、作废 + */ + public static final int CLOSE_CODE_INVALIDATE = 1009; + /** + * close 错误码:异常 + */ + public static final int CLOSE_CODE_EXCEPTION = 1011; + /** + * mj配置文件 + */ + private final MidjourneyConfig midjourneyConfig; + /** + * mj 消息监听 + */ + private final MidjourneyMessageListener userMessageListener; + /** + * 成功回调 + */ + private final SuccessCallback successCallback; + /** + * 失败回调 + */ + private final FailureCallback failureCallback; + /** + * 心跳执行器 + */ + private final ScheduledExecutorService heartExecutor; + /** + * auth数据 + */ + private final DataObject authData; + + @Setter + private String sessionId = null; + @Setter + private Object sequence = null; + @Setter + private String resumeGatewayUrl = null; + + private long interval = 41250; + private boolean heartbeatAck = false; + + private Future heartbeatInterval; + private Future heartbeatTimeout; + + /** + * 处理 message 消息的 Decompressor + */ + private final Decompressor decompressor = new ZlibDecompressor(2048); + + public MidjourneyWebSocketHandler(MidjourneyConfig account, + MidjourneyMessageListener userMessageListener, + SuccessCallback successCallback, + FailureCallback failureCallback) { + this.midjourneyConfig = account; + this.userMessageListener = userMessageListener; + this.successCallback = successCallback; + this.failureCallback = failureCallback; + this.heartExecutor = Executors.newSingleThreadScheduledExecutor(); + this.authData = createAuthData(); + } + + @Override + public void afterConnectionEstablished(@NotNull WebSocketSession session) throws Exception { + // do nothing + } + + @Override + public void handleTransportError(@NotNull WebSocketSession session, @NotNull Throwable e) throws Exception { + log.error("[wss-{}] Transport error", this.midjourneyConfig.getChannelId(), e); + // 通知链接异常 + onFailure(CLOSE_CODE_EXCEPTION, "transport error"); + } + + @Override + public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus closeStatus) throws Exception { + // 链接关闭 + onFailure(closeStatus.getCode(), closeStatus.getReason()); + } + + @Override + public boolean supportsPartialMessages() { + return true; + } + + @Override + public void handleMessage(@NotNull WebSocketSession session, WebSocketMessage message) throws Exception { + // 获取 message 消息 + ByteBuffer buffer = (ByteBuffer) message.getPayload(); + // 解析 message + byte[] decompressed = decompressor.decompress(buffer.array()); + if (decompressed == null) { + return; + } + // 转换 json + String json = new String(decompressed, StandardCharsets.UTF_8); + // 转换 jda 自带的 dataObject(和json object 差不多) + DataObject data = DataObject.fromJson(json); + // 获取消息类型 + int opCode = data.getInt("op"); + switch (opCode) { + case WebSocketCode.HEARTBEAT -> handleHeartbeat(session); + case WebSocketCode.HEARTBEAT_ACK -> { + this.heartbeatAck = true; + clearHeartbeatTimeout(); + } + case WebSocketCode.HELLO -> { + handleHello(session, data); + doResumeOrIdentify(session); + } + case WebSocketCode.RESUME -> onSuccess(); + case WebSocketCode.RECONNECT -> onFailure(CLOSE_CODE_RECONNECT, "receive server reconnect"); + case WebSocketCode.INVALIDATE_SESSION -> onFailure(CLOSE_CODE_INVALIDATE, "receive session invalid"); + case WebSocketCode.DISPATCH -> handleDispatch(data); + default -> log.debug("[wss-{}] Receive unknown code: {}.", midjourneyConfig.getChannelId(), data); + } + } + + private void handleDispatch(DataObject raw) { + this.sequence = raw.opt("s").orElse(null); + if (!raw.isType("d", DataType.OBJECT)) { + return; + } + DataObject content = raw.getObject("d"); + String t = raw.getString("t", null); + if ("READY".equals(t)) { + this.sessionId = content.getString("session_id"); + this.resumeGatewayUrl = content.getString("resume_gateway_url"); + onSuccess(); + } else if ("RESUMED".equals(t)) { + onSuccess(); + } else { + try { + this.userMessageListener.onMessage(raw); + } catch (Exception e) { + log.error("[wss-{}] Handle message error", this.midjourneyConfig.getChannelId(), e); + } + } + } + + private void handleHeartbeat(WebSocketSession session) { + sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); + this.heartbeatTimeout = ThreadUtil.execAsync(() -> { + ThreadUtil.sleep(this.interval); + onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack"); + }); + } + + private void handleHello(WebSocketSession session, DataObject data) { + clearHeartbeatInterval(); + this.interval = data.getObject("d").getLong("heartbeat_interval"); + this.heartbeatAck = true; + this.heartbeatInterval = this.heartExecutor.scheduleAtFixedRate(() -> { + if (this.heartbeatAck) { + this.heartbeatAck = false; + sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); + } else { + onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack interval"); + } + }, (long) Math.floor(RandomUtil.randomDouble(0, 1) * this.interval), this.interval, TimeUnit.MILLISECONDS); + } + + private void doResumeOrIdentify(WebSocketSession session) { + if (CharSequenceUtil.isBlank(this.sessionId)) { + sendMessage(session, WebSocketCode.IDENTIFY, this.authData); + } else { + var data = DataObject.empty().put("token", this.midjourneyConfig.getToken()) + .put("session_id", this.sessionId).put("seq", this.sequence); + sendMessage(session, WebSocketCode.RESUME, data); + } + } + + private void sendMessage(WebSocketSession session, int op, Object d) { + var data = DataObject.empty().put("op", op).put("d", d); + try { + session.sendMessage(new TextMessage(data.toString())); + } catch (IOException e) { + log.error("[wss-{}] Send message error", this.midjourneyConfig.getChannelId(), e); + onFailure(CLOSE_CODE_EXCEPTION, "send message error"); + } + } + + private void onSuccess() { + ThreadUtil.execute(() -> this.successCallback.onSuccess(this.sessionId, this.sequence, this.resumeGatewayUrl)); + } + + private void onFailure(int code, String reason) { + clearHeartbeatTimeout(); + clearHeartbeatInterval(); + ThreadUtil.execute(() -> this.failureCallback.onFailure(code, reason)); + } + + private void clearHeartbeatTimeout() { + if (this.heartbeatTimeout != null) { + this.heartbeatTimeout.cancel(true); + this.heartbeatTimeout = null; + } + } + + private void clearHeartbeatInterval() { + if (this.heartbeatInterval != null) { + this.heartbeatInterval.cancel(true); + this.heartbeatInterval = null; + } + } + + private DataObject createAuthData() { + UserAgent userAgent = UserAgentUtil.parse(this.midjourneyConfig.getUserAage()); + DataObject connectionProperties = DataObject.empty() + .put("browser", userAgent.getBrowser().getName()) + .put("browser_user_agent", this.midjourneyConfig.getUserAage()) + .put("browser_version", userAgent.getVersion()) + .put("client_build_number", 222963) + .put("client_event_source", null) + .put("device", "") + .put("os", userAgent.getOs().getName()) + .put("referer", "https://www.midjourney.com") + .put("referrer_current", "") + .put("referring_domain", "www.midjourney.com") + .put("referring_domain_current", "") + .put("release_channel", "stable") + .put("system_locale", "zh-CN"); + DataObject presence = DataObject.empty() + .put("activities", DataArray.empty()) + .put("afk", false) + .put("since", 0) + .put("status", "online"); + DataObject clientState = DataObject.empty() + .put("api_code_version", 0) + .put("guild_versions", DataObject.empty()) + .put("highest_last_message_id", "0") + .put("private_channels_version", "0") + .put("read_state_version", 0) + .put("user_guild_settings_version", -1) + .put("user_settings_version", -1); + return DataObject.empty() + .put("capabilities", 16381) + .put("client_state", clientState) + .put("compress", false) + .put("presence", presence) + .put("properties", connectionProperties) + .put("token", this.midjourneyConfig.getToken()); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/listener/MidjourneyMessageListener.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/listener/MidjourneyMessageListener.java new file mode 100644 index 0000000..c99309e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/midjourney/webSocket/listener/MidjourneyMessageListener.java @@ -0,0 +1,111 @@ +package org.springframework.ai.models.midjourney.webSocket.listener; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.MidjourneyMessage; +import org.springframework.ai.models.midjourney.constants.MidjourneyConstants; +import org.springframework.ai.models.midjourney.constants.MidjourneyGennerateStatusEnum; +import org.springframework.ai.models.midjourney.constants.MidjourneyMessageTypeEnum; +import org.springframework.ai.models.midjourney.util.MidjourneyUtil; +import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.utils.data.DataObject; + +import java.util.List; + +@Slf4j +public class MidjourneyMessageListener { + + private MidjourneyConfig midjourneyConfig; + private MidjourneyMessageHandler midjourneyMessageHandler = null; + + public MidjourneyMessageListener(MidjourneyConfig midjourneyConfig) { + this.midjourneyConfig = midjourneyConfig; + } + + public MidjourneyMessageListener(MidjourneyConfig midjourneyConfig, + MidjourneyMessageHandler midjourneyMessageHandler) { + this.midjourneyConfig = midjourneyConfig; + this.midjourneyMessageHandler = midjourneyMessageHandler; + } + + public void onMessage(DataObject raw) { + MidjourneyMessageTypeEnum messageType = MidjourneyMessageTypeEnum.of(raw.getString("t")); + if (messageType == null || MidjourneyMessageTypeEnum.DELETE == messageType) { + return; + } + DataObject data = raw.getObject("d"); + if (ignoreAndLogMessage(data, messageType)) { + return; + } + log.info("socket message: {}", raw); + // 转换几个重要的信息 + MidjourneyMessage mjMessage = new MidjourneyMessage(); + mjMessage.setId(getString(data, MidjourneyConstants.MSG_ID, "")); + mjMessage.setNonce(getString(data, MidjourneyConstants.MSG_NONCE, "")); + mjMessage.setType(data.getInt(MidjourneyConstants.MSG_TYPE)); + mjMessage.setRawData(StrUtil.str(raw.toJson(), "UTF-8")); + mjMessage.setContent(MidjourneyUtil.parseContent(data.getString(MidjourneyConstants.MSG_CONTENT))); + // 转换 components + if (!data.getArray(MidjourneyConstants.MSG_COMPONENTS).isEmpty()) { + String componentsJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_COMPONENTS).toJson(), "UTF-8"); + List components = JsonUtils.parseArray(componentsJson, MidjourneyMessage.ComponentType.class); + mjMessage.setComponents(components); + } + // 转换附件 + if (!data.getArray(MidjourneyConstants.MSG_ATTACHMENTS).isEmpty()) { + String attachmentsJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_ATTACHMENTS).toJson(), "UTF-8"); + List attachments = JsonUtils.parseArray(attachmentsJson, MidjourneyMessage.Attachment.class); + mjMessage.setAttachments(attachments); + } + // 转换 embeds 提示信息 + if (!data.getArray(MidjourneyConstants.MSG_EMBEDS).isEmpty()) { + String embedJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_EMBEDS).toJson(), "UTF-8"); + List embeds = JsonUtils.parseArray(embedJson, MidjourneyMessage.Embed.class); + mjMessage.setEmbeds(embeds); + } + // 转换状态 + convertGenerateStatus(mjMessage); + // message handler 调用 + if (midjourneyMessageHandler != null) { + midjourneyMessageHandler.messageHandler(mjMessage); + } + } + + private String getString(DataObject data, String key, String defaultValue) { + if (!data.hasKey(key)) { + return defaultValue; + } + return data.getString(key); + } + + private void convertGenerateStatus(MidjourneyMessage mjMessage) { + // + // tip:提示、警告、异常 content是没有内容的 + // tip: 一般错误信息在 Embeds 只要 Embeds有值,content就没信息。 + if (CollUtil.isNotEmpty(mjMessage.getEmbeds())) { + return; + } + if (mjMessage.getType() == 20 && mjMessage.getContent().getStatus().contains("Waiting")) { + mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.WAITING.getStatus()); + } else if (mjMessage.getType() == 20 && !StrUtil.isBlank(mjMessage.getContent().getProgress())) { + mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.IN_PROGRESS.getStatus()); + } else if (mjMessage.getType() == 0 && !CollUtil.isEmpty(mjMessage.getComponents())) { + mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.COMPLETED.getStatus()); + } + } + + private boolean ignoreAndLogMessage(DataObject data, MidjourneyMessageTypeEnum messageType) { + String channelId = data.getString(MidjourneyConstants.MSG_CHANNEL_ID); + if (!CharSequenceUtil.equals(channelId, midjourneyConfig.getChannelId())) { + return true; + } + String authorName = data.optObject("author").map(a -> a.getString("username")).orElse("System"); + log.debug("{} - {} - {}: {}", midjourneyConfig.getChannelId(), messageType.name(), authorName, data.opt("content").orElse("")); + return false; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageApi.java new file mode 100644 index 0000000..6cde315 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageApi.java @@ -0,0 +1,89 @@ +package org.springframework.ai.models.openai; + +import cn.hutool.json.JSONUtil; +import org.springframework.ai.models.openai.api.OpenAiImageRequest; +import org.springframework.ai.models.openai.api.OpenAiImageResponse; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import io.netty.channel.ChannelOption; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; + +import java.io.IOException; +import java.net.URI; +import java.time.Duration; + +/** + * open ai image + *

+ * author: fansili + * time: 2024/3/17 09:53 + */ +@Slf4j +public class OpenAiImageApi { + + private static final String DEFAULT_BASE_URL = "https://api.openai.com"; + private String apiKey = "your-api-key"; + // 发送请求 webClient + private final WebClient webClient; + + private CloseableHttpClient httpclient = HttpClients.createDefault(); + + public OpenAiImageApi(String apiKey) { + this.apiKey = apiKey; + // 创建一个HttpClient实例并设置超时 + HttpClient httpClient = HttpClient.create() + .responseTimeout(Duration.ofSeconds(300)) // 设置响应超时时间为30秒 + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 * 100); // 设置连接超时为5秒 + this.webClient = WebClient.builder() + .baseUrl(DEFAULT_BASE_URL) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + } + + public OpenAiImageResponse createImage(OpenAiImageRequest request) { + HttpPost httpPost = new HttpPost(); + httpPost.setURI(URI.create(DEFAULT_BASE_URL.concat("/v1/images/generations"))); + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Authorization", "Bearer " + apiKey); + httpPost.setEntity(new StringEntity(JsonUtils.toJsonString(request), "UTF-8")); + + CloseableHttpResponse response= null; + try { + response = httpclient.execute(httpPost); + HttpEntity entity = response.getEntity(); + String resultJson = EntityUtils.toString(entity); + log.info("openai 图片生成结果: {}", resultJson); + return JSONUtil.toBean(resultJson, OpenAiImageResponse.class); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (response != null) { + try { + response.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +// String res = webClient.post() +// .uri(uriBuilder -> uriBuilder.path("/v1/images/generations").build()) +// .header("Content-Type", "application/json") +// .header("Authorization", "Bearer " + apiKey) +// // 设置请求体(这里假设jsonStr是一个JSON格式的字符串) +// .body(BodyInserters.fromValue(JacksonUtil.toJson(request))) +// // 发送请求并获取响应体 +// .retrieve() +// // 转换响应体为String类型 +// .bodyToMono(String.class) +// .block(); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageClient.java new file mode 100644 index 0000000..558a0e6 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageClient.java @@ -0,0 +1,101 @@ +package org.springframework.ai.models.openai; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import org.springframework.ai.chat.ChatException; +import org.springframework.ai.models.yiyan.exception.YiYanApiException; +import cn.iocoder.yudao.framework.ai.core.exception.AiException; +import org.springframework.ai.models.openai.api.OpenAiImageRequest; +import org.springframework.ai.models.openai.api.OpenAiImageResponse; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.ai.image.*; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryListener; +import org.springframework.retry.support.RetryTemplate; + +import java.time.Duration; + +/** + * open ai 生成 image + * + * author: fansili + * time: 2024/3/17 09:51 + */ +@Slf4j +public class OpenAiImageClient implements ImageClient { + + /** + * open image ai + */ + private OpenAiImageApi openAiImageApi; + /** + * 默认使用的 ImageOptions + */ + private OpenAiImageOptions defaultImageOptions; + + + public final RetryTemplate retryTemplate = RetryTemplate.builder() + // 最大重试次数 10 + .maxAttempts(10) + .retryOn(YiYanApiException.class) + // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms + .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) + .withListener(new RetryListener() { + @Override + public void onError(RetryContext context, + RetryCallback callback, Throwable throwable) { + log.warn("重试异常:" + context.getRetryCount(), throwable); + }; + }) + .build(); + + public OpenAiImageClient(OpenAiImageApi openAiImageApi, OpenAiImageOptions defaultImageOptions) { + this.openAiImageApi = openAiImageApi; + this.defaultImageOptions = defaultImageOptions; + } + + @Override + public ImageResponse call(ImagePrompt imagePrompt) { + return this.retryTemplate.execute(ctx -> { + OpenAiImageOptions openAiImageOptions = getOpenAiImageOptions(imagePrompt); + // 创建请求 + OpenAiImageRequest request = new OpenAiImageRequest(); + BeanUtil.copyProperties(openAiImageOptions, request); + request.setPrompt(imagePrompt.getInstructions().get(0).getText()); + request.setModel(openAiImageOptions.getModel()); + request.setStyle(openAiImageOptions.getStyle().getStyle()); + request.setSize(openAiImageOptions.getSize()); + // 发送请求 + OpenAiImageResponse response = openAiImageApi.createImage(request); + if (response.getError() != null && !StrUtil.isBlank(response.getError().getMessage())) { + // code 错误没有编码,就先根据 message 来进行判断 + throw new AiException("openAi 图片生成失败! " + response.getError().getMessage()); + } + return new ImageResponse(response.getData().stream().map(res -> { + byte[] bytes = HttpUtil.downloadBytes(res.getUrl()); + String base64 = Base64.encode(bytes); + return new ImageGeneration(new Image(res.getUrl(), base64)); + }).toList()); + }); + } + + private @NotNull OpenAiImageOptions getOpenAiImageOptions(ImagePrompt imagePrompt) { + // 检查是否配置了 OpenAiImageOptions + if (defaultImageOptions == null && imagePrompt.getOptions() == null) { + throw new ChatException("OpenAiImageOptions 未配置参数!"); + } + // 优先使用 request 中的 ImageOptions + ImageOptions useImageOptions = imagePrompt.getOptions() == null ? defaultImageOptions : imagePrompt.getOptions(); + if (!(useImageOptions instanceof OpenAiImageOptions)) { + throw new ChatException("配置信息不正确,传入的必须是 OpenAiImageOptions!"); + } + // 转换 OpenAiImageOptions + OpenAiImageOptions openAiImageOptions = (OpenAiImageOptions) useImageOptions; + return openAiImageOptions; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageOptions.java new file mode 100644 index 0000000..c6c9030 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/OpenAiImageOptions.java @@ -0,0 +1,95 @@ +package org.springframework.ai.models.openai; + +import org.springframework.ai.image.ImageOptions; +import org.springframework.ai.models.openai.enums.OpenAiImageModelEnum; +import org.springframework.ai.models.openai.enums.OpenAiImageStyleEnum; +import lombok.Data; +import lombok.Getter; +import lombok.experimental.Accessors; + +/** + * open ai 配置文件 + * + * 文档地址:https://platform.openai.com/docs/api-reference/images/create + * + * author: fansili + * time: 2024/3/17 09:53 + */ +@Data +@Accessors(chain = true) +public class OpenAiImageOptions implements ImageOptions { + + // 必填字段,用于描述期望生成图像的文字说明。对于dall-e-2模型最大长度为1000个字符,对于dall-e-3模型最大长度为4000个字符。 + private String prompt; + + // 可选字段,默认为dall-e-2 + // 指定用于生成图像的模型名称。 + private OpenAiImageModelEnum model = OpenAiImageModelEnum.DALL_E_2; + + // 可选字段,默认为1 + // 生成图像的数量,必须在1到10之间。对于dall-e-3模型,目前仅支持n=1。 + private Integer n = 1; + + // 可选字段,默认为standard + // 设置生成图像的质量。hd质量将创建细节更丰富、图像整体一致性更高的图片。该参数仅对dall-e-3模型有效。 + private String quality = "standard"; + + // 可选字段,默认为url + // 返回生成图像的格式。必须是url或b64_json中的一种。URL链接的有效期是从生成图像后开始计算的60分钟内有效。 + private String responseFormat = "url"; + + // 可选字段,默认为1024x1024 + // 生成图像的尺寸大小。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。 + private String size = "1024x1024"; + + // 可选字段,默认为vivid + // 图像生成的风格。可为vivid(生动)或natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 + private OpenAiImageStyleEnum style = OpenAiImageStyleEnum.VIVID; + + // 可选字段 + // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。 + private String endUserId = "UID123456"; + + @Getter + public enum ResponseFormatEnum { + + URL("url"), + BASE64("b64_json"), + + ; + + ResponseFormatEnum(String value) { + this.value = value; + } + + private String value; + } + + // + // 适配 spring ai + + @Override + public Integer getN() { + return this.n; + } + + @Override + public String getModel() { + return this.model.getModel(); + } + + @Override + public Integer getWidth() { + return null; + } + + @Override + public Integer getHeight() { + return null; + } + + @Override + public String getResponseFormat() { + return this.responseFormat; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageRequest.java new file mode 100644 index 0000000..0c01e1e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageRequest.java @@ -0,0 +1,58 @@ +package org.springframework.ai.models.openai.api; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * open ai 配置文件 + * + * 文档地址:https://platform.openai.com/docs/api-reference/images/create + * + * author: fansili + * time: 2024/3/17 09:53 + */ +@Data +@Accessors(chain = true) +public class OpenAiImageRequest { + + // 必填字段,用于描述期望生成图像的文字说明。对于dall-e-2模型最大长度为1000个字符,对于dall-e-3模型最大长度为4000个字符。 + @JsonProperty("prompt") + private String prompt; + + // 可选字段,默认为dall-e-2、dall-e-3 + // 指定用于生成图像的模型名称。 + @JsonProperty("model") + private String model = "dall-e-2"; + + // 可选字段,默认为1 + // 生成图像的数量,必须在1到10之间。对于dall-e-3模型,目前仅支持n=1。 + @JsonProperty("n") + private Integer n = 1; + + // 可选字段,默认为standard + // 设置生成图像的质量。hd质量将创建细节更丰富、图像整体一致性更高的图片。该参数仅对dall-e-3模型有效。 + @JsonProperty("quality") + private String quality = "standard"; + + // 可选字段,默认为url + // 返回生成图像的格式。必须是url或b64_json中的一种。URL链接的有效期是从生成图像后开始计算的60分钟内有效。 + @JsonProperty("response_format") + private String responseFormat = "url"; + + // 可选字段,默认为1024x1024 + // 生成图像的尺寸大小。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。 + @JsonProperty("size") + private String size = "1024x1024"; + + // 可选字段,默认为vivid + // 图像生成的风格。可为vivid(生动)或natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 + @JsonProperty("style") + private String style = "vivid"; + + // 可选字段 + // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。 + @JsonProperty("user") + private String endUserId = "UID123123"; + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageResponse.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageResponse.java new file mode 100644 index 0000000..beab4cd --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/api/OpenAiImageResponse.java @@ -0,0 +1,39 @@ +package org.springframework.ai.models.openai.api; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * image 返回 + * + * author: fansili + * time: 2024/3/17 10:27 + */ +@Data +@Accessors(chain = true) +public class OpenAiImageResponse { + + private long created; + private List data; + private Error error; + + @Data + @Accessors(chain = true) + public static class Item { + + private String url; + private String b64_json; + + } + + @Data + @Accessors(chain = true) + public static class Error { + private String code; + private String message; + private String param; + private String type; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageModelEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageModelEnum.java new file mode 100644 index 0000000..635c62b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageModelEnum.java @@ -0,0 +1,35 @@ +package org.springframework.ai.models.openai.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * open ai + * + * @author fansili + * @time 2024/4/28 14:21 + * @since 1.0 + */ +@AllArgsConstructor +@Getter +public enum OpenAiImageModelEnum { + + DALL_E_2("dall-e-2", "dall-e-2"), + + DALL_E_3("dall-e-3", "dall-e-3") + + ; + + private String model; + + private String name; + + public static OpenAiImageModelEnum valueOfModel(String model) { + for (OpenAiImageModelEnum itemEnum : OpenAiImageModelEnum.values()) { + if (itemEnum.getModel().equals(model)) { + return itemEnum; + } + } + throw new IllegalArgumentException("Invalid MessageType value: " + model); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageStyleEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageStyleEnum.java new file mode 100644 index 0000000..d7999cd --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/openai/enums/OpenAiImageStyleEnum.java @@ -0,0 +1,36 @@ +package org.springframework.ai.models.openai.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * open ai image style + * + * @author fansili + * @time 2024/4/28 16:15 + * @since 1.0 + */ +@AllArgsConstructor +@Getter +public enum OpenAiImageStyleEnum { + + // 图像生成的风格。可为vivid(生动)或 natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 + + VIVID("vivid", "生动"), + NATURAL("natural", "自然"), + + ; + + private String style; + + private String name; + + public static OpenAiImageStyleEnum valueOfStyle(String style) { + for (OpenAiImageStyleEnum itemEnum : OpenAiImageStyleEnum.values()) { + if (itemEnum.getStyle().equals(style)) { + return itemEnum; + } + } + throw new IllegalArgumentException("Invalid MessageType value: " + style); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatClient.java new file mode 100644 index 0000000..7707d68 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatClient.java @@ -0,0 +1,157 @@ +package org.springframework.ai.models.tongyi; + +import cn.iocoder.yudao.framework.ai.chat.*; +import org.springframework.ai.chat.*; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.models.tongyi.api.QianWenApi; +import org.springframework.ai.models.yiyan.exception.YiYanApiException; +import com.alibaba.dashscope.aigc.generation.GenerationResult; +import com.alibaba.dashscope.aigc.generation.models.QwenParam; +import com.alibaba.dashscope.common.Message; +import com.google.common.collect.Lists; +import io.reactivex.Flowable; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryListener; +import org.springframework.retry.support.RetryTemplate; +import reactor.core.publisher.Flux; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 阿里 通义千问 client + *

+ * 文档地址:https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp + *

+ * author: fansili + * time: 2024/3/13 21:06 + */ +@Slf4j +public class QianWenChatClient implements ChatClient, StreamingChatClient { + + private QianWenApi qianWenApi; + + private QianWenOptions qianWenOptions; + + + public QianWenChatClient() { + } + + public QianWenChatClient(QianWenApi qianWenApi) { + this.qianWenApi = qianWenApi; + } + + public QianWenChatClient(QianWenApi qianWenApi, QianWenOptions qianWenOptions) { + this.qianWenApi = qianWenApi; + this.qianWenOptions = qianWenOptions; + } + + // TODO @fansili:看看咋公用出来,允许传入类似异常之类的参数; + public final RetryTemplate retryTemplate = RetryTemplate.builder() + // 最大重试次数 10 + .maxAttempts(10) + .retryOn(YiYanApiException.class) + // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms + .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) + .withListener(new RetryListener() { + @Override + public void onError(RetryContext context, + RetryCallback callback, Throwable throwable) { + log.warn("重试异常:" + context.getRetryCount(), throwable); + } + }) + .build(); + + @Override + public ChatResponse call(Prompt prompt) { + return this.retryTemplate.execute(ctx -> { + // ctx 会有重试的信息 + // 创建 request 请求,stream模式需要供应商支持 + QwenParam request = this.createRequest(prompt, false); + // 调用 callWithFunctionSupport 发送请求 + ResponseEntity responseEntity = qianWenApi.chatCompletionEntity(request); + // 获取结果封装 chatCompletion + GenerationResult response = responseEntity.getBody(); +// if (!response.isSuccess()) { +// return new ChatResponse(List.of(new Generation(String.format("failed to create completion, requestId: %s, code: %s, message: %s\n", +// response.getRequestId(), response.getCode(), response.getMessage())))); +// } + // 转换为 Generation 返回 + return new ChatResponse(response.getOutput().getChoices().stream() + .map(choices -> new Generation(choices.getMessage().getContent())) + .collect(Collectors.toList())); + }); + } + + private QwenParam createRequest(Prompt prompt, boolean stream) { + // 获取 ChatOptions + QianWenOptions chatOptions = getChatOptions(prompt); + // + List messageList = Lists.newArrayList(); + prompt.getInstructions().stream().forEach(instruction -> { + Message message = new Message(); + message.setRole(instruction.getMessageType().getValue()); + message.setContent(instruction.getContent()); + messageList.add(message); + }); + return QwenParam.builder() + .model(qianWenApi.getQianWenChatModal().getModel()) + .prompt(prompt.getContents()) + .messages(messageList) + .maxTokens(chatOptions.getMaxTokens()) + .resultFormat(QwenParam.ResultFormat.MESSAGE) + .topP(Double.valueOf(chatOptions.getTopP())) + .topK(chatOptions.getTopK()) + .temperature(chatOptions.getTemperature()) + // 控制流式输出模式,即后面的内容会包含已经输出的内容;设置为True,将开启增量输出模式,后面的输出不会包含已经输出的内容,您需要自行拼接整体输出 + .incrementalOutput(true) + /* set the random seed, optional, default to 1234 if not set */ + .seed(100) + .apiKey(qianWenApi.getApiKey()) + .build(); + } + + private @NotNull QianWenOptions getChatOptions(Prompt prompt) { + // 两个都为null 则没有配置文件 + if (qianWenOptions == null && prompt.getOptions() == null) { + throw new ChatException("ChatOptions 未配置参数!"); + } + // 优先使用 Prompt 里面的 ChatOptions + ChatOptions options = qianWenOptions; + if (prompt.getOptions() != null) { + options = (ChatOptions) prompt.getOptions(); + } + // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 + if (!(options instanceof QianWenOptions)) { + throw new ChatException("Prompt 传入的不是 QianWenOptions!"); + } + return (QianWenOptions) options; + } + + @Override + public Flux stream(Prompt prompt) { + // ctx 会有重试的信息 + // 创建 request 请求,stream模式需要供应商支持 + QwenParam request = this.createRequest(prompt, true); + // 调用 callWithFunctionSupport 发送请求 + Flowable responseResult = this.qianWenApi.chatCompletionStream(request); + + return Flux.create(fluxSink -> + responseResult.subscribe( + value -> fluxSink.next( + new ChatResponse(value.getOutput().getChoices().stream() + .map(choices -> new Generation(choices.getMessage().getContent())) + .collect(Collectors.toList())) + ), + error -> fluxSink.error(error), + () -> fluxSink.complete() + ) + ); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatModal.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatModal.java new file mode 100644 index 0000000..9474a8d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenChatModal.java @@ -0,0 +1,47 @@ +package org.springframework.ai.models.tongyi; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 千问 chat 模型 + * + * 模型地址:https://help.aliyun.com/document_detail/2712576.html + * 模型介绍:https://help.aliyun.com/document_detail/2666503.html?spm=a2c4g.2701795.0.0.26eb34dfKzcWN4 + * + * @author fansili + * @time 2024/4/26 10:15 + * @since 1.0 + */ +@AllArgsConstructor +@Getter +public enum QianWenChatModal { + + // 千问付费模型 + QWEN_TURBO("通义千问超大规模语言模型", "qwen-turbo"), + QWEN_PLUS("通义千问超大规模语言模型增强版", "qwen-plus"), + QWEN_MAX("通义千问千亿级别超大规模语言模型", "qwen-max"), + QWEN_MAX_0403("通义千问千亿级别超大规模语言模型-0403", "qwen-max-0403"), + QWEN_MAX_0107("通义千问千亿级别超大规模语言模型-0107", "qwen-max-0107"), + QWEN_MAX_1201("通义千问千亿级别超大规模语言模型-1201", "qwen-max-1201"), + QWEN_MAX_LONGCONTEXT("通义千问千亿级别超大规模语言模型-28k tokens", "qwen-max-longcontext"), + + // 开源模型 + // https://help.aliyun.com/document_detail/2666503.html?spm=a2c4g.2701795.0.0.26eb34dfKzcWN4 + QWEN_72B_CHAT("通义千问1.5对外开源的72B规模参数量的经过人类指令对齐的chat模型", "qwen-72b-chat"), + + ; + + private String name; + + private String model; + + public static QianWenChatModal valueOfModel(String model) { + for (QianWenChatModal itemEnum : QianWenChatModal.values()) { + if (itemEnum.getModel().equals(model)) { + return itemEnum; + } + } + throw new IllegalArgumentException("Invalid MessageType value: " + model); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenOptions.java new file mode 100644 index 0000000..9a67760 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/QianWenOptions.java @@ -0,0 +1,122 @@ +package org.springframework.ai.models.tongyi; + +import org.springframework.ai.chat.prompt.ChatOptions; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 阿里云 千问 属性 + * + * 地址:https://help.aliyun.com/document_detail/2684682.html?spm=a2c4g.2621347.0.0.195117e7Ytpkyo + * + * author: fansili + * time: 2024/3/15 19:57 + */ +@Data +@Accessors(chain = true) +public class QianWenOptions implements ChatOptions { + + /** + * 用户与模型的对话历史 + */ + private List messages; + /** + * 生成时,核采样方法的概率阈值。例如,取值为0.8时,仅保留累计概率之和大于等于0.8的概率分布中的token, + * 作为随机采样的候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的随机性越低。 + * 默认值为0.8。注意,取值不要大于等于1 + */ + private Float topP; + /** + * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量。其中qwen1.5-14b-chat、qwen1.5-7b-chat、qwen-14b-chat和qwen-7b-chat最大值和默认值均为1500,qwen-1.8b-chat、qwen-1.8b-longcontext-chat和qwen-72b-chat最大值和默认值均为2000 + */ + private Integer maxTokens = 1500; + + // + // 适配 ChatOptions + + @Override + public Float getTemperature() { + return null; + } + + @Override + public void setTemperature(Float temperature) { + + } + + @Override + public void setTopP(Float topP) { + this.topP = topP; + } + + @Override + public Integer getTopK() { + return null; + } + + @Override + public void setTopK(Integer topK) { + + } + + @Data + @Accessors + public static class Message { + /** + * 角色: system、user或assistant + */ + private String role; + /** + * 提示词或模型内容 + */ + private String content; + } + + @Data + @Accessors + public static class Parameters { + /** + * 输出格式, 默认为"text" + * "text"表示旧版本的text + * "message"表示兼容openai的message + */ + private String resultFormat; + /** + * 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。 + * 取值越大,生成的随机性越高;取值越小,生成的确定性越高。 + * 注意:如果top_k参数为空或者top_k的值大于100,表示不启用top_k策略,此时仅有top_p策略生效,默认是空。 + */ + private Integer topK; + /** + * 生成时使用的随机数种子,用户控制模型生成内容的随机性。 + * seed支持无符号64位整数,默认值为1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 + */ + private Integer seed; + /** + * 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。 + * 较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择, + * 生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 + * 取值范围: [0, 2),系统默认值1.0。不建议取值为0,无意义。 + */ + private Float temperature; + /** + * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量。 + * 其中qwen-turbo 最大值和默认值为1500, qwen-max、qwen-max-1201 、qwen-max-longcontext 和 qwen-plus最大值和默认值均为2000。 + */ + private Integer maxTokens; + /** + * stop参数用于实现内容生成过程的精确控制,在生成内容即将包含指定的字符串或token_ids时自动停止,生成内容不包含指定的内容。 + * 例如,如果指定stop为"你好",表示将要生成"你好"时停止;如果指定stop为[37763, 367],表示将要生成"Observation"时停止。 + */ + private List stop; + /** + * 用于控制流式输出模式,默认False,即后面内容会包含已经输出的内容;设置为True,将开启增量输出模式, + * 后面输出不会包含已经输出的内容,您需要自行拼接整体输出,参考流式输出示例代码。 + */ + private Boolean incrementalOutput; + } +} + + diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenApi.java new file mode 100644 index 0000000..2e90511 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenApi.java @@ -0,0 +1,60 @@ +package org.springframework.ai.models.tongyi.api; + +import org.springframework.ai.models.tongyi.QianWenChatModal; +import cn.iocoder.yudao.framework.ai.core.exception.AiException; +import com.alibaba.dashscope.aigc.generation.Generation; +import com.alibaba.dashscope.aigc.generation.GenerationResult; +import com.alibaba.dashscope.aigc.generation.models.QwenParam; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.exception.NoApiKeyException; +import io.reactivex.Flowable; +import lombok.Getter; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; + +// TODO done @fansili:是不是挪到 api 包里?按照 spring ai 的结构;根目录只放 client 和 options + +/** + * 阿里 通义千问 + *

+ * author: fansili + * time: 2024/3/13 21:09 + */ +@Getter +public class QianWenApi { + + // api key 获取地址:https://bailian.console.aliyun.com/?spm=5176.28197581.0.0.38db29a4G3GcVb&apiKey=1#/api-key + private String apiKey = "sk-Zsd81gZYg7"; + private Generation gen = new Generation(); + private QianWenChatModal qianWenChatModal; + + public QianWenApi(String apiKey, QianWenChatModal qianWenChatModal) { + this.apiKey = apiKey; + this.qianWenChatModal = qianWenChatModal; + } + + public ResponseEntity chatCompletionEntity(QwenParam request) { + GenerationResult call; + try { + call = gen.call(request); + } catch (NoApiKeyException e) { + throw new AiException("没有找到apiKey!" + e.getMessage()); + } catch (InputRequiredException e) { + throw new AiException("chat缺少必填字段!" + e.getMessage()); + } + // 阿里云的这个 http code 随便设置,外面判断是否成功用的 CompletionsResponse.isSuccess + return new ResponseEntity<>(call, HttpStatusCode.valueOf(200)); + } + + public Flowable chatCompletionStream(QwenParam request) { + Flowable resultFlowable; + try { + resultFlowable = gen.streamCall(request); + } catch (NoApiKeyException e) { + throw new AiException("没有找到apiKey!" + e.getMessage()); + } catch (InputRequiredException e) { + throw new AiException("chat缺少必填字段!" + e.getMessage()); + } + return resultFlowable; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletion.java new file mode 100644 index 0000000..1c3c610 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletion.java @@ -0,0 +1,9 @@ +package org.springframework.ai.models.tongyi.api; + + +/** + * author: fansili + * time: 2024/3/13 21:07 + */ +public class QianWenChatCompletion { +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionMessage.java new file mode 100644 index 0000000..4e5d023 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionMessage.java @@ -0,0 +1,8 @@ +package org.springframework.ai.models.tongyi.api; + +/** + * author: fansili + * time: 2024/3/13 21:07 + */ +public class QianWenChatCompletionMessage { +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionRequest.java new file mode 100644 index 0000000..04b6984 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/api/QianWenChatCompletionRequest.java @@ -0,0 +1,16 @@ +package org.springframework.ai.models.tongyi.api; + +import com.alibaba.dashscope.aigc.generation.models.QwenParam; + +/** + * 千问 + * + * author: fansili + * time: 2024/3/13 21:07 + */ +public class QianWenChatCompletionRequest extends QwenParam { + + protected QianWenChatCompletionRequest(QwenParamBuilder b) { + super(b); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/package-info.java new file mode 100644 index 0000000..8d7793b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/tongyi/package-info.java @@ -0,0 +1,11 @@ +/** + * 阿里的 通义千问 + * + * 链接:https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all + * + * 千问所有模型:https://bailian.console.aliyun.com/?spm=5176.28515448.J_TC9GqcHi2edq9zUs9ZsDQ.1.417338b17zJTjy#/efm/my_model + * + * author: fansili + * time: 2024/3/13 21:05 + */ +package org.springframework.ai.models.tongyi; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java new file mode 100644 index 0000000..9025d82 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java @@ -0,0 +1,136 @@ +package org.springframework.ai.models.xinghuo; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.iocoder.yudao.framework.ai.chat.*; +import org.springframework.ai.chat.*; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.models.xinghuo.api.XingHuoApi; +import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletion; +import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletionRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryListener; +import org.springframework.retry.support.RetryTemplate; +import reactor.core.publisher.Flux; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 讯飞星火 client + *

+ * author: fansili + * time: 2024/3/11 10:19 + */ +@Slf4j +public class XingHuoChatClient implements ChatClient, StreamingChatClient { + + private XingHuoApi xingHuoApi; + + private XingHuoOptions xingHuoOptions; + + public final RetryTemplate retryTemplate = RetryTemplate.builder() + // 最大重试次数 10 + .maxAttempts(3) + .retryOn(ChatException.class) + // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms + .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) + .withListener(new RetryListener() { + @Override + public void onError(RetryContext context, + RetryCallback callback, Throwable throwable) { + System.err.println("正在重试... " + ExceptionUtil.getMessage(throwable)); + log.warn("重试异常:" + context.getRetryCount(), throwable); + } + + ; + }) + .build(); + + public XingHuoChatClient(XingHuoApi xingHuoApi) { + this.xingHuoApi = xingHuoApi; + } + + public XingHuoChatClient(XingHuoApi xingHuoApi, XingHuoOptions xingHuoOptions) { + this.xingHuoApi = xingHuoApi; + this.xingHuoOptions = xingHuoOptions; + } + + @Override + public ChatResponse call(Prompt prompt) { + + return this.retryTemplate.execute(ctx -> { + // ctx 会有重试的信息 + // 获取 chatOptions 属性 + XingHuoOptions chatOptions = this.getChatOptions(prompt); + // 创建 request 请求,stream模式需要供应商支持 + XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions); + // 调用 callWithFunctionSupport 发送请求 + ResponseEntity response = xingHuoApi.chatCompletionEntity(request, chatOptions.getChatModel()); + // 获取结果封装 ChatResponse + return new ChatResponse(List.of(new Generation(response.getBody().getPayload().getChoices().getText().get(0).getContent()))); + }); + } + + @Override + public Flux stream(Prompt prompt) { + // 获取 chatOptions 属性 + XingHuoOptions chatOptions = this.getChatOptions(prompt); + // 创建 request 请求,stream模式需要供应商支持 + XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions); + // 发送请求 + Flux response = this.xingHuoApi.chatCompletionStream(request, chatOptions.getChatModel()); + return response.map(res -> { + String content = res.getPayload().getChoices().getText().stream() + .map(item -> item.getContent()).collect(Collectors.joining()); + return new ChatResponse(List.of(new Generation(content))); + }); + } + + private XingHuoOptions getChatOptions(Prompt prompt) { + // 两个都为null 则没有配置文件 + if (xingHuoOptions == null && prompt.getOptions() == null) { + throw new ChatException("ChatOptions 未配置参数!"); + } + // 优先使用 Prompt 里面的 ChatOptions + ChatOptions options = xingHuoOptions; + if (prompt.getOptions() != null) { + options = (ChatOptions) prompt.getOptions(); + } + // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 + if (!(options instanceof XingHuoOptions)) { + throw new ChatException("Prompt 传入的不是 XingHuoOptions!"); + } + return (XingHuoOptions) options; + } + + private XingHuoChatCompletionRequest createRequest(Prompt prompt, XingHuoOptions xingHuoOptions) { + // 创建 header + XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id(xingHuoApi.getAppId()); + // 创建 params + XingHuoChatCompletionRequest.Parameter.Chat chatParameter = new XingHuoChatCompletionRequest.Parameter.Chat(); + BeanUtil.copyProperties(xingHuoOptions, chatParameter); + chatParameter.setDomain(xingHuoOptions.getChatModel().getModel()); + XingHuoChatCompletionRequest.Parameter parameter = new XingHuoChatCompletionRequest.Parameter().setChat(chatParameter); + // 创建 payload text 信息 + List texts = prompt.getInstructions().stream().map(message -> { + XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text(); + text.setContent(message.getContent()); + text.setRole(message.getMessageType().getValue()); + return text; + }).collect(Collectors.toList()); + // 创建 payload + XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload() + .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(texts)); + // 创建 request + return new XingHuoChatCompletionRequest() + .setHeader(header) + .setParameter(parameter) + .setPayload(payload); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java new file mode 100644 index 0000000..859e41c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java @@ -0,0 +1,53 @@ +package org.springframework.ai.models.xinghuo; + +import lombok.Getter; + +/** + * 讯飞星火 模型 + * + * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E + * + * 1tokens 约等于1.5个中文汉字 或者 0.8个英文单词 + * 星火V1.5支持[搜索]内置插件;星火V2.0、V3.0和V3.5支持[搜索]、[天气]、[日期]、[诗词]、[字词]、[股票]六个内置插件 + * 星火V3.5 现已支持system、Function Call 功能。 + * + * author: fansili + * time: 2024/3/11 10:12 + */ +@Getter +public enum XingHuoChatModel { + +// 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E +// general指向V1.5版本; +// generalv2指向V2版本; +// generalv3指向V3版本; +// generalv3.5指向V3.5版本; + + XING_HUO_1_5("星火大模型1.5", "general", "/v1.1/chat"), + XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"), + XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"), + XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"), + + ; + + XingHuoChatModel(String name, String model, String uri) { + this.name = name; + this.model = model; + this.uri = uri; + } + + private String name; + + private String model; + + private String uri; + + public static XingHuoChatModel valueOfModel(String model) { + for (XingHuoChatModel itemEnum : XingHuoChatModel.values()) { + if (itemEnum.getModel().equals(model)) { + return itemEnum; + } + } + throw new IllegalArgumentException("Invalid MessageType value: " + model); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java new file mode 100644 index 0000000..8efa752 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java @@ -0,0 +1,76 @@ +package org.springframework.ai.models.xinghuo; + +import org.springframework.ai.chat.prompt.ChatOptions; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 讯飞星火 + *

+ * author: fansili + * time: 2024/3/16 20:29 + */ +@Data +@Accessors(chain = true) +public class XingHuoOptions implements ChatOptions { + + /** + * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E + *

+ * 指定访问的领域: + * general指向V1.5版本; + * generalv2指向V2版本; + * generalv3指向V3版本; + * generalv3.5指向V3.5版本; + * 注意:不同的取值对应的url也不一样! + */ + private XingHuoChatModel chatModel = XingHuoChatModel.XING_HUO_3_5; + /** + * 取值范围 (0,1] ,默认值0.5 + */ + private Float temperature; + /** + * V1.5取值为[1,4096] + * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。 + */ + private Integer maxTokens; + /** + * 取值为[1,6],默认为4 + */ + private Integer topK; + /** + * 需要保障用户下的唯一性,用于关联用户会话 + */ + private String chatId; + + + @Override + public Float getTemperature() { + return this.temperature; + } + + @Override + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + @Override + public Float getTopP() { + return null; + } + + @Override + public void setTopP(Float topP) { + + } + + @Override + public Integer getTopK() { + return this.topK; + } + + @Override + public void setTopK(Integer topK) { + this.topK = topK; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java new file mode 100644 index 0000000..027e8a2 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java @@ -0,0 +1,149 @@ +package org.springframework.ai.models.xinghuo.api; + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import org.springframework.ai.models.xinghuo.XingHuoChatModel; +import lombok.Data; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.net.URI; +import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 讯飞星火 属性、api + *

+ * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E + *

+ * author: fansili + * time: 2024/3/11 10:12 + */ +@Data +public class XingHuoApi { + + private static final String DEFAULT_BASE_URL = "wss://spark-api.xf-yun.com"; + + private String appId; + private String appKey; + private String secretKey; + private WebClient webClient; + // 创建 WebSocketClient 实例 + private ReactorNettyWebSocketClient socketClient = new ReactorNettyWebSocketClient(); + + public XingHuoApi(String appId, String appKey, String secretKey) { + this.appId = appId; + this.appKey = appKey; + this.secretKey = secretKey; + } + + public ResponseEntity chatCompletionEntity(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) { + String authUrl; + try { +// XingHuoChatModel useChatModel; + authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri()); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException(e); + } + // wss 请求的 URI + URI uri = URI.create(authUrl); + // 发起 wss 请求并处理响应 + Flux messageFlux = Flux.create(sink -> { + socketClient.execute(uri, session -> + session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request)))) + .thenMany(session.receive() + .map(WebSocketMessage -> { + return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class); + }) + .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中 + .doOnError(sink::error) // 处理错误 + .doOnTerminate(sink::complete)) // 完成时关闭 sink + .then()) + .subscribe(); // 订阅以开始会话 + }); + // 阻塞获取所有结果 + List responseList = messageFlux.collectList().block(); + // 拼接 content + String responseContent = responseList.stream().map(item -> { + // 获取 content + return item.getPayload().getChoices().getText().stream().map(XingHuoChatCompletion.Text::getContent).collect(Collectors.joining()); + }).collect(Collectors.joining()); + // 将多个合并成一个 + XingHuoChatCompletion xingHuoChatCompletion = new XingHuoChatCompletion(); + xingHuoChatCompletion.setPayload(new XingHuoChatCompletion.Payload().setChoices(new XingHuoChatCompletion.Choices().setText(List.of(new XingHuoChatCompletion.Text().setContent(responseContent))))); + return new ResponseEntity<>(xingHuoChatCompletion, HttpStatusCode.valueOf(200)); + } + + + /** + * 获取验证请求url + * + * @return + */ + public String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException { + // 获取鉴权时间 date + SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + String date = format.format(new Date()); + + // 获取signature_origin字段 + StringBuilder builder = new StringBuilder("host: ").append(host).append("\n"). + append("date: ").append(date).append("\n"). + append("GET ").append(path).append(" HTTP/1.1"); + + // 获得signatue + Charset charset = Charset.forName("UTF-8"); + Mac mac = Mac.getInstance("hmacsha256"); + SecretKeySpec sp = new SecretKeySpec(secretKey.getBytes(charset), "hmacsha256"); + mac.init(sp); + byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset)); + String signature = Base64.getEncoder().encodeToString(basebefore); + //获得 authorization_origin + String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", appKey, "hmac-sha256", "host date request-line", signature); + //获得authorization + String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset)); + // 获取httpUrl + Map param = new HashMap<>(); + param.put("authorization", authorization); + param.put("date", date); + param.put("host", host); + + String toParams = HttpUtil.toParams(param); + return "wss://" + host + path + "?" + toParams; + } + + public Flux chatCompletionStream(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) { + String authUrl; + try { + authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri()); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException(e); + } + // wss 请求的 URI + URI uri = URI.create(authUrl); + // 发起 wss 请求并处理响应 + // 创建一个 Flux 来处理接收到的消息 + Flux messageFlux = Flux.create(sink -> { + socketClient.execute(uri, session -> + session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request)))) + .thenMany(session.receive() + .map(WebSocketMessage -> JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class)) + .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中 + .doOnError(sink::error) // 处理错误 + .doOnTerminate(sink::complete)) // 完成时关闭 sink + .then()) + .subscribe(); // 订阅以开始会话 + }); + return messageFlux; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java new file mode 100644 index 0000000..e419d52 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java @@ -0,0 +1,48 @@ +package org.springframework.ai.models.xinghuo.api; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * author: fansili + * time: 2024/3/11 10:20 + */ +@Data +@Accessors(chain = true) +public class XingHuoChatCompletion { + private Header header; + private Payload payload; + + @Data + @Accessors(chain = true) + public static class Header { + private int code; + private String message; + private String sid; + private int status; + } + + @Data + @Accessors(chain = true) + public static class Payload { + private Choices choices; + } + + @Data + @Accessors(chain = true) + public static class Choices { + private int status; + private int seq; + private List text; + } + + @Data + @Accessors(chain = true) + public static class Text { + private String content; + private String role; + private int index; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java new file mode 100644 index 0000000..f0e0f8d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java @@ -0,0 +1,8 @@ +package org.springframework.ai.models.xinghuo.api; + +/** + * author: fansili + * time: 2024/3/11 10:20 + */ +public class XingHuoChatCompletionMessage { +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java new file mode 100644 index 0000000..3f2b211 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java @@ -0,0 +1,107 @@ +package org.springframework.ai.models.xinghuo.api; + +import lombok.Data; +import lombok.Getter; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 讯飞星火 request + * + * author: fansili + * time: 2024/3/11 10:20 + */ +@Data +@Accessors(chain = true) +public class XingHuoChatCompletionRequest { + + private Header header; + private Parameter parameter; + private Payload payload; + + @Data + @Accessors(chain = true) + public static class Header { + private String app_id; + private String uid; + } + + @Data + @Accessors(chain = true) + public static class Parameter { + private Chat chat; + + @Data + @Accessors(chain = true) + public static class Chat { + /** + * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E + * + * 指定访问的领域: + * general指向V1.5版本; + * generalv2指向V2版本; + * generalv3指向V3版本; + * generalv3.5指向V3.5版本; + * 注意:不同的取值对应的url也不一样! + */ + private String domain = "generalv3.5"; + /** + * 取值范围 (0,1] ,默认值0.5 + */ + private Float temperature; + /** + * V1.5取值为[1,4096] + * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。 + */ + private Integer max_tokens; + /** + * 取值为[1,6],默认为4 + */ + private Integer top_k; + /** + * 需要保障用户下的唯一性,用于关联用户会话 + */ + private String chat_id; + } + } + + @Data + @Accessors(chain = true) + public static class Payload { + private Message message; + + @Data + @Accessors(chain = true) + public static class Message { + private List text; + + + @Data + @Accessors(chain = true) + public static class Text { + /** + * 角色 + */ + private String role; + /** + * 消息内容 + */ + private String content; + private Integer index; + + @Getter + public static enum Role { + SYSTEM("system"), + USER("user"), + ASSISTANT("assistant"); + private String name; + + private Role(String name) { + this.name = name; + } + } + } + } + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatClient.java new file mode 100644 index 0000000..fecc6ac --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatClient.java @@ -0,0 +1,155 @@ +package org.springframework.ai.models.yiyan; + +import cn.hutool.core.bean.BeanUtil; +import cn.iocoder.yudao.framework.ai.chat.*; +import org.springframework.ai.chat.*; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.models.yiyan.api.YiYanApi; +import org.springframework.ai.models.yiyan.api.YiYanChatCompletion; +import org.springframework.ai.models.yiyan.api.YiYanChatCompletionRequest; +import org.springframework.ai.models.yiyan.exception.YiYanApiException; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryListener; +import org.springframework.retry.support.RetryTemplate; +import reactor.core.publisher.Flux; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 文心一言 + *

+ * author: fansili + * time: 2024/3/8 19:11 + */ +@Slf4j +public class YiYanChatClient implements ChatClient, StreamingChatClient { + + private YiYanApi yiYanApi; + + private YiYanOptions yiYanOptions; + + public YiYanChatClient(YiYanApi yiYanApi) { + this.yiYanApi = yiYanApi; + } + + public YiYanChatClient(YiYanApi yiYanApi, YiYanOptions yiYanOptions) { + this.yiYanApi = yiYanApi; + this.yiYanOptions = yiYanOptions; + } + + public final RetryTemplate retryTemplate = RetryTemplate.builder() + // 最大重试次数 10 + .maxAttempts(10) + .retryOn(YiYanApiException.class) + // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms + .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) + .withListener(new RetryListener() { + @Override + public void onError(RetryContext context, + RetryCallback callback, Throwable throwable) { + log.warn("重试异常:" + context.getRetryCount(), throwable); + } + + ; + }) + .build(); + + @Override + public String call(String message) { + return ChatClient.super.call(message); + } + + @Override + public ChatResponse call(Prompt prompt) { + return this.retryTemplate.execute(ctx -> { + // ctx 会有重试的信息 + // 创建 request 请求,stream模式需要供应商支持 + YiYanChatCompletionRequest request = this.createRequest(prompt, false); + // 调用 callWithFunctionSupport 发送请求 + ResponseEntity response = yiYanApi.chatCompletionEntity(request); + // 获取结果封装 ChatResponse + YiYanChatCompletion chatCompletion = response.getBody(); + return new ChatResponse(List.of(new Generation(chatCompletion.getResult()))); + }); + } + + @Override + public Flux stream(Prompt prompt) { + // ctx 会有重试的信息 + // 创建 request 请求,stream模式需要供应商支持 + YiYanChatCompletionRequest request = this.createRequest(prompt, true); + // 调用 callWithFunctionSupport 发送请求 + Flux response = this.yiYanApi.chatCompletionStream(request); + response.doOnComplete(new Runnable() { + @Override + public void run() { + String a = ";"; + } + }); + return response.map(res -> new ChatResponse(List.of(new Generation(res.getResult())))); + } + + private YiYanChatCompletionRequest createRequest(Prompt prompt, boolean stream) { + // 获取配置 + YiYanOptions useOptions = getYiYanOptions(prompt); + // 创建 request + + // tip: 百度的 system 不在 message 里面 + // tip:百度的 message 只有 user 和 assistant + // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t + + // 获取 user 和 assistant + List messageList = prompt.getInstructions().stream() + // 过滤 system + .filter(msg -> MessageType.SYSTEM != msg.getMessageType()) + .map(msg -> new YiYanChatCompletionRequest.Message() + .setRole(msg.getMessageType().getValue()) + .setContent(msg.getContent()) + ).toList(); + // 获取 system + String systemPrompt = prompt.getInstructions().stream() + .filter(msg -> MessageType.SYSTEM == msg.getMessageType()) + .map(Message::getContent) + .collect(Collectors.joining()); + + YiYanChatCompletionRequest request = new YiYanChatCompletionRequest(messageList); + // 复制 qianWenOptions 属性取 request(这里 options 属性和 request 基本保持一致) + // top: 由于遵循 spring-ai规范,支持在构建client的时候传入默认的 chatOptions + BeanUtil.copyProperties(useOptions, request); + request.setTop_p(useOptions.getTopP()); + request.setMax_output_tokens(useOptions.getMaxOutputTokens()); + request.setTemperature(useOptions.getTemperature()); + request.setSystem(systemPrompt); + // 设置 stream + request.setStream(stream); + return request; + } + + private @NotNull YiYanOptions getYiYanOptions(Prompt prompt) { + // 两个都为null 则没有配置文件 + if (yiYanOptions == null && prompt.getOptions() == null) { + throw new ChatException("ChatOptions 未配置参数!"); + } + // 优先使用 Prompt 里面的 ChatOptions + ChatOptions options = yiYanOptions; + if (prompt.getOptions() != null) { + options = (ChatOptions) prompt.getOptions(); + } + // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 + if (!(options instanceof YiYanOptions)) { + throw new ChatException("Prompt 传入的不是 YiYanOptions!"); + } + // 转换 YiYanOptions + YiYanOptions useOptions = (YiYanOptions) options; + return useOptions; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatModel.java new file mode 100644 index 0000000..abed185 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanChatModel.java @@ -0,0 +1,40 @@ +package org.springframework.ai.models.yiyan; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 一言模型 + * + * 可参考百度文档:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t + * + * author: fansili + * time: 2024/3/9 12:01 + */ +@Getter +@AllArgsConstructor +public enum YiYanChatModel { + + ERNIE4_0("ERNIE 4.0", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro"), + ERNIE4_3_5_8K("ERNIE-3.5-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"), + ERNIE4_3_5_8K_0205("ERNIE-3.5-8K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-0205"), + + ERNIE4_3_5_8K_1222("ERNIE-3.5-8K-1222", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-1222"), + ERNIE4_BOT_8K("ERNIE-Bot-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie_bot_8k"), + ERNIE4_3_5_4K_0205("ERNIE-3.5-4K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-4k-0205"), + + ; + + private String model; + + private String uri; + + public static YiYanChatModel valueOfModel(String model) { + for (YiYanChatModel itemEnum : YiYanChatModel.values()) { + if (itemEnum.getModel().equals(model)) { + return itemEnum; + } + } + throw new IllegalArgumentException("Invalid MessageType value: " + model); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanOptions.java new file mode 100644 index 0000000..aac8e49 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/YiYanOptions.java @@ -0,0 +1,142 @@ +package org.springframework.ai.models.yiyan; + +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.models.yiyan.api.YiYanChatCompletionRequest; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 百度 问心一言 + * + * 文档地址:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t + * + * author: fansili + * time: 2024/3/16 19:33 + */ +@Data +@Accessors(chain = true) +public class YiYanOptions implements ChatOptions { + + /** + * 一个可触发函数的描述列表,说明: + * (1)支持的function数量无限制 + * (2)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens + * 必填:否 + */ + private List functions; + /** + * 说明: + * (1)较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 + * (2)默认0.8,范围 (0, 1.0],不能为0 + * 必填:否 + */ + private Float temperature; + /** + * 说明: + * (1)影响输出文本的多样性,取值越大,生成文本的多样性越强 + * (2)默认0.8,取值范围 [0, 1.0] + * 必填:否 + */ + private Float topP; + /** + * 通过对已生成的token增加惩罚,减少重复生成的现象。说明: + * (1)值越大表示惩罚越大 + * (2)默认1.0,取值范围:[1.0, 2.0] + * + * 必填:否 + */ + private Float penalty_score; + /** + * 是否以流式接口的形式返回数据,默认false + * 必填:否 + */ + private Boolean stream; + /** + * 模型人设,主要用于人设设定,例如,你是xxx公司制作的AI助手,说明: + * (1)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens + * (2)如果同时使用system和functions,可能暂无法保证使用效果,持续进行优化 + * 必填:否 + */ + private String system; + /** + * 生成停止标识,当模型生成结果以stop中某个元素结尾时,停止文本生成。说明: + * (1)每个元素长度不超过20字符 + * (2)最多4个元素 + * 必填:否 + */ + private List stop; + /** + * 是否强制关闭实时搜索功能,默认false,表示不关闭 + * 必填:否 + */ + private Boolean disable_search; + /** + * 是否开启上角标返回,说明: + * (1)开启后,有概率触发搜索溯源信息search_info,search_info内容见响应参数介绍 + * (2)默认false,不开启 + * 必填:否 + */ + private Boolean enable_citation; + /** + * 指定模型最大输出token数,范围[2, 2048] + * 必填:否 + */ + private Integer maxOutputTokens; + /** + * 指定响应内容的格式,说明: + * (1)可选值: + * · json_object:以json格式返回,可能出现不满足效果情况 + * · text:以文本格式返回 + * (2)如果不填写参数response_format值,默认为text + * 必填:否 + */ + private String response_format; + /** + * 表示最终用户的唯一标识符 + * 必填:否 + */ + private String user_id; + /** + * 在函数调用场景下,提示大模型选择指定的函数(非强制),说明:指定的函数名必须在functions中存在 + * 必填:否 + * + * ERNIE-4.0-8K 模型没有这个字段 + */ + private String tool_choice; + + // + // 以下兼容 spring-ai ChatOptions 暂时没有其他地方用到 + + @Override + public Float getTemperature() { + return this.temperature; + } + + @Override + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + @Override + public Float getTopP() { + return topP; + } + + @Override + public void setTopP(Float topP) { + this.topP = topP; + } + + // 百度么有 topK + + @Override + public Integer getTopK() { + return null; + } + + @Override + public void setTopK(Integer topK) { + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanApi.java new file mode 100644 index 0000000..a8f4aa3 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanApi.java @@ -0,0 +1,95 @@ +package org.springframework.ai.models.yiyan.api; + +import org.springframework.ai.models.yiyan.YiYanChatModel; +import org.springframework.ai.models.yiyan.exception.YiYanApiException; +import lombok.Data; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * 文心一言 + *

+ * author: fansili + * time: 2024/3/8 21:47 + */ +@Data +public class YiYanApi { + + private static final String DEFAULT_BASE_URL = "https://aip.baidubce.com"; + + private static final String AUTH_2_TOKEN_URI = "/oauth/2.0/token"; + + public static final String DEFAULT_CHAT_MODEL = "ERNIE 4.0"; + + // 获取access_token流程 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 + private String appKey; + private String secretKey; + private String token; + // token刷新时间(秒) + private int refreshTokenSecondTime; + // 发送请求 webClient + private final WebClient webClient; + // 使用的模型 + private YiYanChatModel useChatModel; + + public YiYanApi(String appKey, String secretKey, YiYanChatModel useChatModel, int refreshTokenSecondTime) { + this.appKey = appKey; + this.secretKey = secretKey; + this.useChatModel = useChatModel; + this.refreshTokenSecondTime = refreshTokenSecondTime; + + this.webClient = WebClient.builder() + .baseUrl(DEFAULT_BASE_URL) + .build(); + + token = getToken(); + } + + private String getToken() { + // 文档地址: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 + ResponseEntity response = this.webClient.post() + .uri(uriBuilder -> uriBuilder.path(AUTH_2_TOKEN_URI) + .queryParam("grant_type", "client_credentials") + .queryParam("client_id", appKey) + .queryParam("client_secret", secretKey) + .build() + ) + .retrieve() + .toEntity(YiYanAuthRes.class) + .block(); + // 检查请求状态 + if (HttpStatusCode.valueOf(200) != response.getStatusCode()) { + throw new YiYanApiException("一言认证失败! api:https://aip.baidubce.com/oauth/2.0/token 请检查 client_id、client_secret 是否正确!"); + } + YiYanAuthRes body = response.getBody(); + return body.getAccess_token(); + } + + public ResponseEntity chatCompletionEntity(YiYanChatCompletionRequest request) { + // TODO: 2024/3/10 小范 这里错误信息返回的结构不一样 +// {"error_code":17,"error_msg":"Open api daily request limit reached"} + return this.webClient.post() + .uri(uriBuilder + -> uriBuilder.path(useChatModel.getUri()) + .queryParam("access_token", token) + .build()) + .body(Mono.just(request), YiYanChatCompletionRequest.class) + .retrieve() + .toEntity(YiYanChatCompletion.class) + .block(); + } + + public Flux chatCompletionStream(YiYanChatCompletionRequest request) { + return this.webClient.post() + .uri(uriBuilder + -> uriBuilder.path(useChatModel.getUri()) + .queryParam("access_token", token) + .build()) + .body(Mono.just(request), YiYanChatCompletionRequest.class) + .retrieve() + .bodyToFlux(YiYanChatCompletion.class); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanAuthRes.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanAuthRes.java new file mode 100644 index 0000000..f9f1f27 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanAuthRes.java @@ -0,0 +1,48 @@ +package org.springframework.ai.models.yiyan.api; + +import lombok.Data; + +/** + * 一言 获取access_token + * + * author: fansili + * time: 2024/3/10 08:51 + */ +@Data +public class YiYanAuthRes { + + /** + * 访问凭证 + */ + private String access_token; + /** + * 有效期,Access Token的有效期。 + * 说明:单位是秒,有效期30天 + */ + private int expires_in; + /** + * 错误码,说明:响应失败时返回该字段,成功时不返回 + */ + private String error; + /** + * 错误描述信息,帮助理解和解决发生的错误 + * 说明:响应失败时返回该字段,成功时不返回 + */ + private String error_description; + /** + * 暂时未使用,可忽略 + */ + private String session_key; + /** + * 暂时未使用,可忽略 + */ + private String refresh_token; + /** + * 暂时未使用,可忽略 + */ + private String scope; + /** + * 暂时未使用,可忽略 + */ + private String session_secret; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletion.java new file mode 100644 index 0000000..8e02db6 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletion.java @@ -0,0 +1,91 @@ +package org.springframework.ai.models.yiyan.api; + +import lombok.Data; + +/** + * 聊天返回 + * 百度链接: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t + * + * author: fansili + * time: 2024/3/9 10:34 + */ +@Data +public class YiYanChatCompletion { + + /** + * 本轮对话的id + */ + private String id; + /** + * 回包类型,chat.completion:多轮对话返回 + */ + private String object; + /** + * 时间戳 + */ + private int created; + /** + * 表示当前子句的序号。只有在流式接口模式下会返回该字段 + */ + private int sentence_id; + /** + * 表示当前子句是否是最后一句。只有在流式接口模式下会返回该字段 + */ + private boolean is_end; + /** + * 当前生成的结果是否被截断 + */ + private boolean is_truncated; + /** + * 输出内容标识,说明: + * · normal:输出内容完全由大模型生成,未触发截断、替换 + * · stop:输出结果命中入参stop中指定的字段后被截断 + * · length:达到了最大的token数,根据EB返回结果is_truncated来截断 + * · content_filter:输出内容被截断、兜底、替换为**等 + */ + private String finish_reason; + /** + * 搜索数据,当请求参数enable_citation为true并且触发搜索时,会返回该字段 + */ + private String search_info; + /** + * 对话返回结果 + */ + private String result; + /** + * 表示用户输入是否存在安全,是否关闭当前会话,清理历史会话信息 + * true:是,表示用户输入存在安全风险,建议关闭当前会话,清理历史会话信息 + * false:否,表示用户输入无安全风险 + */ + private boolean need_clear_history; + /** + * 说明: + * · 0:正常返回 + * · 其他:非正常 + */ + private int flag; + /** + * 当need_clear_history为true时,此字段会告知第几轮对话有敏感信息,如果是当前问题,ban_round=-1 + */ + private int ban_round; + /** + * token统计信息 + */ + private Usage usage; + + @Data + public static class Usage { + /** + * 问题tokens数 + */ + private int prompt_tokens; + /** + * 回答tokens数 + */ + private int completion_tokens; + /** + * tokens总数 + */ + private int total_tokens; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionMessage.java new file mode 100644 index 0000000..278b1a7 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionMessage.java @@ -0,0 +1,8 @@ +package org.springframework.ai.models.yiyan.api; + +/** + * author: fansili + * time: 2024/3/9 10:37 + */ +public class YiYanChatCompletionMessage { +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionRequest.java new file mode 100644 index 0000000..22e9188 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/api/YiYanChatCompletionRequest.java @@ -0,0 +1,178 @@ +package org.springframework.ai.models.yiyan.api; + +import lombok.Data; + +import java.util.List; + +/** + * 一言 Completion req + * + * 百度千帆文档:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/jlil56u11 + * + * author: fansili + * time: 2024/3/9 10:34 + */ +@Data +public class YiYanChatCompletionRequest { + + public YiYanChatCompletionRequest(List messages) { + this.messages = messages; + } + + /** + * 聊天上下文信息。 + * 必填:是 + */ + private List messages; + /** + * 一个可触发函数的描述列表,说明: + * (1)支持的function数量无限制 + * (2)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens + * 必填:否 + */ + private List functions; + /** + * 说明: + * (1)较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 + * (2)默认0.8,范围 (0, 1.0],不能为0 + * 必填:否 + */ + private Float temperature; + /** + * 说明: + * (1)影响输出文本的多样性,取值越大,生成文本的多样性越强 + * (2)默认0.8,取值范围 [0, 1.0] + * 必填:否 + */ + private Float top_p; + /** + * 通过对已生成的token增加惩罚,减少重复生成的现象。说明: + * (1)值越大表示惩罚越大 + * (2)默认1.0,取值范围:[1.0, 2.0] + * + * 必填:否 + */ + private Float penalty_score; + /** + * 是否以流式接口的形式返回数据,默认false + * 必填:否 + */ + private Boolean stream; + /** + * 模型人设,主要用于人设设定,例如,你是xxx公司制作的AI助手,说明: + * (1)长度限制,最后一个message的content长度(即此轮对话的问题)、functions和system字段总内容不能超过20480 个字符,且不能超过5120 tokens + * (2)如果同时使用system和functions,可能暂无法保证使用效果,持续进行优化 + * 必填:否 + */ + private String system; + /** + * 生成停止标识,当模型生成结果以stop中某个元素结尾时,停止文本生成。说明: + * (1)每个元素长度不超过20字符 + * (2)最多4个元素 + * 必填:否 + */ + private List stop; + /** + * 是否强制关闭实时搜索功能,默认false,表示不关闭 + * 必填:否 + */ + private Boolean disable_search; + /** + * 是否开启上角标返回,说明: + * (1)开启后,有概率触发搜索溯源信息search_info,search_info内容见响应参数介绍 + * (2)默认false,不开启 + * 必填:否 + */ + private Boolean enable_citation; + /** + * 指定模型最大输出token数,范围[2, 2048] + * 必填:否 + */ + private Integer max_output_tokens; + /** + * 指定响应内容的格式,说明: + * (1)可选值: + * · json_object:以json格式返回,可能出现不满足效果情况 + * · text:以文本格式返回 + * (2)如果不填写参数response_format值,默认为text + * 必填:否 + */ + private String response_format; + /** + * 表示最终用户的唯一标识符 + * 必填:否 + */ + private String user_id; + /** + * 在函数调用场景下,提示大模型选择指定的函数(非强制),说明:指定的函数名必须在functions中存在 + * 必填:否 + * + * ERNIE-4.0-8K 模型没有这个字段 + */ + private String tool_choice; + + + @Data + public static class Message { + private String role; + + private String content; + } + + @Data + public static class ToolChoice { + /** + * 指定工具类型,function + * 必填: 是 + */ + private String type; + /** + * 指定要使用的函数 + * 必填: 是 + */ + private Function function; + /** + * 指定要使用的函数名 + * 必填: 是 + */ + private String name; + } + + @Data + public static class Function { + /** + * 函数名 + * 必填: 是 + */ + private String name; + /** + * 函数描述 + * 必填: 是 + */ + private String description; + /** + * 函数请求参数,说明: + * (1)JSON Schema 格式,参考JSON Schema描述 + * (2)如果函数没有请求参数,parameters值格式如下: + * {"type": "object","properties": {}} + * 必填: 是 + */ + private String parameters; + /** + * 函数响应参数,JSON Schema 格式,参考JSON Schema描述 + * 必填: 否 + */ + private String responses; + /** + * function调用的一些历史示例,说明: + * (1)可以提供正例(正常触发)和反例(无需触发)的example + * ·正例:从历史请求数据中获取 + * ·反例: + * 当role = user,不会触发请求的query + * 当role = assistant,有固定的格式。function_call的name为空,arguments是空对象:"{}",thought可以填固定的:"我不需要调用任何工具" + * (2)兼容之前的 List(example) 格式 + */ + private String examples; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/exception/YiYanApiException.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/exception/YiYanApiException.java new file mode 100644 index 0000000..94850f5 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/yiyan/exception/YiYanApiException.java @@ -0,0 +1,16 @@ +package org.springframework.ai.models.yiyan.exception; + +/** + * 一言 api 调用异常 + */ +public class YiYanApiException extends RuntimeException { + + public YiYanApiException(String message) { + super(message); + } + + public YiYanApiException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/package-info.java new file mode 100644 index 0000000..b38419f --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/package-info.java @@ -0,0 +1,15 @@ +/** + * 从 https://github.com/spring-projects/spring-ai 拷贝。 + * + * 最大目的:适配 JDK8 兼容性 + * + * 包路径: + * 1. chat、parser、model、parser 包:https://github.com/spring-projects/spring-ai/tree/main/spring-ai-core 拷贝 + * 2. models 包:对标 https://github.com/spring-projects/spring-ai/tree/main/models 拷贝 + * 2.1 tongyi 包:【阿里】通义千问,对标 spring-cloud-alibaba 提供的 ai 包 + * 2.2 yiyan 包:【百度】文心一言,自己实现 + * 2.3 xinghuo 包:【讯飞】星火,自己实现 + * 2.4 openai 包:【OpenAI】ChatGPT,拷贝 spring-ai 提供的 models/openai 包 + * 2.5 midjourney 包:Midjourney,参考 https://github.com/novicezk/midjourney-proxy 实现 + */ +package org.springframework.ai; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractConversionServiceOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractConversionServiceOutputParser.java new file mode 100644 index 0000000..a06827f --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractConversionServiceOutputParser.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.parser; + +import org.springframework.core.convert.support.DefaultConversionService; + +/** + * Abstract {@link OutputParser} implementation that uses a pre-configured + * {@link DefaultConversionService} to convert the LLM output into the desired type + * format. + * + * @param Specifies the desired response type. + * @author Mark Pollack + * @author Christian Tzolov + */ +public abstract class AbstractConversionServiceOutputParser implements OutputParser { + + private final DefaultConversionService conversionService; + + public AbstractConversionServiceOutputParser(DefaultConversionService conversionService) { + this.conversionService = conversionService; + } + + public DefaultConversionService getConversionService() { + return conversionService; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractMessageConverterOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractMessageConverterOutputParser.java new file mode 100644 index 0000000..01dbada --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/AbstractMessageConverterOutputParser.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.parser; + +import org.springframework.messaging.converter.MessageConverter; + +/** + * Abstract {@link OutputParser} implementation that uses a pre-configured + * {@link MessageConverter} to convert the LLM output into the desired type format. + * + * @param Specifies the desired response type. + * @author Mark Pollack + * @author Christian Tzolov + */ +public abstract class AbstractMessageConverterOutputParser implements OutputParser { + + private MessageConverter messageConverter; + + public AbstractMessageConverterOutputParser(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + } + + public MessageConverter getMessageConverter() { + return this.messageConverter; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/BeanOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/BeanOutputParser.java new file mode 100644 index 0000000..fa09c26 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/BeanOutputParser.java @@ -0,0 +1,171 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.parser; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.github.victools.jsonschema.generator.SchemaGenerator; +import com.github.victools.jsonschema.generator.SchemaGeneratorConfig; +import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; +import com.github.victools.jsonschema.module.jackson.JacksonModule; + +import java.util.Map; +import java.util.Objects; + +import static com.github.victools.jsonschema.generator.OptionPreset.PLAIN_JSON; +import static com.github.victools.jsonschema.generator.SchemaVersion.DRAFT_2020_12; + +/** + * An implementation of {@link OutputParser} that transforms the LLM output to a specific + * object type using JSON schema. This parser works by generating a JSON schema based on a + * given Java class, which is then used to validate and transform the LLM output into the + * desired type. + * + * @param The target type to which the output will be converted. + * @author Mark Pollack + * @author Christian Tzolov + * @author Sebastian Ullrich + * @author Kirk Lund + */ +public class BeanOutputParser implements OutputParser { + + /** Holds the generated JSON schema for the target type. */ + private String jsonSchema; + + /** The Java class representing the target type. */ + @SuppressWarnings({ "FieldMayBeFinal", "rawtypes" }) + private Class clazz; + + /** The object mapper used for deserialization and other JSON operations. */ + @SuppressWarnings("FieldMayBeFinal") + private ObjectMapper objectMapper; + + /** + * Constructor to initialize with the target type's class. + * @param clazz The target type's class. + */ + public BeanOutputParser(Class clazz) { + this(clazz, null); + } + + /** + * Constructor to initialize with the target type's class, a custom object mapper, and + * a line endings normalizer to ensure consistent line endings on any platform. + * @param clazz The target type's class. + * @param objectMapper Custom object mapper for JSON operations. endings. + */ + public BeanOutputParser(Class clazz, ObjectMapper objectMapper) { + Objects.requireNonNull(clazz, "Java Class cannot be null;"); + this.clazz = clazz; + this.objectMapper = objectMapper != null ? objectMapper : getObjectMapper(); + generateSchema(); + } + + /** + * Generates the JSON schema for the target type. + */ + private void generateSchema() { + JacksonModule jacksonModule = new JacksonModule(); + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(DRAFT_2020_12, PLAIN_JSON) + .with(jacksonModule); + SchemaGeneratorConfig config = configBuilder.build(); + SchemaGenerator generator = new SchemaGenerator(config); + JsonNode jsonNode = generator.generateSchema(this.clazz); + ObjectWriter objectWriter = new ObjectMapper() + .writer(new DefaultPrettyPrinter().withObjectIndenter(new DefaultIndenter().withLinefeed("\n"))); + try { + this.jsonSchema = objectWriter.writeValueAsString(jsonNode); + } + catch (JsonProcessingException e) { + throw new RuntimeException("Could not pretty print json schema for " + this.clazz, e); + } + } + + @Override + /** + * Parses the given text to transform it to the desired target type. + * @param text The LLM output in string format. + * @return The parsed output in the desired target type. + */ + public T parse(String text) { + try { + // If the response is a JSON Schema, extract the properties and use them as + // the response. + text = this.jsonSchemaToInstance(text); + return (T) this.objectMapper.readValue(text, this.clazz); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * Converts a JSON Schema to an instance based on a given text. + * @param text The JSON Schema in string format. + * @return The JSON instance generated from the JSON Schema, or the original text if + * the input is not a JSON Schema. + */ + private String jsonSchemaToInstance(String text) { + try { + Map map = this.objectMapper.readValue(text, Map.class); + if (map.containsKey("$schema")) { + return this.objectMapper.writeValueAsString(map.get("properties")); + } + } + catch (Exception e) { + } + return text; + } + + /** + * Configures and returns an object mapper for JSON operations. + * @return Configured object mapper. + */ + protected ObjectMapper getObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper; + } + + /** + * Provides the expected format of the response, instructing that it should adhere to + * the generated JSON schema. + * @return The instruction format string. + */ + @Override + public String getFormat() { +// String template = """ +// Your response should be in JSON format. +// Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. +// Do not include markdown code blocks in your response. +// Here is the JSON Schema instance your output must adhere to: +// ```%s``` +// """; + String template = "Your response should be in JSON format.\n" + + "\t\t\t\tDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\n" + + "\t\t\t\tDo not include markdown code blocks in your response.\n" + + "\t\t\t\tHere is the JSON Schema instance your output must adhere to:\n" + + "\t\t\t\t```%s```"; + return String.format(template, this.jsonSchema); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/FormatProvider.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/FormatProvider.java new file mode 100644 index 0000000..8d0cb76 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/FormatProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.parser; + +/** + * Implementations of this interface provides instructions for how the output of a + * language generative should be formatted. + * + * @author Mark Pollack + */ +public interface FormatProvider { + + /** + * @return Returns a string containing instructions for how the output of a language + * generative should be formatted. + */ + String getFormat(); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/ListOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/ListOutputParser.java new file mode 100644 index 0000000..d112257 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/ListOutputParser.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.parser; + +import org.springframework.core.convert.support.DefaultConversionService; + +import java.util.List; + +/** + * {@link OutputParser} implementation that uses a {@link DefaultConversionService} to + * convert the LLM output into a {@link List} instance. + * + * @author Mark Pollack + * @author Christian Tzolov + */ +public class ListOutputParser extends AbstractConversionServiceOutputParser> { + + public ListOutputParser(DefaultConversionService defaultConversionService) { + super(defaultConversionService); + } + + @Override + public String getFormat() { +// return """ +// Your response should be a list of comma separated values +// eg: `foo, bar, baz` +// """; + return "Your response should be a list of comma separated values\n" + + "\t\t\t\teg: `foo, bar, baz`"; + } + + @Override + public List parse(String text) { + return getConversionService().convert(text, List.class); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/MapOutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/MapOutputParser.java new file mode 100644 index 0000000..90f017f --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/MapOutputParser.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.parser; + +import org.springframework.messaging.Message; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.support.MessageBuilder; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * {@link OutputParser} implementation that uses a pre-configured + * {@link MappingJackson2MessageConverter} to convert the LLM output into a + * java.util.Map<String, Object> instance. + * + * @author Mark Pollack + * @author Christian Tzolov + */ +public class MapOutputParser extends AbstractMessageConverterOutputParser> { + + public MapOutputParser() { + super(new MappingJackson2MessageConverter()); + } + + @Override + public Map parse(String text) { + Message message = MessageBuilder.withPayload(text.getBytes(StandardCharsets.UTF_8)).build(); + return (Map) getMessageConverter().fromMessage(message, HashMap.class); + } + + @Override + public String getFormat() { +// String raw = """ +// Your response should be in JSON format. +// The data structure for the JSON should match this Java class: %s +// Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. +// """; + String raw = "Your response should be in JSON format.\n" + + "\t\t\t\tThe data structure for the JSON should match this Java class: %s\n" + + "\t\t\t\tDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation."; + return String.format(raw, "java.util.HashMap"); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/OutputParser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/OutputParser.java new file mode 100644 index 0000000..cbc7e6e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/OutputParser.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.parser; + +/** + * Converts the (raw) LLM output into a structured responses of type. The + * {@link FormatProvider#getFormat()} method should provide the LLM prompt description of + * the desired format. + * + * @param Specifies the desired response type. + * @author Mark Pollack + * @author Christian Tzolov + */ +public interface OutputParser extends Parser, FormatProvider { + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/Parser.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/Parser.java new file mode 100644 index 0000000..559473f --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/Parser.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.parser; + +@FunctionalInterface +public interface Parser { + + T parse(String text); + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/README.md b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/README.md new file mode 100644 index 0000000..740e330 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/parser/README.md @@ -0,0 +1,12 @@ +# Output Parsing + +* [Documentation](https://docs.spring.io/spring-ai/reference/concepts.html#_output_parsing) +* [Usage examples](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIT.java) + +The output of AI models traditionally arrives as a java.util.String, even if you ask for the reply to be in JSON. It may be the correct JSON, but it isn’t a JSON data structure. It is just a string. Also, asking "for JSON" as part of the prompt isn’t 100% accurate. + +This intricacy has led to the emergence of a specialized field involving the creation of prompts to yield the intended output, followed by parsing the resulting simple string into a usable data structure for application integration. + +Output parsing employs meticulously crafted prompts, often necessitating multiple interactions with the model to achieve the desired formatting. + +This challenge has prompted OpenAI to introduce 'OpenAI Functions' as a means to specify the desired output format from the model precisely. diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java index 16ddfa4..0882f72 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java @@ -1,12 +1,13 @@ package cn.iocoder.yudao.framework.ai.chat; -import cn.iocoder.yudao.framework.ai.chat.messages.SystemMessage; -import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatModal; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenOptions; -import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; +import org.springframework.ai.chat.ChatResponse; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.models.tongyi.QianWenChatClient; +import org.springframework.ai.models.tongyi.QianWenChatModal; +import org.springframework.ai.models.tongyi.QianWenOptions; +import org.springframework.ai.models.tongyi.api.QianWenApi; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.aigc.generation.models.QwenParam; import com.alibaba.dashscope.common.Message; @@ -46,7 +47,7 @@ public class QianWenChatClientTests { @Test public void callTest() { - List messages = new ArrayList<>(); + List messages = new ArrayList<>(); messages.add(new SystemMessage("你是一个优质的小红书文艺作者,抒写着各城市的美好文化和风景。")); messages.add(new UserMessage("长沙怎么样?")); @@ -56,7 +57,7 @@ public class QianWenChatClientTests { @Test public void streamTest() { - List messages = new ArrayList<>(); + List messages = new ArrayList<>(); messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); messages.add(new UserMessage("长沙怎么样?")); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java index 674e4dc..77cf9ef 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java @@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.ai.chat; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONUtil; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest; +import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletion; +import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletionRequest; import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; import org.springframework.web.reactive.socket.client.WebSocketClient; import reactor.core.publisher.Flux; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java index ee32b67..4479390 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java @@ -1,13 +1,14 @@ package cn.iocoder.yudao.framework.ai.chat; -import cn.iocoder.yudao.framework.ai.chat.messages.Message; -import cn.iocoder.yudao.framework.ai.chat.messages.SystemMessage; -import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatClient; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatModel; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoOptions; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoApi; +import org.springframework.ai.chat.ChatResponse; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.models.xinghuo.XingHuoChatClient; +import org.springframework.ai.models.xinghuo.XingHuoChatModel; +import org.springframework.ai.models.xinghuo.XingHuoOptions; +import org.springframework.ai.models.xinghuo.api.XingHuoApi; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java index 3f29032..f45f964 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java @@ -2,9 +2,9 @@ package cn.iocoder.yudao.framework.ai.chat; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONUtil; -import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatClient; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest; +import org.springframework.ai.models.xinghuo.XingHuoChatClient; +import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletion; +import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletionRequest; import okhttp3.*; import org.jetbrains.annotations.NotNull; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java index df98541..f54ae39 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java @@ -1,13 +1,14 @@ package cn.iocoder.yudao.framework.ai.chat; -import cn.iocoder.yudao.framework.ai.chat.messages.Message; -import cn.iocoder.yudao.framework.ai.chat.messages.SystemMessage; -import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage; -import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatClient; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatModel; -import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanOptions; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanApi; +import org.springframework.ai.chat.ChatResponse; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.models.yiyan.YiYanChatClient; +import org.springframework.ai.models.yiyan.YiYanChatModel; +import org.springframework.ai.models.yiyan.YiYanOptions; +import org.springframework.ai.models.yiyan.api.YiYanApi; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java index fda7818..d93ac0b 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java @@ -2,11 +2,12 @@ package cn.iocoder.yudao.framework.ai.midjourney; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.framework.ai.midjourney.api.MidjourneyInteractionsApi; -import cn.iocoder.yudao.framework.ai.midjourney.api.req.AttachmentsReq; -import cn.iocoder.yudao.framework.ai.midjourney.api.req.DescribeReq; -import cn.iocoder.yudao.framework.ai.midjourney.api.req.ReRollReq; -import cn.iocoder.yudao.framework.ai.midjourney.api.res.UploadAttachmentsRes; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi; +import org.springframework.ai.models.midjourney.api.req.AttachmentsReq; +import org.springframework.ai.models.midjourney.api.req.DescribeReq; +import org.springframework.ai.models.midjourney.api.req.ReRollReq; +import org.springframework.ai.models.midjourney.api.res.UploadAttachmentsRes; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import org.junit.Before; import org.junit.Test; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java index 39d6493..b5604b8 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.framework.ai.midjourney; -import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; +import org.springframework.ai.models.midjourney.util.MidjourneyUtil; import org.junit.Test; /** diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java index cf90b17..ff03956 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java @@ -1,9 +1,10 @@ package cn.iocoder.yudao.framework.ai.midjourney; import cn.hutool.core.io.FileUtil; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.WssNotify; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MidjourneyMessageListener; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyWebSocketStarter; +import org.springframework.ai.models.midjourney.MidjourneyConfig; +import org.springframework.ai.models.midjourney.webSocket.WssNotify; +import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener; +import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter; import org.junit.Before; import org.junit.Test; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/openAiImage/OpenAiImageClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/openAiImage/OpenAiImageClientTests.java index b85ec88..ba439c6 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/openAiImage/OpenAiImageClientTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/openAiImage/OpenAiImageClientTests.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.framework.ai.openAiImage; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageApi; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageClient; -import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions; -import cn.iocoder.yudao.framework.ai.image.ImagePrompt; -import cn.iocoder.yudao.framework.ai.image.ImageResponse; +import org.springframework.ai.models.openai.OpenAiImageApi; +import org.springframework.ai.models.openai.OpenAiImageClient; +import org.springframework.ai.models.openai.OpenAiImageOptions; +import org.springframework.ai.image.ImagePrompt; +import org.springframework.ai.image.ImageResponse; import org.junit.Before; import org.junit.Test; -- libgit2 0.21.4