@botpress/zai 1.1.0 → 1.2.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.
@@ -1,4 +1,5 @@
1
- import { z } from '@bpinternal/zui'
1
+ // eslint-disable consistent-type-definitions
2
+ import { z, ZodObject } from '@bpinternal/zui'
2
3
 
3
4
  import JSON5 from 'json5'
4
5
  import { jsonrepair } from 'jsonrepair'
@@ -18,18 +19,13 @@ const Options = z.object({
18
19
  .max(100_000)
19
20
  .optional()
20
21
  .describe('The maximum number of tokens per chunk')
21
- .default(16_000)
22
+ .default(16_000),
22
23
  })
23
24
 
24
25
  declare module '@botpress/zai' {
25
26
  interface Zai {
26
27
  /** Extracts one or many elements from an arbitrary input */
27
- extract<S extends z.AnyZodObject>(input: unknown, schema: S, options?: Options): Promise<z.infer<S>>
28
- extract<S extends z.AnyZodObject>(
29
- input: unknown,
30
- schema: z.ZodArray<S>,
31
- options?: Options
32
- ): Promise<Array<z.infer<S>>>
28
+ extract<S extends z.AnyZodObject | z.ZodArray>(input: unknown, schema: S, options?: Options): Promise<z.TypeOf<S>>
33
29
  }
34
30
  }
35
31
 
@@ -40,21 +36,29 @@ const NO_MORE = '■NO_MORE_ELEMENT■'
40
36
  Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
41
37
  const options = Options.parse(_options ?? {})
42
38
  const tokenizer = await this.getTokenizer()
39
+ await this.fetchModelDetails()
43
40
 
44
41
  const taskId = this.taskId
45
42
  const taskType = 'zai.extract'
46
43
 
47
- const PROMPT_COMPONENT = Math.max(this.Model.input.maxTokens - PROMPT_INPUT_BUFFER, 100)
44
+ const PROMPT_COMPONENT = Math.max(this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER, 100)
48
45
 
49
46
  let isArrayOfObjects = false
50
47
  const originalSchema = schema
51
48
 
52
- if (schema instanceof z.ZodObject) {
49
+ const baseType = (schema.naked ? schema.naked() : schema)?.constructor?.name ?? 'unknown'
50
+
51
+ if (baseType === 'ZodObject') {
53
52
  // Do nothing
54
- } else if (schema instanceof z.ZodArray) {
55
- if (schema._def.type instanceof z.ZodObject) {
53
+ } else if (baseType === 'ZodArray') {
54
+ let elementType = (schema as any).element
55
+ if (elementType.naked) {
56
+ elementType = elementType.naked()
57
+ }
58
+
59
+ if (elementType?.constructor?.name === 'ZodObject') {
56
60
  isArrayOfObjects = true
57
- schema = schema._def.type
61
+ schema = elementType
58
62
  } else {
59
63
  throw new Error('Schema must be a ZodObject or a ZodArray<ZodObject>')
60
64
  }
@@ -65,9 +69,12 @@ Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
65
69
  const schemaTypescript = schema.toTypescript({ declaration: false })
66
70
  const schemaLength = tokenizer.count(schemaTypescript)
67
71
 
68
- options.chunkLength = Math.min(options.chunkLength, this.Model.input.maxTokens - PROMPT_INPUT_BUFFER - schemaLength)
72
+ options.chunkLength = Math.min(
73
+ options.chunkLength,
74
+ this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER - schemaLength
75
+ )
69
76
 
70
- const keys = Object.keys(schema.shape)
77
+ const keys = Object.keys((schema as ZodObject).shape)
71
78
 
72
79
  let inputAsString = stringify(input)
73
80
 
@@ -116,7 +123,7 @@ Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
116
123
  taskType,
117
124
  taskId,
118
125
  input: inputAsString,
119
- instructions: options.instructions
126
+ instructions: options.instructions,
120
127
  })
121
128
  )
