@botpress/zai 2.4.1 → 2.4.2
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/dist/index.d.ts +1534 -22
- package/dist/operations/answer.js +6 -4
- package/dist/response.js +166 -1
- package/dist/zai.js +106 -0
- package/e2e/data/cache.jsonl +2 -0
- package/package.json +1 -1
- package/src/context.ts +32 -0
- package/src/operations/answer.ts +105 -9
- package/src/operations/check.ts +75 -1
- package/src/operations/extract.ts +67 -1
- package/src/operations/filter.ts +86 -1
- package/src/operations/group.ts +150 -0
- package/src/operations/label.ts +119 -1
- package/src/operations/rate.ts +112 -2
- package/src/operations/rewrite.ts +84 -1
- package/src/operations/sort.ts +111 -9
- package/src/operations/summarize.ts +74 -1
- package/src/operations/text.ts +50 -1
- package/src/response.ts +264 -2
- package/src/zai.ts +214 -0
|
@@ -241,17 +241,19 @@ Question to answer: "${question}"`;
|
|
|
241
241
|
}
|
|
242
242
|
],
|
|
243
243
|
transform: (text) => {
|
|
244
|
+
text = text.slice(0, text.lastIndexOf(END.slice(0, -1)));
|
|
244
245
|
return parseResponse(text || "", mappings);
|
|
245
246
|
}
|
|
246
247
|
});
|
|
247
248
|
return extracted;
|
|
248
249
|
};
|
|
249
|
-
const parseResponse = (response, mappings) => {
|
|
250
|
+
export const parseResponse = (response, mappings) => {
|
|
250
251
|
const text = response.trim();
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
} else if (text.includes(AMBIGUOUS_START)) {
|
|
252
|
+
const answersCount = (text.match(new RegExp(ANSWER_START, "g")) || []).length;
|
|
253
|
+
if (text.includes(AMBIGUOUS_START) || answersCount >= 2) {
|
|
254
254
|
return parseAmbiguousResponse(text, mappings);
|
|
255
|
+
} else if (text.includes(ANSWER_START)) {
|
|
256
|
+
return parseAnswerResponse(text, mappings);
|
|
255
257
|
} else if (text.includes(OUT_OF_TOPIC_START)) {
|
|
256
258
|
return parseOutOfTopicResponse(text);
|
|
257
259
|
} else if (text.includes(INVALID_QUESTION_START)) {
|
package/dist/response.js
CHANGED
|
@@ -29,19 +29,104 @@ export class Response {
|
|
|
29
29
|
this._eventEmitter.emit("progress", usage);
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Subscribes to events emitted during operation execution.
|
|
34
|
+
*
|
|
35
|
+
* @param type - Event type: 'progress', 'complete', or 'error'
|
|
36
|
+
* @param listener - Callback function to handle the event
|
|
37
|
+
* @returns This Response instance for chaining
|
|
38
|
+
*
|
|
39
|
+
* @example Track progress
|
|
40
|
+
* ```typescript
|
|
41
|
+
* response.on('progress', (usage) => {
|
|
42
|
+
* console.log(`${usage.requests.percentage * 100}% complete`)
|
|
43
|
+
* console.log(`Cost: $${usage.cost.total}`)
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example Handle completion
|
|
48
|
+
* ```typescript
|
|
49
|
+
* response.on('complete', (result) => {
|
|
50
|
+
* console.log('Operation completed:', result)
|
|
51
|
+
* })
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @example Handle errors
|
|
55
|
+
* ```typescript
|
|
56
|
+
* response.on('error', (error) => {
|
|
57
|
+
* console.error('Operation failed:', error)
|
|
58
|
+
* })
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
33
61
|
on(type, listener) {
|
|
34
62
|
this._eventEmitter.on(type, listener);
|
|
35
63
|
return this;
|
|
36
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Unsubscribes from events.
|
|
67
|
+
*
|
|
68
|
+
* @param type - Event type to unsubscribe from
|
|
69
|
+
* @param listener - The exact listener function to remove
|
|
70
|
+
* @returns This Response instance for chaining
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const progressHandler = (usage) => console.log(usage.tokens.total)
|
|
75
|
+
* response.on('progress', progressHandler)
|
|
76
|
+
* // Later...
|
|
77
|
+
* response.off('progress', progressHandler)
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
37
80
|
off(type, listener) {
|
|
38
81
|
this._eventEmitter.off(type, listener);
|
|
39
82
|
return this;
|
|
40
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Subscribes to an event for a single emission.
|
|
86
|
+
*
|
|
87
|
+
* The listener is automatically removed after being called once.
|
|
88
|
+
*
|
|
89
|
+
* @param type - Event type: 'progress', 'complete', or 'error'
|
|
90
|
+
* @param listener - Callback function to handle the event once
|
|
91
|
+
* @returns This Response instance for chaining
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* response.once('complete', (result) => {
|
|
96
|
+
* console.log('Finished:', result)
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
41
100
|
once(type, listener) {
|
|
42
101
|
this._eventEmitter.once(type, listener);
|
|
43
102
|
return this;
|
|
44
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Binds an external AbortSignal to this operation.
|
|
106
|
+
*
|
|
107
|
+
* When the signal is aborted, the operation will be cancelled automatically.
|
|
108
|
+
* Useful for integrating with UI cancel buttons or request timeouts.
|
|
109
|
+
*
|
|
110
|
+
* @param signal - AbortSignal to bind
|
|
111
|
+
* @returns This Response instance for chaining
|
|
112
|
+
*
|
|
113
|
+
* @example With AbortController
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const controller = new AbortController()
|
|
116
|
+
* const response = zai.extract(data, schema).bindSignal(controller.signal)
|
|
117
|
+
*
|
|
118
|
+
* // Cancel from elsewhere
|
|
119
|
+
* cancelButton.onclick = () => controller.abort()
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @example With timeout
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const controller = new AbortController()
|
|
125
|
+
* setTimeout(() => controller.abort('Timeout'), 10000)
|
|
126
|
+
*
|
|
127
|
+
* const response = zai.answer(docs, question).bindSignal(controller.signal)
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
45
130
|
bindSignal(signal) {
|
|
46
131
|
if (signal.aborted) {
|
|
47
132
|
this.abort(signal.reason);
|
|
@@ -54,9 +139,48 @@ export class Response {
|
|
|
54
139
|
void this.once("error", () => signal.removeEventListener("abort", signalAbort));
|
|
55
140
|
return this;
|
|
56
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Aborts the operation in progress.
|
|
144
|
+
*
|
|
145
|
+
* The operation will be cancelled and throw an abort error.
|
|
146
|
+
* Any partial results will not be returned.
|
|
147
|
+
*
|
|
148
|
+
* @param reason - Optional reason for aborting (string or Error)
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const response = zai.extract(largeDocument, schema)
|
|
153
|
+
*
|
|
154
|
+
* // Abort after 5 seconds
|
|
155
|
+
* setTimeout(() => response.abort('Operation timeout'), 5000)
|
|
156
|
+
*
|
|
157
|
+
* try {
|
|
158
|
+
* await response
|
|
159
|
+
* } catch (error) {
|
|
160
|
+
* console.log('Aborted:', error)
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
57
164
|
abort(reason) {
|
|
58
165
|
this._context.controller.abort(reason);
|
|
59
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Promise interface - allows awaiting the Response.
|
|
169
|
+
*
|
|
170
|
+
* When awaited, returns the simplified value (S).
|
|
171
|
+
* Use `.result()` for full output with usage statistics.
|
|
172
|
+
*
|
|
173
|
+
* @param onfulfilled - Success handler
|
|
174
|
+
* @param onrejected - Error handler
|
|
175
|
+
* @returns Promise resolving to simplified value
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* // Simplified value
|
|
180
|
+
* const isPositive = await zai.check(review, 'Is positive?')
|
|
181
|
+
* console.log(isPositive) // true
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
60
184
|
// oxlint-disable-next-line no-thenable
|
|
61
185
|
then(onfulfilled, onrejected) {
|
|
62
186
|
return this._promise.then(
|
|
@@ -72,9 +196,50 @@ export class Response {
|
|
|
72
196
|
}
|
|
73
197
|
);
|
|
74
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Promise interface - handles errors.
|
|
201
|
+
*
|
|
202
|
+
* @param onrejected - Error handler
|
|
203
|
+
* @returns Promise resolving to simplified value or error result
|
|
204
|
+
*/
|
|
75
205
|
catch(onrejected) {
|
|
76
206
|
return this._promise.catch(onrejected);
|
|
77
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Gets the full result with detailed usage statistics and timing.
|
|
210
|
+
*
|
|
211
|
+
* Unlike awaiting the Response directly (which returns simplified value),
|
|
212
|
+
* this method provides:
|
|
213
|
+
* - `output`: Full operation result (not simplified)
|
|
214
|
+
* - `usage`: Detailed token usage, cost, and request statistics
|
|
215
|
+
* - `elapsed`: Operation duration in milliseconds
|
|
216
|
+
*
|
|
217
|
+
* @returns Promise resolving to full result object
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const { output, usage, elapsed } = await zai.check(text, condition).result()
|
|
222
|
+
*
|
|
223
|
+
* console.log(output.value) // true/false
|
|
224
|
+
* console.log(output.explanation) // "The text expresses..."
|
|
225
|
+
* console.log(usage.tokens.total) // 245
|
|
226
|
+
* console.log(usage.cost.total) // 0.0012
|
|
227
|
+
* console.log(elapsed) // 1523 (ms)
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* @example Usage statistics breakdown
|
|
231
|
+
* ```typescript
|
|
232
|
+
* const { usage } = await response.result()
|
|
233
|
+
*
|
|
234
|
+
* console.log('Requests:', usage.requests.requests)
|
|
235
|
+
* console.log('Cached:', usage.requests.cached)
|
|
236
|
+
* console.log('Input tokens:', usage.tokens.input)
|
|
237
|
+
* console.log('Output tokens:', usage.tokens.output)
|
|
238
|
+
* console.log('Input cost:', usage.cost.input)
|
|
239
|
+
* console.log('Output cost:', usage.cost.output)
|
|
240
|
+
* console.log('Total cost:', usage.cost.total)
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
78
243
|
async result() {
|
|
79
244
|
const output = await this._promise;
|
|
80
245
|
const usage = this._context.usage;
|
package/dist/zai.js
CHANGED
|
@@ -47,6 +47,27 @@ export class Zai {
|
|
|
47
47
|
namespace;
|
|
48
48
|
adapter;
|
|
49
49
|
activeLearning;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new Zai instance with the specified configuration.
|
|
52
|
+
*
|
|
53
|
+
* @param config - Configuration object containing client, model, and learning settings
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* import { Client } from '@botpress/client'
|
|
58
|
+
* import { Zai } from '@botpress/zai'
|
|
59
|
+
*
|
|
60
|
+
* const client = new Client({ token: 'your-token' })
|
|
61
|
+
* const zai = new Zai({
|
|
62
|
+
* client,
|
|
63
|
+
* modelId: 'best',
|
|
64
|
+
* namespace: 'my-app',
|
|
65
|
+
* userId: 'user-123'
|
|
66
|
+
* })
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @throws {Error} If the configuration is invalid (e.g., invalid modelId format)
|
|
70
|
+
*/
|
|
50
71
|
constructor(config) {
|
|
51
72
|
this._originalConfig = config;
|
|
52
73
|
const parsed = _ZaiConfig.parse(config);
|
|
@@ -89,12 +110,97 @@ export class Zai {
|
|
|
89
110
|
}
|
|
90
111
|
return `${this.namespace}/${this.activeLearning.taskId}`.replace(/\/+/g, "/");
|
|
91
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates a new Zai instance with merged configuration options.
|
|
115
|
+
*
|
|
116
|
+
* This method allows you to create variations of your Zai instance with different
|
|
117
|
+
* settings without modifying the original. Useful for switching models, namespaces,
|
|
118
|
+
* or other configuration on a per-operation basis.
|
|
119
|
+
*
|
|
120
|
+
* @param options - Partial configuration to override the current settings
|
|
121
|
+
* @returns A new Zai instance with the merged configuration
|
|
122
|
+
*
|
|
123
|
+
* @example Switch to a faster model
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const zai = new Zai({ client })
|
|
126
|
+
*
|
|
127
|
+
* // Use fast model for simple operations
|
|
128
|
+
* const fastZai = zai.with({ modelId: 'fast' })
|
|
129
|
+
* await fastZai.check(text, 'Is this spam?')
|
|
130
|
+
*
|
|
131
|
+
* // Use best model for complex operations
|
|
132
|
+
* const bestZai = zai.with({ modelId: 'best' })
|
|
133
|
+
* await bestZai.extract(document, complexSchema)
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @example Change namespace
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const customerZai = zai.with({ namespace: 'customer-support' })
|
|
139
|
+
* const salesZai = zai.with({ namespace: 'sales' })
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @example Use specific model
|
|
143
|
+
* ```typescript
|
|
144
|
+
* const gpt4 = zai.with({ modelId: 'openai:gpt-4' })
|
|
145
|
+
* const claude = zai.with({ modelId: 'anthropic:claude-3-5-sonnet-20241022' })
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
92
148
|
with(options) {
|
|
93
149
|
return new Zai({
|
|
94
150
|
...this._originalConfig,
|
|
95
151
|
...options
|
|
96
152
|
});
|
|
97
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Creates a new Zai instance with active learning enabled for a specific task.
|
|
156
|
+
*
|
|
157
|
+
* Active learning stores successful operation results and uses them as examples for
|
|
158
|
+
* future operations, improving accuracy and consistency over time. Each task ID
|
|
159
|
+
* maintains its own set of learned examples.
|
|
160
|
+
*
|
|
161
|
+
* @param taskId - Unique identifier for the learning task (alphanumeric, hyphens, underscores, slashes)
|
|
162
|
+
* @returns A new Zai instance with active learning enabled for the specified task
|
|
163
|
+
*
|
|
164
|
+
* @example Sentiment analysis with learning
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const zai = new Zai({
|
|
167
|
+
* client,
|
|
168
|
+
* activeLearning: {
|
|
169
|
+
* enable: false,
|
|
170
|
+
* tableName: 'AppLearningTable',
|
|
171
|
+
* taskId: 'default'
|
|
172
|
+
* }
|
|
173
|
+
* })
|
|
174
|
+
*
|
|
175
|
+
* // Enable learning for sentiment analysis
|
|
176
|
+
* const sentimentZai = zai.learn('sentiment-analysis')
|
|
177
|
+
* const result = await sentimentZai.check(review, 'Is this review positive?')
|
|
178
|
+
*
|
|
179
|
+
* // Each successful call is stored and used to improve future calls
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @example Different tasks for different purposes
|
|
183
|
+
* ```typescript
|
|
184
|
+
* // Extract user info with learning
|
|
185
|
+
* const userExtractor = zai.learn('user-extraction')
|
|
186
|
+
* await userExtractor.extract(text, userSchema)
|
|
187
|
+
*
|
|
188
|
+
* // Extract product info with separate learning
|
|
189
|
+
* const productExtractor = zai.learn('product-extraction')
|
|
190
|
+
* await productExtractor.extract(text, productSchema)
|
|
191
|
+
*
|
|
192
|
+
* // Each task learns independently
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* @example Combining with other configuration
|
|
196
|
+
* ```typescript
|
|
197
|
+
* // Use fast model + learning
|
|
198
|
+
* const fastLearner = zai.with({ modelId: 'fast' }).learn('quick-checks')
|
|
199
|
+
* await fastLearner.check(email, 'Is this spam?')
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* @see {@link ZaiConfig.activeLearning} for configuration options
|
|
203
|
+
*/
|
|
98
204
|
learn(taskId) {
|
|
99
205
|
return new Zai({
|
|
100
206
|
...this._originalConfig,
|
package/e2e/data/cache.jsonl
CHANGED
|
@@ -1799,3 +1799,5 @@
|
|
|
1799
1799
|
{"key":"a7dec8bb","input":"{\"body\":{\"messages\":[{\"content\":\"You are an expert research assistant specialized in answering questions using only the information provided in documents.\\n\\n# Task\\nAnswer the user's question based ONLY on the information in the provided documents. You MUST cite your sources using line numbers.\\n\\n# Document Format\\nDocuments are provided with line numbers:\\n■001 | First line of text\\n■002 | Second line of text\\n■003 | Third line of text\\n\\n# Citation Format\\nYou MUST include citations immediately after statements. Use these formats:\\n- Single line: ■035\\n- Range: ■005-010\\n- Multiple: ■035■046■094\\n\\n# Response Format\\n\\nChoose ONE of these response types:\\n\\n**TYPE 1 - ANSWER** (Use this when you can answer the question)\\n■answer\\n[Your answer with inline citations■001-003. Make sure each part is cited correctly■013. More text. ■015]\\n■end■\\n\\n**TYPE 2 - AMBIGUOUS** (Use when the question has multiple valid interpretations)\\n■ambiguous\\n[Explain the ambiguity]\\n■follow_up\\n[Ask a clarifying question]\\n■answer\\n[First interpretation with citations ■001 and part 2 as well.■002]\\n■answer\\n[Second interpretation with citations ■005 and part 2 of the answer.■006]\\n■end■\\n\\n**TYPE 3 - OUT OF TOPIC** (Use when question is completely unrelated to documents)\\n■out_of_topic\\n[Explain why it's unrelated]\\n■end■\\n\\n**TYPE 4 - INVALID QUESTION** (Use when input is not a proper question, e.g., gibberish, malformed or nonsensical)\\n■invalid_question\\n[Explain why it's invalid, e.g., \\\"The question is incomplete\\\" or \\\"The question contains nonsensical terms\\\", or \\\"Received gibberish\\\"]\\n■end■\\n\\n**TYPE 5 - MISSING KNOWLEDGE** (Use ONLY when documents lack specific details needed)\\n■missing_knowledge\\n[Explain what specific information is missing]\\n■end■\\n\\n# Important Rules\\n- PREFER answering when possible - only use missing_knowledge if truly no relevant info exists\\n- ALWAYS cite sources with line numbers\\n- Use ONLY information from the documents\\n- Be precise and factual\\n- Do NOT fabricate information\\n- Do NOT mention \\\"According to the documents\\\" or similar phrases – just provide a high-quality answer with citations\\n- Do not be too strict on the question format; assume high-level answers are acceptable unless the question clearly asks for very specific details or requests depth beyond the documents\\n\\n# Additional Instructions\\nHere are some additional instructions to follow about how to answer the question:\\nProvide a clear and concise answer based on the documents.\",\"role\":\"system\"},{\"content\":\"<documents>\\n■001 | Some content here\\n</documents>\\n\\nPlease answer the below question using the format specified above.\\nQuestion to answer: \\\"Question?1762472164277\\\"\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.answer\",\"promptSource\":\"zai:zai.answer:default\"},\"model\":\"fast\",\"reasoningEffort\":\"none\",\"signal\":{},\"stopSequences\":[\"■end■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■missing_knowledge\nThe provided document contains only the line “Some content here” and does not include any information related to the question “Question?1762472164277.” Therefore, the necessary details to answer the question are missing.\n■end","metadata":{"provider":"cerebras","usage":{"inputTokens":659,"outputTokens":82,"inputCost":0.00023065,"outputCost":0.0000615},"model":"cerebras:gpt-oss-120b","ttft":191,"latency":893,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.00029215,"warnings":[{"type":"parameter_ignored","message":"Reasoning effort \"none\" is not supported by the \"gpt-oss-120b\" model, using \"low\" effort instead"}]}}}
|
|
1800
1800
|
{"key":"ed1db1d1","input":"{\"body\":{\"messages\":[{\"content\":\"You are an expert research assistant specialized in answering questions using only the information provided in documents.\\n\\n# Task\\nAnswer the user's question based ONLY on the information in the provided documents. You MUST cite your sources using line numbers.\\n\\n# Document Format\\nDocuments are provided with line numbers:\\n■001 | First line of text\\n■002 | Second line of text\\n■003 | Third line of text\\n\\n# Citation Format\\nYou MUST include citations immediately after statements. Use these formats:\\n- Single line: ■035\\n- Range: ■005-010\\n- Multiple: ■035■046■094\\n\\n# Response Format\\n\\nChoose ONE of these response types:\\n\\n**TYPE 1 - ANSWER** (Use this when you can answer the question)\\n■answer\\n[Your answer with inline citations■001-003. Make sure each part is cited correctly■013. More text. ■015]\\n■end■\\n\\n**TYPE 2 - AMBIGUOUS** (Use when the question has multiple valid interpretations)\\n■ambiguous\\n[Explain the ambiguity]\\n■follow_up\\n[Ask a clarifying question]\\n■answer\\n[First interpretation with citations ■001 and part 2 as well.■002]\\n■answer\\n[Second interpretation with citations ■005 and part 2 of the answer.■006]\\n■end■\\n\\n**TYPE 3 - OUT OF TOPIC** (Use when question is completely unrelated to documents)\\n■out_of_topic\\n[Explain why it's unrelated]\\n■end■\\n\\n**TYPE 4 - INVALID QUESTION** (Use when input is not a proper question, e.g., gibberish, malformed or nonsensical)\\n■invalid_question\\n[Explain why it's invalid, e.g., \\\"The question is incomplete\\\" or \\\"The question contains nonsensical terms\\\", or \\\"Received gibberish\\\"]\\n■end■\\n\\n**TYPE 5 - MISSING KNOWLEDGE** (Use ONLY when documents lack specific details needed)\\n■missing_knowledge\\n[Explain what specific information is missing]\\n■end■\\n\\n# Important Rules\\n- PREFER answering when possible - only use missing_knowledge if truly no relevant info exists\\n- ALWAYS cite sources with line numbers\\n- Use ONLY information from the documents\\n- Be precise and factual\\n- Do NOT fabricate information\\n- Do NOT mention \\\"According to the documents\\\" or similar phrases – just provide a high-quality answer with citations\\n- Do not be too strict on the question format; assume high-level answers are acceptable unless the question clearly asks for very specific details or requests depth beyond the documents\\n\\n# Additional Instructions\\nHere are some additional instructions to follow about how to answer the question:\\nProvide a clear and concise answer based on the documents.\",\"role\":\"system\"},{\"content\":\"<documents>\\n■001 | Some content here\\n</documents>\\n\\nPlease answer the below question using the format specified above.\\nQuestion to answer: \\\"Question?\\\"\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.answer\",\"promptSource\":\"zai:zai.answer:default\"},\"model\":\"fast\",\"reasoningEffort\":\"none\",\"signal\":{},\"stopSequences\":[\"■end■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■missing_knowledge\nThe provided document contains only a single line of vague text (\"Some content here\") and does not include any information relevant to answering the question posed. Therefore, the necessary details to respond are missing.\n■end","metadata":{"provider":"cerebras","usage":{"inputTokens":653,"outputTokens":77,"inputCost":0.00022855,"outputCost":0.00005775},"model":"cerebras:gpt-oss-120b","ttft":165,"latency":257,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0002863,"warnings":[{"type":"parameter_ignored","message":"Reasoning effort \"none\" is not supported by the \"gpt-oss-120b\" model, using \"low\" effort instead"}]}}}
|
|
1801
1801
|
{"key":"78e0f785","input":"{\"body\":{\"messages\":[{\"content\":\"You are an expert research assistant specialized in answering questions using only the information provided in documents.\\n\\n# Task\\nAnswer the user's question based ONLY on the information in the provided documents. You MUST cite your sources using line numbers.\\n\\n# Document Format\\nDocuments are provided with line numbers:\\n■001 | First line of text\\n■002 | Second line of text\\n■003 | Third line of text\\n\\n# Citation Format\\nYou MUST include citations immediately after statements. Use these formats:\\n- Single line: ■035\\n- Range: ■005-010\\n- Multiple: ■035■046■094\\n\\n# Response Format\\n\\nChoose ONE of these response types:\\n\\n**TYPE 1 - ANSWER** (Use this when you can answer the question)\\n■answer\\n[Your answer with inline citations■001-003. Make sure each part is cited correctly■013. More text. ■015]\\n■end■\\n\\n**TYPE 2 - AMBIGUOUS** (Use when the question has multiple valid interpretations)\\n■ambiguous\\n[Explain the ambiguity]\\n■follow_up\\n[Ask a clarifying question]\\n■answer\\n[First interpretation with citations ■001 and part 2 as well.■002]\\n■answer\\n[Second interpretation with citations ■005 and part 2 of the answer.■006]\\n■end■\\n\\n**TYPE 3 - OUT OF TOPIC** (Use when question is completely unrelated to documents)\\n■out_of_topic\\n[Explain why it's unrelated]\\n■end■\\n\\n**TYPE 4 - INVALID QUESTION** (Use when input is not a proper question, e.g., gibberish, malformed or nonsensical)\\n■invalid_question\\n[Explain why it's invalid, e.g., \\\"The question is incomplete\\\" or \\\"The question contains nonsensical terms\\\", or \\\"Received gibberish\\\"]\\n■end■\\n\\n**TYPE 5 - MISSING KNOWLEDGE** (Use ONLY when documents lack specific details needed)\\n■missing_knowledge\\n[Explain what specific information is missing]\\n■end■\\n\\n# Important Rules\\n- PREFER answering when possible - only use missing_knowledge if truly no relevant info exists\\n- ALWAYS cite sources with line numbers\\n- Use ONLY information from the documents\\n- Be precise and factual\\n- Do NOT fabricate information\\n- Do NOT mention \\\"According to the documents\\\" or similar phrases – just provide a high-quality answer with citations\\n- Do not be too strict on the question format; assume high-level answers are acceptable unless the question clearly asks for very specific details or requests depth beyond the documents\\n\\n# Additional Instructions\\nHere are some additional instructions to follow about how to answer the question:\\nProvide a clear and concise answer based on the documents.\",\"role\":\"system\"},{\"content\":\"<documents>\\n■001 | Document 1\\n■002 | Document 2\\n■003 | Document 3\\n</documents>\\n\\nPlease answer the below question using the format specified above.\\nQuestion to answer: \\\"What is this about?\\\"\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.answer\",\"promptSource\":\"zai:zai.answer:default\"},\"model\":\"fast\",\"reasoningEffort\":\"none\",\"signal\":{},\"stopSequences\":[\"■end■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■missing_knowledge\nThe provided documents contain only titles (\"Document 1\", \"Document 2\", \"Document 3\") and do not include any substantive information about a topic, making it impossible to determine what \"this\" refers to. Additional content is needed to answer the question.\n■end","metadata":{"provider":"cerebras","usage":{"inputTokens":670,"outputTokens":102,"inputCost":0.0002345,"outputCost":0.0000765},"model":"cerebras:gpt-oss-120b","ttft":189,"latency":279,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.000311,"warnings":[{"type":"parameter_ignored","message":"Reasoning effort \"none\" is not supported by the \"gpt-oss-120b\" model, using \"low\" effort instead"}]}}}
|
|
1802
|
+
{"key":"67eb9851","input":"{\"body\":{\"messages\":[{\"content\":\"You are an expert research assistant specialized in answering questions using only the information provided in documents.\\n\\n# Task\\nAnswer the user's question based ONLY on the information in the provided documents. You MUST cite your sources using line numbers.\\n\\n# Document Format\\nDocuments are provided with line numbers:\\n■001 | First line of text\\n■002 | Second line of text\\n■003 | Third line of text\\n\\n# Citation Format\\nYou MUST include citations immediately after statements. Use these formats:\\n- Single line: ■035\\n- Range: ■005-010\\n- Multiple: ■035■046■094\\n\\n# Response Format\\n\\nChoose ONE of these response types:\\n\\n**TYPE 1 - ANSWER** (Use this when you can answer the question)\\n■answer\\n[Your answer with inline citations■001-003. Make sure each part is cited correctly■013. More text. ■015]\\n■end■\\n\\n**TYPE 2 - AMBIGUOUS** (Use when the question has multiple valid interpretations)\\n■ambiguous\\n[Explain the ambiguity]\\n■follow_up\\n[Ask a clarifying question]\\n■answer\\n[First interpretation with citations ■001 and part 2 as well.■002]\\n■answer\\n[Second interpretation with citations ■005 and part 2 of the answer.■006]\\n■end■\\n\\n**TYPE 3 - OUT OF TOPIC** (Use when question is completely unrelated to documents)\\n■out_of_topic\\n[Explain why it's unrelated]\\n■end■\\n\\n**TYPE 4 - INVALID QUESTION** (Use when input is not a proper question, e.g., gibberish, malformed or nonsensical)\\n■invalid_question\\n[Explain why it's invalid, e.g., \\\"The question is incomplete\\\" or \\\"The question contains nonsensical terms\\\", or \\\"Received gibberish\\\"]\\n■end■\\n\\n**TYPE 5 - MISSING KNOWLEDGE** (Use ONLY when documents lack specific details needed)\\n■missing_knowledge\\n[Explain what specific information is missing]\\n■end■\\n\\n# Important Rules\\n- PREFER answering when possible - only use missing_knowledge if truly no relevant info exists\\n- ALWAYS cite sources with line numbers\\n- Use ONLY information from the documents\\n- Be precise and factual\\n- Do NOT fabricate information\\n- Do NOT mention \\\"According to the documents\\\" or similar phrases – just provide a high-quality answer with citations\\n- Do not be too strict on the question format; assume high-level answers are acceptable unless the question clearly asks for very specific details or requests depth beyond the documents\\n\\n# Additional Instructions\\nHere are some additional instructions to follow about how to answer the question:\\nProvide a clear and concise answer based on the documents.\",\"role\":\"system\"},{\"content\":\"<documents>\\n■001 | Botpress was founded in 2016.\\n■002 | It is an AI agent platform.\\n■003 | The company is headquartered in Quebec, Canada.\\n</documents>\\n\\nPlease answer the below question using the format specified above.\\nQuestion to answer: \\\"Tell me about Botpress.\\\"\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.answer\",\"promptSource\":\"zai:zai.answer:default\"},\"model\":\"fast\",\"reasoningEffort\":\"none\",\"signal\":{},\"stopSequences\":[\"■end■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■answer\nBotpress was founded in 2016. It is an AI agent platform and the company is headquartered in Quebec, Canada.■001■002■003\n■end","metadata":{"provider":"cerebras","usage":{"inputTokens":684,"outputTokens":61,"inputCost":0.0002394,"outputCost":0.00004575},"model":"cerebras:gpt-oss-120b","ttft":281,"latency":649,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.00028515,"warnings":[{"type":"parameter_ignored","message":"Reasoning effort \"none\" is not supported by the \"gpt-oss-120b\" model, using \"low\" effort instead"}]}}}
|
|
1803
|
+
{"key":"cbc6f03c","input":"{\"body\":{\"messages\":[{\"content\":\"You are an expert research assistant specialized in answering questions using only the information provided in documents.\\n\\n# Task\\nAnswer the user's question based ONLY on the information in the provided documents. You MUST cite your sources using line numbers.\\n\\n# Document Format\\nDocuments are provided with line numbers:\\n■001 | First line of text\\n■002 | Second line of text\\n■003 | Third line of text\\n\\n# Citation Format\\nYou MUST include citations immediately after statements. Use these formats:\\n- Single line: ■035\\n- Range: ■005-010\\n- Multiple: ■035■046■094\\n\\n# Response Format\\n\\nChoose ONE of these response types:\\n\\n**TYPE 1 - ANSWER** (Use this when you can answer the question)\\n■answer\\n[Your answer with inline citations■001-003. Make sure each part is cited correctly■013. More text. ■015]\\n■end■\\n\\n**TYPE 2 - AMBIGUOUS** (Use when the question has multiple valid interpretations)\\n■ambiguous\\n[Explain the ambiguity]\\n■follow_up\\n[Ask a clarifying question]\\n■answer\\n[First interpretation with citations ■001 and part 2 as well.■002]\\n■answer\\n[Second interpretation with citations ■005 and part 2 of the answer.■006]\\n■end■\\n\\n**TYPE 3 - OUT OF TOPIC** (Use when question is completely unrelated to documents)\\n■out_of_topic\\n[Explain why it's unrelated]\\n■end■\\n\\n**TYPE 4 - INVALID QUESTION** (Use when input is not a proper question, e.g., gibberish, malformed or nonsensical)\\n■invalid_question\\n[Explain why it's invalid, e.g., \\\"The question is incomplete\\\" or \\\"The question contains nonsensical terms\\\", or \\\"Received gibberish\\\"]\\n■end■\\n\\n**TYPE 5 - MISSING KNOWLEDGE** (Use ONLY when documents lack specific details needed)\\n■missing_knowledge\\n[Explain what specific information is missing]\\n■end■\\n\\n# Important Rules\\n- PREFER answering when possible - only use missing_knowledge if truly no relevant info exists\\n- ALWAYS cite sources with line numbers\\n- Use ONLY information from the documents\\n- Be precise and factual\\n- Do NOT fabricate information\\n- Do NOT mention \\\"According to the documents\\\" or similar phrases – just provide a high-quality answer with citations\\n- Do not be too strict on the question format; assume high-level answers are acceptable unless the question clearly asks for very specific details or requests depth beyond the documents\\n\\n# Additional Instructions\\nHere are some additional instructions to follow about how to answer the question:\\nProvide a clear and concise answer based on the documents.\",\"role\":\"system\"},{\"content\":\"<documents>\\n■001 | The iPhone was first released by Apple in 2007.\\n■002 | Steve Jobs announced the iPhone at the Macworld conference.\\n■003 | The original iPhone had a 3.5-inch display and 2-megapixel camera.\\n■004 | The iPhone revolutionized the smartphone industry.\\n</documents>\\n\\nPlease answer the below question using the format specified above.\\nQuestion to answer: \\\"When was the iPhone released and who announced it?\\\"\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.answer\",\"promptSource\":\"zai:zai.answer:default\"},\"model\":\"fast\",\"reasoningEffort\":\"none\",\"signal\":{},\"stopSequences\":[\"■end■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■answer\nThe iPhone was first released by Apple in 2007■001, and it was announced by Steve Jobs at the Macworld conference■002.\n■end","metadata":{"provider":"cerebras","usage":{"inputTokens":721,"outputTokens":52,"inputCost":0.00025235,"outputCost":0.000039},"model":"cerebras:gpt-oss-120b","ttft":178,"latency":262,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.00029135,"warnings":[{"type":"parameter_ignored","message":"Reasoning effort \"none\" is not supported by the \"gpt-oss-120b\" model, using \"low\" effort instead"}]}}}
|
package/package.json
CHANGED
package/src/context.ts
CHANGED
|
@@ -18,22 +18,54 @@ export type ZaiContextProps = {
|
|
|
18
18
|
source?: GenerateContentInput['meta']
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Usage statistics tracking tokens, cost, and request metrics for an operation.
|
|
23
|
+
*
|
|
24
|
+
* This type is returned via Response events and the `.result()` method, providing
|
|
25
|
+
* real-time visibility into:
|
|
26
|
+
* - Token consumption (input/output/total)
|
|
27
|
+
* - Cost in USD (input/output/total)
|
|
28
|
+
* - Request statistics (count, errors, cache hits, progress percentage)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const { usage } = await zai.extract(text, schema).result()
|
|
33
|
+
*
|
|
34
|
+
* console.log(usage.tokens.total) // 1250
|
|
35
|
+
* console.log(usage.cost.total) // 0.0075 (USD)
|
|
36
|
+
* console.log(usage.requests.cached) // 0
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
21
39
|
export type Usage = {
|
|
40
|
+
/** Request statistics */
|
|
22
41
|
requests: {
|
|
42
|
+
/** Total number of requests initiated */
|
|
23
43
|
requests: number
|
|
44
|
+
/** Number of requests that failed with errors */
|
|
24
45
|
errors: number
|
|
46
|
+
/** Number of successful responses received */
|
|
25
47
|
responses: number
|
|
48
|
+
/** Number of responses served from cache (no tokens used) */
|
|
26
49
|
cached: number
|
|
50
|
+
/** Operation progress as a decimal (0.0 to 1.0) */
|
|
27
51
|
percentage: number
|
|
28
52
|
}
|
|
53
|
+
/** Cost statistics in USD */
|
|
29
54
|
cost: {
|
|
55
|
+
/** Cost for input tokens */
|
|
30
56
|
input: number
|
|
57
|
+
/** Cost for output tokens */
|
|
31
58
|
output: number
|
|
59
|
+
/** Total cost (input + output) */
|
|
32
60
|
total: number
|
|
33
61
|
}
|
|
62
|
+
/** Token usage statistics */
|
|
34
63
|
tokens: {
|
|
64
|
+
/** Input tokens consumed */
|
|
35
65
|
input: number
|
|
66
|
+
/** Output tokens generated */
|
|
36
67
|
output: number
|
|
68
|
+
/** Total tokens (input + output) */
|
|
37
69
|
total: number
|
|
38
70
|
}
|
|
39
71
|
}
|
package/src/operations/answer.ts
CHANGED
|
@@ -136,11 +136,103 @@ const _Options = z.object({
|
|
|
136
136
|
declare module '@botpress/zai' {
|
|
137
137
|
interface Zai {
|
|
138
138
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
139
|
+
* Answers questions from documents with citations and intelligent handling of edge cases.
|
|
140
|
+
*
|
|
141
|
+
* This operation provides a production-ready question-answering system that:
|
|
142
|
+
* - Cites sources with precise line references
|
|
143
|
+
* - Handles ambiguous questions with multiple interpretations
|
|
144
|
+
* - Detects out-of-topic or invalid questions
|
|
145
|
+
* - Identifies missing knowledge
|
|
146
|
+
* - Automatically chunks and processes large document sets
|
|
147
|
+
*
|
|
148
|
+
* @param documents - Array of documents to search (strings, objects, or any type)
|
|
141
149
|
* @param question - The question to answer
|
|
142
|
-
* @param options -
|
|
143
|
-
* @returns Response with answer
|
|
150
|
+
* @param options - Configuration for chunking, examples, and instructions
|
|
151
|
+
* @returns Response with answer + citations, or error states (ambiguous, out_of_topic, invalid, missing_knowledge)
|
|
152
|
+
*
|
|
153
|
+
* @example Basic usage with string documents
|
|
154
|
+
* ```typescript
|
|
155
|
+
* const documents = [
|
|
156
|
+
* 'Botpress was founded in 2016.',
|
|
157
|
+
* 'The company is based in Quebec, Canada.',
|
|
158
|
+
* 'Botpress provides an AI agent platform.'
|
|
159
|
+
* ]
|
|
160
|
+
*
|
|
161
|
+
* const result = await zai.answer(documents, 'When was Botpress founded?')
|
|
162
|
+
* if (result.type === 'answer') {
|
|
163
|
+
* console.log(result.answer) // "Botpress was founded in 2016."
|
|
164
|
+
* console.log(result.citations) // [{ offset: 30, item: documents[0], snippet: '...' }]
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @example With object documents
|
|
169
|
+
* ```typescript
|
|
170
|
+
* const products = [
|
|
171
|
+
* { id: 1, name: 'Pro Plan', price: 99, features: ['AI', 'Analytics'] },
|
|
172
|
+
* { id: 2, name: 'Enterprise', price: 499, features: ['AI', 'Support', 'SLA'] }
|
|
173
|
+
* ]
|
|
174
|
+
*
|
|
175
|
+
* const result = await zai.answer(products, 'What features does the Pro Plan include?')
|
|
176
|
+
* // Returns answer with citations pointing to the product objects
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @example Handling different response types
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const result = await zai.answer(documents, question)
|
|
182
|
+
*
|
|
183
|
+
* switch (result.type) {
|
|
184
|
+
* case 'answer':
|
|
185
|
+
* console.log('Answer:', result.answer)
|
|
186
|
+
* console.log('Sources:', result.citations)
|
|
187
|
+
* break
|
|
188
|
+
*
|
|
189
|
+
* case 'ambiguous':
|
|
190
|
+
* console.log('Question is ambiguous:', result.ambiguity)
|
|
191
|
+
* console.log('Clarifying question:', result.follow_up)
|
|
192
|
+
* console.log('Possible answers:', result.answers)
|
|
193
|
+
* break
|
|
194
|
+
*
|
|
195
|
+
* case 'out_of_topic':
|
|
196
|
+
* console.log('Question unrelated:', result.reason)
|
|
197
|
+
* break
|
|
198
|
+
*
|
|
199
|
+
* case 'invalid_question':
|
|
200
|
+
* console.log('Invalid question:', result.reason)
|
|
201
|
+
* break
|
|
202
|
+
*
|
|
203
|
+
* case 'missing_knowledge':
|
|
204
|
+
* console.log('Insufficient info:', result.reason)
|
|
205
|
+
* break
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* @example With custom instructions
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const result = await zai.answer(documents, 'What is the pricing?', {
|
|
212
|
+
* instructions: 'Provide detailed pricing breakdown including all tiers',
|
|
213
|
+
* chunkLength: 8000 // Process in smaller chunks for accuracy
|
|
214
|
+
* })
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @example Large document sets (auto-chunking)
|
|
218
|
+
* ```typescript
|
|
219
|
+
* // Handles thousands of documents automatically
|
|
220
|
+
* const manyDocs = await loadDocuments() // 1000+ documents
|
|
221
|
+
* const result = await zai.answer(manyDocs, 'What is the refund policy?')
|
|
222
|
+
* // Automatically chunks, processes in parallel, and merges results
|
|
223
|
+
* ```
|
|
224
|
+
*
|
|
225
|
+
* @example Tracking citations
|
|
226
|
+
* ```typescript
|
|
227
|
+
* const result = await zai.answer(documents, question)
|
|
228
|
+
* if (result.type === 'answer') {
|
|
229
|
+
* result.citations.forEach(citation => {
|
|
230
|
+
* console.log(`At position ${citation.offset}:`)
|
|
231
|
+
* console.log(` Cited: "${citation.snippet}"`)
|
|
232
|
+
* console.log(` From document:`, citation.item)
|
|
233
|
+
* })
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
144
236
|
*/
|
|
145
237
|
answer<T>(documents: T[], question: string, options?: Options<T>): Response<AnswerResult<T>, AnswerResult<T>>
|
|
146
238
|
}
|
|
@@ -490,6 +582,7 @@ Question to answer: "${question}"`
|
|
|
490
582
|
},
|
|
491
583
|
],
|
|
492
584
|
transform: (text) => {
|
|
585
|
+
text = text.slice(0, text.lastIndexOf(END.slice(0, -1))) // Remove anything after END
|
|
493
586
|
// Parse and validate response - errors will be caught and retried
|
|
494
587
|
return parseResponse(text || '', mappings)
|
|
495
588
|
},
|
|
@@ -500,15 +593,18 @@ Question to answer: "${question}"`
|
|
|
500
593
|
|
|
501
594
|
/**
|
|
502
595
|
* Parse LLM response into structured result
|
|
596
|
+
* @internal - Exported for testing purposes only
|
|
503
597
|
*/
|
|
504
|
-
const parseResponse = <T>(response: string, mappings: LineMapping<T>[]): AnswerResult<T> => {
|
|
598
|
+
export const parseResponse = <T>(response: string, mappings: LineMapping<T>[]): AnswerResult<T> => {
|
|
505
599
|
const text = response.trim()
|
|
506
600
|
|
|
601
|
+
const answersCount = (text.match(new RegExp(ANSWER_START, 'g')) || []).length
|
|
602
|
+
|
|
507
603
|
// Check response type
|
|
508
|
-
if (text.includes(
|
|
509
|
-
return parseAnswerResponse(text, mappings)
|
|
510
|
-
} else if (text.includes(AMBIGUOUS_START)) {
|
|
604
|
+
if (text.includes(AMBIGUOUS_START) || answersCount >= 2) {
|
|
511
605
|
return parseAmbiguousResponse(text, mappings)
|
|
606
|
+
} else if (text.includes(ANSWER_START)) {
|
|
607
|
+
return parseAnswerResponse(text, mappings)
|
|
512
608
|
} else if (text.includes(OUT_OF_TOPIC_START)) {
|
|
513
609
|
return parseOutOfTopicResponse(text)
|
|
514
610
|
} else if (text.includes(INVALID_QUESTION_START)) {
|
|
@@ -569,7 +665,7 @@ const parseAmbiguousResponse = <T>(text: string, mappings: LineMapping<T>[]): Am
|
|
|
569
665
|
// Extract all possible answers (match until next ■answer or end of string)
|
|
570
666
|
const answerPattern = /■answer(.+?)(?=■answer|$)/gs
|
|
571
667
|
const answers: AnswerWithCitations<T>[] = []
|
|
572
|
-
let match
|
|
668
|
+
let match: RegExpExecArray | null
|
|
573
669
|
|
|
574
670
|
while ((match = answerPattern.exec(text)) !== null) {
|
|
575
671
|
const answerText = match[1].trim()
|