Files
zyt/server/tests/TcmTaskScopedTransportR37Test.php
T
2026-10-27 14:03:35 +08:00

94 lines
5.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
// Exercise the real transport's query/profile/stream construction without a network.
namespace app\common\service {
function config(string $name): array { return $GLOBALS['r37TransportConfig']; }
function curl_init(): \stdClass { return new \stdClass(); }
function curl_setopt_array(\stdClass $handle, array $options): bool
{
$handle->options = $options;
$GLOBALS['r37TransportRequests'][] = json_decode($options[CURLOPT_POSTFIELDS], true, 512, JSON_THROW_ON_ERROR);
$GLOBALS['r37TransportHeaders'][] = $options[CURLOPT_HTTPHEADER];
return true;
}
function curl_exec(\stdClass $handle): string
{
$answer = $GLOBALS['r37TransportAnswer'];
if (isset($handle->options[CURLOPT_WRITEFUNCTION])) {
$handle->options[CURLOPT_HEADERFUNCTION]($handle, "HTTP/1.1 200 OK\r\n");
$handle->options[CURLOPT_WRITEFUNCTION]($handle,
'data: ' . json_encode(['event' => 'message', 'answer' => $answer], JSON_UNESCAPED_UNICODE) . "\n\n"
. "data: {\"event\":\"message_end\"}\n\n");
return '';
}
return json_encode(['answer' => $answer], JSON_UNESCAPED_UNICODE);
}
function curl_errno(\stdClass $handle): int { return 0; }
function curl_getinfo(\stdClass $handle, int $option): int { return 200; }
function curl_close(\stdClass $handle): void {}
}
namespace {
require dirname(__DIR__) . '/vendor/autoload.php';
use app\common\service\ClinicalKnowledgeReference;
use app\common\service\DifyChatService;
use app\common\service\TcmClinicalResponsePolicy;
$checks = 0;
function r37TransportExpect(bool $ok, string $message): void
{
++$GLOBALS['checks'];
if (!$ok) throw new \RuntimeException($message);
}
$r37TransportConfig = ['enable' => true, 'base_url' => 'https://no-network.invalid/v1/chat-messages',
'timeout' => 30, 'models' => [
'qwen' => ['name' => 'synthetic-qwen-original', 'api_key' => 'synthetic-qwen'],
'openai' => ['name' => 'synthetic-openai-original', 'api_key' => 'synthetic-openai'],
]];
$tasks = ['case_analysis' => '分析病历', 'glucose_trend' => '血糖趋势',
'medication_review' => '用药评估', 'complication_risk' => '并发症风险',
'summary' => '生成问诊小结', 'lifestyle_advice' => '健康建议'];
$raw = '2026-10-10空腹血糖8.2mmol/L;2026-10-12空腹血糖7.6mmol/L。舌象未记录。当前用药未记录。SOURCE_TAIL';
$policy = TcmClinicalResponsePolicy::consultationReference();
r37TransportExpect(str_contains($policy, '完成本次选定任务') && str_contains($policy, '缺项只限定依赖该项的判断'),
'missing data qualify dependent conclusions without disabling other analysis');
r37TransportExpect(str_contains($policy, '不将整个回答改成资料核对') && str_contains($policy, '未记录、未采集与不详保持未知'),
'task answers are analysis and retain unknown states');
r37TransportExpect(str_contains($policy, '脉象不参与本次辨证') && str_contains($policy, '不擅停换'),
'pulse exclusion and medication boundaries remain');
r37TransportExpect(str_contains(TcmClinicalResponsePolicy::reference(), '舌象、脉象各单列'),
'nonconsultation reference behavior stays separate');
foreach ($tasks as $task => $label) foreach (['qwen', 'openai'] as $profile) foreach (['blocking', 'streaming'] as $mode) {
$r37TransportRequests = []; $r37TransportHeaders = []; $deltas = [];
$r37TransportAnswer = "MODEL::{$task}::{$profile}::{$mode} 已分析现有资料;舌象未记录,相关判断保留不确定性。";
$query = "任务:{$label}\n请根据已有资料分析本任务,缺项明示。\n<CASE_DATA>{$raw}</CASE_DATA>";
$expectedQuery = ClinicalKnowledgeReference::augmentCaseQuery($query, true);
$inputs = ['task_marker' => $task, 'case_raw' => $raw];
$options = ['knowledge_mode' => 'clinical', 'reference_scope' => 'consultation'];
if ($mode === 'blocking') {
$result = DifyChatService::chat($profile, $inputs, $query, 'synthetic-r37-user', [], $options);
} else {
$result = DifyChatService::streamChat($profile, $inputs, $query, 'synthetic-r37-user',
static function (string $delta) use (&$deltas): void { $deltas[] = $delta; }, null, [], $options);
r37TransportExpect(implode('', $deltas) === $r37TransportAnswer, "{$task}/{$profile} streaming retains actual model text");
}
r37TransportExpect($result['ok'] === true && count($r37TransportRequests) === 1,
"{$task}/{$profile}/{$mode} invokes transport exactly once");
r37TransportExpect($result['content'] === $r37TransportAnswer,
"{$task}/{$profile}/{$mode} retains model answer without a source-only replacement");
r37TransportExpect($r37TransportRequests[0]['query'] === $expectedQuery && str_ends_with($expectedQuery, $query)
&& $r37TransportRequests[0]['inputs'] === $inputs,
"{$task}/{$profile}/{$mode} preserves task question and all raw inputs");
r37TransportExpect(in_array('Authorization: Bearer synthetic-' . $profile, $r37TransportHeaders[0], true),
"{$task}/{$profile}/{$mode} preserves configured provider selection");
r37TransportExpect(ClinicalKnowledgeReference::augmentCaseQuery($expectedQuery, true) === $expectedQuery,
'case query augmentation remains idempotent');
}
echo json_encode(['passed' => $checks, 'transport_calls' => 24, 'network_calls' => 0,
'scope' => 'real Dify transport construction with in-process cURL fakes; no real model, clinical acceptance or deployment'], JSON_UNESCAPED_UNICODE) . "\n";
}