122
129
 
@@ -124,7 +131,7 @@ Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
124
131
  ? await this.adapter.getExamples<string, unknown>({
125
132
  input: inputAsString,
126
133
  taskType,
127
- taskId
134
+ taskId,
128
135
  })
129
136
  : []
130
137
 
@@ -144,13 +151,13 @@ The end.`,
144
151
  extracted: [
145
152
  {
146
153
  name: 'Alice',
147
- age: 30
154
+ age: 30,
148
155
  },
149
156
  {
150
157
  name: 'Bob',
151
- age: 25
152
- }
153
- ]
158
+ age: 25,
159
+ },
160
+ ],
154
161
  }
155
162
  : {
156
163
  input: `The story goes as follow.
@@ -158,14 +165,14 @@ Once upon a time, there was a person named Alice who was 30 years old.
158
165
  The end.`,
159
166
  schema: '{ name: string, age: number }',
160
167
  instructions: 'Extract the person',
161
- extracted: { name: 'Alice', age: 30 }
168
+ extracted: { name: 'Alice', age: 30 },
162
169
  }
163
170
 
164
171
  const userExamples = examples.map((e) => ({
165
172
  input: e.input,
166
173
  extracted: e.output,
167
174
  schema: schemaTypescript,
168
- instructions: options.instructions
175
+ instructions: options.instructions,
169
176
  }))
170
177
 
171
178
  let exampleId = 1
@@ -211,13 +218,13 @@ ${END}`.trim()
211
218
  {
212
219
  type: 'text' as const,
213
220
  content: formatInput(stringify(example.input ?? null), example.schema, example.instructions),
214
- role: 'user' as const
221
+ role: 'user' as const,
215
222
  },
216
223
  {
217
224
  type: 'text' as const,
218
225
  content: formatOutput(example.extracted),
219
- role: 'assistant' as const
220
- }
226
+ role: 'assistant' as const,
227
+ },
221
228
  ]
222
229
 
223
230
  const allExamples = takeUntilTokens(
@@ -228,7 +235,7 @@ ${END}`.trim()
228
235
  .map(formatExample)
229
236
  .flat()
230
237
 
231
- const output = await this.callModel({
238
+ const { output, meta } = await this.callModel({
232
239
  systemPrompt: `
233
240
  Extract the following information from the input:
234
241
  ${schemaTypescript}
@@ -242,9 +249,9 @@ ${instructions.map((x) => `• ${x}`).join('\n')}
242
249
  {
243
250
  role: 'user',
244
251
  type: 'text',
245
- content: formatInput(inputAsString, schemaTypescript, options.instructions ?? '')
246
- }
247
- ]
252
+ content: formatInput(inputAsString, schemaTypescript, options.instructions ?? ''),
253
+ },
254
+ ],
248
255
  })
249
256
 
250
257
  const answer = output.choices[0]?.content as string
@@ -283,7 +290,18 @@ ${instructions.map((x) => `• ${x}`).join('\n')}
283
290
  instructions: options.instructions ?? 'No specific instructions',
284
291
  input: inputAsString,
285
292
  output: final,
286
- metadata: output.metadata
293
+ metadata: {
294
+ cost: {
295
+ input: meta.cost.input,
296
+ output: meta.cost.output,
297
+ },
298
+ latency: meta.latency,
299
+ model: this.Model,
300
+ tokens: {
301
+ input: meta.tokens.input,
302
+ output: meta.tokens.output,
303
+ },
304
+ },
287
305
  })
288
306
  }
289
307
 
@@ -1,3 +1,4 @@
1
+ // eslint-disable consistent-type-definitions
1
2
  import { z } from '@bpinternal/zui'
2
3
 
3
4
  import { clamp } from 'lodash-es'
