@orangekame3/pi-qcaleval 0.0.1 → 0.0.2-beta.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/README.md CHANGED
@@ -26,10 +26,15 @@ for example 2 when an expected reference figure precedes the measured result.
26
26
 
27
27
  ## Configuration
28
28
 
29
- | Variable | Purpose |
30
- |----------|---------|
31
- | `QCALEVAL_MODEL` | `provider/modelId` of the evaluation model in pi's `models.json` (default: `vllm/nvidia/Ising-Calibration-1.5-31B-NVFP4`) |
32
- | `QCALEVAL_MAX_TOKENS` | Output budget for one evaluation (default: `2048`) |
29
+ No setting is required. The evaluation model is resolved in this order:
30
+
31
+ 1. `QCALEVAL_MODEL` (`provider/modelId`), an explicit override for pi CLI use;
32
+ 2. a configured calibration VLM, recognised by its id (`Ising-Calibration`);
33
+ 3. the conversation's own model, when it accepts images. The result is then
34
+ marked `fallback: true` in `details`, so a host without an Ising deployment
35
+ still gets a diagnosis from the vision model it already runs.
36
+
37
+ The output budget follows the model's `maxTokens` (at most 4096).
33
38
 
34
39
  The provider must be declared in pi's `models.json` with its base URL and
35
40
  credential, for example:
@@ -16,20 +16,25 @@ import type {
16
16
  } from "@earendil-works/pi-coding-agent";
17
17
  import { Type, type Static } from "typebox";
18
18
 
19
- /** `provider/modelId`; the model id itself may contain slashes (nvidia/...). */
20
- export const DEFAULT_MODEL = "vllm/nvidia/Ising-Calibration-1.5-31B-NVFP4";
21
-
22
19
  /** Figures per evaluation; a 16k-token deployment holds a few plots comfortably. */
23
20
  export const MAX_IMAGES = 4;
24
21
 
22
+ /** Model ids of models fine-tuned for calibration plots; preferred when configured. */
23
+ export const CALIBRATION_MODEL_PATTERN = /ising-calibration/i;
24
+
25
+ /** Output budget when the model declares none; a diagnosis is a few hundred tokens. */
26
+ const DEFAULT_MAX_TOKENS = 2048;
27
+ const MAX_OUTPUT_TOKENS = 4096;
28
+
25
29
  export interface ModelSpec {
26
30
  provider: string;
27
31
  modelId: string;
28
32
  }
29
33
 
