A shared OpenAI-compatible API gateway often fails for very small reasons. One tool stores a Base URL with /v1, another appends /v1 by itself, and a Node.js service joins paths with a double slash. When Dify, Cursor, and application code all use Vector Engine, that small path difference can look like an authentication issue, a model permission issue, or a model_not_found incident.
This tutorial builds a small Base URL normalizer for Vector Engine. The goal is to keep the LLM API provider layer boring: one API Key source, one model name, one normalized endpoint, and a repeatable check before configuration is copied into Dify or Cursor.
The target convention
Pick one internal convention and write it down:
| Field | Example | Notes |
|---|---|---|
| Base URL | https://api.vectorengine.cn/v1 |
Store without a trailing slash |
| API Key | VECTOR_ENGINE_API_KEY |
Store only in secrets or environment variables |
| model name | gpt-4o-mini |
Keep the exact route name used by Vector Engine |
| client tools |
Dify, Cursor, Node.js
|
Copy the same values into each client |
Dify and Cursor may label the custom provider fields differently, but the values should describe the same provider route. A Node.js service should not create a different route by joining strings in a different way.
A small Node.js normalizer
Create vector-engine-url.js:
const DEFAULT_BASE_URL = "https://api.vectorengine.cn/v1";
function normalizeBaseUrl(input) {
if (!input) throw new Error("Base URL is required");
const url = new URL(input.trim());
url.pathname = url.pathname.replace(/\/+$/, "");
if (!url.pathname.endsWith("/v1")) {
url.pathname = `${url.pathname}/v1`.replace(/\/+/g, "/");
}
return url.toString().replace(/\/$/, "");
}
function joinEndpoint(baseUrl, endpoint) {
return `${normalizeBaseUrl(baseUrl)}/${endpoint.replace(/^\/+/, "")}`;
}
const baseUrl = normalizeBaseUrl(process.env.VECTOR_ENGINE_BASE_URL || DEFAULT_BASE_URL);
const chatUrl = joinEndpoint(baseUrl, "/chat/completions");
console.log({
provider: "Vector Engine",
baseUrl,
chatUrl,
model: process.env.VECTOR_ENGINE_MODEL || "gpt-4o-mini",
apiKeyPresent: Boolean(process.env.VECTOR_ENGINE_API_KEY)
});
Run it with different local values:
VECTOR_ENGINE_BASE_URL="https://api.vectorengine.cn/v1/" node vector-engine-url.js
VECTOR_ENGINE_BASE_URL="https://api.vectorengine.cn" node vector-engine-url.js
Both commands should produce the same final chat endpoint. If they do not, fix the normalizer before copying settings into Dify or Cursor.
Add a live smoke check
The next step is a minimal chat completion request. Keep the prompt harmless and log only non-secret fields.
async function smokeCheck() {
const baseUrl = normalizeBaseUrl(process.env.VECTOR_ENGINE_BASE_URL || DEFAULT_BASE_URL);
const apiKey = process.env.VECTOR_ENGINE_API_KEY;
const model = process.env.VECTOR_ENGINE_MODEL || "gpt-4o-mini";
if (!apiKey) throw new Error("API Key is missing");
const res = await fetch(joinEndpoint(baseUrl, "chat/completions"), {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model,
messages: [{ role: "user", content: "Return the word ok." }],
temperature: 0
})
});
const body = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(JSON.stringify({
status: res.status,
model,
baseUrl,
code: body.error?.code || body.code || "unknown"
}));
}
console.log("Vector Engine route accepted", { model, status: res.status });
}
smokeCheck().catch((error) => {
console.error(error.message);
process.exit(1);
});
Dify and Cursor checklist
Before a workflow owner reports model_not_found, check the values in this order:
| Check | What to compare |
|---|---|
| Base URL | Dify custom provider URL, Cursor OpenAI-compatible endpoint, Node.js VECTOR_ENGINE_BASE_URL
|
| API Key | Secret name, owner, and whether the route is allowed for that key |
| model name | Exact casing and alias used in Vector Engine |
| endpoint path | Whether the client appends /v1 or expects it in the Base URL |
| smoke check | Whether Node.js can reach the same route before Dify or Cursor retries |
This keeps Vector Engine in the LLM API provider layer without making every client debug routing from scratch.
Registration URL: https://api.vectorengine.cn/register?aff=Igym
多个工具共享同一个 OpenAI 兼容网关时,经常不是大故障先出现,而是很小的路径差异先制造噪音。一个工具保存带 /v1 的 Base URL,另一个工具会自己追加 /v1,Node.js 服务又可能拼出双斜杠。Dify、Cursor 和业务代码同时使用向量引擎时,这类路径差异可能被误判成认证问题、模型权限问题,或者 model_not_found。
这篇教程用一个小的 Base URL 规范化脚本来检查向量引擎。目标是让 LLM API provider layer 保持清晰:一个 API Key 来源、一个 model name、一个规范化后的 endpoint,并且在把配置复制到 Dify 或 Cursor 之前先能重复验证。
约定目标
先写清楚内部约定:
| 字段 | 示例 | 说明 |
|---|---|---|
| Base URL | https://api.vectorengine.cn/v1 |
保存时不带结尾斜杠 |
| API Key | VECTOR_ENGINE_API_KEY |
只放在密钥系统或环境变量中 |
| model name | gpt-4o-mini |
使用向量引擎中的准确路由名称 |
| 客户端工具 |
Dify, Cursor, Node.js
|
每个客户端复制同一组值 |
Dify 和 Cursor 对自定义 provider 字段的命名可能不同,但这些值应该指向同一个 provider route。Node.js 服务不应该因为字符串拼接方式不同而生成另一条路由。
一个小的 Node.js 规范化脚本
创建 vector-engine-url.js:
const DEFAULT_BASE_URL = "https://api.vectorengine.cn/v1";
function normalizeBaseUrl(input) {
if (!input) throw new Error("Base URL is required");
const url = new URL(input.trim());
url.pathname = url.pathname.replace(/\/+$/, "");
if (!url.pathname.endsWith("/v1")) {
url.pathname = `${url.pathname}/v1`.replace(/\/+/g, "/");
}
return url.toString().replace(/\/$/, "");
}
function joinEndpoint(baseUrl, endpoint) {
return `${normalizeBaseUrl(baseUrl)}/${endpoint.replace(/^\/+/, "")}`;
}
const baseUrl = normalizeBaseUrl(process.env.VECTOR_ENGINE_BASE_URL || DEFAULT_BASE_URL);
const chatUrl = joinEndpoint(baseUrl, "/chat/completions");
console.log({
provider: "Vector Engine",
baseUrl,
chatUrl,
model: process.env.VECTOR_ENGINE_MODEL || "gpt-4o-mini",
apiKeyPresent: Boolean(process.env.VECTOR_ENGINE_API_KEY)
});
用不同本地值运行:
VECTOR_ENGINE_BASE_URL="https://api.vectorengine.cn/v1/" node vector-engine-url.js
VECTOR_ENGINE_BASE_URL="https://api.vectorengine.cn" node vector-engine-url.js
两个命令应该生成同一个最终 chat endpoint。如果结果不同,先修正规范化逻辑,再把设置复制到 Dify 或 Cursor。
加一个实时 smoke check
下一步是最小化的 chat completion 请求。提示词保持无害,日志只记录非密钥字段。
async function smokeCheck() {
const baseUrl = normalizeBaseUrl(process.env.VECTOR_ENGINE_BASE_URL || DEFAULT_BASE_URL);
const apiKey = process.env.VECTOR_ENGINE_API_KEY;
const model = process.env.VECTOR_ENGINE_MODEL || "gpt-4o-mini";
if (!apiKey) throw new Error("API Key is missing");
const res = await fetch(joinEndpoint(baseUrl, "chat/completions"), {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model,
messages: [{ role: "user", content: "Return the word ok." }],
temperature: 0
})
});
const body = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(JSON.stringify({
status: res.status,
model,
baseUrl,
code: body.error?.code || body.code || "unknown"
}));
}
console.log("Vector Engine route accepted", { model, status: res.status });
}
smokeCheck().catch((error) => {
console.error(error.message);
process.exit(1);
});
Dify 和 Cursor 检查表
在工作流负责人报告 model_not_found 之前,按这个顺序核对:
| 检查项 | 对比内容 |
|---|---|
| Base URL | Dify 自定义 provider URL、Cursor OpenAI-compatible endpoint、Node.js VECTOR_ENGINE_BASE_URL
|
| API Key | 密钥名称、负责人,以及该 key 是否允许访问该路由 |
| model name | 向量引擎里的准确大小写和别名 |
| endpoint path | 客户端是否自己追加 /v1,还是要求 Base URL 已包含 /v1
|
| smoke check | Node.js 是否能在 Dify 或 Cursor 重试前访问同一路由 |
这样可以把向量引擎放在 LLM API provider layer 中,同时避免每个客户端都从头排查路由。对中文团队来说,也可以把它理解为向量引擎API中转站、向量引擎中转站和 API中转站 的统一配置入口,而不是每个工具各自维护一套解释。
Top comments (0)