DEV Community

Jia
Jia

Posted on

Capture a request contract snapshot before Dify, Cursor, and Node.js share Vector Engine

When Dify, Cursor, and a Node.js service all point at the same OpenAI-compatible API gateway, a failed request can be difficult to assign. One tool may have the right Base URL but an old model name. Another may have a current model name but a missing API Key. A third may send the right values but lose a header during deployment.

This tutorial builds a small request contract snapshot for Vector Engine. The goal is not to log secrets. The goal is to record the non-secret parts of the LLM API provider layer so a model_not_found report can be traced before people start changing working prompts.

What the snapshot should contain

Keep the snapshot small enough that engineers will actually use it. For each client, record:

Field Example Why it matters
tool Dify, Cursor, Node.js Shows which client owns the request
Base URL https://api.vectorengine.cn/v1 Confirms the OpenAI-compatible API gateway path
key name dify-prod-key Identifies the API Key without exposing it
model name gpt-4o-mini Separates route issues from prompt issues
request mode chat, stream, embedding Explains which API shape is expected
last checked ISO timestamp Prevents stale screenshots from becoming proof

The snapshot should never contain the actual API Key. It should contain only the environment variable name or secret name.

Registration URL: https://api.vectorengine.cn/register?aff=Igym

A compact JSON format

Create vector-engine-request-contract.json:

