promptfoo 0.20.0 → 0.21.0

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.
Files changed (184) hide show
  1. package/README.md +1 -1
  2. package/dist/package.json +4 -4
  3. package/dist/src/assertions.d.ts.map +1 -1
  4. package/dist/src/assertions.js +5 -0
  5. package/dist/src/assertions.js.map +1 -1
  6. package/dist/src/evaluator.js +1 -1
  7. package/dist/src/evaluator.js.map +1 -1
  8. package/dist/src/index.d.ts +1 -5
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +1 -1
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/matchers.d.ts +3 -2
  13. package/dist/src/matchers.d.ts.map +1 -1
  14. package/dist/src/matchers.js +37 -9
  15. package/dist/src/matchers.js.map +1 -1
  16. package/dist/src/providers/anthropic.d.ts +5 -3
  17. package/dist/src/providers/anthropic.d.ts.map +1 -1
  18. package/dist/src/providers/anthropic.js +8 -10
  19. package/dist/src/providers/anthropic.js.map +1 -1
  20. package/dist/src/providers/azureopenai.d.ts +9 -8
  21. package/dist/src/providers/azureopenai.d.ts.map +1 -1
  22. package/dist/src/providers/azureopenai.js +33 -36
  23. package/dist/src/providers/azureopenai.js.map +1 -1
  24. package/dist/src/providers/openai.d.ts +12 -12
  25. package/dist/src/providers/openai.d.ts.map +1 -1
  26. package/dist/src/providers/openai.js +54 -65
  27. package/dist/src/providers/openai.js.map +1 -1
  28. package/dist/src/providers/replicate.d.ts +4 -2
  29. package/dist/src/providers/replicate.d.ts.map +1 -1
  30. package/dist/src/providers/replicate.js +10 -8
  31. package/dist/src/providers/replicate.js.map +1 -1
  32. package/dist/src/providers/webhook.d.ts +9 -0
  33. package/dist/src/providers/webhook.d.ts.map +1 -0
  34. package/dist/src/providers/webhook.js +54 -0
  35. package/dist/src/providers/webhook.js.map +1 -0
  36. package/dist/src/providers.d.ts +1 -1
  37. package/dist/src/providers.d.ts.map +1 -1
  38. package/dist/src/providers.js +36 -28
  39. package/dist/src/providers.js.map +1 -1
  40. package/dist/src/suggestions.d.ts.map +1 -1
  41. package/dist/src/suggestions.js +1 -3
  42. package/dist/src/suggestions.js.map +1 -1
  43. package/dist/src/types.d.ts +7 -1
  44. package/dist/src/types.d.ts.map +1 -1
  45. package/dist/src/util.js +1 -1
  46. package/dist/src/util.js.map +1 -1
  47. package/dist/src/web/nextui/404/index.html +1 -1
  48. package/dist/src/web/nextui/404.html +1 -1
  49. package/dist/src/web/nextui/_next/static/Bl3o5lF4ON7Fjki46lPhr/_buildManifest.js +1 -0
  50. package/dist/src/web/nextui/_next/static/chunks/226-7bbb6c98a19542fd.js +37 -0
  51. package/dist/src/web/nextui/_next/static/chunks/249-ea9c0f034888ccff.js +125 -0
  52. package/dist/src/web/nextui/_next/static/chunks/339-501c32916b785ef1.js +1 -0
  53. package/dist/src/web/nextui/_next/static/chunks/365-e426ea5bc7e815fc.js +8 -0
  54. package/dist/src/web/nextui/_next/static/chunks/396-0a51429a01e24cdd.js +1 -0
  55. package/dist/src/web/nextui/_next/static/chunks/596-297f7ff4a0436e87.js +25 -0
  56. package/dist/src/web/nextui/_next/static/chunks/613-572c22424de64659.js +1 -0
  57. package/dist/src/web/nextui/_next/static/chunks/706-ae1d3352d28419e9.js +9 -0
  58. package/dist/src/web/nextui/_next/static/chunks/891-7035926a62c1c4e0.js +1 -0
  59. package/dist/src/web/nextui/_next/static/chunks/app/eval/[id]/not-found-366629541fd598e9.js +1 -0
  60. package/dist/src/web/nextui/_next/static/chunks/app/eval/[id]/page-319d2ee38d37574e.js +1 -0
  61. package/dist/src/web/nextui/_next/static/chunks/app/eval/page-a6b1ff91723b7beb.js +1 -0
  62. package/dist/src/web/nextui/_next/static/chunks/app/layout-024c4adc71c9feb0.js +1 -0
  63. package/dist/src/web/nextui/_next/static/chunks/app/page-1ae60660130041b2.js +1 -0
  64. package/dist/src/web/nextui/_next/static/chunks/app/setup/page-6ef16148040bf4f4.js +1 -0
  65. package/dist/src/web/nextui/_next/static/chunks/{ca377847-cb6ae6a6a073aebb.js → ca377847-26b462611379a4f7.js} +3 -3
  66. package/dist/src/web/nextui/_next/static/chunks/{fd9d1056-ac777be631f5a9e9.js → fd9d1056-fba4b53a2f01213b.js} +1 -1
  67. package/dist/src/web/nextui/_next/static/chunks/framework-8883d1e9be70c3da.js +25 -0
  68. package/dist/src/web/nextui/_next/static/chunks/main-8ea85465d428ecfe.js +1 -0
  69. package/dist/src/web/nextui/_next/static/chunks/main-app-581ccf0003955b21.js +1 -0
  70. package/dist/src/web/nextui/_next/static/chunks/pages/_app-52924524f99094ab.js +1 -0
  71. package/dist/src/web/nextui/_next/static/chunks/pages/_error-c92d5c4bb2b49926.js +1 -0
  72. package/dist/src/web/nextui/_next/static/chunks/webpack-55c264ce2fd85eb7.js +1 -0
  73. package/dist/src/web/nextui/_next/static/css/4d399fceacd06992.css +1 -0
  74. package/dist/src/web/nextui/eval/index.html +1 -1
  75. package/dist/src/web/nextui/eval/index.txt +6 -6
  76. package/dist/src/web/nextui/index.html +1 -1
  77. package/dist/src/web/nextui/index.txt +5 -5
  78. package/dist/src/web/nextui/setup/index.html +27 -1
  79. package/dist/src/web/nextui/setup/index.txt +9 -9
  80. package/dist/src/web/server.d.ts.map +1 -1
  81. package/dist/src/web/server.js +9 -5
  82. package/dist/src/web/server.js.map +1 -1
  83. package/package.json +4 -4
  84. package/dist/src/web/nextui/_next/static/US6gOx8LHTX_Hzm9aYNrC/_buildManifest.js +0 -1
  85. package/dist/src/web/nextui/_next/static/chunks/339-4fc8a80fa840e771.js +0 -1
  86. package/dist/src/web/nextui/_next/static/chunks/373-8a280796c0f2d1af.js +0 -1
  87. package/dist/src/web/nextui/_next/static/chunks/583-125d32af505e9bc4.js +0 -1
  88. package/dist/src/web/nextui/_next/static/chunks/596-07e4a23a5c6cdf04.js +0 -25
  89. package/dist/src/web/nextui/_next/static/chunks/658-a62210d07dc4dcb6.js +0 -15
  90. package/dist/src/web/nextui/_next/static/chunks/707-699cbd84b259c37b.js +0 -37
  91. package/dist/src/web/nextui/_next/static/chunks/858-ceb6fa22e614492b.js +0 -125
  92. package/dist/src/web/nextui/_next/static/chunks/891-3000ea7c0a292558.js +0 -1
  93. package/dist/src/web/nextui/_next/static/chunks/app/eval/[id]/not-found-50e40614fa05600e.js +0 -1
  94. package/dist/src/web/nextui/_next/static/chunks/app/eval/[id]/page-c19c44ed1b2dfb58.js +0 -1
  95. package/dist/src/web/nextui/_next/static/chunks/app/eval/page-d4a1813b2f8c4532.js +0 -1
  96. package/dist/src/web/nextui/_next/static/chunks/app/layout-664a8d716d2d24b1.js +0 -1
  97. package/dist/src/web/nextui/_next/static/chunks/app/page-1f8ef6a00a2355f0.js +0 -1
  98. package/dist/src/web/nextui/_next/static/chunks/app/setup/page-182018a3c6397345.js +0 -1
  99. package/dist/src/web/nextui/_next/static/chunks/framework-43665103d101a22d.js +0 -25
  100. package/dist/src/web/nextui/_next/static/chunks/main-50cc0a98559591ce.js +0 -1
  101. package/dist/src/web/nextui/_next/static/chunks/main-app-c9dc13756d166550.js +0 -1
  102. package/dist/src/web/nextui/_next/static/chunks/pages/_app-6b79a29ad0d63b21.js +0 -1
  103. package/dist/src/web/nextui/_next/static/chunks/pages/_error-9aeb3e4d490fe4b8.js +0 -1
  104. package/dist/src/web/nextui/_next/static/chunks/webpack-6e474e42be502dd7.js +0 -1
  105. package/dist/src/web/nextui/_next/static/css/a35c840ac696f161.css +0 -1
  106. package/dist/src/web/nextui/api +0 -1
  107. package/src/__mocks__/esm.ts +0 -3
  108. package/src/assertions.ts +0 -580
  109. package/src/cache.ts +0 -109
  110. package/src/esm.ts +0 -13
  111. package/src/evaluator.ts +0 -500
  112. package/src/index.ts +0 -52
  113. package/src/logger.ts +0 -46
  114. package/src/main.ts +0 -442
  115. package/src/matchers.ts +0 -120
  116. package/src/onboarding.ts +0 -69
  117. package/src/prompts.ts +0 -39
  118. package/src/providers/anthropic.ts +0 -88
  119. package/src/providers/azureopenai.ts +0 -299
  120. package/src/providers/llama.ts +0 -95
  121. package/src/providers/localai.ts +0 -111
  122. package/src/providers/ollama.ts +0 -89
  123. package/src/providers/openai.ts +0 -337
  124. package/src/providers/replicate.ts +0 -99
  125. package/src/providers/scriptCompletion.ts +0 -35
  126. package/src/providers/shared.ts +0 -34
  127. package/src/providers.ts +0 -192
  128. package/src/share.ts +0 -27
  129. package/src/suggestions.ts +0 -63
  130. package/src/table.ts +0 -43
  131. package/src/tableOutput.html +0 -52
  132. package/src/telemetry.ts +0 -70
  133. package/src/types.ts +0 -299
  134. package/src/updates.ts +0 -46
  135. package/src/util.ts +0 -543
  136. package/src/web/nextui/.eslintrc.json +0 -3
  137. package/src/web/nextui/next.config.js +0 -14
  138. package/src/web/nextui/package-lock.json +0 -4644
  139. package/src/web/nextui/package.json +0 -47
  140. package/src/web/nextui/public/favicon.ico +0 -0
  141. package/src/web/nextui/public/logo.svg +0 -30
  142. package/src/web/nextui/src/app/Home.css +0 -3
  143. package/src/web/nextui/src/app/api/route.ts +0 -6
  144. package/src/web/nextui/src/app/components/DarkMode.css +0 -22
  145. package/src/web/nextui/src/app/components/DarkMode.tsx +0 -17
  146. package/src/web/nextui/src/app/components/Logo.css +0 -32
  147. package/src/web/nextui/src/app/components/Logo.tsx +0 -11
  148. package/src/web/nextui/src/app/components/PageShell.css +0 -33
  149. package/src/web/nextui/src/app/components/PageShell.tsx +0 -87
  150. package/src/web/nextui/src/app/eval/ConfigModal.tsx +0 -84
  151. package/src/web/nextui/src/app/eval/Eval.css +0 -13
  152. package/src/web/nextui/src/app/eval/Eval.tsx +0 -79
  153. package/src/web/nextui/src/app/eval/EvalOutputPromptDialog.tsx +0 -127
  154. package/src/web/nextui/src/app/eval/ResultsCharts.tsx +0 -355
  155. package/src/web/nextui/src/app/eval/ResultsTable.css +0 -179
  156. package/src/web/nextui/src/app/eval/ResultsTable.tsx +0 -503
  157. package/src/web/nextui/src/app/eval/ResultsView.tsx +0 -301
  158. package/src/web/nextui/src/app/eval/ShareModal.tsx +0 -70
  159. package/src/web/nextui/src/app/eval/[id]/not-found.tsx +0 -5
  160. package/src/web/nextui/src/app/eval/[id]/page.css +0 -9
  161. package/src/web/nextui/src/app/eval/[id]/page.tsx +0 -20
  162. package/src/web/nextui/src/app/eval/index.css +0 -0
  163. package/src/web/nextui/src/app/eval/page.tsx +0 -8
  164. package/src/web/nextui/src/app/eval/store.ts +0 -18
  165. package/src/web/nextui/src/app/eval/types.ts +0 -20
  166. package/src/web/nextui/src/app/globals.css +0 -58
  167. package/src/web/nextui/src/app/layout.tsx +0 -25
  168. package/src/web/nextui/src/app/page.tsx +0 -7
  169. package/src/web/nextui/src/app/setup/AssertsForm.tsx +0 -118
  170. package/src/web/nextui/src/app/setup/PromptDialog.tsx +0 -77
  171. package/src/web/nextui/src/app/setup/PromptsSection.tsx +0 -190
  172. package/src/web/nextui/src/app/setup/ProviderConfigDialog.tsx +0 -99
  173. package/src/web/nextui/src/app/setup/ProviderSelector.tsx +0 -149
  174. package/src/web/nextui/src/app/setup/RunTestSuiteButton.tsx +0 -88
  175. package/src/web/nextui/src/app/setup/TestCaseDialog.tsx +0 -108
  176. package/src/web/nextui/src/app/setup/TestCasesSection.tsx +0 -154
  177. package/src/web/nextui/src/app/setup/VarsForm.tsx +0 -57
  178. package/src/web/nextui/src/app/setup/page.css +0 -3
  179. package/src/web/nextui/src/app/setup/page.tsx +0 -160
  180. package/src/web/nextui/src/util/api.ts +0 -1
  181. package/src/web/nextui/src/util/store.ts +0 -53
  182. package/src/web/nextui/tsconfig.json +0 -28
  183. package/src/web/server.ts +0 -151
  184. /package/dist/src/web/nextui/_next/static/{US6gOx8LHTX_Hzm9aYNrC → Bl3o5lF4ON7Fjki46lPhr}/_ssgManifest.js +0 -0
