{"record":{"id":"2e013984b85e094b","repo":"sigoden/aichat","slug":"the-model-does-not-support-network-images","errorCode":null,"errorMessage":"The model does not support network images: {:?}","messagePattern":"The model does not support network images: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/client/vertexai.rs","lineNumber":384,"sourceCode":"                                    \"name\": tool_result.call.name,\n                                    \"response\": {\n                                        \"name\": tool_result.call.name,\n                                        \"content\": tool_result.output,\n                                    }\n                                }\n                            })\n                        }).collect();\n                        vec![\n                            json!({ \"role\": \"model\", \"parts\": model_parts }),\n                            json!({ \"role\": \"function\", \"parts\": function_parts }),\n                        ]\n                    }\n                }\n        })\n        .collect();\n\n    if !network_image_urls.is_empty() {\n        bail!(\n            \"The model does not support network images: {:?}\",\n            network_image_urls\n        );\n    }\n\n    let mut body = json!({ \"contents\": contents, \"generationConfig\": {} });\n\n    if let Some(v) = system_message {\n        body[\"systemInstruction\"] = json!({ \"parts\": [{\"text\": v }] });\n    }\n\n    if let Some(v) = model.max_tokens_param() {\n        body[\"generationConfig\"][\"maxOutputTokens\"] = v.into();\n    }\n    if let Some(v) = temperature {\n        body[\"generationConfig\"][\"temperature\"] = v.into();\n    }\n    if let Some(v) = top_p {","sourceCodeStart":366,"sourceCodeEnd":402,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/client/vertexai.rs#L366-L402","documentation":"Thrown by gemini_build_chat_completions_body in src/client/vertexai.rs:384 when a chat completions request includes image URLs that are remote http(s) URLs rather than base64 data URLs. The Gemini/Vertex AI body builder only converts `data:<mime>;base64,<data>` image URLs into inline_data parts; any other URL is collected into network_image_urls and the request is rejected before it is sent. The library does this because the Gemini generateContent endpoint it targets does not accept remote image URLs in message parts.","triggerScenarios":"Calling chat_completions on a Vertex AI/Gemini model with a message content part of type ImageUrl whose url is a network URL (e.g. \"https://example.com/cat.jpg\") instead of a base64 data URL like \"data:image/jpeg;base64,...\". Every non-data: URL in the request causes this bail, listing all offending URLs.","commonSituations":"Porting code from OpenAI-style clients (which accept https image URLs) to this Vertex AI client; loading image URLs directly from a database or user input without downloading/encoding them; forgetting the multipart/inline image step in an image-captioning pipeline.","solutions":["Download the image and inline it as a base64 data URL, e.g. \"data:image/jpeg;base64,<base64 bytes>\", before building the message.","Use a helper to fetch the URL, detect its MIME type, and base64-encode the bytes into the ImageUrl `url` field.","If the images are stored on GCS, switch to a Gemini file/inline reference supported by the API instead of a public https URL.","Strip or replace image parts with a text description when the target model doesn't support vision."],"exampleFix":"// before\nMessageContentPart::ImageUrl { image_url: ImageUrl { url: \"https://example.com/cat.jpg\".into() } }\n// after\nlet bytes = reqwest::get(\"https://example.com/cat.jpg\").await?.bytes().await?;\nlet data_url = format!(\"data:image/jpeg;base64,{}\", base64::engine::general_purpose::STANDARD.encode(&bytes));\nMessageContentPart::ImageUrl { image_url: ImageUrl { url: data_url } }","handlingStrategy":"validation","validationCode":"fn assert_no_network_images(messages: &[Message]) -> Result<(), Vec<String>> {\n    let mut bad = vec![];\n    for msg in messages {\n        if let MessageContent::Array(parts) = &msg.content {\n            for p in parts {\n                if let MessageContentPart::ImageUrl { image_url: ImageUrl { url } } = p {\n                    if !url.starts_with(\"data:\") {\n                        bad.push(url.clone());\n                    }\n                }\n            }\n        }\n    }\n    if bad.is_empty() { Ok(()) } else { Err(bad) }\n}","typeGuard":"fn is_inline_image_url(url: &str) -> bool {\n    url.starts_with(\"data:\") && url.contains(\";base64,\")\n}","tryCatchPattern":"let body = match gemini_build_chat_completions_body(data, &model) {\n    Ok(b) => b,\n    Err(e) if e.to_string().starts_with(\"The model does not support network images\") => {\n        // convert URLs to base64 data URLs and rebuild\n        let data = inline_all_images(data).await?;\n        gemini_build_chat_completions_body(data, &model)?\n    }\n    Err(e) => return Err(e.into()),\n};","preventionTips":["Always convert remote image URLs to base64 data URLs before sending to Gemini/Vertex AI models.","Write a preprocessing step in your pipeline that fetches, MIME-detects, and inlines every image.","Remember the OpenAI API accepts https image URLs but this Gemini client does not — audit ported code.","Store images locally or as data URLs at ingest time so downstream calls never see raw URLs.","Unit-test message builders with a mix of data: and https: image parts to catch regressions."],"tags":["gemini","vertex-ai","images","unsupported-operation","multimodal"],"backgroundTag":"unsupported-operation","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}