Files
zyt/artifacts/prescription-ai-runtime/check_progress.php
T
2026-09-10 15:19:17 +08:00

81 lines
5.8 KiB
PHP

<?php
declare(strict_types=1);
require __DIR__ . '/check_queue.php';
use app\common\service\prescriptionai\PrescriptionAiCipher;
use app\common\service\prescriptionai\PrescriptionAiGenerator;
use think\facade\Db;
$secret = (string) config('prescription_analysis.encryption_key', '');
if ($secret === '') {
$secret = trim((string) file_get_contents(root_path('runtime') . 'prescription_ai_private/snapshot.key'));
}
$cipher = new PrescriptionAiCipher($secret);
$batch = Db::name('prescription_ai_batch')->where('prescription_id', 7556)->order('id', 'desc')->find();
$context = $cipher->decrypt((string) $batch['context_cipher'], 'context');
$budget = max(6000, min(200000, (int) config('prescription_ai.manual_analysis.input_token_budget', 24000))) - 3500;
$units = (new ReflectionMethod(PrescriptionAiGenerator::class, 'sourceUnits'))->invoke(null, $context['source']['records'], $budget);
$chunks = (new ReflectionMethod(PrescriptionAiGenerator::class, 'pack'))->invoke(null, $units, $budget);
$textPromptBytes = [];
foreach ($chunks as $chunk) {
$ids = array_values(array_unique(array_column($chunk, 'source_id')));
$prompt = (new ReflectionMethod(PrescriptionAiGenerator::class, 'evidencePrompt'))->invoke(null, 'text', $ids,
['patient' => $context['source']['patient'] ?? [], 'clinical_field_semantics' => $context['source']['clinical_field_semantics'] ?? [], 'records' => $chunk]);
$textPromptBytes[] = strlen($prompt);
}
$attachmentStatuses = array_count_values(array_column($context['files'] ?? [], 'status'));
$result = [];
foreach (Db::name('prescription_ai_task')->where('batch_id', $batch['id'])->select()->toArray() as $task) {
if (empty($task['progress_cipher'])) { continue; }
$progress = $cipher->decrypt($task['progress_cipher'], 'progress:' . $task['id']);
$item = ['model' => $task['model_key'], 'stage' => $progress['stage'], 'calls' => $progress['usage']['total_calls'], 'steps' => []];
foreach ($progress['steps'] ?? [] as $key => $step) {
$content = (string) ($step['value']['content'] ?? '');
$decoded = json_decode(trim($content), true);
$jsonError = json_last_error_msg();
$fenced = preg_match('/\A```(?:json)?\s*(.*?)\s*```\z/s', trim($content), $match) === 1;
$wrapped = $fenced ? json_decode($match[1], true) : null;
$summary = ['step' => $key, 'bytes' => strlen($content), 'json_error' => $jsonError,
'has_think_tag' => str_contains($content, '<think>'), 'whole_json_fence' => $fenced,
'json_keys' => is_array($decoded) ? array_keys($decoded) : null,
'fenced_json_keys' => is_array($wrapped) ? array_keys($wrapped) : null];
if (str_starts_with($key, 'text:')) {
$expected = array_values(array_unique(array_column($chunks[(int) substr($key, 5)], 'source_id')));
$summary['expected_source_count'] = count($expected);
$summary['raw_valid'] = (new ReflectionMethod(PrescriptionAiGenerator::class, 'parseEvidence'))->invoke(null, $content, $expected) !== null;
$summary['fenced_valid'] = $fenced && (new ReflectionMethod(PrescriptionAiGenerator::class, 'parseEvidence'))->invoke(null, $match[1], $expected) !== null;
$value = is_array($decoded) ? $decoded : (is_array($wrapped) ? $wrapped : []);
$summary['field_types'] = array_map('get_debug_type', $value);
if (is_array($value['covered_source_ids'] ?? null)) {
$summary['covered_source_count'] = count($value['covered_source_ids']);
$summary['missing_source_count'] = count(array_diff($expected, $value['covered_source_ids']));
$summary['extra_source_count'] = count(array_diff($value['covered_source_ids'], $expected));
}
}
if (str_starts_with($key, 'files:')) {
$sendable = array_values(array_filter($context['files'], static fn (array $f): bool => $f['status'] !== 'restricted' && !empty($f['url'])));
$fileBatch = array_chunk($sendable, 3)[(int) substr($key, 6)];
$known = array_values(array_unique(array_merge(array_column($context['source']['records'], 'source_id'), array_column($context['files'], 'file_id'))));
$value = (new ReflectionMethod(PrescriptionAiGenerator::class, 'object'))->invoke(null, $content);
$summary['file_output_valid'] = (new ReflectionMethod(PrescriptionAiGenerator::class, 'parseFiles'))->invoke(null, $content, $fileBatch, $known) !== null;
$summary['returned_count'] = is_array($value['files'] ?? null) ? count($value['files']) : null;
$summary['expected_count'] = count($fileBatch);
$summary['file_shapes'] = [];
foreach ($value['files'] ?? [] as $file) {
$refs = $file['evidence_references'] ?? null;
$summary['file_shapes'][] = ['keys' => array_keys($file), 'types' => array_map('get_debug_type', $file),
'status' => in_array($file['status'] ?? '', ['processed', 'unreadable', 'unsupported'], true) ? $file['status'] : 'INVALID',
'expected_id' => in_array($file['file_id'] ?? null, array_column($fileBatch, 'file_id'), true),
'findings_bytes' => is_string($file['findings'] ?? null) ? strlen($file['findings']) : null,
'refs_count' => is_array($refs) ? count($refs) : null,
'unknown_refs_count' => is_array($refs) && count(array_filter($refs, 'is_string')) === count($refs) ? count(array_diff($refs, $known)) : null];
}
}
$item['steps'][] = $summary;
}
$result[] = $item;
}
echo json_encode(['text_chunks' => count($chunks), 'text_prompt_bytes' => $textPromptBytes, 'input_budget' => $budget + 3500,
'attachments' => count($context['files'] ?? []), 'attachment_statuses' => $attachmentStatuses,
'progress_shape_only' => $result], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), PHP_EOL;