fix: OpenAI adapter tool calling compatibility
Two fixes for OpenAI-compatible provider compatibility: 1. Sanitize JSON Schema `const` → `enum` in tool parameters. Many OpenAI-compatible endpoints (Ollama, DeepSeek, vLLM, etc.) do not support the `const` keyword in JSON Schema. Recursively convert `const: value` to `enum: [value]` which is semantically equivalent. 2. Force stop_reason to `tool_use` when tool_calls are present. Some backends incorrectly return finish_reason "stop" even when the response contains tool_calls. Without this fix, the query loop treats the response as a normal end_turn and never executes the requested tools.
This commit is contained in:
parent
70d6b28853
commit
a442aee5f4
|
|
@ -1,5 +1,8 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
import { anthropicToolsToOpenAI, anthropicToolChoiceToOpenAI } from '../convertTools.js'
|
||||
import {
|
||||
anthropicToolsToOpenAI,
|
||||
anthropicToolChoiceToOpenAI,
|
||||
} from '../convertTools.js'
|
||||
|
||||
describe('anthropicToolsToOpenAI', () => {
|
||||
test('converts basic tool', () => {
|
||||
|
|
@ -18,25 +21,30 @@ describe('anthropicToolsToOpenAI', () => {
|
|||
|
||||
const result = anthropicToolsToOpenAI(tools as any)
|
||||
|
||||
expect(result).toEqual([{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'bash',
|
||||
description: 'Run a bash command',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: { command: { type: 'string' } },
|
||||
required: ['command'],
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'bash',
|
||||
description: 'Run a bash command',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: { command: { type: 'string' } },
|
||||
required: ['command'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}])
|
||||
])
|
||||
})
|
||||
|
||||
test('uses empty schema when input_schema missing', () => {
|
||||
const tools = [{ type: 'custom', name: 'noop', description: 'no-op' }]
|
||||
const result = anthropicToolsToOpenAI(tools as any)
|
||||
|
||||
expect((result[0] as { function: { parameters: unknown } }).function.parameters).toEqual({ type: 'object', properties: {} })
|
||||
expect(result[0].function.parameters).toEqual({
|
||||
type: 'object',
|
||||
properties: {},
|
||||
})
|
||||
})
|
||||
|
||||
test('strips Anthropic-specific fields', () => {
|
||||
|
|
@ -76,7 +84,7 @@ describe('anthropicToolsToOpenAI', () => {
|
|||
},
|
||||
]
|
||||
const result = anthropicToolsToOpenAI(tools as any)
|
||||
const props = (result[0] as { function: { parameters: any } }).function.parameters as any
|
||||
const props = result[0].function.parameters as any
|
||||
expect(props.properties.mode).toEqual({ enum: ['read'] })
|
||||
expect(props.properties.mode.const).toBeUndefined()
|
||||
expect(props.properties.name).toEqual({ type: 'string' })
|
||||
|
|
@ -110,8 +118,10 @@ describe('anthropicToolsToOpenAI', () => {
|
|||
},
|
||||
]
|
||||
const result = anthropicToolsToOpenAI(tools as any)
|
||||
const params = (result[0] as { function: { parameters: any } }).function.parameters as any
|
||||
expect(params.properties.outer.properties.inner).toEqual({ enum: ['fixed'] })
|
||||
const params = result[0].function.parameters as any
|
||||
expect(params.properties.outer.properties.inner).toEqual({
|
||||
enum: ['fixed'],
|
||||
})
|
||||
expect(params.definitions.MyType.properties.field).toEqual({ enum: [42] })
|
||||
})
|
||||
|
||||
|
|
@ -125,18 +135,14 @@ describe('anthropicToolsToOpenAI', () => {
|
|||
type: 'object',
|
||||
properties: {
|
||||
val: {
|
||||
anyOf: [
|
||||
{ const: 'a' },
|
||||
{ const: 'b' },
|
||||
{ type: 'string' },
|
||||
],
|
||||
anyOf: [{ const: 'a' }, { const: 'b' }, { type: 'string' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
const result = anthropicToolsToOpenAI(tools as any)
|
||||
const anyOf = ((result[0] as { function: { parameters: any } }).function.parameters as any).properties.val.anyOf
|
||||
const anyOf = (result[0].function.parameters as any).properties.val.anyOf
|
||||
expect(anyOf[0]).toEqual({ enum: ['a'] })
|
||||
expect(anyOf[1]).toEqual({ enum: ['b'] })
|
||||
expect(anyOf[2]).toEqual({ type: 'string' })
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user