更新
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service {
|
||||
function config(string $name): array
|
||||
{
|
||||
return $GLOBALS['clinicalChatConfig'];
|
||||
}
|
||||
|
||||
function curl_init(): \stdClass
|
||||
{
|
||||
return new \stdClass();
|
||||
}
|
||||
|
||||
function curl_setopt(\stdClass $handle, int $option, mixed $value): bool
|
||||
{
|
||||
$handle->options[$option] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
function curl_exec(\stdClass $handle): string|bool
|
||||
{
|
||||
$GLOBALS['clinicalChatPayloads'][] = json_decode($handle->options[CURLOPT_POSTFIELDS], true);
|
||||
if (isset($handle->options[CURLOPT_WRITEFUNCTION])) {
|
||||
$write = $handle->options[CURLOPT_WRITEFUNCTION];
|
||||
$write($handle, "data: {\"choices\":[{\"delta\":{\"content\":\"offline stream\"}}]}\n\n");
|
||||
return true;
|
||||
}
|
||||
return '{"choices":[{"message":{"content":"offline reply"}}]}';
|
||||
}
|
||||
|
||||
function curl_errno(\stdClass $handle): int { return 0; }
|
||||
function curl_error(\stdClass $handle): string { return ''; }
|
||||
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\AiChatService;
|
||||
use app\common\service\NihaixiaClinicalSkill;
|
||||
|
||||
function chatSkillExpect(bool $condition, string $message): void
|
||||
{
|
||||
if (!$condition) {
|
||||
fwrite(STDERR, "FAIL: {$message}\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$clinicalChatConfig = [
|
||||
'enable' => true,
|
||||
'api_key' => 'offline-fixture',
|
||||
'base_url' => 'https://ai.example.test',
|
||||
'model' => 'offline-model',
|
||||
];
|
||||
$clinicalChatPayloads = [];
|
||||
$messages = [
|
||||
['role' => 'system', 'content' => '只输出 JSON'],
|
||||
['role' => 'user', 'content' => '分析该患者症状'],
|
||||
];
|
||||
|
||||
$blocking = AiChatService::chat($messages);
|
||||
chatSkillExpect($blocking['ok'] === true, 'blocking chat succeeds offline');
|
||||
$deltas = [];
|
||||
$streaming = AiChatService::streamChat($messages, static function (string $delta) use (&$deltas): void {
|
||||
$deltas[] = $delta;
|
||||
});
|
||||
chatSkillExpect($streaming['ok'] === true && $deltas === ['offline stream'], 'streaming chat succeeds offline');
|
||||
chatSkillExpect(count($clinicalChatPayloads) === 2, 'both transports sent one request');
|
||||
foreach ($clinicalChatPayloads as $payload) {
|
||||
$wireMessages = $payload['messages'];
|
||||
chatSkillExpect(str_contains($wireMessages[0]['content'], NihaixiaClinicalSkill::VERSION), 'wire request includes the clinical skill');
|
||||
chatSkillExpect($wireMessages[1] === $messages[0] && $wireMessages[2] === $messages[1], 'original format and patient question are preserved');
|
||||
}
|
||||
|
||||
echo "AiChat clinical skill transport contract passed.\n";
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use app\common\service\NihaixiaClinicalSkill;
|
||||
|
||||
function skillExpect(bool $condition, string $message): void
|
||||
{
|
||||
if (!$condition) {
|
||||
fwrite(STDERR, "FAIL: {$message}\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$reference = NihaixiaClinicalSkill::reference();
|
||||
skillExpect(str_contains($reference, NihaixiaClinicalSkill::VERSION), 'pinned skill version is present');
|
||||
skillExpect(str_contains($reference, '六经主证:太阳:'), 'reference is read from the installed six-meridian table');
|
||||
skillExpect(str_contains($reference, '条件线索:太阳病(脉浮 + 头项强痛 + 恶寒)'), 'clinical clues are read from the installed diagnostic formulas');
|
||||
skillExpect(str_contains($reference, '辨证步骤:定表里→分阴阳→辨寒热'), 'seven-step method is read from the installed skill');
|
||||
skillExpect(str_contains($reference, '未满足关键证据时不得强行归经'), 'diagnostic clues require patient evidence');
|
||||
skillExpect(str_contains($reference, '不得据此建议停用现有治疗'), 'historical cases cannot override treatment safety');
|
||||
skillExpect(strlen($reference) < 3000, 'reference is bounded for staged clinical prompts');
|
||||
|
||||
$question = '请分析当前患者的口渴和高血糖。';
|
||||
$query = NihaixiaClinicalSkill::augmentQuery($question);
|
||||
skillExpect(str_contains($query, $question), 'original clinical question is preserved');
|
||||
skillExpect(substr_count($query, NihaixiaClinicalSkill::VERSION) === 1, 'query receives the skill once');
|
||||
skillExpect(NihaixiaClinicalSkill::augmentQuery($query) === $query, 'query retries do not duplicate the skill');
|
||||
|
||||
$messages = [
|
||||
['role' => 'system', 'content' => '只输出 JSON'],
|
||||
['role' => 'user', 'content' => $question],
|
||||
];
|
||||
$augmented = NihaixiaClinicalSkill::augmentMessages($messages);
|
||||
skillExpect(count($augmented) === 3, 'chat messages receive one skill system reference');
|
||||
skillExpect(($augmented[1]['content'] ?? '') === '只输出 JSON', 'original system format is preserved');
|
||||
skillExpect(($augmented[2]['content'] ?? '') === $question, 'original user question is preserved');
|
||||
skillExpect(NihaixiaClinicalSkill::augmentMessages($augmented) === $augmented, 'stream retries do not duplicate the skill');
|
||||
|
||||
echo "Nihaixia clinical skill contract passed.\n";
|
||||
@@ -37,6 +37,7 @@ namespace {
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use app\common\service\DifyChatService;
|
||||
use app\common\service\NihaixiaClinicalSkill;
|
||||
|
||||
function expectSame($expected, $actual, string $message): void
|
||||
{
|
||||
@@ -210,6 +211,9 @@ foreach (['dify' => 'chat-messages', 'openai' => 'chat/completions'] as $protoco
|
||||
expectSame(true, $strictResult['ok'], 'strict ' . $protocol . ' accepts distinct logical attachments sharing a URL');
|
||||
expectSame(1, count($upstreamTestRequests), 'strict ' . $protocol . ' submits the complete batch once');
|
||||
$payload = $upstreamTestRequests[0]['payload'];
|
||||
$wirePrompt = $protocol === 'dify' ? $payload['query'] : $payload['messages'][0]['content'][0]['text'];
|
||||
expectSame(true, str_contains($wirePrompt, NihaixiaClinicalSkill::VERSION), 'strict ' . $protocol . ' includes the clinical skill on the wire');
|
||||
expectSame(true, str_contains($wirePrompt, 'offline query'), 'strict ' . $protocol . ' preserves the clinical question');
|
||||
$wireUrls = $protocol === 'dify' ? array_column($payload['files'], 'url')
|
||||
: array_column(array_column(array_slice($payload['messages'][0]['content'], 1), 'image_url'), 'url');
|
||||
expectSame(array_column($duplicateFiles, 'url'), $wireUrls, 'strict ' . $protocol . ' transmits every attachment in manifest order');
|
||||
|
||||
Reference in New Issue
Block a user