@modelprofile.com/flexharness 8.3.0 → 8.5.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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +10 -0
- package/dist_ts/classes.flexharness.js +58 -4
- package/dist_ts/interfaces.d.ts +24 -1
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts/plugins.d.ts +1 -1
- package/dist_ts/plugins.js +1 -1
- package/dist_ts_compaction/plugins.d.ts +5 -2
- package/dist_ts_compaction/plugins.js +5 -3
- package/dist_ts_compaction/smartagent.compaction.d.ts +8 -1
- package/dist_ts_compaction/smartagent.compaction.js +52 -10
- package/dist_ts_document/index.d.ts +9 -0
- package/dist_ts_document/index.js +58 -20
- package/dist_ts_document/plugins.d.ts +4 -2
- package/dist_ts_document/plugins.js +4 -3
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +65 -3
- package/ts/interfaces.ts +25 -0
- package/ts/plugins.ts +2 -0
- package/ts_compaction/plugins.ts +8 -2
- package/ts_compaction/smartagent.compaction.ts +56 -9
- package/ts_document/index.ts +64 -20
- package/ts_document/plugins.ts +4 -2
- package/ts_document/readme.md +24 -1
|
@@ -24,6 +24,13 @@ When constructing the summary, try to stick to this template:
|
|
|
24
24
|
|
|
25
25
|
export interface ICompactMessagesOptions {
|
|
26
26
|
abortSignal?: AbortSignal;
|
|
27
|
+
/**
|
|
28
|
+
* Receives the usage of every model call the compaction makes, once per call: `reported` with the
|
|
29
|
+
* provider's usage, or `unreported` for a call that failed or was aborted, retried attempts
|
|
30
|
+
* included. A context compactor passes its own options through, so the calls reach the session's
|
|
31
|
+
* `onUsage`. Must not throw.
|
|
32
|
+
*/
|
|
33
|
+
reportUsage?: plugins.TAgentModelCallUsageReporter;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/**
|
|
@@ -32,7 +39,7 @@ export interface ICompactMessagesOptions {
|
|
|
32
39
|
*
|
|
33
40
|
* @param model The same model used by runAgent, or a cheaper small model
|
|
34
41
|
* @param messages The full message history that overflowed
|
|
35
|
-
* @param options Invocation options, including cancellation
|
|
42
|
+
* @param options Invocation options, including cancellation and usage reporting
|
|
36
43
|
* @returns A minimal ModelMessage[] containing the summary as context
|
|
37
44
|
*/
|
|
38
45
|
export async function compactMessages(
|
|
@@ -40,20 +47,60 @@ export async function compactMessages(
|
|
|
40
47
|
messages: plugins.ModelMessage[],
|
|
41
48
|
options: ICompactMessagesOptions = {},
|
|
42
49
|
): Promise<plugins.ModelMessage[]> {
|
|
43
|
-
const
|
|
50
|
+
const prompt: plugins.ModelMessage[] = [
|
|
51
|
+
...messages,
|
|
52
|
+
{ role: 'user', content: COMPACTION_PROMPT },
|
|
53
|
+
];
|
|
54
|
+
const reportUsage = options.reportUsage;
|
|
55
|
+
if (!reportUsage) {
|
|
56
|
+
const result = await plugins.generateText({
|
|
57
|
+
model,
|
|
58
|
+
abortSignal: options.abortSignal,
|
|
59
|
+
allowSystemInMessages: true,
|
|
60
|
+
messages: prompt,
|
|
61
|
+
});
|
|
62
|
+
return summaryMessages(result.text);
|
|
63
|
+
}
|
|
64
|
+
const modelCalls = new plugins.AgentModelCallUsageRecorder(model, reportUsage);
|
|
65
|
+
// Each attempt, retries included, is one model call: it starts in the middleware, which also
|
|
66
|
+
// settles an attempt that throws; a call whose response arrives ends in onLanguageModelCallEnd.
|
|
67
|
+
const observedModel = plugins.wrapLanguageModel({
|
|
44
68
|
model,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
middleware: {
|
|
70
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
71
|
+
modelCalls.start();
|
|
72
|
+
try {
|
|
73
|
+
return await doGenerate();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
},
|
|
51
80
|
});
|
|
81
|
+
try {
|
|
82
|
+
const result = await plugins.generateText({
|
|
83
|
+
model: observedModel,
|
|
84
|
+
abortSignal: options.abortSignal,
|
|
85
|
+
allowSystemInMessages: true,
|
|
86
|
+
messages: prompt,
|
|
87
|
+
onLanguageModelCallEnd: ({ modelId, usage }) => {
|
|
88
|
+
modelCalls.end(modelId, usage);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
modelCalls.settle('missing');
|
|
92
|
+
return summaryMessages(result.text);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
modelCalls.settle(options.abortSignal?.aborted ? 'aborted' : 'failed');
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
52
98
|
|
|
99
|
+
function summaryMessages(summary: string): plugins.ModelMessage[] {
|
|
53
100
|
return [
|
|
54
101
|
{
|
|
55
102
|
role: 'user',
|
|
56
|
-
content: `[Previous conversation summary]\n\n${
|
|
103
|
+
content: `[Previous conversation summary]\n\n${summary}\n\n[End of summary. Continue from here.]`,
|
|
57
104
|
},
|
|
58
105
|
];
|
|
59
106
|
}
|
package/ts_document/index.ts
CHANGED
|
@@ -36,6 +36,14 @@ export interface IDocumentRequestOptions {
|
|
|
36
36
|
* exceeded the limit are not rendered.
|
|
37
37
|
*/
|
|
38
38
|
maxPages?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Called once for every model call of the request, as soon as its usage is known, whatever the
|
|
41
|
+
* request's outcome: `reported` with the provider's usage, or `unreported` with the reason when
|
|
42
|
+
* the call failed, was aborted or its response carried no usage. A retried attempt is a call of
|
|
43
|
+
* its own. Nothing is reported for a request that ends before its model is called. When a request
|
|
44
|
+
* returns, its reported calls sum to its `usage`. Must not throw.
|
|
45
|
+
*/
|
|
46
|
+
onUsage?: plugins.TAgentModelCallUsageReporter;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
export interface IDocumentOptions extends IDocumentRequestOptions {
|
|
@@ -120,6 +128,7 @@ export async function extractTextFromPdfWithUsage(
|
|
|
120
128
|
providerOptions: options.providerOptions,
|
|
121
129
|
abortSignal: options.abortSignal,
|
|
122
130
|
maxPages: options.maxPages,
|
|
131
|
+
onUsage: options.onUsage,
|
|
123
132
|
});
|
|
124
133
|
}
|
|
125
134
|
|
|
@@ -156,26 +165,61 @@ export async function analyzeDocumentsWithUsage(options: IDocumentOptions): Prom
|
|
|
156
165
|
// Filter out empty buffers
|
|
157
166
|
const validPages = imagePages.filter(page => page && page.length > 0);
|
|
158
167
|
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
168
|
+
const onUsage = options.onUsage;
|
|
169
|
+
const modelCalls = onUsage ? new plugins.AgentModelCallUsageRecorder(options.model, onUsage) : null;
|
|
170
|
+
// Each attempt, retries included, is one model call: it starts in the middleware, which also
|
|
171
|
+
// settles an attempt that throws; a call whose response arrives ends in onLanguageModelCallEnd.
|
|
172
|
+
const model = modelCalls
|
|
173
|
+
? plugins.wrapLanguageModel({
|
|
174
|
+
model: options.model,
|
|
175
|
+
middleware: {
|
|
176
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
177
|
+
modelCalls.start();
|
|
178
|
+
try {
|
|
179
|
+
return await doGenerate();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
: options.model;
|
|
188
|
+
const messages: ModelMessage[] = [
|
|
189
|
+
...(options.messageHistory ?? []),
|
|
190
|
+
{
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: [
|
|
193
|
+
{ type: 'text', text: options.userMessage },
|
|
194
|
+
...validPages.map(page => ({
|
|
195
|
+
type: 'file' as const,
|
|
196
|
+
data: page,
|
|
197
|
+
mediaType: 'image/png' as const,
|
|
198
|
+
})),
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
let result: Awaited<ReturnType<typeof plugins.generateText>>;
|
|
203
|
+
try {
|
|
204
|
+
result = await plugins.generateText({
|
|
205
|
+
model,
|
|
206
|
+
instructions: options.systemMessage,
|
|
207
|
+
messages,
|
|
208
|
+
providerOptions: options.providerOptions,
|
|
209
|
+
abortSignal,
|
|
210
|
+
...(modelCalls
|
|
211
|
+
? {
|
|
212
|
+
onLanguageModelCallEnd: ({ modelId, usage: callUsage }) => {
|
|
213
|
+
modelCalls.end(modelId, callUsage);
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
: {}),
|
|
217
|
+
});
|
|
218
|
+
modelCalls?.settle('missing');
|
|
219
|
+
} catch (error) {
|
|
220
|
+
modelCalls?.settle(abortSignal?.aborted ? 'aborted' : 'failed');
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
179
223
|
const usage = result.totalUsage;
|
|
180
224
|
const inputTokens = usage.inputTokens ?? 0;
|
|
181
225
|
const outputTokens = usage.outputTokens ?? 0;
|
package/ts_document/plugins.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import { generateText } from 'ai';
|
|
1
|
+
import { generateText, wrapLanguageModel } from 'ai';
|
|
2
2
|
import * as smartpdf from '@push.rocks/smartpdf';
|
|
3
|
-
|
|
3
|
+
import { AgentModelCallUsageRecorder } from '@modelprofile.com/flexharness-agent';
|
|
4
|
+
export { generateText, wrapLanguageModel, smartpdf, AgentModelCallUsageRecorder };
|
|
5
|
+
export type { TAgentModelCallUsageReporter } from '@modelprofile.com/flexharness-agent';
|
package/ts_document/readme.md
CHANGED
|
@@ -23,11 +23,12 @@ Supply a model from your selected FlexHarness provider. `extractTextFromPdf` rep
|
|
|
23
23
|
|
|
24
24
|
`analyzeDocuments({ model, userMessage, pdfDocuments, systemMessage?, messageHistory? })` supports general analysis across multiple PDFs. This component owns a shared SmartPDF instance; call `stopSmartpdf()` when all document work has finished. Image-only OCR is available independently in `flexharness/ocr`.
|
|
25
25
|
|
|
26
|
-
Both functions accept
|
|
26
|
+
Both functions accept four request options:
|
|
27
27
|
|
|
28
28
|
- `providerOptions` is passed to the model call unchanged, for example `{ openai: { store: false } }` so the provider does not keep the request as application state.
|
|
29
29
|
- `abortSignal` is checked before each document is rendered and before the model call, and is passed to the model call. SmartPDF cannot stop a document it is already rendering, so that render finishes first.
|
|
30
30
|
- `maxPages` is the most rendered pages one request may send to the model. A request with more pages rejects with `DocumentPageLimitError` (`renderedPages`, `maxPages`) before the model is called, and documents after the one that exceeded the limit are not rendered.
|
|
31
|
+
- `onUsage` receives the usage of each model call of the request, also of a request that fails or is aborted; see [Usage of a request that fails or is aborted](#usage-of-a-request-that-fails-or-is-aborted).
|
|
31
32
|
|
|
32
33
|
`extractTextFromPdfWithUsage` and `analyzeDocumentsWithUsage` take the same arguments and return `{ text, usage, pageCount }`. `usage` has the shape of an agent run's usage (`inputTokens`, `outputTokens`, `totalTokens`, `cacheReadTokens`, `cacheWriteTokens`), summed over the request's model steps, with usage the provider leaves out counted as zero; `pageCount` is the number of rendered pages sent to the model.
|
|
33
34
|
|
|
@@ -49,6 +50,28 @@ try {
|
|
|
49
50
|
}
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
### Usage of a request that fails or is aborted
|
|
54
|
+
|
|
55
|
+
A request that throws returns no `usage`. `onUsage` reports each model call of a request once, as soon as its usage is known, whatever the outcome, with the events of `AgentModelCallUsageRecorder` (the type is `TFlexModelCallUsage` from `@modelprofile.com/flexharness`):
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import type { TFlexModelCallUsage } from '@modelprofile.com/flexharness';
|
|
59
|
+
|
|
60
|
+
const calls: TFlexModelCallUsage[] = [];
|
|
61
|
+
try {
|
|
62
|
+
await extractTextFromPdfWithUsage(pdfBytes, { model, abortSignal, onUsage: (call) => calls.push(call) });
|
|
63
|
+
} finally {
|
|
64
|
+
const reported = calls.filter((call) => call.status === 'reported');
|
|
65
|
+
const complete = reported.length === calls.length; // an unreported call used an unknown number of tokens
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `status: 'reported'`: the provider reported the call's `usage`; `requestedModelId` is the model's id and `responseModelId` the id the response named.
|
|
70
|
+
- `status: 'unreported'`: the call `failed`, was `aborted`, or its response carried no usage (`missing`). The provider may still have consumed tokens for it.
|
|
71
|
+
- Every attempt is a call of its own: a call retried after a failure reports the failed attempt and the retry.
|
|
72
|
+
- A request that ends before its model is called (an abort during rendering, `DocumentPageLimitError`) reports nothing.
|
|
73
|
+
- When a request returns, its reported calls sum to its `usage`; count one or the other. `onUsage` must not throw.
|
|
74
|
+
|
|
52
75
|
## Issue Reporting and Security
|
|
53
76
|
|
|
54
77
|
Report issues and security concerns through [community.foss.global](https://community.foss.global).
|