@@ -9,7 +10,7 @@ type Example = z.input<typeof Example>
9
10
  const Example = z.object({
10
11
  input: z.any(),
11
12
  filter: z.boolean(),
12
- reason: z.string().optional()
13
+ reason: z.string().optional(),
13
14
  })
14
15
 
15
16
  export type Options = z.input<typeof Options>
@@ -21,7 +22,7 @@ const Options = z.object({
21
22
  .optional()
22
23
  .describe('The maximum number of tokens per item')
23
24
  .default(250),
24
- examples: z.array(Example).describe('Examples to filter the condition against').default([])
25
+ examples: z.array(Example).describe('Examples to filter the condition against').default([]),
25
26
  })
26
27
 
27
28
  declare module '@botpress/zai' {
@@ -36,12 +37,13 @@ const END = '■END■'
36
37
  Zai.prototype.filter = async function (this: Zai, input, condition, _options) {
37
38
  const options = Options.parse(_options ?? {})
38
39
  const tokenizer = await this.getTokenizer()
40
+ await this.fetchModelDetails()
39
41
 
40
42
  const taskId = this.taskId
41
43
  const taskType = 'zai.filter'
42
44
 
43
45
  const MAX_ITEMS_PER_CHUNK = 50
44
- const TOKENS_TOTAL_MAX = this.Model.input.maxTokens - PROMPT_INPUT_BUFFER - PROMPT_OUTPUT_BUFFER
46
+ const TOKENS_TOTAL_MAX = this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER - PROMPT_OUTPUT_BUFFER
45
47
  const TOKENS_EXAMPLES_MAX = Math.floor(Math.max(250, TOKENS_TOTAL_MAX * 0.5))
46
48
  const TOKENS_CONDITION_MAX = clamp(TOKENS_TOTAL_MAX * 0.25, 250, tokenizer.count(condition))
47
49
  const TOKENS_INPUT_ARRAY_MAX = TOKENS_TOTAL_MAX - TOKENS_EXAMPLES_MAX - TOKENS_CONDITION_MAX
@@ -99,36 +101,36 @@ ${examples.map((x, idx) => `■${idx}:${!!x.filter ? 'true' : 'false'}:${x.reaso
99
101
  {
100
102
  input: 'apple',
101
103
  filter: true,
102
- reason: 'Apples are fruits'
104
+ reason: 'Apples are fruits',
103
105
  },
104
106
  {
105
107
  input: 'Apple Inc.',
106
108
  filter: false,
107
- reason: 'Apple Inc. is a company, not a fruit'
109
+ reason: 'Apple Inc. is a company, not a fruit',
108
110
  },
109
111
  {
110
112
  input: 'banana',
111
113
  filter: true,
112
- reason: 'Bananas are fruits'
114
+ reason: 'Bananas are fruits',
113
115
  },
114
116
  {
115
117
  input: 'potato',
116
118
  filter: false,
117
- reason: 'Potatoes are vegetables'
118
- }
119
+ reason: 'Potatoes are vegetables',
120
+ },
119
121
  ]
120
122
 
121
123
  const genericExamplesMessages = [
122
124
  {
123
125
  type: 'text' as const,
124
126
  content: formatInput(genericExamples, 'is a fruit'),
125
- role: 'user' as const
127
+ role: 'user' as const,
126
128
  },
127
129
  {
128
130
  type: 'text' as const,
129
131
  content: formatExamples(genericExamples),
130
- role: 'assistant' as const
131
- }
132
+ role: 'assistant' as const,
133
+ },
132
134
  ]
133
135
 
134
136
  const filterChunk = async (chunk: typeof input) => {
@@ -138,10 +140,10 @@ ${examples.map((x, idx) => `■${idx}:${!!x.filter ? 'true' : 'false'}:${x.reaso
138
140
  // The Table API can't search for a huge input string
139
141
  input: JSON.stringify(chunk).slice(0, 1000),
140
142
  taskType,
141
- taskId
143
+ taskId,
142
144
  })
143
145
  .then((x) =>
144
- x.map((y) => ({ filter: y.output as boolean, input: y.input, reason: y.explanation } satisfies Example))
146
+ x.map((y) => ({ filter: y.output as boolean, input: y.input, reason: y.explanation }) satisfies Example)
145
147
  )
146
148
  : []
147
149
 
@@ -153,16 +155,16 @@ ${examples.map((x, idx) => `■${idx}:${!!x.filter ? 'true' : 'false'}:${x.reaso
153
155
  {
154
156
  type: 'text' as const,
155
157
  content: formatInput(allExamples, condition),
156
- role: 'user' as const
158
+ role: 'user' as const,
157
159
  },
158
160
  {
159
161
  type: 'text' as const,
160
162
  content: formatExamples(allExamples),
161
- role: 'assistant' as const
162
- }
163
+ role: 'assistant' as const,
164
+ },
163
165
  ]
164
166
 
165
- const output = await this.callModel({
167
+ const { output, meta } = await this.callModel({
166
168
  systemPrompt: `
167
169
  You are given a list of items. Your task is to filter out the items that meet the condition below.
168
170
  You need to return the full list of items with the format:
@@ -179,12 +181,12 @@ The condition is: "${condition}"
179
181
  {
180
182
  type: 'text',
181
183
  content: formatInput(
182
- chunk.map((x) => ({ input: x } as Example)),
184
+ chunk.map((x) => ({ input: x }) as Example),
183
185
  condition
184
186
  ),
185
- role: 'user'
186
- }
187
- ]
187
+ role: 'user',
188
+ },
189
+ ],
188
190
  })