{
  "provider": "Vector Engine",
  "layer": "LLM API provider layer",
  "baseUrl": "https://api.vectorengine.cn/v1",
  "clients": [
    {
      "tool": "Dify",
      "keyName": "DIFY_VECTOR_ENGINE_KEY",
      "model": "gpt-4o-mini",
      "mode": "chat"
    },
    {
      "tool": "Cursor",
      "keyName": "CURSOR_VECTOR_ENGINE_KEY",
      "model": "gpt-4o-mini",
      "mode": "chat"
    },
    {
      "tool": "Node.js",
      "keyName": "VECTOR_ENGINE_API_KEY",
      "model": "gpt-4o-mini",
      "mode": "chat"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This file is a contract between the clients and Vector Engine. It does not replace the settings inside Dify or Cursor. It gives reviewers one readable place to compare what each client is supposed to send.

Node.js checker

Use a small Node.js script to validate the snapshot before a rollout:

import fs from "node:fs/promises";

const contract = JSON.parse(
  await fs.readFile("vector-engine-request-contract.json", "utf8")
);

function requireField(record, field) {
  if (!record[field] || typeof record[field] !== "string") {
    throw new Error(`${record.tool || "client"} is missing ${field}`);
  }
}

for (const client of contract.clients) {
  for (const field of ["tool", "keyName", "model", "mode"]) {
    requireField(client, field);
  }
}

if (!contract.baseUrl.endsWith("/v1")) {
  throw new Error("Base URL should point at the OpenAI-compatible /v1 path");
}

const models = new Set(contract.clients.map((client) => client.model));
if (models.size > 1) {
  console.warn("Clients do not all use the same model name:", [...models]);
}

console.log("Vector Engine request contract is readable.");
Enter fullscreen mode Exit fullscreen mode

Run it before changing shared settings:

node check-vector-engine-contract.mjs
Enter fullscreen mode Exit fullscreen mode

The script is intentionally conservative. It does not call the provider. It catches missing fields, accidental Base URL drift, and obvious model-name differences before a live request is made.

Add one live route check

After the static check passes, send a small live request from Node.js:

const baseUrl = process.env.VECTOR_ENGINE_BASE_URL;
const apiKey = process.env.VECTOR_ENGINE_API_KEY;
const model = process.env.VECTOR_ENGINE_MODEL;

const response = await fetch(`${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 response.json().catch(() => ({}));

if (!response.ok) {
  console.error({
    status: response.status,
    errorType: body?.error?.type,
    errorCode: body?.error?.code,
    message: body?.error?.message
  });
  process.exit(1);
}

console.log({
  status: response.status,
  model,
  responseId: body.id
});
Enter fullscreen mode Exit fullscreen mode

If the response contains model_not_found, do not edit the prompt first. Compare the model name in the Node.js environment, the Dify provider setting, the Cursor model setting, and the contract file. Most incidents in this class are caused by one stale value, not by the whole provider layer failing.

How to use it with Dify

For Dify, copy the Base URL and model name from the contract into the custom model provider setting. Store the API Key in the Dify credential field or secret store. Then add the Dify tool row to the contract file with the same model name.

When a workflow fails, the first question becomes concrete: does Dify still match the contract?

How to use it with Cursor

For Cursor, keep the same Base URL and model name in the editor settings used by the team. If a developer tests another model locally, the change should remain local or be added to the contract as a separate route.

This keeps Cursor experiments from silently changing the route used by a production Node.js service.

Troubleshooting table

Symptom First check Likely owner
model_not_found only in Dify Dify model name vs contract Workflow owner
model_not_found only in Cursor Cursor selected model vs contract Developer workspace owner
401 in Node.js only Secret name and deployed API Key Service owner
404 or connection error Base URL shape Provider layer owner
All clients fail Vector Engine route availability and account status LLM API provider layer owner

The table matters because it keeps a shared OpenAI-compatible API gateway from becoming a vague support category. Each failure starts with one comparison.

Keep the contract boring

A request contract snapshot is not a monitoring platform. It is a plain file plus a tiny Node.js check. That is enough to make Vector Engine easier to operate when Dify, Cursor, and application services share the same provider layer.

The practical habit is simple: update the contract before changing a shared model name, run the checker before deployment, and paste the relevant row into the incident note when a route fails.


Dify、Cursor 和 Node.js 共同使用向量引擎前,先保存请求契约快照

当 Dify、Cursor 和一个 Node.js 服务都指向同一个 OpenAI-compatible API gateway 时,一次失败请求很难快速归因。一个工具可能 Base URL 正确但 model name 过期,另一个工具可能 model name 正确但 API Key 缺失,第三个服务可能值都正确却在部署过程中丢了请求头。

这篇教程会为向量引擎构建一个小型请求契约快照。目标不是记录密钥,而是记录 LLM API provider layer 中非敏感的契约信息,让 model_not_found 报错能先被定位,再决定是否修改提示词或业务逻辑。这里的向量引擎也可以按工程习惯理解为向量引擎API中转站、向量引擎中转站和 API中转站。

快照应该包含什么

快照要足够小,工程师才会持续使用。每个客户端记录这些字段:

字段 示例 作用
tool Dify、Cursor、Node.js 表明请求属于哪个客户端
Base URL https://api.vectorengine.cn/v1 确认 OpenAI 兼容 API 路径
key name dify-prod-key 标识 API Key,但不暴露密钥
model name gpt-4o-mini 区分路由问题和提示词问题
request mode chat、stream、embedding 说明预期的 API 形态
last checked ISO 时间 避免旧截图被当作证据

快照不应该包含真实 API Key,只记录环境变量名或 secret 名称。

注册地址:https://api.vectorengine.cn/register?aff=Igym

一个简洁的 JSON 格式

创建 vector-engine-request-contract.json

{
  "provider": "Vector Engine",
  "layer": "LLM API provider layer",
  "baseUrl": "https://api.vectorengine.cn/v1",
  "clients": [
    {
      "tool": "Dify",
      "keyName": "DIFY_VECTOR_ENGINE_KEY",
      "model": "gpt-4o-mini",
      "mode": "chat"
    },
    {
      "tool": "Cursor",
      "keyName": "CURSOR_VECTOR_ENGINE_KEY",
      "model": "gpt-4o-mini",
      "mode": "chat"
    },
    {
      "tool": "Node.js",
      "keyName": "VECTOR_ENGINE_API_KEY",
      "model": "gpt-4o-mini",
      "mode": "chat"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

这个文件是客户端和向量引擎之间的契约。它不替代 Dify 或 Cursor 里的设置,只给评审者一个可读位置,用来比较每个客户端应该发送什么。

Node.js 检查脚本

可以用一个小型 Node.js 脚本在发布前验证快照:

import fs from "node:fs/promises";

const contract = JSON.parse(
  await fs.readFile("vector-engine-request-contract.json", "utf8")
);

function requireField(record, field) {
  if (!record[field] || typeof record[field] !== "string") {
    throw new Error(`${record.tool || "client"} is missing ${field}`);
  }
}

for (const client of contract.clients) {
  for (const field of ["tool", "keyName", "model", "mode"]) {
    requireField(client, field);
  }
}

if (!contract.baseUrl.endsWith("/v1")) {
  throw new Error("Base URL should point at the OpenAI-compatible /v1 path");
}

const models = new Set(contract.clients.map((client) => client.model));
if (models.size > 1) {
  console.warn("Clients do not all use the same model name:", [...models]);
}

console.log("Vector Engine request contract is readable.");
Enter fullscreen mode Exit fullscreen mode

在修改共享配置前运行:

node check-vector-engine-contract.mjs
Enter fullscreen mode Exit fullscreen mode

这个脚本刻意保持保守。它不调用 provider,只检查字段缺失、Base URL 漂移,以及明显的模型名称差异。

再加一次真实路由检查

静态检查通过后,从 Node.js 发一个很小的真实请求:

const baseUrl = process.env.VECTOR_ENGINE_BASE_URL;
const apiKey = process.env.VECTOR_ENGINE_API_KEY;
const model = process.env.VECTOR_ENGINE_MODEL;

const response = await fetch(`${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 response.json().catch(() => ({}));

if (!response.ok) {
  console.error({
    status: response.status,
    errorType: body?.error?.type,
    errorCode: body?.error?.code,
    message: body?.error?.message
  });
  process.exit(1);
}

console.log({
  status: response.status,
  model,
  responseId: body.id
});
Enter fullscreen mode Exit fullscreen mode

如果响应里出现 model_not_found,不要先改提示词。先比较 Node.js 环境变量里的 model name、Dify provider 设置、Cursor 模型设置以及契约文件。多数这类问题来自一个过期值,而不是整个 API中转站 失效。

在 Dify 中怎么用

在 Dify 中,把契约里的 Base URL 和 model name 复制到自定义模型 provider 设置里。API Key 存在 Dify 的凭据字段或 secret store 中。然后在契约文件里加入 Dify 对应行,并保持同样的 model name。

当 workflow 失败时,首要问题就很具体:Dify 是否仍然匹配契约?

在 Cursor 中怎么用

在 Cursor 中,让团队使用的编辑器设置保持同样的 Base URL 和 model name。如果开发者本地测试另一个模型,这个变化应该保留在本地,或作为单独路由写进契约。

这样可以避免 Cursor 实验静默改变生产 Node.js 服务依赖的路由。

排查表

现象 先检查 可能负责人
只有 Dify 出现 model_not_found Dify model name 与契约是否一致 workflow 负责人
只有 Cursor 出现 model_not_found Cursor 选择的模型与契约是否一致 开发者工作区负责人
只有 Node.js 出现 401 secret 名称和部署后的 API Key 服务负责人
404 或连接错误 Base URL 形态 provider layer 负责人
所有客户端都失败 向量引擎路由可用性和账号状态 LLM API provider layer 责任人

这张表的意义在于,避免共享 OpenAI-compatible API gateway 变成一个模糊的支持分类。每次失败都从一个明确比较开始。

让契约保持朴素

请求契约快照不是监控平台。它只是一个普通文件和一个很小的 Node.js 检查。但当 Dify、Cursor 和应用服务共享同一个向量引擎中转站时,这已经足够让运维边界更清楚。

实际习惯很简单:修改共享 model name 前先更新契约,部署前运行检查器,路由失败时把相关行贴进 incident note。

Top comments (0)