oh-my-promptfoo 1.22.1 → 1.22.2-next.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/providers.cjs +43 -9
- package/dist/providers.cjs.map +1 -1
- package/dist/providers.d.cts +5 -0
- package/dist/providers.d.ts +5 -0
- package/dist/providers.js +43 -9
- package/dist/providers.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,6 +135,7 @@ providers:
|
|
|
135
135
|
config:
|
|
136
136
|
model: gpt-5.6-sol # required, exactly as Copilot lists it
|
|
137
137
|
reasoning_effort: medium # optional: low, medium or high
|
|
138
|
+
max_tokens: 32000 # optional; defaults to the model's output limit
|
|
138
139
|
defaultTest:
|
|
139
140
|
options:
|
|
140
141
|
provider:
|
|
@@ -145,7 +146,7 @@ defaultTest:
|
|
|
145
146
|
response_format: { type: json_object } # optional; the only accepted format
|
|
146
147
|
```
|
|
147
148
|
|
|
148
|
-
Those
|
|
149
|
+
Those four keys are the whole config. The endpoint and credentials always come
|
|
149
150
|
from Copilot: a private Copilot SDK process signs in with `GH_TOKEN` (never your
|
|
150
151
|
stored logins or `OPENAI_*`, `ANTHROPIC_*` or `AZURE_*` settings), asks Copilot for
|
|
151
152
|
the model's endpoint and short-lived credentials, and refreshes them before they
|
|
@@ -169,6 +170,21 @@ API in its own shape:
|
|
|
169
170
|
- `response_format: { type: json_object }` works on Responses and Chat
|
|
170
171
|
Completions. The Anthropic Messages API has no JSON-object mode, so a Claude
|
|
171
172
|
model with `response_format` fails and names the model.
|
|
173
|
+
- `max_tokens` caps each reply's output tokens: the Responses
|
|
174
|
+
`max_output_tokens`, the Chat Completions `max_tokens` (or
|
|
175
|
+
`max_completion_tokens` for models Promptfoo treats as reasoning models) and
|
|
176
|
+
the Messages `max_tokens`. Without it, the cap is the model's
|
|
177
|
+
`capabilities.limits.max_output_tokens` from Copilot's model list, so long
|
|
178
|
+
answers are not cut off at Promptfoo's default of 1024 (2048 for Claude
|
|
179
|
+
thinking models). When the list reports no limit, Promptfoo's default
|
|
180
|
+
applies. The cap in force is in `metadata.copilotMaxOutputTokens`.
|
|
181
|
+
`OPENAI_MAX_TOKENS`, `OPENAI_MAX_COMPLETION_TOKENS` and
|
|
182
|
+
`ANTHROPIC_MAX_TOKENS` never override it.
|
|
183
|
+
- No request carries a `temperature`, so each model uses its own default.
|
|
184
|
+
Promptfoo's default of 0 and `OPENAI_TEMPERATURE` or `ANTHROPIC_TEMPERATURE`
|
|
185
|
+
are not sent; some models, such as Claude Opus 4.7 and later, reject them.
|
|
186
|
+
- Messages requests stream, because the Anthropic SDK refuses a
|
|
187
|
+
non-streaming request with a large `max_tokens`.
|
|
172
188
|
- Claude replies contain only the answer, not its thinking.
|
|
173
189
|
|
|
174
190
|
Every instance in a run shares that one SDK process. The provider id is
|
package/dist/providers.cjs
CHANGED
|
@@ -5659,6 +5659,7 @@ const CONFIG_KEYS = {
|
|
|
5659
5659
|
model: true,
|
|
5660
5660
|
reasoning_effort: true,
|
|
5661
5661
|
response_format: true,
|
|
5662
|
+
max_tokens: true,
|
|
5662
5663
|
basePath: true
|
|
5663
5664
|
};
|
|
5664
5665
|
/** Validate authored config; anything that could redirect endpoint or authentication is rejected. */
|
|
@@ -5667,17 +5668,30 @@ function copilotModelConfig(input) {
|
|
|
5667
5668
|
const config = input;
|
|
5668
5669
|
const unsupported = Object.keys(config).filter((key) => !Object.hasOwn(CONFIG_KEYS, key));
|
|
5669
5670
|
if (unsupported.length) throw new Error(`CopilotModel does not accept config.${unsupported.join(", config.")}; the endpoint and credentials come from Copilot discovery.`);
|
|
5670
|
-
const { model, reasoning_effort, response_format } = config;
|
|
5671
|
+
const { model, reasoning_effort, response_format, max_tokens } = config;
|
|
5671
5672
|
if (typeof model !== "string" || !model.trim() || model !== model.trim()) throw new Error("CopilotModel requires an explicit config.model.");
|
|
5672
5673
|
if (reasoning_effort !== void 0 && !EFFORTS.includes(reasoning_effort)) throw new Error(`CopilotModel config.reasoning_effort must be one of: ${EFFORTS.join(", ")}.`);
|
|
5673
5674
|
if (response_format !== void 0 && (typeof response_format !== "object" || response_format === null || Object.keys(response_format).join() !== "type" || !("type" in response_format) || response_format.type !== "json_object")) throw new Error("CopilotModel config.response_format must be { type: \"json_object\" }.");
|
|
5675
|
+
if (max_tokens !== void 0 && !positiveInteger(max_tokens)) throw new Error("CopilotModel config.max_tokens must be a positive integer.");
|
|
5674
5676
|
return Object.freeze({
|
|
5675
5677
|
model,
|
|
5676
5678
|
...reasoning_effort === void 0 ? {} : { reasoning_effort },
|
|
5677
|
-
...response_format === void 0 ? {} : { response_format: Object.freeze({ type: "json_object" }) }
|
|
5679
|
+
...response_format === void 0 ? {} : { response_format: Object.freeze({ type: "json_object" }) },
|
|
5680
|
+
...max_tokens === void 0 ? {} : { max_tokens }
|
|
5678
5681
|
});
|
|
5679
5682
|
}
|
|
5680
5683
|
const describe = (value) => typeof value === "string" ? JSON.stringify(value) : "none";
|
|
5684
|
+
const positiveInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
5685
|
+
/**
|
|
5686
|
+
* The model's maximum output tokens from Copilot's model list
|
|
5687
|
+
* (`capabilities.limits.max_output_tokens`), when reported.
|
|
5688
|
+
*/
|
|
5689
|
+
function modelOutputLimit(models, model) {
|
|
5690
|
+
if (!Array.isArray(models)) return void 0;
|
|
5691
|
+
const limits = models.find((candidate) => candidate?.id === model)?.capabilities?.limits;
|
|
5692
|
+
const limit = limits && typeof limits === "object" ? limits.max_output_tokens : void 0;
|
|
5693
|
+
return positiveInteger(limit) ? limit : void 0;
|
|
5694
|
+
}
|
|
5681
5695
|
/** The Copilot API a discovery snapshot describes; anything but the three HTTP APIs is rejected. */
|
|
5682
5696
|
function endpointOf(snapshot, model) {
|
|
5683
5697
|
const { type, wireApi, transport } = snapshot ?? {};
|
|
@@ -5751,8 +5765,15 @@ function anthropicReasoning(models, model, effort) {
|
|
|
5751
5765
|
effort
|
|
5752
5766
|
};
|
|
5753
5767
|
}
|
|
5754
|
-
/**
|
|
5755
|
-
|
|
5768
|
+
/**
|
|
5769
|
+
* The Promptfoo provider id and options that send `config` to the discovered endpoint.
|
|
5770
|
+
* `maxOutputTokens` replaces Promptfoo's default output cap and ambient `*_MAX_TOKENS`;
|
|
5771
|
+
* without it Promptfoo's default stands. Temperature is never sent: Promptfoo defaults it
|
|
5772
|
+
* to 0 (or ambient `*_TEMPERATURE`), which some models reject, so an `undefined` override
|
|
5773
|
+
* that is spread last drops the key from the request body.
|
|
5774
|
+
*/
|
|
5775
|
+
function nativeProvider(config, snapshot, { endpoint, headers }, models, maxOutputTokens) {
|
|
5776
|
+
const noTemperature = { temperature: void 0 };
|
|
5756
5777
|
const { model, reasoning_effort, response_format } = config;
|
|
5757
5778
|
if (endpoint === "messages") {
|
|
5758
5779
|
if (response_format !== void 0) throw new Error(`CopilotModel config.response_format is not supported for ${model}: Copilot serves it over the Anthropic Messages API, which has no JSON-object mode.`);
|
|
@@ -5762,6 +5783,9 @@ function nativeProvider(config, snapshot, { endpoint, headers }, models) {
|
|
|
5762
5783
|
options: {
|
|
5763
5784
|
config: {
|
|
5764
5785
|
...reasoning_effort === void 0 ? {} : anthropicReasoning(models, model, reasoning_effort),
|
|
5786
|
+
...maxOutputTokens === void 0 ? {} : { max_tokens: maxOutputTokens },
|
|
5787
|
+
stream: true,
|
|
5788
|
+
extra_body: noTemperature,
|
|
5765
5789
|
showThinking: false,
|
|
5766
5790
|
apiBaseUrl: snapshot.baseUrl,
|
|
5767
5791
|
apiKey: snapshot.apiKey,
|
|
@@ -5785,9 +5809,14 @@ function nativeProvider(config, snapshot, { endpoint, headers }, models) {
|
|
|
5785
5809
|
if (endpoint === "chat/completions") return {
|
|
5786
5810
|
id: `openai:chat:${model}`,
|
|
5787
5811
|
options: { config: {
|
|
5788
|
-
...reasoning_effort === void 0 ? {} : {
|
|
5789
|
-
|
|
5790
|
-
|
|
5812
|
+
...reasoning_effort === void 0 ? {} : { reasoning_effort },
|
|
5813
|
+
...maxOutputTokens === void 0 ? {} : {
|
|
5814
|
+
max_tokens: maxOutputTokens,
|
|
5815
|
+
max_completion_tokens: maxOutputTokens
|
|
5816
|
+
},
|
|
5817
|
+
passthrough: {
|
|
5818
|
+
...reasoning_effort === void 0 ? {} : { reasoning_effort },
|
|
5819
|
+
...noTemperature
|
|
5791
5820
|
},
|
|
5792
5821
|
...openai
|
|
5793
5822
|
} }
|
|
@@ -5796,6 +5825,8 @@ function nativeProvider(config, snapshot, { endpoint, headers }, models) {
|
|
|
5796
5825
|
id: `openai:responses:${model}`,
|
|
5797
5826
|
options: { config: {
|
|
5798
5827
|
...reasoning_effort === void 0 ? {} : { reasoning_effort },
|
|
5828
|
+
...maxOutputTokens === void 0 ? {} : { max_output_tokens: maxOutputTokens },
|
|
5829
|
+
passthrough: noTemperature,
|
|
5799
5830
|
store: false,
|
|
5800
5831
|
...openai
|
|
5801
5832
|
} }
|
|
@@ -5857,7 +5888,8 @@ function copilotReply(response, generation) {
|
|
|
5857
5888
|
metadata: {
|
|
5858
5889
|
...reply.metadata,
|
|
5859
5890
|
copilotEndpoint: generation.endpoint,
|
|
5860
|
-
...generation.prices ? { copilotTokenPrices: generation.prices } : {}
|
|
5891
|
+
...generation.prices ? { copilotTokenPrices: generation.prices } : {},
|
|
5892
|
+
...generation.maxOutputTokens === void 0 ? {} : { copilotMaxOutputTokens: generation.maxOutputTokens }
|
|
5861
5893
|
}
|
|
5862
5894
|
}, generation.endpoint);
|
|
5863
5895
|
}
|
|
@@ -5897,7 +5929,8 @@ function createCopilotModel({ createEndpointClient = () => createCopilotEndpoint
|
|
|
5897
5929
|
try {
|
|
5898
5930
|
models = await owner.models();
|
|
5899
5931
|
} catch {}
|
|
5900
|
-
const
|
|
5932
|
+
const maxOutputTokens = config.max_tokens ?? modelOutputLimit(models, config.model);
|
|
5933
|
+
const { id, options } = nativeProvider(config, snapshot, route, models, maxOutputTokens);
|
|
5901
5934
|
const { endpoint, refreshAt } = route;
|
|
5902
5935
|
const prices = tokenPrices(models, config.model);
|
|
5903
5936
|
const provider = await loadProvider(id, { options });
|
|
@@ -5907,6 +5940,7 @@ function createCopilotModel({ createEndpointClient = () => createCopilotEndpoint
|
|
|
5907
5940
|
provider,
|
|
5908
5941
|
endpoint,
|
|
5909
5942
|
prices,
|
|
5943
|
+
maxOutputTokens,
|
|
5910
5944
|
refreshAt,
|
|
5911
5945
|
active: 0,
|
|
5912
5946
|
retired: false
|