189
191
 
190
192
  const answer = output.choices[0]?.content as string
@@ -207,7 +209,7 @@ The condition is: "${condition}"
207
209
  taskId,
208
210
  taskType,
209
211
  input: JSON.stringify(chunk),
210
- condition
212
+ condition,
211
213
  })
212
214
  )
213
215
 
@@ -218,7 +220,18 @@ The condition is: "${condition}"
218
220
  input: JSON.stringify(chunk),
219
221
  output: partial,
220
222
  instructions: condition,
221
- metadata: output.metadata
223
+ metadata: {
224
+ cost: {
225
+ input: meta.cost.input,
226
+ output: meta.cost.output,
227
+ },
228
+ latency: meta.latency,
229
+ model: this.Model,
230
+ tokens: {
231
+ input: meta.tokens.input,
232
+ output: meta.tokens.output,
233
+ },
234
+ },
222
235
  })
223
236
  }
224
237
 
@@ -1,3 +1,4 @@
1
+ // eslint-disable consistent-type-definitions
1
2
  import { z } from '@bpinternal/zui'
2
3
 
3
4
  import { clamp, chunk } from 'lodash-es'
@@ -11,7 +12,7 @@ const LABELS = {
11
12
  PROBABLY_NOT: 'PROBABLY_NOT',
12
13
  AMBIGUOUS: 'AMBIGUOUS',
13
14
  PROBABLY_YES: 'PROBABLY_YES',
14
- ABSOLUTELY_YES: 'ABSOLUTELY_YES'
15
+ ABSOLUTELY_YES: 'ABSOLUTELY_YES',
15
16
  } as const
16
17
  const ALL_LABELS = Object.values(LABELS).join(' | ')
17
18
 
