This commit is contained in:
van
2026-04-05 20:46:59 +08:00
parent ab1231bd04
commit a23f35b512

View File

@@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method; import cn.hutool.http.Method;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -15,8 +17,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* 社媒文案/关键词所用大模型客户端:支持本地 Ollama 或与 OpenAI 兼容的 HTTP 接口(含远程 API、Ollama /v1 等) * 社媒文案/关键词所用大模型客户端:支持本地 Ollama 或与 OpenAI 兼容的 HTTP 接口。
* 配置存 Redis,与若依后台「大模型接入」一致;未配置时走 {@link OllamaClientUtil} 的 yml 默认。 * 多套配置存 RedisJSON通过 activeId 选择当前使用的一套;无激活或空存储时走 {@link OllamaClientUtil} 的 yml 默认。
*/ */
@Component @Component
public class SocialMediaLlmClient { public class SocialMediaLlmClient {
@@ -24,6 +26,7 @@ public class SocialMediaLlmClient {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SocialMediaLlmClient.class); private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SocialMediaLlmClient.class);
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String LLM_PROFILES_STORE_KEY = "social_media:llm:profiles_store";
private static final String KEY_MODE = "social_media:llm:mode"; private static final String KEY_MODE = "social_media:llm:mode";
private static final String KEY_BASE_URL = "social_media:llm:base_url"; private static final String KEY_BASE_URL = "social_media:llm:base_url";
private static final String KEY_API_KEY = "social_media:llm:api_key"; private static final String KEY_API_KEY = "social_media:llm:api_key";
@@ -43,28 +46,77 @@ public class SocialMediaLlmClient {
return ollamaClientUtil.getResponse(inputText); return ollamaClientUtil.getResponse(inputText);
} }
String mode = trimOrNull(redisTemplate.opsForValue().get(KEY_MODE)); ResolvedLlm cfg = resolveConfig();
String baseUrl = trimOrNull(redisTemplate.opsForValue().get(KEY_BASE_URL)); if (cfg == null) {
String apiKey = trimOrNull(redisTemplate.opsForValue().get(KEY_API_KEY));
String model = trimOrNull(redisTemplate.opsForValue().get(KEY_MODEL));
if (mode == null && baseUrl == null && model == null && apiKey == null) {
return ollamaClientUtil.getResponse(inputText); return ollamaClientUtil.getResponse(inputText);
} }
String effectiveMode = mode != null ? mode.toLowerCase() : MODE_OLLAMA; String effectiveMode = cfg.mode != null ? cfg.mode.toLowerCase() : MODE_OLLAMA;
if (MODE_OPENAI.equals(effectiveMode)) { if (MODE_OPENAI.equals(effectiveMode)) {
if (StrUtil.isBlank(baseUrl)) { if (StrUtil.isBlank(cfg.baseUrl)) {
throw new IOException("OpenAI 兼容模式需在后台配置完整的 Chat Completions 地址"); throw new IOException("OpenAI 兼容模式需在后台配置完整的 Chat Completions 地址");
} }
if (StrUtil.isBlank(model)) { if (StrUtil.isBlank(cfg.model)) {
throw new IOException("OpenAI 兼容模式需在后台配置模型名称"); throw new IOException("OpenAI 兼容模式需在后台配置模型名称");
} }
return callOpenAiCompatible(baseUrl, apiKey, model, inputText); return callOpenAiCompatible(cfg.baseUrl, cfg.apiKey, cfg.model, inputText);
} }
return ollamaClientUtil.getResponse(inputText, baseUrl, model); return ollamaClientUtil.getResponse(inputText, cfg.baseUrl, cfg.model);
}
private ResolvedLlm resolveConfig() {
try {
String raw = redisTemplate.opsForValue().get(LLM_PROFILES_STORE_KEY);
if (StrUtil.isNotBlank(raw)) {
JSONObject store = JSON.parseObject(raw);
if (store != null) {
String activeId = store.getString("activeId");
if (StrUtil.isBlank(activeId)) {
return null;
}
JSONObject profiles = store.getJSONObject("profiles");
if (profiles == null || profiles.isEmpty()) {
return null;
}
JSONObject p = profiles.getJSONObject(activeId);
if (p == null) {
log.warn("大模型 activeId={} 在 profiles 中不存在", activeId);
return null;
}
ResolvedLlm r = new ResolvedLlm();
r.mode = trimOrNull(p.getString("mode"));
if (r.mode == null) {
r.mode = MODE_OLLAMA;
}
r.baseUrl = trimOrNull(p.getString("baseUrl"));
r.model = trimOrNull(p.getString("model"));
r.apiKey = trimOrNull(p.getString("apiKey"));
return r;
}
}
} catch (Exception e) {
log.warn("解析多套 LLM 配置失败,尝试旧版单键: {}", e.getMessage());
}
return resolveLegacySingleKeys();
}
/** 兼容尚未迁移的旧版 Redis 四键 */
private ResolvedLlm resolveLegacySingleKeys() {
String mode = trimOrNull(redisTemplate.opsForValue().get(KEY_MODE));
String baseUrl = trimOrNull(redisTemplate.opsForValue().get(KEY_BASE_URL));
String model = trimOrNull(redisTemplate.opsForValue().get(KEY_MODEL));
String apiKey = trimOrNull(redisTemplate.opsForValue().get(KEY_API_KEY));
if (mode == null && baseUrl == null && model == null && apiKey == null) {
return null;
}
ResolvedLlm r = new ResolvedLlm();
r.mode = mode != null ? mode : MODE_OLLAMA;
r.baseUrl = baseUrl;
r.model = model;
r.apiKey = apiKey;
return r;
} }
private static String trimOrNull(String s) { private static String trimOrNull(String s) {
@@ -112,4 +164,11 @@ public class SocialMediaLlmClient {
} }
return choices.get(0).path("message").path("content").asText(); return choices.get(0).path("message").path("content").asText();
} }
private static final class ResolvedLlm {
String mode;
String baseUrl;
String model;
String apiKey;
}
} }