| | |
| | | package cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo; |
| | | |
| | | import lombok.RequiredArgsConstructor; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.ai.chat.model.ChatModel; |
| | | import org.springframework.ai.chat.model.ChatResponse; |
| | | import org.springframework.ai.chat.prompt.ChatOptions; |
| | | import org.springframework.ai.chat.prompt.Prompt; |
| | | import org.springframework.ai.deepseek.DeepSeekChatModel; |
| | | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| | | import org.springframework.ai.deepseek.api.DeepSeekApi; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | /** |
| | |
| | | * @author fansili |
| | | */ |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class XingHuoChatModel implements ChatModel { |
| | | |
| | | public static final String BASE_URL_V1 = "https://spark-api-open.xf-yun.com"; |
| | | |
| | | public static final String BASE_URL_V2 = "https://spark-api-open.xf-yun.com"; |
| | | public static final String BASE_COMPLETIONS_PATH_V2 = "/v2/chat/completions"; |
| | | /** |
| | | * 星火 X2 |
| | | * |
| | | * @see <a href="https://spark-api-open.xf-yun.com/x2/chat/completions">接口地址</a> |
| | | */ |
| | | public static final String MODEL_X2 = "x2"; |
| | | |
| | | /** |
| | | * 已知模型名列表:x1、4.0Ultra、generalv3.5、max-32k、generalv3、pro-128k、lite |
| | | * 星火 X2 Flash |
| | | * |
| | | * @see <a href="https://spark-api-open.xf-yun.com/agent/v1/chat/completions">接口地址</a> |
| | | */ |
| | | public static final String MODEL_DEFAULT = "4.0Ultra"; |
| | | public static final String MODEL_X2_FLASH = "x2-flash"; |
| | | |
| | | private static final String BASE_URL_X2 = "https://spark-api-open.xf-yun.com/x2"; |
| | | |
| | | private static final String BASE_URL_X2_FLASH = "https://spark-api-open.xf-yun.com/agent/v1"; |
| | | |
| | | public static final String MODEL_DEFAULT = MODEL_X2_FLASH; |
| | | |
| | | private static String getBaseUrl(String model) { |
| | | if (MODEL_X2_FLASH.equals(model)) { |
| | | return BASE_URL_X2_FLASH; |
| | | } |
| | | return BASE_URL_X2; |
| | | } |
| | | |
| | | private final String apiKey; |
| | | |
| | | private final DeepSeekChatOptions options; |
| | | |
| | | /** |
| | | * v1 兼容 OpenAI 接口,进行复用 |
| | | * 兼容 OpenAI 接口,进行复用 |
| | | */ |
| | | private final ChatModel openAiChatModelV1; |
| | | private final ChatModel chatModelX2; |
| | | |
| | | private final ChatModel chatModelX2Flash; |
| | | |
| | | private XingHuoChatModel(String apiKey, DeepSeekChatOptions options) { |
| | | this.apiKey = apiKey; |
| | | this.options = options; |
| | | this.chatModelX2 = buildChatModel(MODEL_X2); |
| | | this.chatModelX2Flash = buildChatModel(MODEL_X2_FLASH); |
| | | } |
| | | |
| | | public static Builder builder() { |
| | | return new Builder(); |
| | | } |
| | | |
| | | @Override |
| | | public ChatResponse call(Prompt prompt) { |
| | | return openAiChatModelV1.call(prompt); |
| | | return getChatModel(prompt).call(buildApiPrompt(prompt)); |
| | | } |
| | | |
| | | @Override |
| | | public Flux<ChatResponse> stream(Prompt prompt) { |
| | | return openAiChatModelV1.stream(prompt); |
| | | return getChatModel(prompt).stream(buildApiPrompt(prompt)); |
| | | } |
| | | |
| | | @Override |
| | | public ChatOptions getOptions() { |
| | | return options; |
| | | } |
| | | |
| | | @Override |
| | | @Deprecated(forRemoval = true) |
| | | @SuppressWarnings("removal") |
| | | public ChatOptions getDefaultOptions() { |
| | | return openAiChatModelV1.getDefaultOptions(); |
| | | return getOptions(); |
| | | } |
| | | |
| | | private ChatModel getChatModel(Prompt prompt) { |
| | | String model = options.getModel(); |
| | | ChatOptions options = prompt.getOptions(); |
| | | if (options != null && isBusinessModel(options.getModel())) { |
| | | model = options.getModel(); |
| | | } |
| | | return getChatModel(model); |
| | | } |
| | | |
| | | private ChatModel getChatModel(String model) { |
| | | if (MODEL_X2_FLASH.equals(model)) { |
| | | return chatModelX2Flash; |
| | | } |
| | | return chatModelX2; |
| | | } |
| | | |
| | | private ChatModel buildChatModel(String model) { |
| | | return DeepSeekChatModel.builder() |
| | | .deepSeekApi(DeepSeekApi.builder() |
| | | .baseUrl(getBaseUrl(model)) |
| | | .apiKey(apiKey) |
| | | .build()) |
| | | .options(options.mutate().model("spark-x").build()) |
| | | .build(); |
| | | } |
| | | |
| | | private static Prompt buildApiPrompt(Prompt prompt) { |
| | | ChatOptions options = prompt.getOptions(); |
| | | if (options == null) { |
| | | return prompt; |
| | | } |
| | | return Prompt.builder() |
| | | .messages(prompt.getInstructions()) |
| | | .chatOptions(options.mutate().model("spark-x").build()) |
| | | .build(); |
| | | } |
| | | |
| | | private static boolean isBusinessModel(String model) { |
| | | return MODEL_X2.equals(model) || MODEL_X2_FLASH.equals(model); |
| | | } |
| | | |
| | | public static final class Builder { |
| | | |
| | | private String apiKey; |
| | | |
| | | private DeepSeekChatOptions options; |
| | | |
| | | public Builder apiKey(String apiKey) { |
| | | this.apiKey = apiKey; |
| | | return this; |
| | | } |
| | | |
| | | public Builder options(DeepSeekChatOptions options) { |
| | | this.options = options; |
| | | return this; |
| | | } |
| | | |
| | | public XingHuoChatModel build() { |
| | | DeepSeekChatOptions options = this.options != null ? this.options : DeepSeekChatOptions.builder().build(); |
| | | if (StrUtil.isEmpty(options.getModel())) { |
| | | options = options.mutate().model(MODEL_DEFAULT).build(); |
| | | } |
| | | return new XingHuoChatModel(apiKey, options); |
| | | } |
| | | |
| | | } |
| | | |
| | | } |