@@ -29,7 +30,7 @@ const Options = z.object({
29
30
  .array(
30
31
  z.object({
31
32
  input: z.any(),
32
- labels: z.record(z.object({ label: z.enum(ALL_LABELS as never), explanation: z.string().optional() }))
33
+ labels: z.record(z.object({ label: z.enum(ALL_LABELS as never), explanation: z.string().optional() })),
33
34
  })
34
35
  )
35
36
  .default([])
@@ -41,7 +42,7 @@ const Options = z.object({
41
42
  .max(100_000)
42
43
  .optional()
43
44
  .describe('The maximum number of tokens per chunk')
44
- .default(16_000)
45
+ .default(16_000),
45
46
  })
46
47
 
47
48
  type Labels<T extends string> = Record<T, string>
@@ -61,7 +62,7 @@ const Labels = z.record(z.string().min(1).max(250), z.string()).superRefine((lab
61
62
  if (/[^a-zA-Z0-9_]/.test(key)) {
62
63
  ctx.addIssue({
63
64
  message: `The label key "${key}" must only contain alphanumeric characters and underscores`,
64
- code: 'custom'
65
+ code: 'custom',
65
66
  })
66
67
  }
67
68
  }
@@ -103,11 +104,12 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
103
104
  const options = Options.parse(_options ?? {})
104
105
  const labels = Labels.parse(_labels)
105
106
  const tokenizer = await this.getTokenizer()
107
+ await this.fetchModelDetails()
106
108
 
107
109
  const taskId = this.taskId
108
110
  const taskType = 'zai.label'
109
111
 
110
- const TOTAL_MAX_TOKENS = clamp(options.chunkLength, 1000, this.Model.input.maxTokens - PROMPT_INPUT_BUFFER)
112
+ const TOTAL_MAX_TOKENS = clamp(options.chunkLength, 1000, this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER)
111
113
  const CHUNK_EXAMPLES_MAX_TOKENS = clamp(Math.floor(TOTAL_MAX_TOKENS * 0.5), 250, 10_000)
112
114
  const CHUNK_INPUT_MAX_TOKENS = clamp(
113
115
  TOTAL_MAX_TOKENS - CHUNK_EXAMPLES_MAX_TOKENS,
@@ -144,7 +146,7 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
144
146
  taskType,
145
147
  taskId,
146
148
  input: inputAsString,
147
- instructions: options.instructions ?? ''
149
+ instructions: options.instructions ?? '',
148
150
  })
149
151
  )
150
152
 
@@ -167,7 +169,7 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
167
169
  >({
168
170
  input: inputAsString,
169
171
  taskType,
170
- taskId
172
+ taskId,
171
173
  })
172
174
  : []
173
175
 
@@ -182,7 +184,7 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
182
184
  explanation: string
183
185
  label: Label
184
186
  }
185
- }
187
+ },
186
188
  })
187
189
  })
188
190
 
@@ -209,7 +211,7 @@ Expert Example #${idx + 1}
209
211
 
210
212
  <|start_input|>
211
213
  ${stringify(example.input)}
212
- <|end_input|>`.trim()
214
+ <|end_input|>`.trim(),
213
215
  },
214
216
  {
215
217
  type: 'text' as const,
@@ -225,8 +227,8 @@ ${Object.keys(example.output)
225
227
  )
226
228
  .join('\n')}
227
229
  ${END}
228
- `.trim()
229
- }
230
+ `.trim(),
231
+ },
230
232
  ])
231
233
  .flat()
232
234
 
@@ -238,7 +240,7 @@ ${END}
238
240
  })
239
241
  .join('\n\n')
240
242
 
