Skip to content

Commit cf61f74

Browse files
christsoclaude
andcommitted
feat(core): add api_format option to OpenAI/Azure targets
Add api_format field ("chat" | "responses") to OpenAI and Azure target configs. Defaults to "chat" (Chat Completions API) which is universally supported by all OpenAI-compatible endpoints. Users can opt in to the Responses API by setting api_format: responses. Closes #896 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5a24352 commit cf61f74

6 files changed

Lines changed: 78 additions & 9 deletions

File tree

apps/web/src/content/docs/docs/targets/llm-providers.mdx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,52 @@ sidebar:
77

88
LLM provider targets call language model APIs directly. These are used both as evaluation targets and as grader targets for scoring.
99

10+
## OpenAI
11+
12+
```yaml
13+
targets:
14+
- name: openai-target
15+
provider: openai
16+
api_key: ${{ OPENAI_API_KEY }}
17+
model: gpt-4o
18+
```
19+
20+
| Field | Required | Description |
21+
|-------|----------|-------------|
22+
| `api_key` | Yes | OpenAI API key |
23+
| `model` | Yes | Model identifier |
24+
| `base_url` | No | Custom base URL for OpenAI-compatible endpoints |
25+
| `api_format` | No | API format: `chat` (default) or `responses` |
26+
27+
### `api_format`
28+
29+
Controls which OpenAI API endpoint is used:
30+
31+
| Value | Endpoint | When to use |
32+
|-------|----------|-------------|
33+
| `chat` (default) | `/chat/completions` | All OpenAI-compatible endpoints (GitHub Models, local proxies, etc.) |
34+
| `responses` | `/responses` | Only `api.openai.com` — opt in to the Responses API |
35+
36+
Most users should leave this unset. The default `chat` format is universally supported. Use `responses` only when you need Responses API features on `api.openai.com` directly.
37+
38+
```yaml
39+
# OpenAI-compatible endpoint (default chat format works)
40+
targets:
41+
- name: github-models
42+
provider: openai
43+
api_format: chat
44+
base_url: https://models.github.ai/inference/v1
45+
api_key: ${{ GH_MODELS_TOKEN }}
46+
model: ${{ GH_MODELS_MODEL }}
47+
48+
# Opt in to Responses API for api.openai.com
49+
- name: openai-responses
50+
provider: openai
51+
api_format: responses
52+
api_key: ${{ OPENAI_API_KEY }}
53+
model: gpt-4o
54+
```
55+
1056
## Azure OpenAI
1157

1258
```yaml

packages/core/src/evaluation/providers/ai-sdk.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,8 @@ export class OpenAIProvider implements Provider {
5353
apiKey: config.apiKey,
5454
baseURL: config.baseURL,
5555
});
56-
// Default to Chat Completions API (/chat/completions) which is
57-
// universally supported by all OpenAI-compatible endpoints.
58-
// Only use the Responses API (/responses) for actual OpenAI, which
59-
// is the only provider that supports it.
60-
const isOpenAI = config.baseURL.includes('api.openai.com');
61-
this.model = isOpenAI ? openai(config.model) : openai.chat(config.model);
56+
this.model =
57+
config.apiFormat === 'responses' ? openai(config.model) : openai.chat(config.model);
6258
}
6359

6460
async invoke(request: ProviderRequest): Promise<ProviderResponse> {

packages/core/src/evaluation/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export { extractLastAssistantContent } from './types.js';
4242
export type {
4343
AgentVResolvedConfig,
4444
AnthropicResolvedConfig,
45+
ApiFormat,
4546
AzureResolvedConfig,
4647
ClaudeResolvedConfig,
4748
CliResolvedConfig,

packages/core/src/evaluation/providers/targets.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,15 @@ export interface RetryConfig {
389389
readonly retryableStatusCodes?: readonly number[];
390390
}
391391

392+
/**
393+
* Selects which OpenAI-compatible API endpoint to use.
394+
* - "chat" (default): POST /chat/completions — universally supported by all OpenAI-compatible providers.
395+
* - "responses": POST /responses — only supported by api.openai.com.
396+
*
397+
* Maps to Vercel AI SDK methods: "chat" → provider.chat(model), "responses" → provider(model).
398+
*/
399+
export type ApiFormat = 'chat' | 'responses';
400+
392401
/**
393402
* Azure OpenAI settings used by the Vercel AI SDK.
394403
*/
@@ -409,6 +418,7 @@ export interface OpenAIResolvedConfig {
409418
readonly baseURL: string;
410419
readonly apiKey: string;
411420
readonly model: string;
421+
readonly apiFormat?: ApiFormat;
412422
readonly temperature?: number;
413423
readonly maxOutputTokens?: number;
414424
readonly retry?: RetryConfig;
@@ -927,6 +937,18 @@ function resolveAzureConfig(
927937
};
928938
}
929939

940+
function resolveApiFormat(
941+
target: z.infer<typeof BASE_TARGET_SCHEMA>,
942+
targetName: string,
943+
): ApiFormat | undefined {
944+
const raw = target.api_format ?? target.apiFormat;
945+
if (raw === undefined) return undefined;
946+
if (raw === 'chat' || raw === 'responses') return raw;
947+
throw new Error(
948+
`Invalid api_format '${raw}' for target '${targetName}'. Must be 'chat' or 'responses'.`,
949+
);
950+
}
951+
930952
function resolveOpenAIConfig(
931953
target: z.infer<typeof BASE_TARGET_SCHEMA>,
932954
env: EnvLookup,
@@ -951,6 +973,7 @@ function resolveOpenAIConfig(
951973
baseURL,
952974
apiKey,
953975
model,
976+
apiFormat: resolveApiFormat(target, target.name),
954977
temperature: resolveOptionalNumber(temperatureSource, `${target.name} temperature`),
955978
maxOutputTokens: resolveOptionalNumber(maxTokensSource, `${target.name} max output tokens`),
956979
retry,

packages/core/src/evaluation/validation/targets-validator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ const OPENAI_SETTINGS = new Set([
6060
'model',
6161
'deployment',
6262
'variant',
63+
'api_format',
64+
'apiFormat',
6365
'temperature',
6466
'max_output_tokens',
6567
'maxTokens',

packages/core/test/evaluation/providers/targets.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ const createAzureMock = mock((options: unknown) => ({
2323
chat: () => ({ provider: 'azure', options }),
2424
}));
2525
const createOpenAIMock = mock((options: unknown) => {
26-
const defaultFn = () => ({ provider: 'openai', options });
27-
defaultFn.chat = () => ({ provider: 'openai', options, api: 'chat' });
28-
return defaultFn;
26+
const fn = () => ({ provider: 'openai', options });
27+
fn.chat = () => ({ provider: 'openai', options });
28+
fn.responses = () => ({ provider: 'openai', options });
29+
return fn;
2930
});
3031
const createOpenRouterMock = mock((options: unknown) => () => ({
3132
provider: 'openrouter',

0 commit comments

Comments
 (0)