一键导入
add-ai-provider
Procedure for adding a new AI provider to emp-script-ai (Java library wrapping multiple LLM providers via OpenAI-compatible interfaces)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Procedure for adding a new AI provider to emp-script-ai (Java library wrapping multiple LLM providers via OpenAI-compatible interfaces)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
通过 Maven profiles 构建同时支持 javax.servlet-api 和 jakarta.servlet-api 的双版本 JAR,包含构建目录切换、finalName 自定义、依赖 jar 复制重命名
Architecture and implementation patterns for the AI API proxy/gateway module (switchproxy) that converts between OpenAI/Anthropic/Responses API formats, with IP access control, key-based auth, multi-host listening, HTML status page, Web admin UI, and tool config export
emp-script-ai 及其下游项目(travelagent、pf2023)所用 AI Mode XML schema 的撰写参考。覆盖 `<mode>` / `<step>` / `<prompt>` / `<apis>` / `<sqls>` / `<actions>` / `<paramChecks>` / `<ui>` 各块结构、prompt 数据源(sqlRef / api / action / CDATA)、函数调用 prompt(`apisCheck`)、URL / 请求占位符、输出自定义标签(`<day>`、`<rq>`、`<cid>`、`<enj>`、`<id>`、`<num>`、`<sersday>`、`<prices>`、`<gn>`)以及 step 控制属性(`innerCall`、`validateParams`、`multiOnlyUserMsg`、`action`、`actionSqlRef`)。
Debugging SSE streaming failures after refactoring AI provider code, focusing on JSON null-safety and field ordering
Setting up integrationTest framework with HSQLDB in-memory database and real AI APIs for emp-script-ai
Compare two SQL Server databases and sync missing tables and columns from source to target using sqlcmd and a Python script.
| name | add-ai-provider |
| description | Procedure for adding a new AI provider to emp-script-ai (Java library wrapping multiple LLM providers via OpenAI-compatible interfaces) |
| source | auto-skill |
| extracted_at | 2026-06-05T01:43:21.257Z |
emp-script-ai is a Java 17 Maven project that wraps multiple LLM providers (Qwen, OpenAI, Gemini, Doubao, Grok, Tencent) behind unified IRequestAI and IRequestData interfaces. Most providers use OpenAI-compatible API endpoints.
ProviderType enumEdit src/main/java/com/gdxsoft/ai/request/ProviderType.java:
public enum ProviderType {
// ... existing entries ...
DEEPSEEK("deepseek"); // lowercase name used as identifier
mkdir -p src/main/java/com/gdxsoft/ai/providers/<provider_name>/
RequestData.javapackage com.gdxsoft.ai.providers.<provider_name>;
import org.json.JSONObject;
import com.gdxsoft.ai.request.ProviderType;
import com.gdxsoft.ai.request.RequestDataBase;
public class RequestData extends RequestDataBase {
public static String DEFAULT_MODEL_NAME = "<default-model-name>";
public RequestData() {
super(DEFAULT_MODEL_NAME);
this.providerType = ProviderType.<PROVIDER_NAME>;
}
@Override
public JSONObject build() {
JSONObject requestData = new JSONObject(parameters.toString());
requestData.put("model", this.model);
requestData.put("messages", messages);
return requestData;
}
}
parameters (from base) holds stream, temperature, top_p, etc.messages (from base) is the JSONArray of conversation messages.thinking(), stream(), or responseFormat().RequestAI.javapackage com.gdxsoft.ai.providers.<provider_name>;
import com.gdxsoft.ai.request.ProviderType;
import com.gdxsoft.ai.request.RequestAIBase;
public class RequestAI extends RequestAIBase {
public static final String DEFAULT_URL = "https://<api-domain>/v1/chat/completions";
public RequestAI() {
this.providerType = ProviderType.<PROVIDER_NAME>;
}
}
RequestAIBase already handles HTTP POST, SSE streaming, JSON extraction, curl generation, and token usage tracking. No overrides needed.doStream(), extraceJson(), curl(), and createUrl() as needed. See com.gdxsoft.ai.providers.gemini.RequestAI for reference.RequestAIFactoryEdit src/main/java/com/gdxsoft/ai/request/RequestAIFactory.java:
case DEEPSEEK:
return new com.gdxsoft.ai.providers.deepseek.RequestAI();
RequestDataFactoryEdit src/main/java/com/gdxsoft/ai/request/RequestDataFactory.java:
In createRequestData(ProviderType type):
case DEEPSEEK:
return new com.gdxsoft.ai.providers.deepseek.RequestData();
In inferProviderFromModel(String modelName):
// <ProviderName> model detection
if (lowerModelName.contains("<provider-keyword>")) {
return ProviderType.<PROVIDER_NAME>;
}
mvn -DskipTests compile
Ensure BUILD SUCCESS and no new warnings/errors.
| Interface | Purpose |
|---|---|
IRequestAI | HTTP request execution (POST/Stream), JSON extraction, curl generation |
IRequestData | Request body construction (model, messages, parameters) |
RequestAIBase | Base class with full HTTP/SSE handling for OpenAI-compatible APIs |
RequestDataBase | Base class with message management and parameter building |
RequestAIBase.createHttpRequest() handles Authorization: Bearer for all providers except Gemini (x-goog-api-key) and Anthropic (x-api-key + anthropic-version). If your provider uses a different header scheme, add a case in both createHttpRequest() and curl() in RequestAIBase.extraceJson() expects choices[0].delta.content (streaming) or choices[0].message.content (non-streaming). If the provider returns a different structure, override extraceJson() in RequestAI.inferProviderFromModel() method uses substring matching on model names. Choose keywords that won't cause false positives. Prefix-based matching (e.g., openai*, anthropic* for compat modes) should be placed at the end of the method as a fallback, after specific provider keywords, so models like anthropic/claude-sonnet-4 match ANTHROPIC (contains "claude") before falling back to ANTHROPIC_COMPAT (starts with "anthropic").x-api-key, anthropic-version) — add handling in RequestAIBase.createHttpRequest() and curl()system as a top-level field, mandatory max_tokens) — override systemMessage(), maxTokens(), thinking(), and build() in RequestDatacontent_block_delta, message_delta) — override doStream() and extraceJson() in RequestAIcom.gdxsoft.ai.providers.anthropic.* for a complete examplethinking parameter format: Not all providers accept thinking: boolean.
{"thinking": {"type": "enabled"}} when enabled and the field removed when disabled.{"reasoning": {"enabled": true}} (different field name) — see providers/openrouter/RequestData.java.thinking() in RequestData if the provider has a non-standard format.RequestAIBase does NOT auto-fallback to DEFAULT_URL when initUrlAndKey(null, apiKey) is called with a null URL — it will throw NPE. Always pass RequestAI.DEFAULT_URL as the first argument, or ensure the provider's RequestAI overrides createUrl() to return DEFAULT_URL when apiUrl is null.System.getenv("PROVIDER_API_KEY") so keys come from the environment.For users who need to connect to arbitrary custom endpoints that follow OpenAI or Anthropic formats, two generic providers exist:
openai_compat — Generic OpenAI-compatible endpointproviders/openai_compat/RequestData.java — OpenAI format (messages array with system role), no default model/URLproviders/openai_compat/RequestAI.java — Bearer Token auth, OpenAI SSE parsinginitUrlAndKey("http://your-server/v1/chat/completions", apiKey) — URL is requiredanthropic_compat — Generic Anthropic-compatible endpointproviders/anthropic_compat/RequestData.java — Anthropic format (system as top-level field, required max_tokens)providers/anthropic_compat/RequestAI.java — x-api-key + anthropic-version auth, Anthropic SSE parsinginitUrlAndKey("http://your-server/v1/messages", apiKey) — URL is requiredBoth are already registered in factories and ProviderType. No additional registration needed.
If you need to rename a provider (e.g., openroute → openrouter):
ProviderType enum: Change both the enum constant name AND the string value (e.g., OPENROUTE("openroute") → OPENROUTER("openrouter")).providers/<new_name>/ with RequestData.java and RequestAI.java, updating package declarations, ProviderType references, and DEFAULT_URL if needed.RequestAIFactory and RequestDataFactory switch cases to use the new enum constant and import path.rm -rf src/main/java/com/gdxsoft/ai/providers/<old_name>/.mvn -DskipTests compile to verify.