Skip to content
Open
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
11 changes: 8 additions & 3 deletions packages/types/src/providers/friendli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ export const friendliModels = {
outputPrice: 4.4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.26,
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does GLM offer a way to disable reasoning effort completely using 'disable' as a key?

reasoningEffort: "high",
description:
"GLM-5.2 is Zhipu's flagship model with a 1M context window and 128k max output, served via Friendli Model APIs. It delivers top-tier long-context reasoning, coding, and agentic performance for extended engineering sessions.",
"GLM-5.2 is Zhipu's flagship model with a 1M context window and 128k max output, served via Friendli Model APIs. It delivers top-tier long-context reasoning, coding, and agentic performance for extended engineering sessions. (controllable reasoning model)",
},
"zai-org/GLM-5.1": {
maxTokens: 131_072,
Expand All @@ -33,8 +35,10 @@ export const friendliModels = {
outputPrice: 4.4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.26,
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],
reasoningEffort: "high",
description:
"GLM-5.1 is Zhipu's most capable model with a 200k context window and 128k max output, served via Friendli Model APIs. It delivers top-tier reasoning, coding, and agentic performance.",
"GLM-5.1 is Zhipu's most capable model with a 200k context window and 128k max output, served via Friendli Model APIs. It delivers top-tier reasoning, coding, and agentic performance. (controllable reasoning model)",
},
"deepseek-ai/DeepSeek-V3.2": {
maxTokens: 16384,
Expand All @@ -57,7 +61,8 @@ export const friendliModels = {
outputPrice: 1.2,
cacheWritesPrice: 0,
cacheReadsPrice: 0.06,
supportsReasoningBinary: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On MiniMax-M2.5 This will render the checkbox Use reasoning in the settings.... but the thing is, this model always has reasoning on though right? We should introduce or reuse metadata that distinguishes required/always reasoning from optional binary reasoning, hide or disable the on/off control for this model, and always request parsed reasoning for MiniMax-M2.5. In addition to this, we should add tests for MiniMax with the reasoning flag false and unset, not only true.

I think these changes should be in a separate PR in order not to bloat this one

description:
"MiniMax M2.5 is a high-performance language model with a 204.8K context window, optimized for long-context understanding and generation tasks, served via Friendli Model APIs.",
"MiniMax M2.5 is a high-performance language model with a 204.8K context window, optimized for long-context understanding and generation tasks, served via Friendli Model APIs. (always-reasoning model)",
},
} as const satisfies Record<string, ModelInfo>
207 changes: 207 additions & 0 deletions src/api/providers/__tests__/friendli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,210 @@ describe("Friendli model max output tokens (clamping behavior)", () => {
expect(result).toBe(80_000)
})
})

describe("FriendliHandler — Friendli-specific reasoning params", () => {
beforeEach(() => {
vi.clearAllMocks()
})

it("should include reasoning_effort, chat_template_kwargs, parse_reasoning for GLM-5.2 with reasoning enabled", async () => {
const handler = new FriendliHandler({
apiModelId: "zai-org/GLM-5.2",
friendliApiKey: "test-key",
enableReasoningEffort: true,
reasoningEffort: "high",
})

mockCreate.mockImplementationOnce(() => ({
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
}))

await handler.createMessage("system", []).next()

expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "zai-org/GLM-5.2",
reasoning_effort: "high",
chat_template_kwargs: { enable_thinking: true },
parse_reasoning: true,
include_reasoning: true,
}),
undefined,
)
})

it("should send enable_thinking: false when enableReasoningEffort is false on controllable model", async () => {
const handler = new FriendliHandler({
apiModelId: "zai-org/GLM-5.2",
friendliApiKey: "test-...ey",
enableReasoningEffort: false,
})

mockCreate.mockImplementationOnce(() => ({
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
}))

await handler.createMessage("system", []).next()

const callArgs = mockCreate.mock.calls[0][0] as any
expect(callArgs.reasoning_effort).toBeUndefined()
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: false })
expect(callArgs.parse_reasoning).toBeUndefined()
expect(callArgs.include_reasoning).toBeUndefined()
})

