@oqtopus-team/pi-qdash 0.1.3 → 0.1.4
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/qdash.ts +151 -0
- package/package.json +1 -1
- package/skills/qdash/SKILL.md +2 -1
package/extensions/qdash.ts
CHANGED
|
@@ -393,6 +393,58 @@ type TaskResultFigureFile = DownloadedFile & {
|
|
|
393
393
|
jsonFigurePaths: string[];
|
|
394
394
|
};
|
|
395
395
|
|
|
396
|
+
type CalibrationEvidence = {
|
|
397
|
+
id?: string;
|
|
398
|
+
source: string;
|
|
399
|
+
target?: Record<string, unknown>;
|
|
400
|
+
task?: Record<string, unknown>;
|
|
401
|
+
metrics?: Record<string, number | string | boolean | null>;
|
|
402
|
+
parameters?: Record<string, number | string | boolean | null>;
|
|
403
|
+
resultJson?: unknown;
|
|
404
|
+
figures?: Array<Record<string, unknown>>;
|
|
405
|
+
logs?: string[];
|
|
406
|
+
notes?: string;
|
|
407
|
+
history?: Array<Record<string, unknown>>;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
function parameterValueRecord(value: unknown): Record<string, number | string | boolean | null> {
|
|
411
|
+
const output: Record<string, number | string | boolean | null> = {};
|
|
412
|
+
const input = value && typeof value === "object" ? value as Record<string, unknown> : {};
|
|
413
|
+
for (const [key, raw] of Object.entries(input)) {
|
|
414
|
+
const object = raw && typeof raw === "object" ? raw as Record<string, unknown> : undefined;
|
|
415
|
+
const candidate = object && "value" in object ? object.value : raw;
|
|
416
|
+
if (typeof candidate === "number" || typeof candidate === "string" || typeof candidate === "boolean" || candidate === null) output[key] = candidate;
|
|
417
|
+
}
|
|
418
|
+
return output;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function firstStringField(object: Record<string, unknown>, keys: string[]): string | undefined {
|
|
422
|
+
for (const key of keys) {
|
|
423
|
+
const value = object[key];
|
|
424
|
+
if (typeof value === "string" && value) return value;
|
|
425
|
+
}
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function qcalEvidenceText(evidence: CalibrationEvidence): string {
|
|
430
|
+
const metrics = Object.entries(evidence.metrics ?? {}).slice(0, 8).map(([key, value]) => ` - ${key}: ${String(value)}`);
|
|
431
|
+
const figures = evidence.figures ?? [];
|
|
432
|
+
return boxed("QDash QCal Evidence", [
|
|
433
|
+
`id ${evidence.id ?? "n/a"}`,
|
|
434
|
+
`source ${evidence.source}`,
|
|
435
|
+
`target ${evidence.target?.id ?? evidence.target?.couplingId ?? evidence.target?.qids ?? "n/a"}`,
|
|
436
|
+
`task ${evidence.task?.name ?? "n/a"} (${evidence.task?.status ?? "unknown"})`,
|
|
437
|
+
"",
|
|
438
|
+
"Metrics",
|
|
439
|
+
...(metrics.length ? metrics : [" - none"]),
|
|
440
|
+
"",
|
|
441
|
+
`Figures ${figures.length}`,
|
|
442
|
+
...figures.map((figure, index) => ` - #${index + 1} ${String(figure.role ?? "plot")} ${String(figure.path ?? figure.url ?? "")}`),
|
|
443
|
+
"",
|
|
444
|
+
"Use qcal_evaluate_bundle with details.evidence to evaluate this evidence with pi-qcal.",
|
|
445
|
+
]).join("\n");
|
|
446
|
+
}
|
|
447
|
+
|
|
396
448
|
function cleanQuery(values: Record<string, unknown> = {}): Record<string, unknown> {
|
|
397
449
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined && value !== null));
|
|
398
450
|
}
|
|
@@ -2462,6 +2514,105 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2462
2514
|
},
|
|
2463
2515
|
});
|
|
2464
2516
|
|
|
2517
|
+
pi.registerTool({
|
|
2518
|
+
name: "qdash_build_qcal_evidence",
|
|
2519
|
+
label: "QDash Build QCal Evidence",
|
|
2520
|
+
description: "Build a provider-neutral CalibrationEvidence bundle from a QDash task result for evaluation by pi-qcal.",
|
|
2521
|
+
promptSnippet: "Convert QDash calibration task results into pi-qcal CalibrationEvidence",
|
|
2522
|
+
promptGuidelines: [
|
|
2523
|
+
"Use qdash_build_qcal_evidence before qcal_evaluate_bundle when the user asks to evaluate QDash calibration data with pi-qcal.",
|
|
2524
|
+
"qdash_build_qcal_evidence is read-only and only converts QDash data into a generic evidence bundle; it does not evaluate, execute, commit, or apply calibration parameters.",
|
|
2525
|
+
"After qdash_build_qcal_evidence, pass details.evidence to qcal_evaluate_bundle and treat the result as advisory evidence.",
|
|
2526
|
+
],
|
|
2527
|
+
parameters: Type.Object({
|
|
2528
|
+
...connectionParams,
|
|
2529
|
+
taskId: Type.String(),
|
|
2530
|
+
includeFigures: Type.Optional(Type.Boolean({ description: "Embed task image figures as data URLs. Defaults to true." })),
|
|
2531
|
+
includeFigureAnalysis: Type.Optional(Type.Boolean({ description: "Analyze Plotly JSON figures and include summaries/warnings in notes. Defaults to true." })),
|
|
2532
|
+
maxFigures: Type.Optional(Type.Number({ description: "Maximum image figures to embed. Defaults to 1." })),
|
|
2533
|
+
includeRawTaskResult: Type.Optional(Type.Boolean({ description: "Include the full raw task result in resultJson. Defaults to false." })),
|
|
2534
|
+
}),
|
|
2535
|
+
async execute(_toolCallId, params: { profile?: string; configPath?: string; useEnv?: boolean; taskId: string; includeFigures?: boolean; includeFigureAnalysis?: boolean; maxFigures?: number; includeRawTaskResult?: boolean }) {
|
|
2536
|
+
const client = await makeClient(params);
|
|
2537
|
+
const task = await client.getTaskResult(params.taskId) as unknown as Record<string, unknown>;
|
|
2538
|
+
const figures: Array<Record<string, unknown>> = [];
|
|
2539
|
+
const notes: string[] = [];
|
|
2540
|
+
const figurePaths = Array.isArray(task.figure_path) ? task.figure_path.filter((item): item is string => typeof item === "string") : [];
|
|
2541
|
+
const jsonFigurePaths = Array.isArray(task.json_figure_path) ? task.json_figure_path.filter((item): item is string => typeof item === "string") : [];
|
|
2542
|
+
|
|
2543
|
+
if (params.includeFigureAnalysis !== false) {
|
|
2544
|
+
const limit = Math.min(jsonFigurePaths.length, params.maxFigures ?? 1);
|
|
2545
|
+
for (let index = 0; index < limit; index += 1) {
|
|
2546
|
+
try {
|
|
2547
|
+
const file: TaskResultFigureFile = await client.getTaskResultFigure(params.taskId, { index, preferJson: true });
|
|
2548
|
+
const figure = JSON.parse(Buffer.from(file.data).toString("utf8")) as unknown;
|
|
2549
|
+
const analysis = analyzePlotlyFigure(file.path ?? jsonFigurePaths[index] ?? "", figure, params.taskId);
|
|
2550
|
+
notes.push(`Figure JSON analysis #${index + 1}:`, ...analysis.summaries.map((line) => `- ${line}`));
|
|
2551
|
+
if (analysis.warnings.length) notes.push("Warnings:", ...analysis.warnings.map((line) => `- ${line}`));
|
|
2552
|
+
figures.push({ path: file.path ?? jsonFigurePaths[index], role: "plotly", data: figure });
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
notes.push(`Figure JSON analysis #${index + 1} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
if (params.includeFigures !== false) {
|
|
2560
|
+
const limit = Math.min(figurePaths.length, params.maxFigures ?? 1);
|
|
2561
|
+
for (let index = 0; index < limit; index += 1) {
|
|
2562
|
+
try {
|
|
2563
|
+
const file: TaskResultFigureFile = await client.getTaskResultFigure(params.taskId, { index, preferJson: false });
|
|
2564
|
+
const bytes = Buffer.from(file.data);
|
|
2565
|
+
const mediaType = file.mediaType || mediaTypeForPath(file.path ?? figurePaths[index] ?? "");
|
|
2566
|
+
if (mediaType.startsWith("image/")) {
|
|
2567
|
+
figures.push({ path: file.path ?? figurePaths[index], url: `data:${mediaType};base64,${bytes.toString("base64")}`, mimeType: mediaType, role: "plot" });
|
|
2568
|
+
}
|
|
2569
|
+
} catch (error) {
|
|
2570
|
+
notes.push(`Figure image #${index + 1} embedding failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
const couplingId = firstStringField(task, ["coupling_id", "couplingId", "qid"]);
|
|
2576
|
+
const qid = firstStringField(task, ["qid"]);
|
|
2577
|
+
const evidence: CalibrationEvidence = {
|
|
2578
|
+
id: params.taskId,
|
|
2579
|
+
source: "external",
|
|
2580
|
+
target: {
|
|
2581
|
+
kind: couplingId && couplingId.includes("-") ? "coupling" : qid ? "qubit" : "system",
|
|
2582
|
+
id: couplingId ?? qid,
|
|
2583
|
+
couplingId: couplingId && couplingId.includes("-") ? couplingId : undefined,
|
|
2584
|
+
qids: qid && !qid.includes("-") ? [qid] : undefined,
|
|
2585
|
+
chipId: firstStringField(task, ["chip_id", "chipId"]),
|
|
2586
|
+
},
|
|
2587
|
+
task: {
|
|
2588
|
+
name: firstStringField(task, ["task_name", "taskName", "name"]),
|
|
2589
|
+
objective: `Evaluate QDash calibration task ${firstStringField(task, ["task_name", "taskName", "name"]) ?? params.taskId}`,
|
|
2590
|
+
backend: firstStringField(task, ["backend", "chip_id", "chipId"]),
|
|
2591
|
+
startedAt: firstStringField(task, ["start_at", "startAt"]),
|
|
2592
|
+
completedAt: firstStringField(task, ["end_at", "endAt"]),
|
|
2593
|
+
status: firstStringField(task, ["status"]),
|
|
2594
|
+
},
|
|
2595
|
+
metrics: parameterValueRecord(task.output_parameters),
|
|
2596
|
+
parameters: { ...parameterValueRecord(task.input_parameters), ...parameterValueRecord(task.run_parameters) },
|
|
2597
|
+
figures: figures.length ? figures : undefined,
|
|
2598
|
+
notes: [
|
|
2599
|
+
typeof task.message === "string" ? `Task message: ${task.message}` : undefined,
|
|
2600
|
+
notes.length ? notes.join("\n") : undefined,
|
|
2601
|
+
].filter(Boolean).join("\n\n") || undefined,
|
|
2602
|
+
resultJson: params.includeRawTaskResult ? task : {
|
|
2603
|
+
task_id: task.task_id,
|
|
2604
|
+
execution_id: task.execution_id,
|
|
2605
|
+
flow_name: task.flow_name,
|
|
2606
|
+
figure_path: figurePaths,
|
|
2607
|
+
json_figure_path: jsonFigurePaths,
|
|
2608
|
+
links: task._links,
|
|
2609
|
+
},
|
|
2610
|
+
};
|
|
2611
|
+
|
|
2612
|
+
return toTextToolResult(qcalEvidenceText(evidence), { evidence }, { tool: "qdash_build_qcal_evidence", taskId: params.taskId, evidence });
|
|
2613
|
+
},
|
|
2614
|
+
});
|
|
2615
|
+
|
|
2465
2616
|
registerQueryTool({
|
|
2466
2617
|
name: "qdash_get_provenance_stats",
|
|
2467
2618
|
label: "QDash Provenance Stats",
|
package/package.json
CHANGED
package/skills/qdash/SKILL.md
CHANGED
|
@@ -22,7 +22,7 @@ Use the pi-qdash tools instead of scraping the UI or hand-writing auth headers.
|
|
|
22
22
|
- `qdash_list_executions`, `qdash_get_execution`
|
|
23
23
|
- `qdash_list_ai_reviews`, `qdash_get_provenance_stats`
|
|
24
24
|
- `qdash_list_forum_posts`, `qdash_get_forum_post`, `qdash_list_forum_replies`
|
|
25
|
-
- `qdash_get_figure`, `qdash_get_task_figures`, `qdash_recent_calibration_figure`, `qdash_analyze_figure_json`
|
|
25
|
+
- `qdash_get_figure`, `qdash_get_task_figures`, `qdash_recent_calibration_figure`, `qdash_analyze_figure_json`, `qdash_build_qcal_evidence`
|
|
26
26
|
- `qdash_create_forum_evidence_reply` for confirmed evidence curation replies with visible task figures
|
|
27
27
|
3. Use harness overview tools for status and triage:
|
|
28
28
|
- `qdash_investigate` for natural-language requests to investigate or compare recent calibration, target history, figures, failures, issues, and Forum context
|
|
@@ -108,4 +108,5 @@ When the user wants to preserve an investigated observation in QDash forum/notes
|
|
|
108
108
|
- Use `qdash_compare_calibration` when the user asks what changed between recent calibration experiments; treat differences as evidence, not automatic approval to commit/apply parameters.
|
|
109
109
|
- Use `qdash_recent_calibration_figure` when the user asks to see recent experiment images without a task ID.
|
|
110
110
|
- Use `qdash_analyze_figure_json` for read-only numeric summaries of Plotly JSON calibration figures; keep domain-specific interpretation in the matching skill.
|
|
111
|
+
- Use `qdash_build_qcal_evidence` when the user wants to evaluate a QDash calibration task with pi-qcal. It only converts QDash data into provider-neutral `CalibrationEvidence`; pass `details.evidence` to `qcal_evaluate_bundle` for the actual evaluation.
|
|
111
112
|
- Prefer summarizing large responses with counts, IDs, time ranges, and notable values.
|