@orangekame3/pi-qcaleval 0.0.2-beta.4 → 0.0.2-beta.6
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/extensions/qcaleval.ts +133 -25
- package/package.json +1 -1
- package/skills/qcaleval/SKILL.md +8 -0
package/extensions/qcaleval.ts
CHANGED
|
@@ -20,6 +20,10 @@ import { Type, type Static } from "typebox";
|
|
|
20
20
|
/** Figures per evaluation; a 16k-token deployment holds a few plots comfortably. */
|
|
21
21
|
export const MAX_IMAGES = 4;
|
|
22
22
|
|
|
23
|
+
/** Labeled example plots shown before the query, and the figures they may hold in total. */
|
|
24
|
+
export const MAX_EXAMPLES = 4;
|
|
25
|
+
export const MAX_EXAMPLE_IMAGES = 4;
|
|
26
|
+
|
|
23
27
|
/** Model ids of models fine-tuned for calibration plots; preferred when configured. */
|
|
24
28
|
export const CALIBRATION_MODEL_PATTERN = /ising-calibration/i;
|
|
25
29
|
|
|
@@ -113,36 +117,111 @@ export const SYSTEM_PROMPT = [
|
|
|
113
117
|
"Base every statement on the plot, the reference, and the given context only.",
|
|
114
118
|
].join("\n");
|
|
115
119
|
|
|
120
|
+
/** One labeled example: its figure(s) followed by the expert's diagnosis. */
|
|
121
|
+
export interface LabeledExample {
|
|
122
|
+
label: string;
|
|
123
|
+
images: ReadonlyArray<{ data: string; mimeType: string }>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type UserContentBlock =
|
|
127
|
+
| { type: "text"; text: string }
|
|
128
|
+
| { type: "image"; data: string; mimeType: string };
|
|
129
|
+
|
|
116
130
|
/**
|
|
117
|
-
*
|
|
118
|
-
* reference for the
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
131
|
+
* The single user turn sent to the evaluation model, as the QCalEval papers
|
|
132
|
+
* lay it out: the reference for the experiment, then each labeled example as
|
|
133
|
+
* its figure followed by its diagnosis, then the context and question, then
|
|
134
|
+
* the figure to judge. Everything is one ordered content array; images are
|
|
135
|
+
* native blocks, never placeholders in the text.
|
|
136
|
+
*
|
|
137
|
+
* `prompt` is the same turn as text, with `[figure]` where an image was, for
|
|
138
|
+
* the record of what the model saw.
|
|
122
139
|
*/
|
|
123
|
-
export function
|
|
140
|
+
export function buildUserContent(
|
|
124
141
|
context: string,
|
|
125
142
|
question: string | undefined,
|
|
126
|
-
|
|
143
|
+
images: ReadonlyArray<{ data: string; mimeType: string }>,
|
|
127
144
|
knowledge?: string,
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
examples: ReadonlyArray<LabeledExample> = [],
|
|
146
|
+
): { content: UserContentBlock[]; prompt: string; examplesUsed: number } {
|
|
147
|
+
const content: UserContentBlock[] = [];
|
|
148
|
+
const shown = fitExamples(examples);
|
|
149
|
+
const text = (lines: string[]) => {
|
|
150
|
+
content.push({ type: "text", text: lines.join("\n") });
|
|
151
|
+
};
|
|
152
|
+
const image = (figure: { data: string; mimeType: string }) => {
|
|
153
|
+
content.push({ type: "image", data: figure.data, mimeType: figure.mimeType });
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const opening: string[] = [];
|
|
130
157
|
if (knowledge?.trim()) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
158
|
+
opening.push("Reference for this experiment (expected plot, outcomes, parameters):", knowledge.trim(), "");
|
|
159
|
+
}
|
|
160
|
+
if (shown.length > 0) {
|
|
161
|
+
opening.push(
|
|
162
|
+
`${shown.length} labeled example${shown.length === 1 ? "" : "s"} of this experiment follow, each figure followed by the diagnosis an expert validated. Use them as the standard for the figure to judge, which comes last.`,
|
|
135
163
|
);
|
|
136
164
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
165
|
+
if (opening.length > 0) text(opening);
|
|
166
|
+
|
|
167
|
+
shown.forEach((example, index) => {
|
|
168
|
+
text(["", `Example ${index + 1} of ${shown.length}:`]);
|
|
169
|
+
for (const figure of example.images) image(figure);
|
|
170
|
+
text([`Diagnosis of example ${index + 1}:`, example.label.trim()]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const closing: string[] = [];
|
|
174
|
+
if (shown.length > 0) closing.push("", "Now the figure to judge, evaluated independently of the examples.");
|
|
175
|
+
closing.push("Experiment context:", context.trim() || "(none)");
|
|
176
|
+
if (images.length > 1) {
|
|
177
|
+
closing.push(
|
|
140
178
|
"",
|
|
141
|
-
`${
|
|
179
|
+
`${images.length} figures follow, in the order the context describes; the last one is the measured result to judge.`,
|
|
142
180
|
);
|
|
143
181
|
}
|
|
144
|
-
if (question?.trim())
|
|
145
|
-
|
|
182
|
+
if (question?.trim()) closing.push("", "Specific question:", question.trim());
|
|
183
|
+
// Repeated here because a long reference ahead of the plot pulls the model
|
|
184
|
+
// towards prose; the last instruction it reads is the output format.
|
|
185
|
+
closing.push("", "Answer with the JSON object described in the system prompt, and nothing else.");
|
|
186
|
+
text(closing);
|
|
187
|
+
for (const figure of images) image(figure);
|
|
188
|
+
|
|
189
|
+
const prompt = content
|
|
190
|
+
.map((block) => (block.type === "text" ? block.text : "[figure]"))
|
|
191
|
+
.join("\n")
|
|
192
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
193
|
+
.trim();
|
|
194
|
+
return { content, prompt, examplesUsed: shown.length };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Keep examples in order within the example and figure budgets; later ones are dropped. */
|
|
198
|
+
export function fitExamples(examples: ReadonlyArray<LabeledExample>): LabeledExample[] {
|
|
199
|
+
const kept: LabeledExample[] = [];
|
|
200
|
+
let figures = 0;
|
|
201
|
+
for (const example of examples) {
|
|
202
|
+
if (kept.length >= MAX_EXAMPLES) break;
|
|
203
|
+
if (!example.label?.trim() || !example.images?.length) continue;
|
|
204
|
+
if (figures + example.images.length > MAX_EXAMPLE_IMAGES) break;
|
|
205
|
+
figures += example.images.length;
|
|
206
|
+
kept.push(example);
|
|
207
|
+
}
|
|
208
|
+
return kept;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The user turn as text only (no examples), kept for callers that render the prompt. */
|
|
212
|
+
export function buildUserPrompt(
|
|
213
|
+
context: string,
|
|
214
|
+
question: string | undefined,
|
|
215
|
+
imageCount = 1,
|
|
216
|
+
knowledge?: string,
|
|
217
|
+
): string {
|
|
218
|
+
const placeholder = { data: "", mimeType: "image/png" };
|
|
219
|
+
return buildUserContent(context, question, Array(imageCount).fill(placeholder), knowledge)
|
|
220
|
+
.content.filter((block) => block.type === "text")
|
|
221
|
+
.map((block) => (block as { text: string }).text)
|
|
222
|
+
.join("\n")
|
|
223
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
224
|
+
.trim();
|
|
146
225
|
}
|
|
147
226
|
|
|
148
227
|
/** Pull the text blocks of the assistant answer, dropping thinking. */
|
|
@@ -203,6 +282,11 @@ const imageSchema = Type.Object({
|
|
|
203
282
|
mimeType: Type.String({ description: "image/png or image/jpeg" }),
|
|
204
283
|
});
|
|
205
284
|
|
|
285
|
+
const exampleSchema = Type.Object({
|
|
286
|
+
label: Type.String({ description: "The expert's diagnosis of the example figure(s)" }),
|
|
287
|
+
images: Type.Array(imageSchema, { minItems: 1 }),
|
|
288
|
+
});
|
|
289
|
+
|
|
206
290
|
const parameters = Type.Object({
|
|
207
291
|
context: Type.String({
|
|
208
292
|
description:
|
|
@@ -237,6 +321,19 @@ const parameters = Type.Object({
|
|
|
237
321
|
"How many of the newest figures the host should supply when `images` is omitted (default 1). Use 2 when an expected reference figure precedes the measured one, and describe their order in `context`.",
|
|
238
322
|
}),
|
|
239
323
|
),
|
|
324
|
+
use_examples: Type.Optional(
|
|
325
|
+
Type.Boolean({
|
|
326
|
+
description:
|
|
327
|
+
"Set true only when the user asked to judge against labeled reference examples: the host then supplies its expert-labeled example plots for `task_name` (in-context examples). Default false.",
|
|
328
|
+
}),
|
|
329
|
+
),
|
|
330
|
+
examples: Type.Optional(
|
|
331
|
+
Type.Array(exampleSchema, {
|
|
332
|
+
maxItems: MAX_EXAMPLES,
|
|
333
|
+
description:
|
|
334
|
+
"Normally omitted: the host supplies labeled examples when `use_examples` is true. Each is one or more real figures followed by the expert's diagnosis.",
|
|
335
|
+
}),
|
|
336
|
+
),
|
|
240
337
|
});
|
|
241
338
|
|
|
242
339
|
/** Stored on the tool result; the host reads `diagnosis` without reparsing text. */
|
|
@@ -248,8 +345,10 @@ export interface EvaluationDetails {
|
|
|
248
345
|
diagnosis?: Record<string, unknown> | null;
|
|
249
346
|
/** The model's answer when it was not the requested JSON. */
|
|
250
347
|
raw?: string;
|
|
251
|
-
/** The user turn the model was sent (reference, context, question), so the host can show what it saw. */
|
|
348
|
+
/** The user turn the model was sent (reference, examples, context, question), so the host can show what it saw. */
|
|
252
349
|
prompt?: string;
|
|
350
|
+
/** How many labeled examples were shown before the figure. */
|
|
351
|
+
examples_used?: number;
|
|
253
352
|
}
|
|
254
353
|
|
|
255
354
|
function failure(spec: ModelSpec | undefined, text: string): AgentToolResult<EvaluationDetails> {
|
|
@@ -271,6 +370,7 @@ export default function qcalevalExtension(pi: ExtensionAPI) {
|
|
|
271
370
|
"Use qcal_evaluate for every calibration figure you need to judge, instead of judging it yourself.",
|
|
272
371
|
"Pass the task name, target, and expected result as context so the diagnosis is specific.",
|
|
273
372
|
"Pass task_name when you know it: the host then supplies its reference knowledge for that task.",
|
|
373
|
+
"Set use_examples true only when the user asked for a judgement against labeled reference examples.",
|
|
274
374
|
"Treat the returned diagnosis as the evaluation of record; add your own reasoning only on top of it.",
|
|
275
375
|
],
|
|
276
376
|
parameters,
|
|
@@ -294,7 +394,13 @@ export default function qcalevalExtension(pi: ExtensionAPI) {
|
|
|
294
394
|
return failure(spec, "no plot was given. Pass the figure in `images` or fetch it first.");
|
|
295
395
|
}
|
|
296
396
|
|
|
297
|
-
const
|
|
397
|
+
const { content, prompt, examplesUsed } = buildUserContent(
|
|
398
|
+
params.context,
|
|
399
|
+
params.question,
|
|
400
|
+
images.map((image) => ({ data: image.data, mimeType: image.mimeType })),
|
|
401
|
+
params.knowledge,
|
|
402
|
+
params.examples ?? [],
|
|
403
|
+
);
|
|
298
404
|
const message = await ctx.modelRegistry.complete(
|
|
299
405
|
model,
|
|
300
406
|
{
|
|
@@ -302,10 +408,11 @@ export default function qcalevalExtension(pi: ExtensionAPI) {
|
|
|
302
408
|
messages: [
|
|
303
409
|
{
|
|
304
410
|
role: "user",
|
|
305
|
-
content:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
411
|
+
content: content.map((block) =>
|
|
412
|
+
block.type === "text"
|
|
413
|
+
? { type: "text" as const, text: block.text }
|
|
414
|
+
: { type: "image" as const, data: block.data, mimeType: block.mimeType },
|
|
415
|
+
),
|
|
309
416
|
timestamp: Date.now(),
|
|
310
417
|
},
|
|
311
418
|
],
|
|
@@ -325,6 +432,7 @@ export default function qcalevalExtension(pi: ExtensionAPI) {
|
|
|
325
432
|
diagnosis: diagnosis ?? null,
|
|
326
433
|
...(diagnosis ? {} : { raw: text }),
|
|
327
434
|
prompt,
|
|
435
|
+
...(examplesUsed ? { examples_used: examplesUsed } : {}),
|
|
328
436
|
},
|
|
329
437
|
...(message.usage ? { usage: message.usage } : {}),
|
|
330
438
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangekame3/pi-qcaleval",
|
|
3
|
-
"version": "0.0.2-beta.
|
|
3
|
+
"version": "0.0.2-beta.6",
|
|
4
4
|
"description": "pi extension that sends calibration plots to a calibration-evaluation VLM such as NVIDIA Ising-Calibration and returns a structured diagnosis",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
package/skills/qcaleval/SKILL.md
CHANGED
|
@@ -27,6 +27,14 @@ Ising-Calibration by default) and returns a JSON diagnosis with
|
|
|
27
27
|
in `context` that the first figure is the reference and the last the
|
|
28
28
|
measurement. The host then supplies both; the default is the newest figure only.
|
|
29
29
|
|
|
30
|
+
## Labeled examples (in-context)
|
|
31
|
+
|
|
32
|
+
- When the user asks to judge a plot against the labeled reference examples
|
|
33
|
+
(in-context examples, few-shot), pass `use_examples: true` with `task_name`.
|
|
34
|
+
The host supplies its expert-labeled example plots for that task, and the
|
|
35
|
+
model sees each example figure followed by its diagnosis before the figure
|
|
36
|
+
to judge, as in the QCalEval papers. Leave it off otherwise.
|
|
37
|
+
|
|
30
38
|
## How to use the result
|
|
31
39
|
|
|
32
40
|
- Treat the diagnosis as the evaluation of record; reason on top of it instead
|