it("should send enable_thinking: false when reasoningEffort is none on controllable model", async () => {
const handler = new FriendliHandler({
apiModelId: "zai-org/GLM-5.2",
friendliApiKey: "test-...ey",
enableReasoningEffort: true,
reasoningEffort: "none",
})

mockCreate.mockImplementationOnce(() => ({
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
}))

await handler.createMessage("system", []).next()

const callArgs = mockCreate.mock.calls[0][0] as any
expect(callArgs.reasoning_effort).toBeUndefined()
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: false })
expect(callArgs.parse_reasoning).toBeUndefined()
})

it("should send enable_thinking: false when reasoningEffort is disable on controllable model", async () => {
const handler = new FriendliHandler({
apiModelId: "zai-org/GLM-5.2",
friendliApiKey: "test-...ey",
enableReasoningEffort: true,
reasoningEffort: "disable",
})

mockCreate.mockImplementationOnce(() => ({
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
}))

await handler.createMessage("system", []).next()

const callArgs = mockCreate.mock.calls[0][0] as any
expect(callArgs.reasoning_effort).toBeUndefined()
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: false })
expect(callArgs.parse_reasoning).toBeUndefined()
})

it("should use model default reasoningEffort when no explicit settings are provided", async () => {
const handler = new FriendliHandler({
apiModelId: "zai-org/GLM-5.2",
friendliApiKey: "test-...ey",
// No enableReasoningEffort or reasoningEffort — model default "high" kicks in
})

mockCreate.mockImplementationOnce(() => ({
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
}))

await handler.createMessage("system", []).next()

const callArgs = mockCreate.mock.calls[0][0] as any
expect(callArgs.reasoning_effort).toBe("high")
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: true })
expect(callArgs.parse_reasoning).toBe(true)
expect(callArgs.include_reasoning).toBe(true)
})

it("should include parse_reasoning for always-reasoning MiniMax-M2.5", async () => {
const handler = new FriendliHandler({
apiModelId: "MiniMaxAI/MiniMax-M2.5",
friendliApiKey: "test-key",
enableReasoningEffort: true,
})

mockCreate.mockImplementationOnce(() => ({
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
}))

await handler.createMessage("system", []).next()

const callArgs = mockCreate.mock.calls[0][0] as any
expect(callArgs.parse_reasoning).toBe(true)
expect(callArgs.include_reasoning).toBe(true)
expect(callArgs.chat_template_kwargs).toBeUndefined()
expect(callArgs.reasoning_effort).toBeUndefined()
})

it("should not include any reasoning params for non-reasoning DeepSeek-V3.2", async () => {
const handler = new FriendliHandler({
apiModelId: "deepseek-ai/DeepSeek-V3.2",
friendliApiKey: "test-key",
enableReasoningEffort: true,
reasoningEffort: "high",
})

mockCreate.mockImplementationOnce(() => ({
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
}))

await handler.createMessage("system", []).next()

const callArgs = mockCreate.mock.calls[0][0] as any
expect(callArgs.reasoning_effort).toBeUndefined()
expect(callArgs.chat_template_kwargs).toBeUndefined()
expect(callArgs.parse_reasoning).toBeUndefined()
})

it("should handle delta.reasoning_content from parse_reasoning=true stream", async () => {
const handler = new FriendliHandler({
apiModelId: "zai-org/GLM-5.2",
friendliApiKey: "test-key",
enableReasoningEffort: true,
reasoningEffort: "high",
})

mockCreate.mockImplementationOnce(async () => ({
[Symbol.asyncIterator]: async function* () {
yield {
choices: [{ delta: { reasoning_content: "Let me think..." } }],
usage: null,
}
yield {
choices: [{ delta: { content: "The answer is 42" } }],
usage: null,
}
yield {
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
}
},
}))

const stream = handler.createMessage("system", [])
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}

expect(chunks).toContainEqual({ type: "reasoning", text: "Let me think..." })
expect(chunks).toContainEqual({ type: "text", text: "The answer is 42" })
})

it("completePrompt should include reasoning params when enabled", async () => {
const handler = new FriendliHandler({
apiModelId: "zai-org/GLM-5.2",
friendliApiKey: "test-key",
enableReasoningEffort: true,
reasoningEffort: "medium",
})

mockCreate.mockResolvedValueOnce({
choices: [{ message: { content: "test result" } }],
})

await handler.completePrompt("test")

const callArgs = mockCreate.mock.calls[0][0] as any
expect(callArgs.reasoning_effort).toBe("medium")
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: true })
expect(callArgs.parse_reasoning).toBe(true)
})
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading
Loading