更新
This commit is contained in:
@@ -5,12 +5,14 @@ declare(strict_types=1);
|
||||
namespace app\common\service\prescriptionai;
|
||||
|
||||
use app\common\service\DifyChatService;
|
||||
use app\common\service\ClinicalKnowledgeReference;
|
||||
use app\common\service\NihaixiaClinicalSkill;
|
||||
use app\common\service\TcmAncientBooksReference;
|
||||
|
||||
/** One model branch. It receives a saved context and never queries patient data. */
|
||||
final class PrescriptionAiGenerator
|
||||
{
|
||||
public const PROMPT_VERSION = 'manual-prescription-required-candidate-nihaixia-v5';
|
||||
public const PROMPT_VERSION = 'manual-prescription-required-candidate-nihaixia-ancient-v6';
|
||||
private const REPORT_KEYS = ['summary', 'diagnosis', 'risk_assessment', 'treatment_advice', 'evidence_references', 'missing_information'];
|
||||
private const RETRYABLE = ['UPSTREAM_TIMEOUT', 'UPSTREAM_BUSY', 'UPSTREAM_UNAVAILABLE', 'INCOMPLETE_RESPONSE', 'EMPTY_RESPONSE',
|
||||
'CANDIDATE_WITHHELD_BY_MODEL', 'INVALID_EVIDENCE_OUTPUT', 'INVALID_REPORT_OUTPUT'];
|
||||
@@ -213,12 +215,12 @@ final class PrescriptionAiGenerator
|
||||
// model prescribes plain names such as 麦冬 while the clinic stocks 生麦冬, and every
|
||||
// row is then an unmappable identity rather than a comparable one.
|
||||
$catalogNames = self::catalogNames($context, $inputBudget);
|
||||
if (strlen(NihaixiaClinicalSkill::augmentQuery(self::finalPrompt([], $coverage, $candidateBlocked, $requireCandidate, $dispensing, $catalogNames, $readIds))) + ($insistRounds > 0 ? self::INSIST_RESERVE : 0) > $inputBudget) {
|
||||
if (strlen(ClinicalKnowledgeReference::augmentQuery(self::finalPrompt([], $coverage, $candidateBlocked, $requireCandidate, $dispensing, $catalogNames, $readIds))) + ($insistRounds > 0 ? self::INSIST_RESERVE : 0) > $inputBudget) {
|
||||
throw new \RuntimeException('FINAL_CONTEXT_EXCEEDS_BUDGET');
|
||||
}
|
||||
// Include the complete coverage and prompt overhead when deciding to reduce evidence.
|
||||
$prompt = self::finalPrompt($summaries, $coverage, $candidateBlocked, $requireCandidate, $dispensing, $catalogNames, $readIds);
|
||||
for ($round = 0; strlen(NihaixiaClinicalSkill::augmentQuery($prompt)) + ($insistRounds > 0 ? self::INSIST_RESERVE : 0) > $inputBudget; $round++) {
|
||||
for ($round = 0; strlen(ClinicalKnowledgeReference::augmentQuery($prompt)) + ($insistRounds > 0 ? self::INSIST_RESERVE : 0) > $inputBudget; $round++) {
|
||||
if ($round >= 8) {
|
||||
throw new \RuntimeException('SYNTHESIS_BUDGET_EXCEEDED');
|
||||
}
|
||||
@@ -267,6 +269,7 @@ final class PrescriptionAiGenerator
|
||||
throw new \RuntimeException('INVALID_REPORT_OUTPUT');
|
||||
}
|
||||
$parsed = $retried;
|
||||
$value = $asked['value'];
|
||||
}
|
||||
// A single medicine name outside the institution dictionary makes the whole plan
|
||||
// unmappable, so name it and re-ask instead of accepting an uncomparable candidate.
|
||||
@@ -324,7 +327,10 @@ final class PrescriptionAiGenerator
|
||||
self::checkpoint($checkpoint, $progress, false);
|
||||
return ['ok' => true, 'report' => $parsed['report'], 'candidate' => $parsed['candidate'], 'coverage' => $coverage,
|
||||
'usage' => $progress['usage'], 'model_name' => $value['model_name'] ?? $modelName,
|
||||
'configured_model_name' => $config['models'][$modelKey]['name'] ?? null, 'prompt_version' => self::PROMPT_VERSION];
|
||||
'configured_model_name' => $config['models'][$modelKey]['name'] ?? null, 'prompt_version' => self::PROMPT_VERSION,
|
||||
'knowledge_source' => NihaixiaClinicalSkill::knowledgeSource(),
|
||||
'ancient_book_sources' => is_array($value['ancient_book_sources'] ?? null)
|
||||
? $value['ancient_book_sources'] : TcmAncientBooksReference::sourcesForText($prompt)];
|
||||
} catch (\Throwable $e) {
|
||||
$code = preg_match('/^[A-Z][A-Z0-9_]{2,80}$/', $e->getMessage()) ? $e->getMessage() : 'GENERATION_FAILED';
|
||||
return self::failure($code, in_array($code, self::RETRYABLE, true), $coverage, $progress['usage']);
|
||||
@@ -355,7 +361,7 @@ final class PrescriptionAiGenerator
|
||||
$progress['format_rejects'][count($progress['format_rejects'] ?? []) - 1]['content_length'] = $reject['content_length'];
|
||||
self::invalidateStep($key, $progress, $checkpoint);
|
||||
$repairPrompt = self::repairPrompt($prompt, $reject['rule']);
|
||||
if (strlen(NihaixiaClinicalSkill::augmentQuery($repairPrompt)) > $inputBudget) {
|
||||
if (strlen(ClinicalKnowledgeReference::augmentQuery($repairPrompt)) > $inputBudget) {
|
||||
return ['value' => $value, 'parsed' => null];
|
||||
}
|
||||
$repaired = self::step($key . ':repair', $repairPrompt, $files, $modelKey, $context, $progress, $transport, $checkpoint, $inputBudget, $maxCalls);
|
||||
@@ -383,7 +389,7 @@ final class PrescriptionAiGenerator
|
||||
|
||||
private static function step(string $key, string $prompt, array $files, string $model, array $context, array &$progress, callable $transport, ?callable $checkpoint, int $inputBudget, int $maxCalls): array
|
||||
{
|
||||
$prompt = NihaixiaClinicalSkill::augmentQuery($prompt);
|
||||
$prompt = ClinicalKnowledgeReference::augmentQuery($prompt);
|
||||
// UTF-8 byte length is a conservative upper bound for byte-fallback tokenizers. File
|
||||
// vision tokens depend on provider preprocessing and are tracked as unknown usage.
|
||||
if (strlen($prompt) > $inputBudget) {
|
||||
|
||||
Reference in New Issue
Block a user