Repository navigation
fix: send images one at a time using existing sizes or public URLs - #4
Merged
Merged
Conversation
Generating tags base64-encoded every original upload into a single request. On a post with seven 1MB+ photos that is a 10.7MB JSON body and ~40MB of peak memory, which exhausts the 128M memory_limit admin-ajax runs under on shared hosts and returns a 500. Providers that accept only one image per prompt also rejected the request with a 400. Each image is now analyzed in its own request. When the image is a media library item, an already-generated intermediate size (1536x1536, large, medium_large, medium) is used instead of the original. When the URL is on a public host it is sent as-is for the provider to fetch, with an automatic base64 fallback for development hosts, private addresses, native Ollama, or providers that reject URLs. Per-image tag lists are merged by how many images each tag appears in. Once a reasoning model needs the larger token budget, later requests in the same run start with it instead of retrying. New filters: kwik_ai_analysis_image_sizes, kwik_ai_analysis_image_url, kwik_ai_send_image_urls, kwik_ai_max_images_per_post. Claude-Session: https://claude.ai/code/session_01QcttG1fpMj2rg6axwhTvFA
There was a problem hiding this comment.
🟡 Changes recommended
Five unresolved moderate comments address URL handling and avoidable repeated API requests.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR reduces image-tagging memory usage and supports providers that accept one image per request.
Changes:
- Analyzes bounded images individually and merges tags.
- Uses existing image sizes, public URLs, and base64 fallback.
- Adds image/tag limits, URL handling, retry budgeting, and tests.
File summaries
| File | Summary |
|---|---|
tests/unit/KwikAiImageAnalysisTest.php |
Tests image selection, URL validation, and tag merging. |
tests/bootstrap.php |
Adds WordPress attachment and API mocks. |
includes/tag-generation.php |
Implements per-image analysis, limits, and tag merging. |
includes/ollama-api.php |
Adds URL payloads, fallback handling, and reasoning-budget retries. Moderate comments: cache native endpoint detection (1 vote); memoize the accepted token field (1 vote); only memoize larger budgets after a successful non-empty retry (2 votes). |
includes/image-processing.php |
Selects image sizes and validates public URLs. Moderate comments: apply the final URL filter to unresolved/external URLs (2 votes); normalize trailing-dot hosts before validation (1 vote). |
core/constants.php |
Adds image and tag limits. |
Review details
Suppressed comments (3)
includes/image-processing.php:455
- A trailing-dot host such as
127.0.0.1.ormysite.local.is not accepted byFILTER_VALIDATE_IP, then has an empty TLD and passes this check as public. Normalize the trailing dot before the IP and development-TLD checks, otherwise local URLs can take the provider-URL path instead of the intended base64 fallback.
$host = strtolower(trim($parts['host'], '[]'));
includes/ollama-api.php:386
- For a custom native Ollama endpoint, every base64 image reaches this branch after the
/chat/completionsprobe has already returned 404/405 for the previous image. Because that endpoint capability is not cached, the new per-image loop performs an extra failed HTTP request per image, adding latency and load and making the documented one-request-per-image behavior inaccurate. Cache the detected native endpoint, or otherwise skip the compatibility probe for the remainder of the request.
// Native Ollama only accepts base64 images; it cannot fetch URLs. Tell the
// caller so it can retry with image data instead.
foreach ($images as $image) {
if (kwik_ai_tags_image_is_url($image)) {
kwik_ai_log('Kwik AI: /api/generate cannot fetch image URLs; caller must supply base64');
return null;
includes/ollama-api.php:217
- This memo records only that a larger budget is needed; it does not remember that the model required
max_completion_tokens. For models that rejectmax_tokens, every subsequent per-image call still sends a guaranteed 400 with the legacy field before retrying with the larger field, so the new loop retains an avoidable extra request for every image. Store the accepted token field along with the budget and skip the legacy attempt once it is known to be unsupported.
static $needs_large_budget = array();
$model_key = isset($payload['model']) ? (string) $payload['model'] : '';
$large_budget = max($max_tokens * 8, 2048);
if ($model_key !== '' && !empty($needs_large_budget[$model_key])) {
$max_tokens = $large_budget;
}
// Most models accept the legacy 'max_tokens'; start there for compatibility.
$token_field = 'max_tokens';
$payload[$token_field] = $max_tokens;
- Files reviewed: 6/6 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+395
to
+397
| $attachment_id = attachment_url_to_postid($original_url); | ||
| if (!$attachment_id) { | ||
| return $url; |
Comment on lines
+245
to
249
| if ($model_key !== '') { | ||
| $needs_large_budget[$model_key] = true; | ||
| } | ||
| $payload[$token_field] = $large_budget; | ||
| $response = $post($payload); |
|
🎉 This PR is included in version 1.1.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Clicking Generate AI Tags returned a 500 in production while working locally. The production
wp-admin/error_logshowed:Tag generation base64-encoded every original upload into a single
/chat/completionsrequest. On a post with seven 1MB+ photos that is a 10.7MB JSON body and ~40MB of peak memory on top of the WordPress baseline.admin-ajax.phpnever callswp_raise_memory_limit(), so on a host withmemory_limit = 128Mthe request dies insidewp_json_encode(). Locally the container allows 512M, which is why it passed there.A second issue hid behind the first: providers that accept only one image per prompt (vLLM-style servers by default) rejected the multi-image request with a 400, so image tags never worked and "success" came from the text fallback alone.
Changes
KWIK_AI_MAX_IMAGE_TAGS. Analysis is capped atKWIK_AI_MAX_IMAGES(10) per post.1536x1536,large,medium_large,mediuminstead of the original upload. For the test post that is ~210KB instead of ~1.2MB per image. No resizing at request time..test/.local/localhost/private IP) the URL is sent for the provider to fetch, so WordPress downloads and encodes nothing. If the provider returns nothing for the URL, the image is fetched and sent as base64, and the remaining images go straight to base64. Native Ollama's/api/generatepath refuses URL entries, which triggers the same fallback.finish_reason=lengthand the retry at the larger token budget succeeds, later requests in the same run start with the larger budget instead of paying for a doomed first attempt on every image.kwik_ai_analysis_image_sizes,kwik_ai_analysis_image_url,kwik_ai_send_image_urls,kwik_ai_max_images_per_post.Verification
Same 7-image post,
memory_limitforced to 128M:.testhost)PHPUnit: 54 tests, 160 assertions passing (14 new). PHPCS clean.
Notes
Runtime now scales with image count, roughly 3 seconds per image against a vLLM server. The public-host check is a name test, not a network test: a site behind basic auth will pass it, hit the one-time fallback, and continue in base64.