Reject non-finite values in embedding vectors - #263
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## trunk #263 +/- ##
=========================================
Coverage 86.49% 86.50%
- Complexity 1327 1330 +3
=========================================
Files 68 68
Lines 4295 4298 +3
=========================================
+ Hits 3715 3718 +3
Misses 580 580
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
One downstream coordination note: WordPress/wordpress-develop#12530 currently imports PHP AI Client 1.4.0 for the WordPress 7.1 embedding API, so its vendored If this fix looks right, would a 1.4.1 patch release followed by updating #12530 to that tag be the cleanest path? I’m not suggesting this should block the Core wrapper review—just flagging the dependency while the import is still open. |
There was a problem hiding this comment.
Fix looks solid 👍
Verification script
<?php
declare(strict_types=1);
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Results\DTO\Embedding;
require_once __DIR__ . '/vendor/autoload.php';
$failures = 0;
function assertRejected(string $label, float $value): void
{
global $failures;
try {
new Embedding([0.1, $value], 2);
echo "[FAIL] {$label}: expected InvalidArgumentException, none thrown\n";
$failures++;
} catch (InvalidArgumentException $e) {
echo "[PASS] {$label}: rejected (\"{$e->getMessage()}\")\n";
}
}
function assertArrayRejected(string $label, array $values): void
{
global $failures;
try {
new Embedding($values, count($values));
echo "[FAIL] {$label}: expected InvalidArgumentException, none thrown\n";
$failures++;
} catch (InvalidArgumentException $e) {
echo "[PASS] {$label}: rejected (\"{$e->getMessage()}\")\n";
}
}
function assertAccepted(string $label, array $values): void
{
global $failures;
try {
$embedding = new Embedding($values, count($values));
} catch (InvalidArgumentException $e) {
echo "[FAIL] {$label}: expected success, got exception (\"{$e->getMessage()}\")\n";
$failures++;
return;
}
$json = json_encode($embedding);
if ($json === false) {
echo "[FAIL] {$label}: constructed but json_encode() failed (\"" . json_last_error_msg() . "\")\n";
$failures++;
return;
}
echo "[PASS] {$label}: accepted, encodes to {$json}\n";
}
echo "-- Non-finite values (must be rejected) --\n";
assertRejected('NAN', NAN);
assertRejected('INF', INF);
assertRejected('-INF', -INF);
echo "\n-- Non-finite values in longer/other positions (must still be rejected) --\n";
assertArrayRejected('trailing INF', [0.1, 0.2, INF]);
assertArrayRejected('trailing NAN', [0.1, 0.2, NAN]);
assertArrayRejected('trailing -INF', [0.1, 0.2, -INF]);
assertArrayRejected('leading INF', [INF, 0.1, 0.2]);
echo "\n-- Finite values (must still be accepted) --\n";
assertAccepted('floats', [0.1, 0.2]);
assertAccepted('integers', [1, 2]);
assertAccepted('mixed int/float', [1, 0.5]);
echo "\n" . ($failures === 0 ? "All checks passed.\n" : "{$failures} check(s) failed.\n");
exit($failures === 0 ? 0 : 1);Straightforward, correct fix. is_finite() seems right guard, it catches NAN/INF/-INF, so every ordinary float and every int passes through untouched.
I was thinking would it be simpler to handle the type and finiteness checks directly in the existing validation loop and remove isEmbeddingList()? Something like:
if (!is_array($values) || !array_is_list($values)) {
throw new InvalidArgumentException('Embedding values must be a list array.');
}
foreach ($values as $value) {
if (!is_int($value) && !is_float($value)) {
throw new InvalidArgumentException('Embedding values must be integers or floats.');
}
if (is_float($value) && !is_finite($value)) {
throw new InvalidArgumentException('Embedding values must be finite numbers.');
}
}
Summary
NAN,INF, and-INFat theEmbeddingconstructor boundary.Fixes #262.
Why
Non-finite values satisfy PHP's
is_float()check but cannot be represented in JSON. Rejecting them when constructing anEmbeddingprevents an invalid result object from failing later during serialization, logging, caching, persistence, or custom provider handling.Testing
A test-only commit first demonstrated the gap: all three new cases failed because no
InvalidArgumentExceptionwas thrown.After the implementation commit:
AI assistance