30
- /** Parse `provider/modelId`, splitting at the first slash only. */
31
- export function parseModelSpec(value: string | undefined): ModelSpec {
32
- const spec = (value ?? "").trim() || DEFAULT_MODEL;
34
+ /** Parse an explicit `provider/modelId` override; the model id may contain slashes (nvidia/...). */
35
+ export function parseModelSpec(value: string | undefined): ModelSpec | undefined {
36
+ const spec = (value ?? "").trim();
37
+ if (!spec) return undefined;
33
38
  const slash = spec.indexOf("/");
34
39
  if (slash <= 0 || slash === spec.length - 1) {
35
40
  throw new Error(`QCALEVAL_MODEL must be provider/modelId, got "${spec}"`);
@@ -37,6 +42,54 @@ export function parseModelSpec(value: string | undefined): ModelSpec {
37
42
  return { provider: spec.slice(0, slash), modelId: spec.slice(slash + 1) };
38
43
  }
39
44
 
45
+ /** The slice of pi's model registry the resolution needs. */
46
+ export interface ModelLookup {
47
+ find(provider: string, modelId: string): Model<Api> | undefined;
48
+ getAvailable(): Model<Api>[];
49
+ }
50
+
51
+ export type EvaluationModel =
52
+ | { model: Model<Api>; fallback: false }
53
+ | { model: Model<Api>; fallback: true }
54
+ | { model?: undefined; error: string };
55
+
56
+ /**
57
+ * Pick the model that judges the plot, without any host-specific setting:
58
+ *
59
+ * 1. `QCALEVAL_MODEL` (`provider/modelId`), an explicit override for the pi CLI;
60
+ * 2. a configured calibration VLM, recognised by its id (Ising-Calibration);
61
+ * 3. the conversation's own model, when it accepts images.
62
+ *
63
+ * A host that declares the calibration model in its models.json gets it
64
+ * everywhere; one that does not (no Ising deployment) still gets a diagnosis
65
+ * from the vision model it already runs, marked as a fallback.
66
+ */
67
+ export function resolveEvaluationModel(
68
+ registry: ModelLookup,
69
+ current: Model<Api> | undefined,
70
+ override: string | undefined,
71
+ ): EvaluationModel {
72
+ const spec = parseModelSpec(override);
73
+ if (spec) {
74
+ const model = registry.find(spec.provider, spec.modelId);
75
+ return model
76
+ ? { model, fallback: false }
77
+ : { error: `model ${spec.provider}/${spec.modelId} is not configured (QCALEVAL_MODEL)` };
78
+ }
79
+ const calibration = registry.getAvailable().find((model) => CALIBRATION_MODEL_PATTERN.test(model.id));
80
+ if (calibration) return { model: calibration, fallback: false };
81
+ if (current?.input.includes("image")) return { model: current, fallback: true };
82
+ return {
83
+ error:
84
+ "no evaluation model: declare a calibration VLM (Ising-Calibration) in models.json, or run with a vision model",
85
+ };
86
+ }
87
+
88
+ /** Output budget for one evaluation, within what the model allows. */
89
+ export function outputBudget(model: Pick<Model<Api>, "maxTokens">): number {
90
+ return model.maxTokens > 0 ? Math.min(model.maxTokens, MAX_OUTPUT_TOKENS) : DEFAULT_MAX_TOKENS;
91
+ }
92
+
40
93
  /** Categories the Ising-Calibration model card describes; asked for in this order. */
41
94
  export const DIAGNOSIS_SECTIONS = [
42
95
  "technical_description",
@@ -164,14 +217,21 @@ const parameters = Type.Object({
164
217
 
165
218
  /** Stored on the tool result; the host reads `diagnosis` without reparsing text. */
166
219
  export interface EvaluationDetails {
167
- model: ModelSpec;
220
+ /** The model that judged the plot; absent when none could be resolved. */
221
+ model?: ModelSpec;
222
+ /** True when no calibration VLM was configured and the conversation's model judged instead. */
223
+ fallback?: boolean;
168
224
  diagnosis?: Record<string, unknown> | null;
169
225
  /** The model's answer when it was not the requested JSON. */
170
226
  raw?: string;
171
227
  }
172
228
 
173
- function failure(spec: ModelSpec, text: string): AgentToolResult<EvaluationDetails> {
174
- return { content: [{ type: "text", text: `qcal_evaluate: ${text}` }], details: { model: spec }, isError: true };
229
+ function failure(spec: ModelSpec | undefined, text: string): AgentToolResult<EvaluationDetails> {
230
+ return {
231
+ content: [{ type: "text", text: `qcal_evaluate: ${text}` }],
232
+ details: spec ? { model: spec } : {},
233
+ isError: true,
234
+ };
175
235
  }
176
236
 
177
237
  export default function qcalevalExtension(pi: ExtensionAPI) {
@@ -194,11 +254,10 @@ export default function qcalevalExtension(pi: ExtensionAPI) {
194
254
  _onUpdate: unknown,
195
255
  ctx: ExtensionToolContext,
196
256
  ): Promise<AgentToolResult<EvaluationDetails>> {
197
- const spec = parseModelSpec(process.env.QCALEVAL_MODEL);
198
- const model: Model<Api> | undefined = ctx.modelRegistry.find(spec.provider, spec.modelId);
199
- if (!model) {
200
- return failure(spec, `model ${spec.provider}/${spec.modelId} is not configured (QCALEVAL_MODEL)`);
201
- }
257
+ const resolved = resolveEvaluationModel(ctx.modelRegistry, ctx.model, process.env.QCALEVAL_MODEL);
258
+ if (!resolved.model) return failure(undefined, resolved.error);
259
+ const { model, fallback } = resolved;
260
+ const spec: ModelSpec = { provider: model.provider, modelId: model.id };
202
261
  const images: ImageContent[] = (params.images ?? []).map((image) => ({
203
262
  type: "image",
204
263
  data: image.data,
@@ -226,7 +285,7 @@ export default function qcalevalExtension(pi: ExtensionAPI) {
226
285
  },
227
286
  ],
228
287
  },
229
- { signal, maxTokens: Number(process.env.QCALEVAL_MAX_TOKENS ?? 2048) },
288
+ { signal, maxTokens: outputBudget(model) },
230
289
  );
231
290
  if (message.stopReason === "error") {
232
291
  return failure(spec, message.errorMessage ?? "model error");
@@ -237,6 +296,7 @@ export default function qcalevalExtension(pi: ExtensionAPI) {
237
296
  content: [{ type: "text", text: diagnosis ? JSON.stringify(diagnosis, null, 2) : text }],
238
297
  details: {
239
298
  model: spec,
299
+ ...(fallback ? { fallback: true } : {}),
240
300
  diagnosis: diagnosis ?? null,
241
301
  ...(diagnosis ? {} : { raw: text }),
242
302
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangekame3/pi-qcaleval",
3
- "version": "0.0.1",
3
+ "version": "0.0.2-beta.2",
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": {