[feat] add support for OpenRouter AI provider (#3439)

Signed-off-by: Yang Chen <1597081640@qq.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: Jast <shenghang@apache.org>
This commit is contained in:
Yang Chen
2025-06-09 23:35:08 +08:00
committed by GitHub
co-authored by tomsun28 aias00 Jast
parent 8065b63057
commit 199466cd59
6 changed files with 152 additions and 3 deletions
@@ -142,12 +142,40 @@ public interface AiConstants {
float TEMPERATURE = 0.7f;
}
/**
* Ollama constants
*/
interface OllamaConstants {
/**
* request role param
*/
String REQUEST_ROLE = "user";
/**
* The model outputs the maximum tokens, with a maximum output of 8192 and a default value of 3072
*/
Integer MAX_TOKENS = 3072;
/**
* The sampling temperature, which controls the randomness of the output, must be positive
* The value ranges from 0.0 to 1.0, and cannot be equal to 0. The default value is 0.95. The larger the value,
* the more random and creative the output will be. The smaller the value, the more stable or certain the output will be
* You are advised to adjust top_p or temperature parameters based on application scenarios, but do not adjust the two parameters at the same time
*/
float TEMPERATURE = 0.7f;
}
/**
* OpenRouter constants
*/
interface OpenRouterConstants {
/**
* OpenRouter Ai URL
*/
String URL = "https://openrouter.ai/api/v1/chat/completions";
/**
* request role param
*/
@@ -47,8 +47,12 @@ public enum AiTypeEnum {
/**
* Ollama AI
*/
ollama;
ollama,
/**
* OpenRouter
*/
openRouter;
/**
* get type
@@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://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.apache.hertzbeat.manager.service.ai;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.AiConstants;
import org.apache.hertzbeat.common.constants.AiTypeEnum;
import org.apache.hertzbeat.manager.config.AiProperties;
import org.apache.hertzbeat.manager.pojo.dto.AiMessage;
import org.apache.hertzbeat.manager.pojo.dto.OpenAiRequestParamDTO;
import org.apache.hertzbeat.manager.pojo.dto.OpenAiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* OpenRouter service
*/
@Service("OpenRouterServiceImpl")
@ConditionalOnProperty(prefix = "ai", name = "type", havingValue = "openRouter")
@Slf4j
public class OpenRouterServiceImpl implements AiService {
@Autowired
private AiProperties aiProperties;
private WebClient webClient;
@PostConstruct
private void init() {
this.webClient = WebClient.builder()
.baseUrl(AiConstants.OpenRouterConstants.URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + aiProperties.getApiKey())
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(item -> item.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
.build())
.build();
}
@Override
public AiTypeEnum getType() {
return AiTypeEnum.openRouter;
}
@Override
public Flux<ServerSentEvent<String>> requestAi(String text) {
checkParam(text, aiProperties.getModel(), aiProperties.getApiKey());
OpenAiRequestParamDTO openRouterParam = OpenAiRequestParamDTO.builder()
.model(aiProperties.getModel())
.stream(Boolean.TRUE)
.maxTokens(AiConstants.OpenRouterConstants.MAX_TOKENS)
.temperature(AiConstants.OpenRouterConstants.TEMPERATURE)
.messages(List.of(new AiMessage(AiConstants.OpenRouterConstants.REQUEST_ROLE, text)))
.build();
return webClient.post()
.body(BodyInserters.fromValue(openRouterParam))
.retrieve()
.bodyToFlux(String.class)
.filter(aiResponse -> !"[DONE]".equals(aiResponse))
.map(OpenAiResponse::convertToResponse)
.doOnError(error -> log.info("OpenRouterAiServiceImpl.requestAi exception:{}", error.getMessage()));
}
private void checkParam(String param, String model, String apiKey) {
Assert.notNull(param, "text is null");
Assert.notNull(model, "model is null");
Assert.notNull(apiKey, "ai.api-key is null");
}
}
@@ -273,7 +273,7 @@ grafana:
# See the documentation for details : https://hertzbeat.apache.org/zh-cn/docs/help/aiConfig
ai:
# AI Type:zhiPu、alibabaAi、kimiAi、sparkDesk、ollama
# AI Type:zhiPu、alibabaAi、kimiAi、sparkDesk、ollama、openRouter
type:
# Model nameglm-4、qwen-turboo、moonshot-v1-8k、generalv3.5
model:
+10
View File
@@ -69,3 +69,13 @@ QuickStart: <https://github.com/ollama/ollama/tree/main/docs>
| type | ollama (must be exactly the same as example) | |
| model | deepseek-r1:latest、qwen3:latest、llama4:16x17b | <https://ollama.com/search> |
| api-url | <http://127.0.0.1:11434/v1/chat/completions> | |
#### OpenRouter
QuickStart: <https://openrouter.ai/docs/quickstart>
| Name of the parameter | Example | Link |
|-----------------------|------------------------------------------------|-----------------------------------------|
| type | openRouter (must be exactly the same as example) | |
| model | openai/gpt-4o, anthropic/claude-sonnet-4 | <https://openrouter.ai/models> |
| api-key | xxxxxxxxxxx | <https://openrouter.ai/settings/provisioning-keys> |
@@ -69,3 +69,13 @@ keywords: [人工智能 AI]
| type | ollama (必须和示例完全相同) | |
| model | deepseek-r1:latest、qwen3:latest、llama4:16x17b | <https://ollama.com/search> |
| api-url | <http://127.0.0.1:11434/v1/chat/completions> | |
#### OpenRouter
快速入门: <https://openrouter.ai/docs/quickstart>
| 参数名称 | 示例 | 链接 |
|---------|------------------------------------------|----------------------------------------------------|
| type | openRouter (必须和示例完全相同) | |
| model | openai/gpt-4o, anthropic/claude-sonnet-4 | <https://openrouter.ai/models> |
| api-key | xxxxxxxxxxx | <https://openrouter.ai/settings/provisioning-keys> |