mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-11-14 23:10:41 +00:00
fix: support Atlas multimodal generation and media preview
This commit is contained in:
@@ -16,6 +16,12 @@ public final class ChatModelCredentialPolicy {
|
||||
public static final String DEEPSEEK_API_HOST = "https://api.deepseek.com";
|
||||
public static final String PPIO_PROVIDER = "ppio";
|
||||
public static final String PPIO_API_HOST = "https://api.ppio.com/openai/v1";
|
||||
public static final String ATLAS_PROVIDER = "atlas";
|
||||
private static final Set<String> ATLAS_API_HOSTS = Set.of(
|
||||
"https://api.atlascloud.ai", "https://api.atlascloud.ai/",
|
||||
"https://api.atlascloud.ai/v1", "https://api.atlascloud.ai/v1/",
|
||||
"https://api.atlascloud.ai/api/v1", "https://api.atlascloud.ai/api/v1/"
|
||||
);
|
||||
|
||||
private static final Set<String> PPIO_API_HOSTS = Set.of(
|
||||
PPIO_API_HOST, PPIO_API_HOST + "/",
|
||||
@@ -69,6 +75,10 @@ public final class ChatModelCredentialPolicy {
|
||||
String apiHost,
|
||||
String apiKey) {
|
||||
requireSecureApiHost(apiHost);
|
||||
if (ATLAS_PROVIDER.equals(providerCode)) {
|
||||
requireAtlasConfiguration(providerCode, modelName, apiHost, apiKey);
|
||||
return;
|
||||
}
|
||||
if (CustomApiCredentialPolicy.isCustomProvider(providerCode)) {
|
||||
CustomApiCredentialPolicy.requireConfiguration(providerCode, modelName, apiHost, apiKey);
|
||||
return;
|
||||
@@ -121,6 +131,8 @@ public final class ChatModelCredentialPolicy {
|
||||
expectedReference = "env:DEEPSEEK_API_KEY";
|
||||
} else if (PPIO_PROVIDER.equals(providerCode)) {
|
||||
expectedReference = "env:PPIO_API_KEY";
|
||||
} else if (ATLAS_PROVIDER.equals(providerCode)) {
|
||||
expectedReference = "env:ATLAS_API_KEY";
|
||||
} else {
|
||||
throw new IllegalArgumentException("Model credential provider is not allowlisted");
|
||||
}
|
||||
@@ -137,11 +149,29 @@ public final class ChatModelCredentialPolicy {
|
||||
CustomApiCredentialPolicy.requireConfiguration(providerCode, modelName, apiHost, apiKey);
|
||||
} else if (PPIO_PROVIDER.equals(providerCode)) {
|
||||
requirePpioConfiguration(providerCode, modelName, apiHost, apiKey);
|
||||
} else if (ATLAS_PROVIDER.equals(providerCode)) {
|
||||
requireAtlasConfiguration(providerCode, modelName, apiHost, apiKey);
|
||||
} else {
|
||||
requireDeepSeekConfiguration(providerCode, modelName, apiHost, apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
/** Atlas credentials may only be sent to its official media API origin. */
|
||||
public static void requireAtlasConfiguration(String providerCode, String modelName,
|
||||
String apiHost, String apiKey) {
|
||||
if (!ATLAS_PROVIDER.equals(providerCode)) {
|
||||
throw new IllegalArgumentException("Atlas model provider is not trusted");
|
||||
}
|
||||
if (modelName == null || !modelName.matches("[A-Za-z0-9][A-Za-z0-9._/-]{0,254}")) {
|
||||
throw new IllegalArgumentException("Atlas model ID is invalid");
|
||||
}
|
||||
requireSecureApiHost(apiHost);
|
||||
if (!ATLAS_API_HOSTS.contains(apiHost)) {
|
||||
throw new IllegalArgumentException("Atlas API host is not allowlisted");
|
||||
}
|
||||
requireProviderReference(providerCode, apiKey);
|
||||
}
|
||||
|
||||
/** Validates PPIO and returns the base URL expected by the OpenAI Chat Completions client. */
|
||||
public static String requirePpioConfiguration(String providerCode, String modelName,
|
||||
String apiHost, String apiKey) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.regex.Pattern;
|
||||
public final class ChatModelSecretReference {
|
||||
|
||||
public static final String ENV_REFERENCE_REGEXP =
|
||||
"^env:(?:(?:DEEPSEEK|PPIO)_API_KEY|" + CustomApiCredentialPolicy.REFERENCE_PATTERN + ")$";
|
||||
"^env:(?:(?:DEEPSEEK|PPIO|ATLAS)_API_KEY|" + CustomApiCredentialPolicy.REFERENCE_PATTERN + ")$";
|
||||
|
||||
private static final String ENV_PREFIX = "env:";
|
||||
private static final Pattern ENV_REFERENCE = Pattern.compile(ENV_REFERENCE_REGEXP);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.ruoyi.common.chat.security;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Tag("dev")
|
||||
class AtlasCredentialPolicyTest {
|
||||
private static final String MODEL = "bytedance/seed-audio-1.0";
|
||||
private static final String HOST = "https://api.atlascloud.ai/v1";
|
||||
private static final String REF = "env:ATLAS_API_KEY";
|
||||
|
||||
@Test
|
||||
void acceptsOfficialEndpointFormsAtSaveAndUse() {
|
||||
for (String suffix : new String[]{"", "/", "/v1", "/v1/", "/api/v1", "/api/v1/"}) {
|
||||
String host = "https://api.atlascloud.ai" + suffix;
|
||||
assertDoesNotThrow(() -> ChatModelCredentialPolicy.requirePersistableConfiguration("atlas", MODEL, host, REF));
|
||||
assertEquals("test-secret", ChatModelCredentialPolicy.resolveApiKeyForUse("atlas", "atlas", MODEL, host, REF,
|
||||
name -> "ATLAS_API_KEY".equals(name) ? "test-secret" : null));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUntrustedEndpointsBeforeReadingEnvironment() {
|
||||
for (String host : new String[]{"http://api.atlascloud.ai/v1", "https://evil.example/v1",
|
||||
"https://api.atlascloud.ai.evil.example/v1", "https://api.atlascloud.ai:8443/v1",
|
||||
"https://user@api.atlascloud.ai/v1", "https://api.atlascloud.ai/v1?x=1", "https://api.atlascloud.ai/other"}) {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ChatModelCredentialPolicy.requirePersistableConfiguration("atlas", MODEL, host, REF));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ChatModelCredentialPolicy.resolveApiKeyForUse("atlas", "atlas", MODEL, host, REF,
|
||||
name -> { fail("Must validate endpoint before resolving secret"); return null; }));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsWrongProviderReferenceAndMissingSecret() {
|
||||
assertThrows(IllegalArgumentException.class, () -> ChatModelCredentialPolicy.resolveApiKeyForUse(
|
||||
"openai", "atlas", MODEL, HOST, REF, name -> "secret"));
|
||||
for (String key : new String[]{"env:PPIO_API_KEY", "plaintext", "", null}) {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ChatModelCredentialPolicy.requirePersistableConfiguration("atlas", MODEL, HOST, key));
|
||||
}
|
||||
assertThrows(IllegalStateException.class, () -> ChatModelCredentialPolicy.resolveApiKeyForUse(
|
||||
"atlas", "atlas", MODEL, HOST, REF, name -> null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ChatModelCredentialPolicy.requirePersistableConfiguration("atlas", "bad model", HOST, REF));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user