61 lines
2.5 KiB
PHP
61 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\adminapi\service;
|
|
|
|
use app\common\service\NihaixiaClinicalSkill;
|
|
use app\common\service\TcmAncientBooksReference;
|
|
use app\common\service\TcmClinicalResponsePolicy;
|
|
use think\facade\Cache;
|
|
|
|
/** Short-lived evidence only. The caller must re-authorize and aggregate before lookup. */
|
|
final class AiContextCompactionCache
|
|
{
|
|
public static function remember(string $scope, string $profile, string $source, callable $produce, ?callable $shouldAbort = null): array
|
|
{
|
|
$config = config('prescription_ai') ?: [];
|
|
$ttl = max(0, min(600, (int) ($config['context_compaction_cache_ttl'] ?? 300)));
|
|
$key = self::key($scope, $profile, $source, $config);
|
|
if ($scope !== '' && $ttl > 0) {
|
|
try {
|
|
$cached = Cache::get($key);
|
|
if (self::valid($cached, strlen($source))) {
|
|
$cached['cache_hit'] = true;
|
|
return $cached;
|
|
}
|
|
} catch (\Throwable $ignored) {
|
|
// An unavailable cache must not prevent a complete fresh analysis.
|
|
}
|
|
}
|
|
$result = $produce();
|
|
if ($shouldAbort !== null && $shouldAbort()) throw new \RuntimeException('Context operation cancelled');
|
|
if ($scope !== '' && $ttl > 0 && self::valid($result, strlen($source))) {
|
|
try {
|
|
Cache::set($key, $result, $ttl);
|
|
} catch (\Throwable $ignored) {
|
|
}
|
|
}
|
|
return $result + ['cache_hit' => false];
|
|
}
|
|
|
|
private static function key(string $scope, string $profile, string $source, array $config): string
|
|
{
|
|
return 'ai-context-evidence:' . hash('sha256', serialize([
|
|
'evidence-extraction-v2', $scope, $profile, hash('sha256', $source),
|
|
$config['base_url'] ?? '', $config['models'][$profile] ?? [],
|
|
NihaixiaClinicalSkill::VERSION, TcmAncientBooksReference::COMMIT, TcmClinicalResponsePolicy::VERSION,
|
|
]));
|
|
}
|
|
|
|
private static function valid($value, int $sourceBytes): bool
|
|
{
|
|
return is_array($value) && is_string($value['text'] ?? null)
|
|
&& trim($value['text']) !== '' && strlen($value['text']) <= 240000
|
|
&& ($value['source_bytes'] ?? null) === $sourceBytes
|
|
&& is_int($value['chunk_count'] ?? null) && $value['chunk_count'] > 0
|
|
&& is_int($value['reduction_rounds'] ?? null) && $value['reduction_rounds'] >= 0
|
|
&& ($value['compacted'] ?? null) === true;
|
|
}
|
|
}
|