Skip to content

Commit eb3cd4c

Browse files
committed
feat(friendli): add reasoning params support — reasoning_effort, chat_template_kwargs, parse_reasoning
- Add supportsReasoningEffort + reasoningEffort to GLM-5.x model catalog entries - Add supportsReasoningBinary to MiniMax-M2.5 model catalog entry - Add 'ultracode' to supportsReasoningEffort schema in model.ts - Override createStream + completePrompt in FriendliHandler - buildFriendliReasoningParams(): injects Friendli-specific reasoning params - Controllable (GLM-5.x): reasoning_effort + chat_template_kwargs + parse_reasoning - Always-reasoning (MiniMax-M2.5): parse_reasoning only - Non-reasoning: no extra params - Add 8 reasoning-specific test cases (29 total)
1 parent 9e218a5 commit eb3cd4c

3 files changed

Lines changed: 366 additions & 3 deletions

File tree

‎packages/types/src/providers/friendli.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ export const friendliModels = {
2020
outputPrice: 4.4,
2121
cacheWritesPrice: 0,
2222
cacheReadsPrice: 0.26,
23+
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],
24+
reasoningEffort: "high",
2325
description:
24-
"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.",
26+
"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)",
2527
},
2628
"zai-org/GLM-5.1": {
2729
maxTokens: 131_072,
@@ -33,8 +35,10 @@ export const friendliModels = {
3335
outputPrice: 4.4,
3436
cacheWritesPrice: 0,
3537
cacheReadsPrice: 0.26,
38+
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],
39+
reasoningEffort: "high",
3640
description:
37-
"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.",
41+
"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)",
3842
},
3943
"deepseek-ai/DeepSeek-V3.2": {
4044
maxTokens: 16384,
@@ -57,7 +61,8 @@ export const friendliModels = {
5761
outputPrice: 1.2,
5862
cacheWritesPrice: 0,
5963
cacheReadsPrice: 0.06,
64+
supportsReasoningBinary: true,
6065
description:
61-
"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.",
66+
"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)",
6267
},
6368
} as const satisfies Record<string, ModelInfo>

