|
|
|
|
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
|
import { stream, streamSimple } from "../src/index.ts";
|
|
|
|
|
import { getModel } from "../src/models.ts";
|
|
|
|
|
import { convertMessages } from "../src/providers/openai-completions.ts";
|
|
|
|
|
import type { AssistantMessage, Model, Tool, ToolResultMessage } from "../src/types.ts";
|
|
|
|
|
import type { AssistantMessage, Model, SimpleStreamOptions, Tool, ToolResultMessage } from "../src/types.ts";
|
|
|
|
|
|
|
|
|
|
const mockState = vi.hoisted(() => ({
|
|
|
|
|
lastParams: undefined as unknown,
|
|
|
|
|
@@ -64,6 +64,46 @@ vi.mock("openai", () => {
|
|
|
|
|
return { default: FakeOpenAI };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const localOpenAICompletionsModel = {
|
|
|
|
|
api: "openai-completions",
|
|
|
|
|
provider: "local-vllm",
|
|
|
|
|
baseUrl: "http://localhost:8000/v1",
|
|
|
|
|
reasoning: true,
|
|
|
|
|
input: ["text"],
|
|
|
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
|
|
|
contextWindow: 128000,
|
|
|
|
|
maxTokens: 8192,
|
|
|
|
|
} satisfies Omit<Model<"openai-completions">, "id" | "name" | "compat">;
|
|
|
|
|
|
|
|
|
|
type CapturedParams = {
|
|
|
|
|
chat_template_kwargs?: Record<string, unknown>;
|
|
|
|
|
thinking?: unknown;
|
|
|
|
|
reasoning_effort?: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async function captureSimpleParams(
|
|
|
|
|
model: Model<"openai-completions">,
|
|
|
|
|
reasoning?: SimpleStreamOptions["reasoning"],
|
|
|
|
|
): Promise<CapturedParams> {
|
|
|
|
|
let payload: unknown;
|
|
|
|
|
|
|
|
|
|
await streamSimple(
|
|
|
|
|
model,
|
|
|
|
|
{
|
|
|
|
|
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
apiKey: "test",
|
|
|
|
|
reasoning,
|
|
|
|
|
onPayload: (params: unknown) => {
|
|
|
|
|
payload = params;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
).result();
|
|
|
|
|
|
|
|
|
|
return (payload ?? mockState.lastParams) as CapturedParams;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("openai-completions tool_choice", () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mockState.lastParams = undefined;
|
|
|
|
|
@@ -1142,6 +1182,7 @@ describe("openai-completions tool_choice", () => {
|
|
|
|
|
thinkingFormat: "openai",
|
|
|
|
|
openRouterRouting: {},
|
|
|
|
|
vercelGatewayRouting: {},
|
|
|
|
|
chatTemplateKwargs: {},
|
|
|
|
|
zaiToolStream: false,
|
|
|
|
|
supportsStrictMode: true,
|
|
|
|
|
sendSessionAffinityHeaders: false,
|
|
|
|
|
@@ -1449,6 +1490,77 @@ describe("openai-completions tool_choice", () => {
|
|
|
|
|
expect(params.reasoning_effort).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("uses configurable chat template boolean thinking kwargs", async () => {
|
|
|
|
|
const model = {
|
|
|
|
|
...localOpenAICompletionsModel,
|
|
|
|
|
id: "deepseek-ai/DeepSeek-V3.1",
|
|
|
|
|
name: "DeepSeek V3.1 via vLLM",
|
|
|
|
|
compat: {
|
|
|
|
|
thinkingFormat: "chat-template",
|
|
|
|
|
supportsReasoningEffort: false,
|
|
|
|
|
chatTemplateKwargs: { thinking: { $var: "thinking.enabled" } },
|
|
|
|
|
},
|
|
|
|
|
} satisfies Model<"openai-completions">;
|
|
|
|
|
|
|
|
|
|
for (const testCase of [
|
|
|
|
|
{ reasoning: "high" as const, expected: true },
|
|
|
|
|
{ reasoning: undefined, expected: false },
|
|
|
|
|
]) {
|
|
|
|
|
const params = await captureSimpleParams(model, testCase.reasoning);
|
|
|
|
|
|
|
|
|
|
expect(params.chat_template_kwargs).toEqual({ thinking: testCase.expected });
|
|
|
|
|
expect(params.thinking).toBeUndefined();
|
|
|
|
|
expect(params.reasoning_effort).toBeUndefined();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("uses qwen chat template thinking kwargs", async () => {
|
|
|
|
|
const model = {
|
|
|
|
|
...localOpenAICompletionsModel,
|
|
|
|
|
id: "Qwen/Qwen3-Coder",
|
|
|
|
|
name: "Qwen3 Coder via vLLM",
|
|
|
|
|
compat: {
|
|
|
|
|
thinkingFormat: "qwen-chat-template",
|
|
|
|
|
supportsReasoningEffort: false,
|
|
|
|
|
},
|
|
|
|
|
} satisfies Model<"openai-completions">;
|
|
|
|
|
|
|
|
|
|
for (const testCase of [
|
|
|
|
|
{ reasoning: "high" as const, expected: true },
|
|
|
|
|
{ reasoning: undefined, expected: false },
|
|
|
|
|
]) {
|
|
|
|
|
const params = await captureSimpleParams(model, testCase.reasoning);
|
|
|
|
|
|
|
|
|
|
expect(params.chat_template_kwargs).toEqual({
|
|
|
|
|
enable_thinking: testCase.expected,
|
|
|
|
|
preserve_thinking: true,
|
|
|
|
|
});
|
|
|
|
|
expect(params.reasoning_effort).toBeUndefined();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("uses configurable chat template effort kwargs with static kwargs", async () => {
|
|
|
|
|
const model = {
|
|
|
|
|
...localOpenAICompletionsModel,
|
|
|
|
|
id: "unsloth/gpt-oss-120b-GGUF",
|
|
|
|
|
name: "GPT OSS via vLLM",
|
|
|
|
|
thinkingLevelMap: { xhigh: "max" },
|
|
|
|
|
compat: {
|
|
|
|
|
thinkingFormat: "chat-template",
|
|
|
|
|
supportsReasoningEffort: false,
|
|
|
|
|
chatTemplateKwargs: {
|
|
|
|
|
preserve_thinking: true,
|
|
|
|
|
reasoning_effort: { $var: "thinking.effort", omitWhenOff: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
} satisfies Model<"openai-completions">;
|
|
|
|
|
|
|
|
|
|
const params = await captureSimpleParams(model, "xhigh");
|
|
|
|
|
|
|
|
|
|
expect(params.chat_template_kwargs).toEqual({ preserve_thinking: true, reasoning_effort: "max" });
|
|
|
|
|
expect(params.reasoning_effort).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("uses Ant Ling compatibility metadata", async () => {
|
|
|
|
|
const model = getModel("ant-ling", "Ring-2.6-1T")!;
|
|
|
|
|
let payload: unknown;
|
|
|
|
|
|