@@ -1,337 +0,0 @@
1
- import logger from '../logger';
2
- import { fetchWithCache } from '../cache';
3
- import { REQUEST_TIMEOUT_MS, parseChatPrompt } from './shared';
4
-
5
- import type { ApiProvider, ProviderEmbeddingResponse, ProviderResponse } from '../types.js';
6
-
7
- const DEFAULT_OPENAI_HOST = 'api.openai.com';
8
-
9
- interface OpenAiCompletionOptions {
10
- temperature?: number;
11
- max_tokens?: number;
12
- top_p?: number;
13
- frequency_penalty?: number;
14
- presence_penalty?: number;
15
- best_of?: number;
16
- functions?: {
17
- name: string;
18
- description?: string;
19
- parameters: any;
20
- }[];
21
- function_call?: 'none' | 'auto';
22
- apiKey?: string;
23
- organization?: string;
24
- }
25
-
26
- class OpenAiGenericProvider implements ApiProvider {
27
- modelName: string;
28
- apiKey?: string;
29
- apiHost: string;
30
-
31
- constructor(modelName: string, apiKey?: string) {
32
- this.modelName = modelName;
33
-
34
- this.apiKey = apiKey;
35
-
36
- this.apiHost = process.env.OPENAI_API_HOST || DEFAULT_OPENAI_HOST;
37
- }
38
-
39
- id(): string {
40
- return `openai:${this.modelName}`;
41
- }
42
-
43
- toString(): string {
44
- return `[OpenAI Provider ${this.modelName}]`;
45
- }
46
-
47
- getOrganization(options?: OpenAiCompletionOptions): string | undefined {
48
- return options?.organization || process.env.OPENAI_ORGANIZATION;
49
- }
50
-
51
- getApiKey(options?: OpenAiCompletionOptions): string | undefined {
52
- return options?.apiKey || this.apiKey || process.env.OPENAI_API_KEY;
53
- }
54
-
55
- // @ts-ignore: Prompt is not used in this implementation
56
- async callApi(prompt: string, options?: OpenAiCompletionOptions): Promise<ProviderResponse> {
57
- throw new Error('Not implemented');
58
- }
59
- }
60
-
61
- export class OpenAiEmbeddingProvider extends OpenAiGenericProvider {
62
- async callEmbeddingApi(text: string): Promise<ProviderEmbeddingResponse> {
63
- if (!this.getApiKey()) {
64
- throw new Error('OpenAI API key must be set for similarity comparison');
65
- }
66
-
67
- const body = {
68
- input: text,
69
- model: this.modelName,
70
- };
71
- let data,
72
- cached = false;
73
- try {
74
- ({ data, cached } = (await fetchWithCache(
75
- `https://${this.apiHost}/v1/embeddings`,
76
- {
77
- method: 'POST',
78
- headers: {
79
- 'Content-Type': 'application/json',
80
- Authorization: `Bearer ${this.getApiKey()}`,
81
- ...(this.getOrganization() ? { 'OpenAI-Organization': this.getOrganization() } : {}),
82
- },
83
- body: JSON.stringify(body),
84
- },
85
- REQUEST_TIMEOUT_MS,
86
- )) as unknown as any);
87
- } catch (err) {
88
- return {
89
- error: `API call error: ${String(err)}`,
90
- tokenUsage: {
91
- total: 0,
92
- prompt: 0,
93
- completion: 0,
94
- },
95
- };
96
- }
97
- logger.debug(`\tOpenAI API response (embeddings): ${JSON.stringify(data)}`);
98
-
99
- try {
100
- const embedding = data?.data?.[0]?.embedding;
101
- if (!embedding) {
102
- throw new Error('No embedding returned');
103
- }
104
- const ret = {
105
- embedding,
106
- tokenUsage: cached
107
- ? { cached: data.usage.total_tokens }
108
- : {
109
- total: data.usage.total_tokens,
110
- prompt: data.usage.prompt_tokens,
111
- completion: data.usage.completion_tokens,
112
- },
113
- };
114
- return ret;
115
- } catch (err) {
116
- return {
117
- error: `API response error: ${String(err)}: ${JSON.stringify(data)}`,
118
- tokenUsage: {
119
- total: data?.usage?.total_tokens,
120
- prompt: data?.usage?.prompt_tokens,
121
- completion: data?.usage?.completion_tokens,
122
- },
123
- };
124
- }
125
- }
126
- }
127
-
128
- export class OpenAiCompletionProvider extends OpenAiGenericProvider {
129
- static OPENAI_COMPLETION_MODELS = [
130
- 'text-davinci-003',
131
- 'text-davinci-002',
132
- 'text-curie-001',
133
- 'text-babbage-001',
134
- 'text-ada-001',
135
- ];
136
-
137
- options: OpenAiCompletionOptions;
138
-
139
- constructor(modelName: string, apiKey?: string, context?: OpenAiCompletionOptions, id?: string) {
140
- if (!OpenAiCompletionProvider.OPENAI_COMPLETION_MODELS.includes(modelName)) {
141
- logger.warn(`Using unknown OpenAI completion model: ${modelName}`);
142
- }
143
- super(modelName, apiKey);
144
- this.options = context || {};
145
- this.id = id ? () => id : this.id;
146
- }
147
-
148
- async callApi(prompt: string, options?: OpenAiCompletionOptions): Promise<ProviderResponse> {
149
- if (!this.getApiKey(options)) {
150
- throw new Error(
151
- 'OpenAI API key is not set. Set OPENAI_API_KEY environment variable or pass it as an argument to the constructor.',
152
- );
153
- }
154
-
155
- let stop: string;
156
- try {
157
- stop = process.env.OPENAI_STOP
158
- ? JSON.parse(process.env.OPENAI_STOP)
159
- : ['<|im_end|>', '<|endoftext|>'];
160
- } catch (err) {
161
- throw new Error(`OPENAI_STOP is not a valid JSON string: ${err}`);
162
- }
163
- const body = {
164
- model: this.modelName,
165
- prompt,
166
- max_tokens:
167
- options?.max_tokens ??
168
- this.options.max_tokens ??
169
- parseInt(process.env.OPENAI_MAX_TOKENS || '1024'),
170
- temperature:
171
- options?.temperature ??
172
- this.options.temperature ??
173
- parseFloat(process.env.OPENAI_TEMPERATURE || '0'),
174
- top_p: options?.top_p ?? this.options.top_p ?? parseFloat(process.env.OPENAI_TOP_P || '1'),
175
- presence_penalty:
176
- options?.presence_penalty ??
177
- this.options.presence_penalty ??
178
- parseFloat(process.env.OPENAI_PRESENCE_PENALTY || '0'),
179
- frequency_penalty:
180
- options?.frequency_penalty ??
181
- this.options.frequency_penalty ??
182
- parseFloat(process.env.OPENAI_FREQUENCY_PENALTY || '0'),
183
- best_of:
184
- options?.best_of ?? this.options.best_of ?? parseInt(process.env.OPENAI_BEST_OF || '1'),
185
- stop,
186
- };
187
- logger.debug(`Calling OpenAI API: ${JSON.stringify(body)}`);
188
- let data,
189
- cached = false;
190
- try {
191
- ({ data, cached } = (await fetchWithCache(
192
- `https://${this.apiHost}/v1/completions`,
193
- {
194
- method: 'POST',
195
- headers: {
196
- 'Content-Type': 'application/json',
197
- Authorization: `Bearer ${this.getApiKey(options)}`,
198
- ...(this.getOrganization(options)
199
- ? { 'OpenAI-Organization': this.getOrganization(options) }
200
- : {}),
201
- },
202
- body: JSON.stringify(body),
203
- },
204
- REQUEST_TIMEOUT_MS,
205
- )) as unknown as any);
206
- } catch (err) {
207
- return {
208
- error: `API call error: ${String(err)}`,
209
- };
210
- }
211
- logger.debug(`\tOpenAI API response: ${JSON.stringify(data)}`);
212
- try {
213
- return {
214
- output: data.choices[0].text,
215
- tokenUsage: cached
216
- ? { cached: data.usage.total_tokens }
217
- : {
218
- total: data.usage.total_tokens,
219
- prompt: data.usage.prompt_tokens,
220
- completion: data.usage.completion_tokens,
221
- },
222
- };
223
- } catch (err) {
224
- return {
225
- error: `API response error: ${String(err)}: ${JSON.stringify(data)}`,
226
- };
227
- }
228
- }
229
- }
230
-
231
- export class OpenAiChatCompletionProvider extends OpenAiGenericProvider {
232
- static OPENAI_CHAT_MODELS = [
233
- 'gpt-4',
234
- 'gpt-4-0314',
235
- 'gpt-4-0613',
236
- 'gpt-4-32k',
237
- 'gpt-4-32k-0314',
238
- 'gpt-3.5-turbo',
239
- 'gpt-3.5-turbo-0301',
240
- 'gpt-3.5-turbo-0613',
241
- 'gpt-3.5-turbo-16k',
242
- 'gpt-3.5-turbo-16k-0613',
243
- ];
244
-
245
- options: OpenAiCompletionOptions;
246
-
247
- constructor(modelName: string, apiKey?: string, context?: OpenAiCompletionOptions, id?: string) {
248
- if (!OpenAiChatCompletionProvider.OPENAI_CHAT_MODELS.includes(modelName)) {
249
- logger.warn(`Using unknown OpenAI chat model: ${modelName}`);
250
- }
251
- super(modelName, apiKey);
252
- this.options = context || {};
253
- this.id = id ? () => id : this.id;
254
- }
255
-
256
- async callApi(prompt: string, options?: OpenAiCompletionOptions): Promise<ProviderResponse> {
257
- if (!this.getApiKey(options)) {
258
- throw new Error(
259
- 'OpenAI API key is not set. Set OPENAI_API_KEY environment variable or pass it as an argument to the constructor.',
260
- );
261
- }
262
-
263
- const messages = parseChatPrompt(prompt);
264
- const body = {
265
- model: this.modelName,
266
- messages: messages,
267
- max_tokens:
268
- options?.max_tokens ??
269
- this.options.max_tokens ??
270
- parseInt(process.env.OPENAI_MAX_TOKENS || '1024'),
271
- temperature:
272
- options?.temperature ??
273
- this.options.temperature ??
274
- parseFloat(process.env.OPENAI_TEMPERATURE || '0'),
275
- top_p: options?.top_p ?? this.options.top_p ?? parseFloat(process.env.OPENAI_TOP_P || '1'),
276
- presence_penalty:
277
- options?.presence_penalty ??
278
- this.options.presence_penalty ??
279
- parseFloat(process.env.OPENAI_PRESENCE_PENALTY || '0'),
280
- frequency_penalty:
281
- options?.frequency_penalty ??
282
- this.options.frequency_penalty ??
283
- parseFloat(process.env.OPENAI_FREQUENCY_PENALTY || '0'),
284
- functions: options?.functions || this.options.functions || undefined,
285
- function_call: options?.function_call || this.options.function_call || undefined,
286
- };
287
- logger.debug(`Calling OpenAI API: ${JSON.stringify(body)}`);
288
-
289
- let data,
290
- cached = false;
291
- try {
292
- ({ data, cached } = (await fetchWithCache(
293
- `https://${this.apiHost}/v1/chat/completions`,
294
- {
295
- method: 'POST',
296
- headers: {
297
- 'Content-Type': 'application/json',
298
- Authorization: `Bearer ${this.getApiKey(options)}`,
299
- ...(this.getOrganization(options)
300
- ? { 'OpenAI-Organization': this.getOrganization(options) }
301
- : {}),
302
- },
303
- body: JSON.stringify(body),
304
- },
305
- REQUEST_TIMEOUT_MS,
306
- )) as unknown as any);
307
- } catch (err) {
308
- return {
309
- error: `API call error: ${String(err)}`,
310
- };
311
- }
312
-
313
- logger.debug(`\tOpenAI API response: ${JSON.stringify(data)}`);
314
- try {
315
- const message = data.choices[0].message;
316
- const output = message.content === null ? message.function_call : message.content;
317
- return {
318
- output,
319
- tokenUsage: cached
320
- ? { cached: data.usage.total_tokens }
321
- : {
322
- total: data.usage.total_tokens,
323
- prompt: data.usage.prompt_tokens,
324
- completion: data.usage.completion_tokens,
325
- },
326
- };
327
- } catch (err) {
328
- return {
329
- error: `API response error: ${String(err)}: ${JSON.stringify(data)}`,
330
- };
331
- }
332
- }
333
- }
334
-
335
- export const DefaultEmbeddingProvider = new OpenAiEmbeddingProvider('text-embedding-ada-002');
336
- export const DefaultGradingProvider = new OpenAiChatCompletionProvider('gpt-4-0613');
337
- export const DefaultSuggestionsProvider = new OpenAiChatCompletionProvider('gpt-4-0613');
@@ -1,99 +0,0 @@
1
- import Replicate from 'replicate';
2
-
3
- import fetch from 'node-fetch';
4
- import logger from '../logger';
5
- import { getCache, isCacheEnabled } from '../cache';
6
-
7
- import type { ApiProvider, ProviderResponse } from '../types.js';
8
-
9
- interface ReplicateCompletionOptions {
10
- temperature?: number;
11
- max_length?: number;
12
- repetition_penalty?: number;
13
- }
14
-
15
- export class ReplicateProvider implements ApiProvider {
16
- modelName: string;
17
- apiKey?: string;
18
- replicate: any;
19
- options: ReplicateCompletionOptions;
20
-
21
- constructor(modelName: string, apiKey?: string, options?: ReplicateCompletionOptions) {
22
- this.modelName = modelName;
23
- this.apiKey = apiKey || process.env.REPLICATE_API_TOKEN || process.env.REPLICATE_API_KEY;
24
- this.options = options || {};
25
- }
26
-
27
- id(): string {
28
- return `replicate:${this.modelName}`;
29
- }
30
-
31
- toString(): string {
32
- return `[Replicate Provider ${this.modelName}]`;
33
- }
34
-
35
- async callApi(prompt: string): Promise<ProviderResponse> {
36
- if (!this.apiKey) {
37
- throw new Error(
38
- 'Replicate API key is not set. Set REPLICATE_API_TOKEN environment variable or pass it as an argument to the constructor.',
39
- );
40
- }
41
-
42
- let cache;
43
- let cacheKey;
44
- if (isCacheEnabled()) {
45
- cache = await getCache();
46
- cacheKey = `replicate:${this.modelName}:${prompt}`;
47
-
48
- // Try to get the cached response
49
- const cachedResponse = await cache.get(cacheKey);
50
-
51
- if (cachedResponse) {
52
- logger.debug(`Returning cached response for ${prompt}: ${cachedResponse}`);
53
- return JSON.parse(cachedResponse as string);
54
- }
55
- }
56
-
57
- const replicate = new Replicate({
58
- auth: this.apiKey,
59
- fetch,
60
- });
61
-
62
- logger.debug(`Calling Replicate: ${prompt}`);
63
- let response;
64
- try {
65
- const data = {
66
- input: {
67
- prompt,
68
- max_length:
69
- this.options.max_length || parseInt(process.env.REPLICATE_MAX_LENGTH || '2046', 10),
70
- temperature:
71
- this.options.temperature || parseFloat(process.env.REPLICATE_TEMPERATURE || '0.01'),
72
- repetition_penalty:
73
- this.options.repetition_penalty ||
74
- parseFloat(process.env.REPLICATE_REPETITION_PENALTY || '1.0'),
75
- },
76
- };
77
- response = await replicate.run(this.modelName as any, data);
78
- } catch (err) {
79
- return {
80
- error: `API call error: ${String(err)}`,
81
- };
82
- }
83
- logger.debug(`\tReplicate API response: ${JSON.stringify(response)}`);
84
- try {
85
- const result = {
86
- output: (response as string[]).join(''),
87
- tokenUsage: {}, // TODO: add token usage once Replicate API supports it
88
- };
89
- if (cache && cacheKey) {
90
- await cache.set(cacheKey, JSON.stringify(result));
91
- }
92
- return result;
93
- } catch (err) {
94
- return {
95
- error: `API response error: ${String(err)}: ${JSON.stringify(response)}`,
96
- };
97
- }
98
- }
99
- }
@@ -1,35 +0,0 @@
1
- import { exec } from 'child_process';
2
-
3
- import { ApiProvider, ProviderOptions, ProviderResponse } from '../types';
4
-
5
- const ANSI_ESCAPE = /\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
6
-
7
- function stripText(text: string) {
8
- return text.replace(ANSI_ESCAPE, '');
9
- }
10
-
11
- export class ScriptCompletionProvider implements ApiProvider {
12
- constructor(private scriptPath: string, private config?: ProviderOptions) {}
13
-
14
- id() {
15
- return `exec:${this.scriptPath}`;
16
- }
17
-
18
- async callApi(prompt: string) {
19
- return new Promise((resolve, reject) => {
20
- // TODO(ian): This config is real ugly.
21
- const command = this.config?.config.basePath
22
- ? `cd ${this.config.config.basePath} && ${this.scriptPath} "${prompt}"`
23
- : `${this.scriptPath} "${prompt}"`;
24
-
25
- // TODO(ian): Caching
26
- exec(command, (error, stdout, stderr) => {
27
- if (error) {
28
- reject(error);
29
- } else {
30
- resolve({ output: stripText(stdout.trim()) });
31
- }
32
- });
33
- }) as Promise<ProviderResponse>;
34
- }
35
- }
@@ -1,34 +0,0 @@
1
- import yaml from 'js-yaml';
2
-
3
- export const REQUEST_TIMEOUT_MS = process.env.REQUEST_TIMEOUT_MS
4
- ? parseInt(process.env.REQUEST_TIMEOUT_MS, 10)
5
- : 300_000;
6
-
7
- export function parseChatPrompt(
8
- prompt: string,
9
- ): { role: string; content: string; name?: string }[] {
10
- const trimmedPrompt = prompt.trim();
11
- if (trimmedPrompt.startsWith('- role:')) {
12
- try {
13
- // Try YAML
14
- return yaml.load(prompt) as { role: string; content: string }[];
15
- } catch (err) {
16
- throw new Error(`Chat Completion prompt is not a valid YAML string: ${err}\n\n${prompt}`);
17
- }
18
- } else {
19
- try {
20
- // Try JSON
21
- return JSON.parse(prompt) as { role: string; content: string }[];
22
- } catch (err) {
23
- if (
24
- process.env.PROMPTFOO_REQUIRE_JSON_PROMPTS ||
25
- trimmedPrompt.startsWith('{') ||
26
- trimmedPrompt.startsWith('[')
27
- ) {
28
- throw new Error(`Chat Completion prompt is not a valid JSON string: ${err}\n\n${prompt}`);
29
- }
30
- // Fall back to wrapping the prompt in a user message
31
- return [{ role: 'user', content: prompt }];
32
- }
33
- }
34
- }
package/src/providers.ts DELETED
@@ -1,192 +0,0 @@
1
- import path from 'path';
2
-
3
- import { OpenAiCompletionProvider, OpenAiChatCompletionProvider } from './providers/openai';
4
- import { AnthropicCompletionProvider } from './providers/anthropic';
5
- import { ReplicateProvider } from './providers/replicate';
6
- import { LocalAiCompletionProvider, LocalAiChatProvider } from './providers/localai';
7
- import { LlamaProvider } from './providers/llama';
8
- import { OllamaProvider } from './providers/ollama';
9
- import { ScriptCompletionProvider } from './providers/scriptCompletion';
10
- import {
11
- AzureOpenAiChatCompletionProvider,
12
- AzureOpenAiCompletionProvider,
13
- } from './providers/azureopenai';
14
-
15
- import type {
16
- ApiProvider,
17
- ProviderOptions,
18
- ProviderFunction,
19
- ProviderId,
20
- ProviderOptionsMap,
21
- } from './types';
22
-
23
- export async function loadApiProviders(
24
- providerPaths:
25
- | ProviderId
26
- | ProviderId[]
27
- | ProviderOptionsMap[]
28
- | ProviderOptions[]
29
- | ProviderFunction,
30
- basePath?: string,
31
- ): Promise<ApiProvider[]> {
32
- if (typeof providerPaths === 'string') {
33
- return [await loadApiProvider(providerPaths, undefined, basePath)];
34
- } else if (typeof providerPaths === 'function') {
35
- return [
36
- {
37
- id: () => 'custom-function',
38
- callApi: providerPaths,
39
- },
40
- ];
41
- } else if (Array.isArray(providerPaths)) {
42
- return Promise.all(
43
- providerPaths.map((provider, idx) => {
44
- if (typeof provider === 'string') {
45
- return loadApiProvider(provider, undefined, basePath);
46
- } else if (typeof provider === 'function') {
47
- return {
48
- id: () => `custom-function-${idx}`,
49
- callApi: provider,
50
- };
51
- } else if (provider.id) {
52
- // List of ProviderConfig objects
53
- return loadApiProvider((provider as ProviderOptions).id!, provider, basePath);
54
- } else {
55
- // List of { id: string, config: ProviderConfig } objects
56
- const id = Object.keys(provider)[0];
57
- const providerObject = (provider as ProviderOptionsMap)[id];
58
- const context = { ...providerObject, id: providerObject.id || id };
59
- return loadApiProvider(id, context, basePath);
60
- }
61
- }),
62
- );
63
- }
64
- throw new Error('Invalid providers list');
65
- }
66
-
67
- export async function loadApiProvider(
68
- providerPath: string,
69
- context?: ProviderOptions,
70
- basePath?: string,
71
- ): Promise<ApiProvider> {
72
- context = context || {};
73
- if (providerPath?.startsWith('exec:')) {
74
- // Load script module
75
- const scriptPath = providerPath.split(':')[1];
76
- return new ScriptCompletionProvider(scriptPath, {
77
- id: `exec:${scriptPath}`,
78
- config: { basePath },
79
- });
80
- } else if (providerPath?.startsWith('openai:')) {
81
- // Load OpenAI module
82
- const options = providerPath.split(':');
83
- const modelType = options[1];
84
- const modelName = options[2];
85
-
86
- if (modelType === 'chat') {
87
- return new OpenAiChatCompletionProvider(
88
- modelName || 'gpt-3.5-turbo',
89
- undefined,
90
- context.config,
91
- );
92
- } else if (modelType === 'completion') {
93
- return new OpenAiCompletionProvider(
94
- modelName || 'text-davinci-003',
95
- undefined,
96
- context.config,
97
- );
98
- } else if (OpenAiChatCompletionProvider.OPENAI_CHAT_MODELS.includes(modelType)) {
99
- return new OpenAiChatCompletionProvider(modelType, undefined, context.config, context.id);
100
- } else if (OpenAiCompletionProvider.OPENAI_COMPLETION_MODELS.includes(modelType)) {
101
- return new OpenAiCompletionProvider(modelType, undefined, context.config, context.id);
102
- } else {
103
- throw new Error(
104
- `Unknown OpenAI model type: ${modelType}. Use one of the following providers: openai:chat:<model name>, openai:completion:<model name>`,
105
- );
106
- }
107
- } else if (providerPath?.startsWith('azureopenai:')) {
108
- // Load Azure OpenAI module
109
- const options = providerPath.split(':');
110
- const modelType = options[1];
111
- const deploymentName = options[2];
112
-
113
- if (modelType === 'chat') {
114
- return new AzureOpenAiChatCompletionProvider(
115
- deploymentName,
116
- undefined,
117
- context.config,
118
- context.id,
119
- );
120
- } else if (modelType === 'completion') {
121
- return new AzureOpenAiCompletionProvider(
122
- deploymentName,
123
- undefined,
124
- context.config,
125
- context.id,
126
- );
127
- } else {
128
- throw new Error(
129
- `Unknown Azure OpenAI model type: ${modelType}. Use one of the following providers: openai:chat:<model name>, openai:completion:<model name>`,
130
- );
131
- }
132
- } else if (providerPath?.startsWith('anthropic:')) {
133
- // Load Anthropic module
134
- const options = providerPath.split(':');
135
- const modelType = options[1];
136
- const modelName = options[2];
137
-
138
- if (modelType === 'completion') {
139
- return new AnthropicCompletionProvider(
140
- modelName || 'claude-instant-1',
141
- undefined,
142
- context.config,
143
- );
144
- } else if (AnthropicCompletionProvider.ANTHROPIC_COMPLETION_MODELS.includes(modelType)) {
145
- return new AnthropicCompletionProvider(modelType, undefined, context.config);
146
- } else {
147
- throw new Error(
148
- `Unknown Anthropic model type: ${modelType}. Use one of the following providers: anthropic:completion:<model name>`,
149
- );
150
- }
151
- } else if (providerPath?.startsWith('replicate:')) {
152
- // Load Replicate module
153
- const options = providerPath.split(':');
154
- const modelName = options.slice(1).join(':');
155
-
156
- return new ReplicateProvider(modelName, undefined, context.config);
157
- }
158
-
159
- if (providerPath === 'llama' || providerPath.startsWith('llama:')) {
160
- const modelName = providerPath.split(':')[1];
161
- return new LlamaProvider(modelName, context.config);
162
- } else if (providerPath.startsWith('ollama:')) {
163
- const modelName = providerPath.split(':')[1];
164
- return new OllamaProvider(modelName);
165
- } else if (providerPath?.startsWith('localai:')) {
166
- const options = providerPath.split(':');
167
- const modelType = options[1];
168
- const modelName = options[2];
169
-
170
- if (modelType === 'chat') {
171
- return new LocalAiChatProvider(modelName);
172
- } else if (modelType === 'completion') {
173
- return new LocalAiCompletionProvider(modelName);
174
- } else {
175
- return new LocalAiChatProvider(modelType);
176
- }
177
- }
178
-
179
- // Load custom module
180
- const CustomApiProvider = (await import(path.join(process.cwd(), providerPath))).default;
181
- return new CustomApiProvider(context);
182
- }
183
-
184
- export default {
185
- OpenAiCompletionProvider,
186
- OpenAiChatCompletionProvider,
187
- AnthropicCompletionProvider,
188
- ReplicateProvider,
189
- LocalAiCompletionProvider,
190
- LocalAiChatProvider,
191
- loadApiProvider,
192
- };