Skip to content

Commit c57292f

Browse files
committed
fix(nebius): normalize unsupported forced tools
1 parent 8f9b8ae commit c57292f

4 files changed

Lines changed: 68 additions & 13 deletions

File tree

‎apps/sim/components/icons/nebius.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { SVGProps } from 'react'
22

3-
export function NebiusIcon(props: SVGProps<SVGSVGElement>) {
3+
interface NebiusIconProps extends SVGProps<SVGSVGElement> {}
4+
5+
export function NebiusIcon(props: NebiusIconProps) {
46
return (
57
<svg {...props} viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
68
<rect width='32' height='32' fill='#052B42' rx='8' />
File renamed without changes.

‎apps/sim/providers/nebius/index.test.ts‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { providersTraceEnrichmentMock } from '@sim/testing/mocks/providers-trace
55
import { beforeEach, describe, expect, it, vi } from 'vitest'
66
import type { StreamingExecution } from '@/executor/types'
77
import { nebiusProvider } from '@/providers/nebius'
8-
import type { ProviderResponse } from '@/providers/types'
8+
import type { ProviderResponse, ProviderToolConfig } from '@/providers/types'
99

1010
vi.mock('@/providers', () => providersMock)
1111
vi.mock('@/providers/trace-enrichment', () => providersTraceEnrichmentMock)
@@ -90,6 +90,50 @@ describe('Nebius Chat Completions wire contract', () => {
9090
})
9191
})
9292

93+
it.each([
94+
{ model: 'nebius/deepseek-ai/DeepSeek-V4.1-Flash', forced: false },
95+
{ model: 'nebius/openbmb/MiniCPM-V-4_5', forced: false },
96+
{ model: 'nebius/openbmb/minicpm-v-4_5', forced: false },
97+
{ model: MODEL, forced: true },
98+
])('honors the forced-tool wire contract for $model', async (scenario) => {
99+
const tools: ProviderToolConfig[] = [
100+
{
101+
id: 'http_request',
102+
description: 'Fetch a URL',
103+
params: {},
104+
parameters: { type: 'object', properties: {}, required: [] },
105+
usageControl: 'force',
106+
},
107+
{
108+
id: 'disabled_tool',
109+
description: 'Disabled tool',
110+
params: {},
111+
parameters: { type: 'object', properties: {}, required: [] },
112+
usageControl: 'none',
113+
},
114+
]
115+
const payloads: Array<{
116+
tool_choice: unknown
117+
tools: Array<{ function: { name: string } }>
118+
}> = []
119+
vi.mocked(fetch).mockImplementationOnce(async (_url, init) => {
120+
payloads.push(JSON.parse(String(init?.body)))
121+
return jsonResponse(completion('42'))
122+
})
123+
const result = (await nebiusProvider.executeRequest({
124+
...REQUEST,
125+
model: scenario.model,
126+
tools,
127+
})) as ProviderResponse
128+
expect(payloads[0]?.tool_choice).toEqual(
129+
scenario.forced ? { type: 'function', function: { name: 'http_request' } } : 'auto'
130+
)
131+
expect(payloads[0]?.tools.map((tool) => tool.function.name)).toEqual(['http_request'])
132+
expect(result.content).toBe('42')
133+
expect(tools[0].usageControl).toBe('force')
134+
expect(tools[1].usageControl).toBe('none')
135+
})
136+
93137
it('decodes SSE reasoning and usage while retaining Nebius pricing', async () => {
94138
vi.mocked(fetch).mockImplementationOnce(async (_url, init) => {
95139
const payload = JSON.parse(String(init?.body))

‎apps/sim/providers/nebius/index.ts‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getModelCapabilities,
77
getProviderDefaultModel,
88
getProviderModels,
9+
supportsForcedToolUse,
910
} from '@/providers/models'
1011
import {
1112
type ChatCompletionPayload,
@@ -58,16 +59,24 @@ export const nebiusProvider: ProviderConfig = {
5859
(model) => model.toLowerCase() === request.model.toLowerCase()
5960
)
6061
const requestedModel = (catalogModel ?? request.model).replace(/^nebius\//i, '')
61-
return executeChatCompletionRequest(request, {
62-
providerId: 'nebius',
63-
providerName: 'Nebius',
64-
client,
65-
requestedModel,
66-
reportedModel: request.model,
67-
logger,
68-
applyResponseFormat,
69-
reasoningFields: ['reasoning_content', 'reasoning'],
70-
recordPendingUsage: true,
71-
})
62+
const tools = supportsForcedToolUse(`nebius/${requestedModel}`)
63+
? request.tools
64+
: request.tools?.map((tool) =>
65+
tool.usageControl === 'force' ? { ...tool, usageControl: 'auto' as const } : tool
66+
)
67+
return executeChatCompletionRequest(
68+
{ ...request, tools },
69+
{
70+
providerId: 'nebius',
71+
providerName: 'Nebius',
72+
client,
73+
requestedModel,
74+
reportedModel: request.model,
75+
logger,
76+
applyResponseFormat,
77+
reasoningFields: ['reasoning_content', 'reasoning'],
78+
recordPendingUsage: true,
79+
}
80+
)
7281
},
7382
}

0 commit comments

Comments
 (0)