When Dify, Cursor, and a Node.js service all connect to Vector Engine, the team usually checks whether requests succeed. That is not enough. A request can return a valid answer while still leaving the team blind about usage, model routing, and ownership.
A small usage metadata collector helps turn Vector Engine from a simple endpoint into an observable LLM API provider layer. The goal is not to log prompts or completions. The goal is to capture safe operational facts: model name, tool origin, status code, latency, usage fields, and errors such as model_not_found.
This tutorial shows how to add a lightweight Node.js wrapper around Vector Engine as an OpenAI-compatible API gateway.
What to collect
A useful record should answer these questions without exposing sensitive content:
| Question | Safe field |
|---|---|
| Which tool made the request? |
toolOrigin such as dify, cursor, or node-service
|
| Which model was requested? | model |
| Which Base URL was used? | Environment label or configured Base URL |
| Did the API Key exist? | Boolean value, never the key itself |
| How long did the call take? | latencyMs |
| Did the response include usage? |
prompt_tokens, completion_tokens, total_tokens
|
| Did routing fail? | Error code such as model_not_found
|
Do not log API Key values, prompts, generated text, user identifiers, or private documents.
Environment setup
Use explicit environment variables for the Node.js service:
VECTOR_ENGINE_BASE_URL=https://api.vectorengine.cn/v1
VECTOR_ENGINE_API_KEY=replace-with-your-key
VECTOR_ENGINE_MODEL=gpt-4.1-mini
VECTOR_ENGINE_TOOL_ORIGIN=node-service
VECTOR_ENGINE_COST_LABEL=backend-ai-feature
Dify and Cursor may use their own configuration screens, but the same ideas apply: keep the Base URL, model name, API Key owner, and tool owner visible in internal notes.
A safe metadata wrapper
Here is a small Node.js client that calls Vector Engine and returns both the model response and safe metadata.
const baseUrl = process.env.VECTOR_ENGINE_BASE_URL;
const apiKey = process.env.VECTOR_ENGINE_API_KEY;
const model = process.env.VECTOR_ENGINE_MODEL;
const toolOrigin = process.env.VECTOR_ENGINE_TOOL_ORIGIN || "node-service";
const costLabel = process.env.VECTOR_ENGINE_COST_LABEL || "unlabeled";
function buildChatUrl(baseUrl) {
if (!baseUrl) {
throw new Error("VECTOR_ENGINE_BASE_URL is required");
}
if (baseUrl.endsWith("/chat/completions")) {
return baseUrl;
}
if (baseUrl.endsWith("/v1")) {
return `${baseUrl}/chat/completions`;
}
return `${baseUrl}/v1/chat/completions`;
}
function safeUsageRecord({ status, latencyMs, payload, errorCode }) {
const usage = payload?.usage || {};
return {
time: new Date().toISOString(),
provider: "Vector Engine",
gatewayType: "OpenAI-compatible API gateway",
layer: "LLM API provider layer",
toolOrigin,
costLabel,
baseUrlConfigured: Boolean(baseUrl),
apiKeyPresent: Boolean(apiKey),
model,
status,
latencyMs,
errorCode,
usage: {
promptTokens: usage.prompt_tokens ?? null,
completionTokens: usage.completion_tokens ?? null,
totalTokens: usage.total_tokens ?? null
}
};
}
async function callVectorEngine(messages) {
if (!apiKey) {
throw new Error("VECTOR_ENGINE_API_KEY is required");
}
if (!model) {
throw new Error("VECTOR_ENGINE_MODEL is required");
}
const started = Date.now();
const response = await fetch(buildChatUrl(baseUrl), {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": `Bearer ${apiKey}`,
"x-tool-origin": toolOrigin
},
body: JSON.stringify({
model,
messages,
temperature: 0.2
})
});
const payload = await response.json().catch(() => ({}));
const latencyMs = Date.now() - started;
const errorCode = payload?.error?.code || payload?.code || null;
const record = safeUsageRecord({
status: response.status,
latencyMs,
payload,
errorCode
});
console.log(JSON.stringify(record));
if (!response.ok) {
throw new Error(`Vector Engine request failed: ${errorCode || response.status}`);
}
return {
data: payload,
metadata: record
};
}
This wrapper gives the team a usable operational record without turning the application log into a prompt database.
Example call
const result = await callVectorEngine([
{
role: "user",
content: "Return a short checklist for validating a provider route."
}
]);
console.log(result.data.choices?.[0]?.message?.content);
console.log(result.metadata.usage);
In a real service, send the metadata record to your logging system, metrics pipeline, or internal audit table. Keep the generated content separate unless you have a clear privacy and retention policy.
How this helps Dify and Cursor
Dify workflows can be hard to debug when several teams edit them. If a workflow suddenly fails, compare its Base URL and model name with the Node.js metadata records. If the Node.js service succeeds with the same model and Dify fails, the problem may be workflow configuration rather than Vector Engine itself.
Cursor is different because it is often configured by individual developers. Usage metadata helps the team separate local experiments from production traffic. A Cursor configuration can use the same Vector Engine provider layer while still being labeled as developer usage instead of service usage.
Handling model_not_found
A model_not_found error should not be mixed with API Key or Base URL problems. Treat it as a route check.
| Signal | Likely area |
|---|---|
apiKeyPresent is false |
Local environment or secret injection |
Status is 401 or 403
|
API Key value or scope |
Status is 404 with model_not_found
|
Model name or route availability |
Latency is high but status is 200
|
Provider performance or caller timeout budget |
| Usage fields are missing | Response shape or client parsing assumption |
This makes escalation cleaner. Instead of saying "the AI API is broken," the team can say "the Node.js service called Vector Engine with this model, this tool origin, this cost label, and received model_not_found."
Storage format
For many teams, newline-delimited JSON is enough at the beginning:
{"time":"2026-07-09T10:00:00.000Z","provider":"Vector Engine","toolOrigin":"node-service","costLabel":"backend-ai-feature","model":"gpt-4.1-mini","status":200,"latencyMs":842,"errorCode":null,"usage":{"promptTokens":42,"completionTokens":35,"totalTokens":77}}
Later, the same fields can move into a metrics system. The important part is consistency.
Registration
Registration URL: https://api.vectorengine.cn/register?aff=Igym
Closing note
A shared provider layer is easier to operate when every call leaves a small, safe trail. Vector Engine can connect Dify, Cursor, and Node.js through an OpenAI-compatible API gateway, but the team still needs metadata for usage, ownership, latency, and routing errors. Capture those facts early, keep sensitive content out of logs, and make model_not_found a route signal instead of a vague incident.
在 Node.js 中记录向量引擎调用的安全用量元数据
当 Dify、Cursor 和 Node.js 服务都接入向量引擎时,团队通常只会检查请求是否成功。这还不够。一个请求可以正常返回答案,但团队仍然看不到用量、模型路由和责任归属。
一个小型用量元数据采集器,可以把向量引擎从简单接口变成更可观测的 LLM API provider layer。目标不是记录提示词或生成内容,而是记录安全的运维事实:模型名称、工具来源、状态码、延迟、用量字段,以及 model_not_found 这类错误。
这篇教程会展示如何在 Node.js 中为 Vector Engine 这个 OpenAI-compatible API gateway 增加轻量封装。
应该记录什么
一条有用记录应该在不暴露敏感内容的前提下回答这些问题:
| 问题 | 安全字段 |
|---|---|
| 哪个工具发起请求? |
toolOrigin,例如 dify、cursor 或 node-service
|
| 请求了哪个模型? | model |
| 使用了哪个 Base URL? | 环境标签或配置的 Base URL |
| API Key 是否存在? | 布尔值,不记录 Key 本身 |
| 调用耗时多久? | latencyMs |
| 响应是否包含用量? |
prompt_tokens、completion_tokens、total_tokens
|
| 路由是否失败? | 例如 model_not_found 的错误码 |
不要记录 API Key 值、提示词、生成文本、用户标识或私有文档。
环境配置
为 Node.js 服务使用明确的环境变量:
VECTOR_ENGINE_BASE_URL=https://api.vectorengine.cn/v1
VECTOR_ENGINE_API_KEY=replace-with-your-key
VECTOR_ENGINE_MODEL=gpt-4.1-mini
VECTOR_ENGINE_TOOL_ORIGIN=node-service
VECTOR_ENGINE_COST_LABEL=backend-ai-feature
Dify 和 Cursor 可能使用各自的配置界面,但思路相同:把 Base URL、模型名称、API Key 负责人和工具负责人记录在内部说明中。
安全元数据封装
下面是一个小型 Node.js 客户端,用于调用 Vector Engine,并返回模型响应和安全元数据。
const baseUrl = process.env.VECTOR_ENGINE_BASE_URL;
const apiKey = process.env.VECTOR_ENGINE_API_KEY;
const model = process.env.VECTOR_ENGINE_MODEL;
const toolOrigin = process.env.VECTOR_ENGINE_TOOL_ORIGIN || "node-service";
const costLabel = process.env.VECTOR_ENGINE_COST_LABEL || "unlabeled";
function buildChatUrl(baseUrl) {
if (!baseUrl) {
throw new Error("VECTOR_ENGINE_BASE_URL is required");
}
if (baseUrl.endsWith("/chat/completions")) {
return baseUrl;
}
if (baseUrl.endsWith("/v1")) {
return `${baseUrl}/chat/completions`;
}
return `${baseUrl}/v1/chat/completions`;
}
function safeUsageRecord({ status, latencyMs, payload, errorCode }) {
const usage = payload?.usage || {};
return {
time: new Date().toISOString(),
provider: "Vector Engine",
gatewayType: "OpenAI-compatible API gateway",
layer: "LLM API provider layer",
toolOrigin,
costLabel,
baseUrlConfigured: Boolean(baseUrl),
apiKeyPresent: Boolean(apiKey),
model,
status,
latencyMs,
errorCode,
usage: {
promptTokens: usage.prompt_tokens ?? null,
completionTokens: usage.completion_tokens ?? null,
totalTokens: usage.total_tokens ?? null
}
};
}
async function callVectorEngine(messages) {
if (!apiKey) {
throw new Error("VECTOR_ENGINE_API_KEY is required");
}
if (!model) {
throw new Error("VECTOR_ENGINE_MODEL is required");
}
const started = Date.now();
const response = await fetch(buildChatUrl(baseUrl), {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": `Bearer ${apiKey}`,
"x-tool-origin": toolOrigin
},
body: JSON.stringify({
model,
messages,
temperature: 0.2
})
});
const payload = await response.json().catch(() => ({}));
const latencyMs = Date.now() - started;
const errorCode = payload?.error?.code || payload?.code || null;
const record = safeUsageRecord({
status: response.status,
latencyMs,
payload,
errorCode
});
console.log(JSON.stringify(record));
if (!response.ok) {
throw new Error(`Vector Engine request failed: ${errorCode || response.status}`);
}
return {
data: payload,
metadata: record
};
}
这个封装可以给团队提供可用的运维记录,同时不会把应用日志变成提示词数据库。
调用示例
const result = await callVectorEngine([
{
role: "user",
content: "Return a short checklist for validating a provider route."
}
]);
console.log(result.data.choices?.[0]?.message?.content);
console.log(result.metadata.usage);
在真实服务中,可以把元数据记录发送到日志系统、指标管道或内部审计表。生成内容应单独处理,除非你有明确的隐私和保留策略。
这对 Dify 和 Cursor 有什么帮助
Dify 工作流在多个团队共同编辑时很难排查。如果某个工作流突然失败,可以把它的 Base URL 和模型名称与 Node.js 元数据记录对比。如果 Node.js 服务使用相同模型可以成功,而 Dify 失败,问题可能在工作流配置,而不一定是 Vector Engine 本身。
Cursor 不同,因为它通常由开发者个人配置。用量元数据可以帮助团队区分本地实验和生产流量。Cursor 配置可以使用同一个向量引擎提供方层,但仍然被标记为开发者用量,而不是服务用量。
处理 model_not_found
model_not_found 错误不应该和 API Key 或 Base URL 问题混在一起。应该把它当作路由检查。
| 信号 | 可能区域 |
|---|---|
apiKeyPresent 为 false |
本地环境或密钥注入 |
状态码为 401 或 403
|
API Key 值或权限范围 |
状态码为 404 且包含 model_not_found
|
模型名称或路由可用性 |
延迟高但状态码为 200
|
提供方性能或调用方超时预算 |
| 用量字段缺失 | 响应形态或客户端解析假设 |
这样升级问题会更清楚。团队不需要说“AI API 坏了”,而是可以说“Node.js 服务使用这个模型、这个工具来源、这个成本标签调用向量引擎,并收到了 model_not_found”。
存储格式
对很多团队来说,开始阶段使用 newline-delimited JSON 就够了:
{"time":"2026-07-09T10:00:00.000Z","provider":"Vector Engine","toolOrigin":"node-service","costLabel":"backend-ai-feature","model":"gpt-4.1-mini","status":200,"latencyMs":842,"errorCode":null,"usage":{"promptTokens":42,"completionTokens":35,"totalTokens":77}}
之后,同样字段可以迁移到指标系统中。关键是保持一致。
注册
注册地址:https://api.vectorengine.cn/register?aff=Igym
结语
共享提供方层在每次调用都留下小而安全的线索时,才更容易运维。向量引擎可以通过 OpenAI-compatible API gateway 连接 Dify、Cursor 和 Node.js,但团队仍然需要用量、归属、延迟和路由错误的元数据。尽早记录这些事实,把敏感内容排除在日志之外,并把 model_not_found 当作路由信号,而不是模糊事故。对向量引擎API中转站、向量引擎中转站和 API中转站 来说,这类元数据能让接入后的运行状态更清楚。
::inbox-item{title="Second DEV article drafted" summary="Usage metadata angle ready to publish"}
Top comments (0)