Skip to content
Closed
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
65 changes: 65 additions & 0 deletions packages/actions/src/prepare-request-body.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,71 @@ describe("prepareRequestBody - Moonshot thinking", () => {
);
});

describe("prepareRequestBody - InclusionAI Ling-3.0-flash thinking", () => {
async function prepare(options: {
provider: Parameters<typeof prepareRequestBody>[0];
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "max";
}) {
return (await prepareRequestBody(
options.provider,
"ling-3.0-flash",
null,
options.provider === "deepinfra"
? "inclusionAI/Ling-3.0-flash"
: "inclusionai/ling-3.0-flash",
[{ role: "user", content: "Hello!" }],
false, // stream
undefined, // temperature
undefined, // max_tokens
undefined, // top_p
undefined, // frequency_penalty
undefined, // presence_penalty
undefined, // response_format
undefined, // tools
undefined, // tool_choice
options.reasoningEffort, // reasoning_effort
true, // supportsReasoning
)) as any;
}

test.each(["deepinfra", "novita"] as const)(
"maps none to enable_thinking disabled on %s and never forwards reasoning_effort",
async (provider) => {
const requestBody = await prepare({
provider,
reasoningEffort: "none",
});
expect(requestBody.chat_template_kwargs).toEqual({
enable_thinking: false,
});
expect(requestBody.reasoning_effort).toBeUndefined();
},
);

test.each(["deepinfra", "novita"] as const)(
"maps high to enable_thinking enabled on %s and never forwards reasoning_effort",
async (provider) => {
const requestBody = await prepare({
provider,
reasoningEffort: "high",
});
expect(requestBody.chat_template_kwargs).toEqual({
enable_thinking: true,
});
expect(requestBody.reasoning_effort).toBeUndefined();
},
);

test.each(["deepinfra", "novita"] as const)(
"keeps the provider default (thinking on) when no reasoning_effort is requested on %s",
async (provider) => {
const requestBody = await prepare({ provider });
expect(requestBody.chat_template_kwargs).toBeUndefined();
expect(requestBody.reasoning_effort).toBeUndefined();
},
);
});

describe("prepareRequestBody - embercloud reasoning shape", () => {
// embercloud's documented request body uses a nested `reasoning` object
// (reasoning.enabled + reasoning.effort) — the flat OpenAI-style
Expand Down
13 changes: 12 additions & 1 deletion packages/actions/src/prepare-request-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4185,13 +4185,24 @@ export async function prepareRequestBody(
// Novita) ignore `reasoning_effort` and require the vLLM chat-template
// flag to turn reasoning on. Only set it when the caller asked for
// reasoning so plain requests stay non-thinking.
//
// Mappings that think by default (e.g. InclusionAI Ling-3.0-flash) declare
// `chatTemplateThinkingKey` for the chat-template flag that controls
// thinking; `reasoning_effort: "none"` flips it to false to turn thinking
// off, and any other effort flips it to true.
if (supportsReasoning && (reasoning_effort || reasoning_max_tokens)) {
const thinkingMapping = modelDef?.providers.find(
(p) =>
p.providerId === usedProvider &&
((p as ProviderModelMapping).region ?? null) === usedRegion,
) as ProviderModelMapping | undefined;
if (thinkingMapping?.requiresEnableThinking) {
if (thinkingMapping?.chatTemplateThinkingKey) {
requestBody.chat_template_kwargs = {
...(requestBody.chat_template_kwargs ?? {}),
[thinkingMapping.chatTemplateThinkingKey]:
reasoning_effort !== "none",
};
} else if (thinkingMapping?.requiresEnableThinking) {
requestBody.chat_template_kwargs = {
...(requestBody.chat_template_kwargs ?? {}),
thinking: true,
Expand Down
11 changes: 11 additions & 0 deletions packages/models/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { bytedanceModels } from "./models/bytedance.js";
import { deepseekModels } from "./models/deepseek.js";
import { elevenlabsModels } from "./models/elevenlabs.js";
import { googleModels } from "./models/google.js";
import { inclusionaiModels } from "./models/inclusionai.js";
import { llmgatewayModels } from "./models/llmgateway.js";
import { metaModels } from "./models/meta.js";
import { microsoftModels } from "./models/microsoft.js";
Expand Down Expand Up @@ -391,6 +392,15 @@ export interface ProviderModelMapping {
* declare `none` in `reasoningEfforts`.
*/
requiresDisableThinkingParam?: boolean;
/**
* Name of the chat-template kwargs key used to control thinking on
* mappings that think by default and expose only a chat-template flag
* (e.g. vLLM-hosted hybrid models). When set, `reasoning_effort: "none"`
* sends `chat_template_kwargs: { [key]: false }` to turn thinking off, and
* any other effort sends `{ [key]: true }`. Differs from
* `requiresDisableThinkingParam`, which sends a binary `thinking` object.
*/
chatTemplateThinkingKey?: string;
/**
* Whether this model supports the OpenAI responses API (defaults to true if reasoning is true)
*/
Expand Down Expand Up @@ -741,6 +751,7 @@ export const models = [
...openaiModels,
...anthropicModels,
...googleModels,
...inclusionaiModels,
...perplexityModels,
...xaiModels,
...xiaomiModels,
Expand Down
79 changes: 79 additions & 0 deletions packages/models/src/models/inclusionai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ModelDefinition } from "@/models.js";

export const inclusionaiModels = [
{
id: "ling-3.0-flash",
name: "InclusionAI Ling 3.0 Flash",
description:
"InclusionAI's native hybrid-reasoning MoE model (124B total, 5.1B active) with strong agentic and coding performance.",
family: "inclusionai",
releasedAt: new Date("2026-08-02"),
providers: [
{
providerId: "deepinfra",
externalId: "inclusionAI/Ling-3.0-flash",
// DeepInfra is running a promotional sale on this model; these rates
// are the sale price and will revert once the promo ends.
inputPrice: "0.045e-6",
cachedInputPrice: "0.008e-6",
outputPrice: "0.1e-6",
requestPrice: "0",
contextSize: 262144,
maxOutput: 32768,
streaming: true,
// Thinking is enabled by default via the vLLM chat template; only
// disabling is exposed through the chat-template flag, so only
// `none` is advertised as a supported reasoning effort.
reasoning: true,
reasoningEfforts: ["none"],
chatTemplateThinkingKey: "enable_thinking",
// Thinking is controlled solely via the chat-template flag, so
// `reasoning_effort` must NOT be forwarded to the provider.
supportedParameters: [
"temperature",
"max_tokens",
"top_p",
"frequency_penalty",
"presence_penalty",
"stop",
"stream",
"response_format",
"tools",
"tool_choice",
],
vision: false,
tools: true,
jsonOutput: true,
},
{
providerId: "novita",
externalId: "inclusionai/ling-3.0-flash",
inputPrice: "0.06e-6",
cachedInputPrice: "0.012e-6",
outputPrice: "0.18e-6",
requestPrice: "0",
contextSize: 262144,
maxOutput: 32768,
streaming: true,
reasoning: true,
reasoningEfforts: ["none"],
chatTemplateThinkingKey: "enable_thinking",
supportedParameters: [
"temperature",
"max_tokens",
"top_p",
"frequency_penalty",
"presence_penalty",
"stop",
"stream",
"response_format",
"tools",
"tool_choice",
],
vision: false,
tools: true,
jsonOutput: true,
},
],
},
] as const satisfies ModelDefinition[];