‎src/api/providers/__tests__/friendli.spec.ts‎

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,190 @@ describe("Friendli model max output tokens (clamping behavior)", () => {
394394
expect(result).toBe(80_000)
395395
})
396396
})
397+
398+
describe("FriendliHandler — Friendli-specific reasoning params", () => {
399+
beforeEach(() => {
400+
vi.clearAllMocks()
401+
})
402+
403+
it("should include reasoning_effort, chat_template_kwargs, parse_reasoning for GLM-5.2 with reasoning enabled", async () => {
404+
const handler = new FriendliHandler({
405+
apiModelId: "zai-org/GLM-5.2",
406+
friendliApiKey: "test-key",
407+
enableReasoningEffort: true,
408+
reasoningEffort: "high",
409+
})
410+
411+
mockCreate.mockImplementationOnce(() => ({
412+
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
413+
}))
414+
415+
await handler.createMessage("system", []).next()
416+
417+
expect(mockCreate).toHaveBeenCalledWith(
418+
expect.objectContaining({
419+
model: "zai-org/GLM-5.2",
420+
reasoning_effort: "high",
421+
chat_template_kwargs: { enable_thinking: true },
422+
parse_reasoning: true,
423+
include_reasoning: true,
424+
}),
425+
undefined,
426+
)
427+
})
428+
429+
it("should not send any reasoning params when enableReasoningEffort is false — model default applies", async () => {
430+
const handler = new FriendliHandler({
431+
apiModelId: "zai-org/GLM-5.2",
432+
friendliApiKey: "test-friendli-api-key",
433+
enableReasoningEffort: false,
434+
})
435+
436+
mockCreate.mockImplementationOnce(() => ({
437+
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
438+
}))
439+
440+
await handler.createMessage("system", []).next()
441+
442+
const callArgs = mockCreate.mock.calls[0][0] as any
443+
expect(callArgs.reasoning_effort).toBeUndefined()
444+
expect(callArgs.chat_template_kwargs).toBeUndefined()
445+
expect(callArgs.parse_reasoning).toBeUndefined()
446+
expect(callArgs.include_reasoning).toBeUndefined()
447+
})
448+
449+
it("should not send any reasoning params when reasoningEffort is none — model default applies", async () => {
450+
const handler = new FriendliHandler({
451+
apiModelId: "zai-org/GLM-5.2",
452+
friendliApiKey: "test-friendli-api-key",
453+
enableReasoningEffort: true,
454+
reasoningEffort: "none",
455+
})
456+
457+
mockCreate.mockImplementationOnce(() => ({
458+
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
459+
}))
460+
461+
await handler.createMessage("system", []).next()
462+
463+
const callArgs = mockCreate.mock.calls[0][0] as any
464+
expect(callArgs.reasoning_effort).toBeUndefined()
465+
expect(callArgs.chat_template_kwargs).toBeUndefined()
466+
expect(callArgs.parse_reasoning).toBeUndefined()
467+
})
468+
469+
it("should not send any reasoning params when reasoningEffort is disable — model default applies", async () => {
470+
const handler = new FriendliHandler({
471+
apiModelId: "zai-org/GLM-5.2",
472+
friendliApiKey: "test-friendli-api-key",
473+
enableReasoningEffort: true,
474+
reasoningEffort: "disable",
475+
})
476+
477+
mockCreate.mockImplementationOnce(() => ({
478+
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
479+
}))
480+
481+
await handler.createMessage("system", []).next()
482+
483+
const callArgs = mockCreate.mock.calls[0][0] as any
484+
expect(callArgs.reasoning_effort).toBeUndefined()
485+
expect(callArgs.chat_template_kwargs).toBeUndefined()
486+
expect(callArgs.parse_reasoning).toBeUndefined()
487+
})
488+
489+
it("should include parse_reasoning for always-reasoning MiniMax-M2.5", async () => {
490+
const handler = new FriendliHandler({
491+
apiModelId: "MiniMaxAI/MiniMax-M2.5",
492+
friendliApiKey: "test-key",
493+
enableReasoningEffort: true,
494+
})
495+
496+
mockCreate.mockImplementationOnce(() => ({
497+
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
498+
}))
499+
500+
await handler.createMessage("system", []).next()
501+
502+
const callArgs = mockCreate.mock.calls[0][0] as any
503+
expect(callArgs.parse_reasoning).toBe(true)
504+
expect(callArgs.include_reasoning).toBe(true)
505+
expect(callArgs.chat_template_kwargs).toBeUndefined()
506+
expect(callArgs.reasoning_effort).toBeUndefined()
507+
})
508+
509+
it("should not include any reasoning params for non-reasoning DeepSeek-V3.2", async () => {
510+
const handler = new FriendliHandler({
511+
apiModelId: "deepseek-ai/DeepSeek-V3.2",
512+
friendliApiKey: "test-key",
513+
enableReasoningEffort: true,
514+
reasoningEffort: "high",
515+
})
516+
517+
mockCreate.mockImplementationOnce(() => ({
518+
[Symbol.asyncIterator]: () => ({ async next() { return { done: true } } }),
519+
}))
520+
521+
await handler.createMessage("system", []).next()
522+
523+
const callArgs = mockCreate.mock.calls[0][0] as any
524+
expect(callArgs.reasoning_effort).toBeUndefined()
525+
expect(callArgs.chat_template_kwargs).toBeUndefined()
526+
expect(callArgs.parse_reasoning).toBeUndefined()
527+
})
528+
529+
it("should handle delta.reasoning_content from parse_reasoning=true stream", async () => {
530+
const handler = new FriendliHandler({
531+
apiModelId: "zai-org/GLM-5.2",
532+
friendliApiKey: "test-key",
533+
enableReasoningEffort: true,
534+
reasoningEffort: "high",
535+
})
536+
537+
mockCreate.mockImplementationOnce(async () => ({
538+
[Symbol.asyncIterator]: async function* () {
539+
yield {
540+
choices: [{ delta: { reasoning_content: "Let me think..." } }],
541+
usage: null,
542+
}
543+
yield {
544+
choices: [{ delta: { content: "The answer is 42" } }],
545+
usage: null,
546+
}
547+
yield {
548+
choices: [{ delta: {} }],
549+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
550+
}
551+
},
552+
}))
553+
554+
const stream = handler.createMessage("system", [])
555+
const chunks = []
556+
for await (const chunk of stream) {
557+
chunks.push(chunk)
558+
}
559+
560+
expect(chunks).toContainEqual({ type: "reasoning", text: "Let me think..." })
561+
expect(chunks).toContainEqual({ type: "text", text: "The answer is 42" })
562+
})
563+
564+
it("completePrompt should include reasoning params when enabled", async () => {
565+
const handler = new FriendliHandler({
566+
apiModelId: "zai-org/GLM-5.2",
567+
friendliApiKey: "test-key",
568+
enableReasoningEffort: true,
569+
reasoningEffort: "medium",
570+
})
571+
572+
mockCreate.mockResolvedValueOnce({
573+
choices: [{ message: { content: "test result" } }],
574+
})
575+
576+
await handler.completePrompt("test")
577+
578+
const callArgs = mockCreate.mock.calls[0][0] as any
579+
expect(callArgs.reasoning_effort).toBe("medium")
580+
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: true })
581+
expect(callArgs.parse_reasoning).toBe(true)
582+
})
583+
})

0 commit comments

Comments
 (0)