fix(openai): send native tool definitions by default (#10314)

Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
Hannes Rudolph 2025-12-23 15:57:18 -07:00 committed by GitHub
parent dfa7c357c2
commit 6afc59b928
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 85 additions and 1 deletions

View file

@ -0,0 +1,77 @@
import OpenAI from "openai"
import { OpenAiHandler } from "../openai"
describe("OpenAiHandler native tools", () => {
it("includes tools in request when custom model info lacks supportsNativeTools (regression test)", async () => {
const mockCreate = vi.fn().mockImplementationOnce(() => ({
[Symbol.asyncIterator]: async function* () {
yield {
choices: [{ delta: { content: "Test response" } }],
}
},
}))
// Set openAiCustomModelInfo WITHOUT supportsNativeTools to simulate
// a user-provided custom model info that doesn't specify native tool support.
// The getModel() fix should merge NATIVE_TOOL_DEFAULTS to ensure
// supportsNativeTools defaults to true.
const handler = new OpenAiHandler({
openAiApiKey: "test-key",
openAiBaseUrl: "https://example.com/v1",
openAiModelId: "test-model",
openAiCustomModelInfo: {
maxTokens: 4096,
contextWindow: 128000,
},
} as unknown as import("../../../shared/api").ApiHandlerOptions)
// Patch the OpenAI client call
const mockClient = {
chat: {
completions: {
create: mockCreate,
},
},
} as unknown as OpenAI
;(handler as unknown as { client: OpenAI }).client = mockClient
const tools: OpenAI.Chat.ChatCompletionTool[] = [
{
type: "function",
function: {
name: "test_tool",
description: "test",
parameters: { type: "object", properties: {} },
},
},
]
// Mimic the behavior in Task.attemptApiRequest() where tools are only
// included when modelInfo.supportsNativeTools is true. This is the
// actual regression path being tested - without the getModel() fix,
// supportsNativeTools would be undefined and tools wouldn't be passed.
const modelInfo = handler.getModel().info
const supportsNativeTools = modelInfo.supportsNativeTools ?? false
const stream = handler.createMessage("system", [], {
taskId: "test-task-id",
...(supportsNativeTools && { tools }),
...(supportsNativeTools && { toolProtocol: "native" as const }),
})
await stream.next()
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
tools: expect.arrayContaining([
expect.objectContaining({
type: "function",
function: expect.objectContaining({ name: "test_tool" }),
}),
]),
parallel_tool_calls: false,
}),
expect.anything(),
)
})
})

View file

@ -6,6 +6,7 @@ import {
type ModelInfo,
azureOpenAiDefaultApiVersion,
openAiModelInfoSaneDefaults,
NATIVE_TOOL_DEFAULTS,
DEEP_SEEK_DEFAULT_TEMPERATURE,
OPENAI_AZURE_AI_INFERENCE_PATH,
} from "@roo-code/types"
@ -291,7 +292,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
override getModel() {
const id = this.options.openAiModelId ?? ""
const info = this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults
// Ensure OpenAI-compatible models default to supporting native tool calling.
// This is required for [`Task.attemptApiRequest()`](src/core/task/Task.ts:3817) to
// include tool definitions in the request.
const info: ModelInfo = {
...NATIVE_TOOL_DEFAULTS,
...(this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults),
}
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
return { id, info, ...params }
}