84 lines
5.3 KiB
PHP
84 lines
5.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace think\facade {
|
|
class Log { public static function warning(string $message): void {} }
|
|
}
|
|
|
|
namespace app\common\service {
|
|
function config(string $name): array { return $GLOBALS['loopbackRequiredConfig']; }
|
|
}
|
|
|
|
namespace {
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
use app\common\service\DifyChatService;
|
|
function loopbackRequiredExpect(bool $ok, string $message): void { if (!$ok) throw new RuntimeException($message); }
|
|
|
|
if (($argv[1] ?? '') === 'server') {
|
|
$listener = stream_socket_server('tcp://127.0.0.1:0', $error, $message);
|
|
if ($listener === false) exit(2);
|
|
echo json_encode(['address' => stream_socket_get_name($listener, false)]) . "\n";
|
|
flush();
|
|
$client = stream_socket_accept($listener, 5);
|
|
if ($client === false) { fclose($listener); exit(3); }
|
|
stream_set_timeout($client, 5);
|
|
$wire = '';
|
|
while (!str_contains($wire, "\r\n\r\n")) {
|
|
$chunk = fread($client, 8192);
|
|
if ($chunk === '' || $chunk === false) break;
|
|
$wire .= $chunk;
|
|
}
|
|
[$headers, $body] = array_pad(explode("\r\n\r\n", $wire, 2), 2, '');
|
|
preg_match('/\r\nContent-Length:\s*(\d+)/i', $headers, $length);
|
|
$expected = (int) ($length[1] ?? 0);
|
|
while (strlen($body) < $expected) {
|
|
$chunk = fread($client, $expected - strlen($body));
|
|
if ($chunk === '' || $chunk === false) break;
|
|
$body .= $chunk;
|
|
}
|
|
$payload = json_decode($body, true);
|
|
$stream = ($payload['response_mode'] ?? '') === 'streaming' || !empty($payload['stream']);
|
|
$status = ($argv[2] ?? '') === 'reject' ? 413 : 200;
|
|
$response = $status === 413 ? '{"code":"invalid_param","message":"Number of files exceeds the limit."}'
|
|
: ($stream ? "data: {\"event\":\"message\",\"answer\":\"synthetic-answer\"}\n\ndata: {\"event\":\"message_end\"}\n\n" : '{"answer":"synthetic-answer"}');
|
|
fwrite($client, 'HTTP/1.1 ' . $status . " Synthetic\r\nContent-Type: " . ($stream ? 'text/event-stream' : 'application/json')
|
|
. "\r\nContent-Length: " . strlen($response) . "\r\nConnection: close\r\n\r\n" . $response);
|
|
fclose($client); fclose($listener);
|
|
echo json_encode(['payload' => $payload, 'body_bytes' => strlen($body), 'expected_body_bytes' => $expected], JSON_UNESCAPED_SLASHES) . "\n";
|
|
exit;
|
|
}
|
|
|
|
$files = [];
|
|
for ($index = 1; $index <= 8; ++$index) $files[] = ['type' => $index <= 4 ? 'image' : 'document',
|
|
'url' => 'https://synthetic.invalid/required-' . $index . ($index <= 4 ? '.png' : '.pdf')];
|
|
$files[] = ['type' => 'video', 'url' => 'https://synthetic.invalid/not-transmitted.mp4'];
|
|
$sections = [['key' => 'chat', 'label' => '聊天', 'count' => 0, 'availability' => 'empty', 'status' => 'prepared']];
|
|
foreach (['blocking', 'streaming', 'reject'] as $scenario) {
|
|
$process = proc_open([PHP_BINARY, __FILE__, 'server', $scenario],
|
|
[0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, null, ['bypass_shell' => true]);
|
|
loopbackRequiredExpect(is_resource($process), 'loopback peer starts');
|
|
$ready = json_decode((string) fgets($pipes[1]), true);
|
|
loopbackRequiredExpect(is_string($ready['address'] ?? null), 'loopback peer supplies ephemeral port');
|
|
$loopbackRequiredConfig = ['enable' => true, 'base_url' => 'http://' . $ready['address'] . '/v1/chat-messages',
|
|
'timeout' => 5, 'max_files' => 3, 'models' => ['qwen' => ['name' => 'synthetic', 'api_key' => 'loopback-fixture-only']]];
|
|
$options = ['required_files' => true, 'required_sections' => $sections, 'knowledge_mode' => 'evidence'];
|
|
$result = $scenario === 'streaming'
|
|
? DifyChatService::streamChat('qwen', [], '合成资料;聊天:无', 'synthetic-user', static fn (string $delta): bool => true, null, $files, $options)
|
|
: DifyChatService::chat('qwen', [], '合成资料;聊天:无', 'synthetic-user', $files, $options);
|
|
$captured = json_decode((string) fgets($pipes[1]), true);
|
|
fclose($pipes[0]); fclose($pipes[1]);
|
|
$stderr = stream_get_contents($pipes[2]); fclose($pipes[2]);
|
|
$exit = proc_close($process);
|
|
loopbackRequiredExpect($exit === 0 && $stderr === '', 'loopback peer exits cleanly');
|
|
loopbackRequiredExpect($captured['body_bytes'] === $captured['expected_body_bytes']
|
|
&& count($captured['payload']['files']) === 8, 'actual HTTP body carries all eight required attachments with complete Content-Length');
|
|
loopbackRequiredExpect(!str_contains(json_encode($captured['payload']), 'not-transmitted.mp4')
|
|
&& str_contains($captured['payload']['query'], '聊天:无'), 'actual HTTP wire contains explicit empty text and no video');
|
|
if ($scenario === 'reject') loopbackRequiredExpect(($result['error_code'] ?? '') === 'REQUIRED_FILES_REJECTED', 'real 413 terminates as required data failure');
|
|
else loopbackRequiredExpect(!empty($result['ok']) && $result['attachment_count'] === 8
|
|
&& $result['required_sections'][0]['status'] === 'submitted', 'actual successful HTTP delivery has final complete manifest');
|
|
}
|
|
echo "Dify required sources actual loopback HTTP blocking / streaming / hard-limit rejection: OK\n";
|
|
}
|