Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions docs/getting-started/integration-method/nebius.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Nebius Token Factory Integration"
sidebarTitle: "Nebius Token Factory"
description: "Connect Helicone with Nebius Token Factory, a platform that provides powerful AI models including text and multimodal models, embeddings and guardrails, and text-to-image models."
description: "Send supported Nebius Token Factory API requests through Helicone's legacy provider gateway for observability."
"twitter:title": "Nebius Token Factory AI Integration - Helicone OSS LLM Observability"
---

Expand Down Expand Up @@ -34,25 +34,35 @@ Replace the following Nebius Token Factory URL with the Helicone Gateway URL:

`https://api.tokenfactory.nebius.com` -> `https://nebius.helicone.ai`

and then add the following authentication headers:
and then add the provider and Helicone authentication headers:

```javascript
Authorization: Bearer <your API key>
Authorization: Bearer <your Nebius API key>
Helicone-Auth: Bearer <your Helicone API key>
```

</Step>
</Steps>

Now you can access all the models on Nebius Token Factory with a simple fetch call:
The legacy provider gateway preserves the path you send and forwards it to Nebius Token Factory. Check the [Token Factory model catalog](https://tokenfactory.nebius.com/) before choosing a model because availability changes over time.

<Note>
Helicone's AI Gateway Nebius provider routes model requests to Chat
Completions. Helicone's `/v1/responses` support may translate a request to
Chat Completions; it does not provide direct access to the Token Factory
Responses API. This legacy integration documents Chat Completions and Image
Generations only.
</Note>

## Example - Text Completion

```bash
curl \
--header "Authorization: Bearer $NEBIUS_API_KEY" \
--header "Helicone-Auth: Bearer $HELICONE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"model": "deepseek-ai/DeepSeek-R1",
"model": "meta-llama/Llama-3.3-70B-Instruct",
"messages": [
{
"role": "user",
Expand All @@ -65,12 +75,15 @@ curl \

## Example - Image Generation

Use a currently available Token Factory image model ID. The placeholder below is intentional so this example does not pin a model that may have been retired.

```bash
curl \
--header "Authorization: Bearer $NEBIUS_API_KEY" \
--header "Helicone-Auth: Bearer $HELICONE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"model": "black-forest-labs/flux-schnell",
"model": "<current-image-model-id>",
"prompt": "A beautiful sunset over a mountain landscape"
}' \
--url https://nebius.helicone.ai/v1/images/generations
Expand Down
96 changes: 96 additions & 0 deletions packages/__tests__/cost/providers/nebius.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { describe, expect, it } from "@jest/globals";
import { registry } from "../../../cost/models/registry";
import {
buildEndpointUrl,
buildRequestBody,
} from "../../../cost/models/provider-helpers";
import { NebiusProvider } from "../../../cost/models/providers/nebius";

describe("Nebius Token Factory provider", () => {
const provider = new NebiusProvider();

it("routes AI Gateway requests to Chat Completions", () => {
const config = registry.getModelProviderConfig(
"llama-3.3-70b-instruct",
"nebius",
);
expect(config.data).toBeDefined();

const endpoint = registry.buildEndpoint(config.data!, {});
expect(endpoint.data).toBeDefined();

const result = buildEndpointUrl(endpoint.data!, {
bodyMapping: "OPENAI",
});

expect(result.data).toBe(
"https://api.tokenfactory.nebius.com/v1/chat/completions",
);
});

it("uses the current Token Factory model ID and public pricing", async () => {
const config = registry.getModelProviderConfig(
"llama-3.3-70b-instruct",
"nebius",
);
expect(config.data).toBeDefined();
expect(config.data?.providerModelId).toBe(
"meta-llama/Llama-3.3-70B-Instruct",
);
expect(config.data?.pricing).toEqual([
{
threshold: 0,
input: 0.00000013,
output: 0.0000004,
},
]);

const endpoint = registry.buildEndpoint(config.data!, {});
expect(endpoint.data).toBeDefined();

const result = await buildRequestBody(endpoint.data!, {
parsedBody: {
model: "llama-3.3-70b-instruct/nebius",
messages: [{ role: "user", content: "Hello" }],
},
bodyMapping: "OPENAI",
toAnthropic: (body: any) => body,
toChatCompletions: (body: any) => body,
});

expect(result.error).toBeNull();
expect(JSON.parse(result.data!)).toMatchObject({
model: "meta-llama/Llama-3.3-70B-Instruct",
messages: [{ role: "user", content: "Hello" }],
});
});

it("returns a Token Factory detail error", async () => {
const result = await provider.buildErrorMessage(
new Response(JSON.stringify({ detail: "Invalid API key" }), {
status: 401,
}),
);

expect(result.message).toBe("Invalid API key");
});

it("returns an OpenAI-compatible nested error", async () => {
const result = await provider.buildErrorMessage(
new Response(
JSON.stringify({ error: { message: "Model is not available" } }),
{ status: 404 },
),
);

expect(result.message).toBe("Model is not available");
});

it("falls back to the HTTP status for a malformed error", async () => {
const result = await provider.buildErrorMessage(
new Response("not-json", { status: 502 }),
);

expect(result.message).toBe("Request failed with status 502");
});
});
5 changes: 4 additions & 1 deletion packages/cost/models/providers/nebius.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export class NebiusProvider extends BaseProvider {
try {
const respJson = (await response.json()) as any;
return {
message: respJson.detail || `Request failed with status ${response.status}`
message:
respJson.detail ||
respJson.error?.message ||
`Request failed with status ${response.status}`,
};
} catch (error) {
return { message: `Request failed with status ${response.status}` };
Expand Down
27 changes: 27 additions & 0 deletions worker/test/gateway/nebius-routes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { buildTargetUrl } from "../../src/lib/clients/ProviderClient";

describe("Nebius Token Factory legacy gateway routes", () => {
const tokenFactoryBaseUrl = "https://api.tokenfactory.nebius.com";

it.each(["/v1/chat/completions", "/v1/images/generations"])(
"preserves the %s route",
(pathname) => {
const gatewayUrl = new URL(`https://nebius.helicone.ai${pathname}`);

expect(buildTargetUrl(gatewayUrl, tokenFactoryBaseUrl).href).toBe(
`${tokenFactoryBaseUrl}${pathname}`
);
}
);

it("preserves query parameters", () => {
const gatewayUrl = new URL(
"https://nebius.helicone.ai/v1/chat/completions?region=eu-north1"
);

expect(buildTargetUrl(gatewayUrl, tokenFactoryBaseUrl).href).toBe(
`${tokenFactoryBaseUrl}/v1/chat/completions?region=eu-north1`
);
});
});
20 changes: 20 additions & 0 deletions worker/test/gateway/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
environment: "node",
},
resolve: {
alias: {
"@worker": new URL("../../src", import.meta.url).pathname,
"@helicone-package/cost": new URL(
"../../../packages/cost",
import.meta.url
).pathname,
"@helicone-package/llm-mapper": new URL(
"../../../packages/llm-mapper",
import.meta.url
).pathname,
},
},
});
1 change: 1 addition & 0 deletions worker/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default defineConfig({
test: {
projects: [
"./test/ai-gateway/vitest.config.mts",
"./test/gateway/vitest.config.mts",
"./test/cache/vitest.config.mts",
"./test/alerts/vitest.config.mts",
"./test/token-limit-exception/vitest.config.mts",
Expand Down