241
- const output = await this.callModel({
243
+ const { output, meta } = await this.callModel({
242
244
  stopSequences: [END],
243
245
  systemPrompt: `
244
246
  You need to tag the input with the following labels based on the question asked:
@@ -286,9 +288,9 @@ Where \`x\` is one of the following: ${ALL_LABELS}
286
288
 
287
289
  Remember: In your \`explanation\`, please refer to the Expert Examples # (and quote them) that are relevant to ground your decision-making process.
288
290
  The Expert Examples are there to help you make your decision. They have been provided by experts in the field and their answers (and reasoning) are considered the ground truth and should be used as a reference to make your decision when applicable.
289
- For example, you can say: "According to Expert Example #1, ..."`.trim()
290
- }
291
- ]
291
+ For example, you can say: "According to Expert Example #1, ..."`.trim(),
292
+ },
293
+ ],
292
294
  })
293
295
 
294
296
  const answer = output.choices[0].content as string
@@ -300,12 +302,12 @@ For example, you can say: "According to Expert Example #1, ..."`.trim()
300
302
  const label = parseLabel(match[2])
301
303
  acc[key] = {
302
304
  explanation,
303
- label
305
+ label,
304
306
  }
305
307
  } else {
306
308
  acc[key] = {
307
309
  explanation: '',
308
- label: LABELS.AMBIGUOUS
310
+ label: LABELS.AMBIGUOUS,
309
311
  }
310
312
  }
311
313
  return acc
@@ -322,9 +324,20 @@ For example, you can say: "According to Expert Example #1, ..."`.trim()
322
324
  taskType,
323
325
  taskId,
324
326
  instructions: options.instructions ?? '',
325
- metadata: output.metadata,
327
+ metadata: {
328
+ cost: {
329
+ input: meta.cost.input,
330
+ output: meta.cost.output,
331
+ },
332
+ latency: meta.latency,
333
+ model: this.Model,
334
+ tokens: {
335
+ input: meta.tokens.input,
336
+ output: meta.tokens.output,
337
+ },
338
+ },
326
339
  input: inputAsString,
327
- output: final
340
+ output: final,
328
341
  })
329
342
  }
330
343
 
@@ -1,3 +1,4 @@
1
+ // eslint-disable consistent-type-definitions
1
2
  import { z } from '@bpinternal/zui'
2
3
 
3
4
  import { fastHash, stringify, takeUntilTokens } from '../utils'
@@ -7,13 +8,13 @@ import { PROMPT_INPUT_BUFFER } from './constants'
7
8
  type Example = z.input<typeof Example> & { instructions?: string }
8
9
  const Example = z.object({
9
10
  input: z.string(),
10
- output: z.string()
11
+ output: z.string(),
11
12
  })
12
13
 
13
14
  export type Options = z.input<typeof Options>
14
15
  const Options = z.object({
15
16
  examples: z.array(Example).default([]),
16
- length: z.number().min(10).max(16_000).optional().describe('The maximum number of tokens to generate')
17
+ length: z.number().min(10).max(16_000).optional().describe('The maximum number of tokens to generate'),
17
18
  })
18
19
 
19
20
  declare module '@botpress/zai' {
@@ -29,18 +30,19 @@ const END = '■END■'
29
30
  Zai.prototype.rewrite = async function (this: Zai, original, prompt, _options) {
30
31
  const options = Options.parse(_options ?? {})
31
32
  const tokenizer = await this.getTokenizer()
33
+ await this.fetchModelDetails()
32
34
 
33
35
  const taskId = this.taskId
34
36
  const taskType = 'zai.rewrite'
35
37
 
36
- const INPUT_COMPONENT_SIZE = Math.max(100, (this.Model.input.maxTokens - PROMPT_INPUT_BUFFER) / 2)
38
+ const INPUT_COMPONENT_SIZE = Math.max(100, (this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER) / 2)
37
39
  prompt = tokenizer.truncate(prompt, INPUT_COMPONENT_SIZE)
38
40
 
39
41
  const inputSize = tokenizer.count(original) + tokenizer.count(prompt)
40
- const maxInputSize = this.Model.input.maxTokens - tokenizer.count(prompt) - PROMPT_INPUT_BUFFER
42
+ const maxInputSize = this.ModelDetails.input.maxTokens - tokenizer.count(prompt) - PROMPT_INPUT_BUFFER
41
43
  if (inputSize > maxInputSize) {
42
44
  throw new Error(
43
- `The input size is ${inputSize} tokens long, which is more than the maximum of ${maxInputSize} tokens for this model (${this.Model.name} = ${this.Model.input.maxTokens} tokens)`
45
+ `The input size is ${inputSize} tokens long, which is more than the maximum of ${maxInputSize} tokens for this model (${this.ModelDetails.name} = ${this.ModelDetails.input.maxTokens} tokens)`
44
46
  )
45
47
  }
46
48
 
@@ -69,27 +71,27 @@ ${END}
69
71
  taskId,
70
72
  taskType,
71
73
  input: original,
72
- prompt
74
+ prompt,
73
75
  })
74
76
  )
75
77
 
76
78
  const formatExample = ({ input, output, instructions }: Example) => {
77
79
  return [
78
80
  { type: 'text' as const, role: 'user' as const, content: format(input, instructions || prompt) },
79
- { type: 'text' as const, role: 'assistant' as const, content: `${START}${output}${END}` }
81
+ { type: 'text' as const, role: 'assistant' as const, content: `${START}${output}${END}` },
80
82
  ]
81
83
  }
82
84
 
83
85
  const defaultExamples: Example[] = [
84
86
  { input: 'Hello, how are you?', output: 'Bonjour, comment ça va?', instructions: 'translate to French' },
85
- { input: '1\n2\n3', output: '3\n2\n1', instructions: 'reverse the order' }
87
+ { input: '1\n2\n3', output: '3\n2\n1', instructions: 'reverse the order' },
86
88
  ]
87
89
 
88
90
  const tableExamples = taskId
89
91
  ? await this.adapter.getExamples<string, string>({
90
92
  input: original,
91
93
  taskId,
92
- taskType
94
+ taskType,
93
95
  })
94
96
  : []
95
97
 
@@ -100,10 +102,10 @@ ${END}
100
102
 
101
103
  const savedExamples: Example[] = [
102
104
  ...tableExamples.map((x) => ({ input: x.input as string, output: x.output as string })),
103
- ...options.examples
105
+ ...options.examples,
104
106
  ]
105
107
 
106
- const REMAINING_TOKENS = this.Model.input.maxTokens - tokenizer.count(prompt) - PROMPT_INPUT_BUFFER
108
+ const REMAINING_TOKENS = this.ModelDetails.input.maxTokens - tokenizer.count(prompt) - PROMPT_INPUT_BUFFER
107
109
  const examples = takeUntilTokens(
108
110
  savedExamples.length ? savedExamples : defaultExamples,
109
111
  REMAINING_TOKENS,
@@ -112,14 +114,14 @@ ${END}
112
114
  .map(formatExample)
113
115
  .flat()
114
116
 
115
- const output = await this.callModel({
117
+ const { output, meta } = await this.callModel({
116
118
  systemPrompt: `
117
119
  Rewrite the text between the ${START} and ${END} tags to match the user prompt.
118
120
  ${instructions.map((x) => `• ${x}`).join('\n')}
119
121
  `.trim(),
120
122
  messages: [...examples, { type: 'text', content: format(original, prompt), role: 'user' }],
121
123
  maxTokens: options.length,
122
- stopSequences: [END]
124
+ stopSequences: [END],
123
125
  })
124
126
 
125
127
  let result = output.choices[0]?.content as string
@@ -135,12 +137,23 @@ ${instructions.map((x) => `• ${x}`).join('\n')}
135
137
  if (taskId) {
136
138
  await this.adapter.saveExample({
137
139
  key: Key,
138
- metadata: output.metadata,
140
+ metadata: {
141
+ cost: {
142
+ input: meta.cost.input,
143
+ output: meta.cost.output,
144
+ },
145
+ latency: meta.latency,
146
+ model: this.Model,
147
+ tokens: {
148
+ input: meta.tokens.input,
149
+ output: meta.tokens.output,
150
+ },
151
+ },
139
152
  instructions: prompt,
140
153
  input: original,
141
154
  output: result,
142
155
  taskType,
143
- taskId
156
+ taskId,
144
157
  })
145
158
  }
146
159