{"repo": "9ao2hen/luca-pro", "files": {"luca-module-ai/luca-spring-boot-starter-ai/src/main/java/cn/mervyn/luca/framework/ai/core/model/deepseek/DeepSeekChatModel.java": {"c757bedb257b57d1a1f11c48a1904c149314f180": {"t": 1785746122.0487106, "n": 7441, "h": "285ece47127b4f0c", "c": "package cn.mervyn.luca.framework.ai.core.model.deepseek;\n\nimport cn.hutool.core.lang.Assert;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ai.chat.metadata.ChatGenerationMetadata;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.chat.model.ChatResponse;\nimport org.springframework.ai.chat.model.Generation;\nimport org.springframework.ai.chat.prompt.ChatOptions;\nimport org.springframework.ai.chat.prompt.Prompt;\nimport org.springframework.ai.model.ModelOptionsUtils;\nimport org.springframework.ai.openai.OpenAiChatOptions;\nimport org.springframework.ai.openai.api.OpenAiApi;\nimport org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata;\nimport org.springframework.ai.retry.RetryUtils;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.retry.support.RetryTemplate;\nimport reactor.core.publisher.Flux;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static cn.mervyn.luca.framework.ai.core.model.deepseek.DeepSeekChatOptions.MODEL_DEFAULT;\n\n/**\n * DeepSeek {@link ChatModel} 实现类\n *\n * @author fansili\n */\n@Slf4j\npublic class DeepSeekChatModel implements ChatModel {\n\n private static final String BASE_URL = \"https://api.deepseek.com\";\n\n private final DeepSeekChatOptions defaultOptions;\n private final RetryTemplate retryTemplate;\n\n /**\n * DeepSeek 兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本\n *\n * 不过要注意,DeepSeek 没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它\n */\n private final OpenAiApi openAiApi;\n\n public DeepSeekChatModel(String apiKey) {\n this(apiKey, DeepSeekChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build());\n }\n\n public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options) {\n this(apiKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE);\n }\n\n public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options, RetryTemplate retryTemplate) {\n Assert.notEmpty(apiKey, \"apiKey 不能为空\");\n Assert.notNull(options, \"options 不能为空\");\n Assert.notNull(retryTemplate, \"retryTemplate 不能为空\");\n this.openAiApi = new OpenAiApi(BASE_URL, apiKey);\n this.defaultOptions = options;\n this.retryTemplate = retryTemplate;\n }\n\n @Override\n public ChatResponse call(Prompt prompt) {\n OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false);\n return this.retryTemplate.execute(ctx -> {\n // 1.1 发起调用\n ResponseEntity completionEntity = openAiApi.chatCompletionEntity(request);\n // 1.2 校验结果\n OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody();\n if (chatCompletion == null) {\n log.warn(\"No chat completion returned for prompt: {}\", prompt);\n return new ChatResponse(List.of());\n }\n List choices = chatCompletion.choices();\n if (choices == null) {\n log.warn(\"No choices returned for prompt: {}\", prompt);\n return new ChatResponse(List.of());\n }\n\n // 2. 转换 ChatResponse 返回\n List generations = choices.stream().map(choice -> {\n Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice));\n if (choice.finishReason() != null) {\n generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null));\n }\n return generation;\n }).toList();\n return new ChatResponse(generations,\n OpenAiChatResponseMetadata.from(completionEntity.getBody()));\n });\n }\n\n private Map toMap(String id, OpenAiApi.ChatCompletion.Choice choice) {\n Map map = new HashMap<>();\n OpenAiApi.ChatCompletionMessage message = choice.message();\n if (message.role() != null) {\n map.put(\"role\", message.role().name());\n }\n if (choice.finishReason() != null) {\n map.put(\"finishReason\", choice.finishReason().name());\n }\n map.put(\"id\", id);\n return map;\n }\n\n @Override\n public Flux stream(Prompt prompt) {\n OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true);\n return this.retryTemplate.execute(ctx -> {\n // 1. 发起调用\n Flux response = this.openAiApi.chatCompletionStream(request);\n return response.map(chatCompletion -> {\n String id = chatCompletion.id();\n // 2. 转换 ChatResponse 返回\n List generations = chatCompletion.choices().stream().map(choice -> {\n String finish = (choice.finishReason() != null ? choice.finishReason().name() : \"\");\n String role = (choice.delta().role() != null ? choice.delta().role().name() : \"\");\n if (choice.finishReason() == OpenAiApi.ChatCompletionFinishReason.STOP) {\n // 兜底处理 DeepSeek 返回 STOP 时,role 为空的情况\n role = OpenAiApi.ChatCompletionMessage.Role.ASSISTANT.name();\n }\n Generation generation = new Generation(choice.delta().content(),\n Map.of(\"id\", id, \"role\", role, \"finishReason\", finish));\n if (choice.finishReason() != null) {\n generation = generation.withGenerationMetadata(\n ChatGenerationMetadata.from(choice.finishReason().name(), null));\n }\n return generation;\n }).toList();\n return new ChatResponse(generations);\n });\n });\n }\n\n OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {\n // 1. 构建 ChatCompletionMessage 对象\n List chatCompletionMessages = prompt.getInstructions().stream().map(m ->\n new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList();\n OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream);\n\n // 2.1 补充 prompt 内置的 options\n if (prompt.getOptions() != null) {\n if (prompt.getOptions() instanceof ChatOptions runtimeOptions) {\n OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions,\n ChatOptions.class, OpenAiChatOptions.class);\n request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class);\n } else {\n throw new IllegalArgumentException(\"Prompt options are not of type ChatOptions: \"\n + prompt.getOptions().getClass().getSimpleName());\n }\n }\n // 2.2 补充默认 options\n if (this.defaultOptions != null) {\n request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class);\n }\n return request;\n }\n\n @Override\n public ChatOptions getDefaultOptions() {\n return DeepSeekChatOptions.fromOptions(defaultOptions);\n }\n\n}\n"}, "7b4546b1e018dfec16618d144d663c3764f74cb2": {"t": 1785760008.7278576, "n": 7441, "h": "285ece47127b4f0c", "c": "package cn.mervyn.luca.framework.ai.core.model.deepseek;\n\nimport cn.hutool.core.lang.Assert;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ai.chat.metadata.ChatGenerationMetadata;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.chat.model.ChatResponse;\nimport org.springframework.ai.chat.model.Generation;\nimport org.springframework.ai.chat.prompt.ChatOptions;\nimport org.springframework.ai.chat.prompt.Prompt;\nimport org.springframework.ai.model.ModelOptionsUtils;\nimport org.springframework.ai.openai.OpenAiChatOptions;\nimport org.springframework.ai.openai.api.OpenAiApi;\nimport org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata;\nimport org.springframework.ai.retry.RetryUtils;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.retry.support.RetryTemplate;\nimport reactor.core.publisher.Flux;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static cn.mervyn.luca.framework.ai.core.model.deepseek.DeepSeekChatOptions.MODEL_DEFAULT;\n\n/**\n * DeepSeek {@link ChatModel} 实现类\n *\n * @author fansili\n */\n@Slf4j\npublic class DeepSeekChatModel implements ChatModel {\n\n private static final String BASE_URL = \"https://api.deepseek.com\";\n\n private final DeepSeekChatOptions defaultOptions;\n private final RetryTemplate retryTemplate;\n\n /**\n * DeepSeek 兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本\n *\n * 不过要注意,DeepSeek 没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它\n */\n private final OpenAiApi openAiApi;\n\n public DeepSeekChatModel(String apiKey) {\n this(apiKey, DeepSeekChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build());\n }\n\n public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options) {\n this(apiKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE);\n }\n\n public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options, RetryTemplate retryTemplate) {\n Assert.notEmpty(apiKey, \"apiKey 不能为空\");\n Assert.notNull(options, \"options 不能为空\");\n Assert.notNull(retryTemplate, \"retryTemplate 不能为空\");\n this.openAiApi = new OpenAiApi(BASE_URL, apiKey);\n this.defaultOptions = options;\n this.retryTemplate = retryTemplate;\n }\n\n @Override\n public ChatResponse call(Prompt prompt) {\n OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false);\n return this.retryTemplate.execute(ctx -> {\n // 1.1 发起调用\n ResponseEntity completionEntity = openAiApi.chatCompletionEntity(request);\n // 1.2 校验结果\n OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody();\n if (chatCompletion == null) {\n log.warn(\"No chat completion returned for prompt: {}\", prompt);\n return new ChatResponse(List.of());\n }\n List choices = chatCompletion.choices();\n if (choices == null) {\n log.warn(\"No choices returned for prompt: {}\", prompt);\n return new ChatResponse(List.of());\n }\n\n // 2. 转换 ChatResponse 返回\n List generations = choices.stream().map(choice -> {\n Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice));\n if (choice.finishReason() != null) {\n generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null));\n }\n return generation;\n }).toList();\n return new ChatResponse(generations,\n OpenAiChatResponseMetadata.from(completionEntity.getBody()));\n });\n }\n\n private Map toMap(String id, OpenAiApi.ChatCompletion.Choice choice) {\n Map map = new HashMap<>();\n OpenAiApi.ChatCompletionMessage message = choice.message();\n if (message.role() != null) {\n map.put(\"role\", message.role().name());\n }\n if (choice.finishReason() != null) {\n map.put(\"finishReason\", choice.finishReason().name());\n }\n map.put(\"id\", id);\n return map;\n }\n\n @Override\n public Flux stream(Prompt prompt) {\n OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true);\n return this.retryTemplate.execute(ctx -> {\n // 1. 发起调用\n Flux response = this.openAiApi.chatCompletionStream(request);\n return response.map(chatCompletion -> {\n String id = chatCompletion.id();\n // 2. 转换 ChatResponse 返回\n List generations = chatCompletion.choices().stream().map(choice -> {\n String finish = (choice.finishReason() != null ? choice.finishReason().name() : \"\");\n String role = (choice.delta().role() != null ? choice.delta().role().name() : \"\");\n if (choice.finishReason() == OpenAiApi.ChatCompletionFinishReason.STOP) {\n // 兜底处理 DeepSeek 返回 STOP 时,role 为空的情况\n role = OpenAiApi.ChatCompletionMessage.Role.ASSISTANT.name();\n }\n Generation generation = new Generation(choice.delta().content(),\n Map.of(\"id\", id, \"role\", role, \"finishReason\", finish));\n if (choice.finishReason() != null) {\n generation = generation.withGenerationMetadata(\n ChatGenerationMetadata.from(choice.finishReason().name(), null));\n }\n return generation;\n }).toList();\n return new ChatResponse(generations);\n });\n });\n }\n\n OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {\n // 1. 构建 ChatCompletionMessage 对象\n List chatCompletionMessages = prompt.getInstructions().stream().map(m ->\n new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList();\n OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream);\n\n // 2.1 补充 prompt 内置的 options\n if (prompt.getOptions() != null) {\n if (prompt.getOptions() instanceof ChatOptions runtimeOptions) {\n OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions,\n ChatOptions.class, OpenAiChatOptions.class);\n request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class);\n } else {\n throw new IllegalArgumentException(\"Prompt options are not of type ChatOptions: \"\n + prompt.getOptions().getClass().getSimpleName());\n }\n }\n // 2.2 补充默认 options\n if (this.defaultOptions != null) {\n request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class);\n }\n return request;\n }\n\n @Override\n public ChatOptions getDefaultOptions() {\n return DeepSeekChatOptions.fromOptions(defaultOptions);\n }\n\n}\n"}}, "luca-module-ai/luca-spring-boot-starter-ai/src/test/java/cn/mervyn/luca/framework/ai/chat/DeepSeekChatModelTests.java": {"c757bedb257b57d1a1f11c48a1904c149314f180": {"t": 1785747155.8321664, "n": 1583, "h": "1066bd866a795585", "c": "package cn.mervyn.luca.framework.ai.chat;\n\nimport cn.mervyn.luca.framework.ai.core.model.deepseek.DeepSeekChatModel;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.ai.chat.messages.Message;\nimport org.springframework.ai.chat.messages.SystemMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatResponse;\nimport org.springframework.ai.chat.prompt.Prompt;\nimport reactor.core.publisher.Flux;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * {@link DeepSeekChatModel} 集成测试\n *\n * @author 9ao2hen\n */\npublic class DeepSeekChatModelTests {\n\n private final DeepSeekChatModel chatModel = new DeepSeekChatModel(\"sk-e94db327cc7d457d99a8de8810fc6b12\");\n\n @Test\n @Disabled\n public void testCall() {\n // 准备参数\n List messages = new ArrayList<>();\n messages.add(new SystemMessage(\"你是一个优质的文言文作者,用文言文描述着各城市的人文风景。\"));\n messages.add(new UserMessage(\"1 + 1 = ?\"));\n\n // 调用\n ChatResponse response = chatModel.call(new Prompt(messages));\n // 打印结果\n System.out.println(response);\n }\n\n @Test\n @Disabled\n public void testStream() {\n // 准备参数\n List messages = new ArrayList<>();\n messages.add(new SystemMessage(\"你是一个优质的文言文作者,用文言文描述着各城市的人文风景。\"));\n messages.add(new UserMessage(\"1 + 1 = ?\"));\n\n // 调用\n Flux flux = chatModel.stream(new Prompt(messages));\n // 打印结果\n flux.doOnNext(System.out::println).then().block();\n }\n\n}\n"}, "70930cc4597d834048507eb285c7b7b7b0431e0c": {"t": 1785760009.3686285, "n": 1583, "h": "1066bd866a795585", "c": "package cn.mervyn.luca.framework.ai.chat;\n\nimport cn.mervyn.luca.framework.ai.core.model.deepseek.DeepSeekChatModel;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.ai.chat.messages.Message;\nimport org.springframework.ai.chat.messages.SystemMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatResponse;\nimport org.springframework.ai.chat.prompt.Prompt;\nimport reactor.core.publisher.Flux;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * {@link DeepSeekChatModel} 集成测试\n *\n * @author 9ao2hen\n */\npublic class DeepSeekChatModelTests {\n\n private final DeepSeekChatModel chatModel = new DeepSeekChatModel(\"sk-e94db327cc7d457d99a8de8810fc6b12\");\n\n @Test\n @Disabled\n public void testCall() {\n // 准备参数\n List messages = new ArrayList<>();\n messages.add(new SystemMessage(\"你是一个优质的文言文作者,用文言文描述着各城市的人文风景。\"));\n messages.add(new UserMessage(\"1 + 1 = ?\"));\n\n // 调用\n ChatResponse response = chatModel.call(new Prompt(messages));\n // 打印结果\n System.out.println(response);\n }\n\n @Test\n @Disabled\n public void testStream() {\n // 准备参数\n List messages = new ArrayList<>();\n messages.add(new SystemMessage(\"你是一个优质的文言文作者,用文言文描述着各城市的人文风景。\"));\n messages.add(new UserMessage(\"1 + 1 = ?\"));\n\n // 调用\n Flux flux = chatModel.stream(new Prompt(messages));\n // 打印结果\n flux.doOnNext(System.out::println).then().block();\n }\n\n}\n"}, "7b4546b1e018dfec16618d144d663c3764f74cb2": {"t": 1785760011.0089662, "n": 1580, "h": "83b20e6c7d84c841", "c": "package cn.mervyn.luca.framework.ai.chat;\n\nimport cn.mervyn.luca.framework.ai.core.model.deepseek.DeepSeekChatModel;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.ai.chat.messages.Message;\nimport org.springframework.ai.chat.messages.SystemMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatResponse;\nimport org.springframework.ai.chat.prompt.Prompt;\nimport reactor.core.publisher.Flux;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * {@link DeepSeekChatModel} 集成测试\n *\n * @author 芋道源码\n */\npublic class DeepSeekChatModelTests {\n\n private final DeepSeekChatModel chatModel = new DeepSeekChatModel(\"sk-e94db327cc7d457d99a8de8810fc6b12\");\n\n @Test\n @Disabled\n public void testCall() {\n // 准备参数\n List messages = new ArrayList<>();\n messages.add(new SystemMessage(\"你是一个优质的文言文作者,用文言文描述着各城市的人文风景。\"));\n messages.add(new UserMessage(\"1 + 1 = ?\"));\n\n // 调用\n ChatResponse response = chatModel.call(new Prompt(messages));\n // 打印结果\n System.out.println(response);\n }\n\n @Test\n @Disabled\n public void testStream() {\n // 准备参数\n List messages = new ArrayList<>();\n messages.add(new SystemMessage(\"你是一个优质的文言文作者,用文言文描述着各城市的人文风景。\"));\n messages.add(new UserMessage(\"1 + 1 = ?\"));\n\n // 调用\n Flux flux = chatModel.stream(new Prompt(messages));\n // 打印结果\n flux.doOnNext(System.out::println).then().block();\n }\n\n}\n"}}}}