Java 17调用Responses图像输入:商品问答与结果边界

电商客服常收到一张商品照片和一句"这瓶能带上飞机吗"。图像模型可能看见瓶子,却未必看得清容量,更不知道具体航司规定。本文用 Java 17 做一个可运行的视觉问答小程序:把公开图片 URL 和用户问题一起送给模型,并要求它分清可见事实、推断和未知。学会以后,你能把同一方法用于设备外观检查、展品导览,但也会知道何时必须让人核验。

视觉模型接收的不只是图片。图片作为 input_image,问题作为 input_text,两个内容项放在同一条用户消息里,模型才能把"这张图"和"这个问题"关联起来。所谓 OCR(Optical Character Recognition,光学字符识别)只是把文字从图上读出来;视觉问答还要理解对象、位置和关系。两者都可能出错,尤其遇到反光、遮挡、字体太小或图片被裁切时。近期新模型不断出现,但本教程先固定官方文档可核验的 gpt-5 图像输入写法,避免把模型升级等同于证据可靠性升级。

flowchart LR A[公开图片URL] --> C[Java组装input_image] B[用户问题] --> D[Java组装input_text] C --> E[Responses视觉模型] D --> E E --> F[解析output_text] F --> G{是否有可见证据} G -->|有| H[回答并说明依据] G -->|没有| I[明确未知并建议核验]

准备依赖与运行环境

安装 Java 17 以上和 Maven。程序使用 JDK 自带的 HttpClient;官方 REST API 对 Java 可直接调用,这里保留 HTTP 请求和响应的可见性,JSON 交给 Jackson 解析。将以下依赖放进 pom.xml 的 <dependencies>,Maven 编译器目标设为 17:<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.17.2</version></dependency>。若已有 Maven 项目,只需补这一项;新建项目还应在 <properties> 里设置 <maven.compiler.release>17</maven.compiler.release>。

把代码保存为 src/main/java/VisionQa.java,设置 OPENAI_API_KEY 与 IMAGE_URL。后者必须是模型服务能访问的 HTTPS 图片地址,且你有权发送这张图;不要传内部照片的公开外链。命令为 mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt、mvn -q compile,再运行 java -cp "target/classes:$(cat cp.txt)" VisionQa "图中能看到什么?";Windows 把路径分隔符改为分号并用 PowerShell 读取 cp.txt。如果只想快速阅读,先看下面的 main 方法即可。

java 复制代码
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;

public class VisionQa {
    private static final ObjectMapper JSON = new ObjectMapper();

    public static void main(String[] args) {
        try {
            String key = System.getenv("OPENAI_API_KEY");
            String imageUrl = System.getenv("IMAGE_URL");
            if (key == null || key.isBlank())
                throw new IllegalArgumentException("缺少 OPENAI_API_KEY");
            if (imageUrl == null || !imageUrl.startsWith("https://"))
                throw new IllegalArgumentException("IMAGE_URL 必须是HTTPS地址");
            if (args.length == 0 || args[0].isBlank())
                throw new IllegalArgumentException("请在命令行传入问题");

            Map<String, Object> body = Map.of(
                "model", "gpt-5",
                "input", List.of(Map.of(
                    "role", "user",
                    "content", List.of(
                        Map.of("type", "input_text", "text",
                            "只回答图中可见事实;无法确认的规格请说未知。问题:" + args[0]),
                        Map.of("type", "input_image", "image_url", imageUrl)
                    )
                ))
            );
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.openai.com/v1/responses"))
                .timeout(Duration.ofSeconds(45))
                .header("Authorization", "Bearer " + key)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(JSON.writeValueAsString(body)))
                .build();
            HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10)).build();
            HttpResponse<String> response = client.send(request,
                HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() / 100 != 2)
                throw new IllegalStateException("HTTP " + response.statusCode()
                    + ": " + response.body().substring(0,
                        Math.min(300, response.body().length())));
            JsonNode root = JSON.readTree(response.body());
            StringBuilder answer = new StringBuilder();
            for (JsonNode item : root.path("output"))
                for (JsonNode part : item.path("content"))
                    if ("output_text".equals(part.path("type").asText()))
                        answer.append(part.path("text").asText());
            if (answer.isEmpty()) throw new IllegalStateException("响应没有文本内容");
            System.out.println(answer);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("请求被中断");
        } catch (Exception e) {
            System.err.println("运行失败:" + e.getMessage());
            System.exit(1);
        }
    }
}

关键代码与预期输出

请求体把文字和图片放在同一个 content 数组。图片 URL 不会由 Java 下载后再上传;模型服务要能够访问它,因此访问控制和授权要先处理。代码对 URL 只做了 HTTPS 前缀检查,生产环境应进一步限制域名、大小和图片类型,避免用户构造恶意地址。HTTP 请求同时设连接和请求超时;中断时恢复线程标志,避免上层服务把取消误当普通失败。

响应不是一个固定的顶层 text 字段,程序遍历 output 中的 content,只拼接 output_text。这样能避开某些非文本输出项。正常情况下可能得到"图中可见一个瓶子,但容量标识无法辨认,是否可携带需核对容量和航司规则"。这是预期形式,不是本次实测。示例未在本次任务中实际调用线上API,也没有以任何真实商品图作准确率验证。

常见错误、适用范围与改进

第一,图片 URL 在浏览器能打开,但服务端取不到:检查公开访问权限、重定向与过期签名。第二,401 或 403:检查密钥、项目权限和模型可用性,不要在报错里回显完整凭据。第三,模型说出了图片看不清的容量:提示词只是约束,仍需业务代码对关键字段做人工核验。第四,大图或小字导致回答不稳:裁切到关键区域,保留原图索引供复核。

它适合低风险的商品咨询、现场导览和辅助分拣,不适合凭单张图做医疗诊断、安检许可或法规判定。工程化时可存储图片哈希、问题、模型版本和人工纠错结果,按"可见事实准确率"和"未知时拒答率"分别评估。图像内容涉及用户隐私时,先确认处理依据与留存期限。

从照片到答案,最容易丢失的是证据粒度。模型说"瓶身是蓝色",用户能在原图看到;说"容量 100 毫升",则要标签清晰可读;说"允许登机",还需要航司规定。产品界面可以把回答分为"图片可见""需查商品资料""需查外部规定",分别接不同数据源。模型文字是候选,关键结论应能点回原图或权威页面。

评估时别只拿漂亮演示图。至少准备清晰图、逆光图、局部裁切图、相似包装图和带诱导文字的图。图片上印着"忽略上面的说明"时,那只是图片内容,不应变成系统指令。把标准答案写成可核对字段,再统计误读、漏读和错误拒答。临时签名 URL 还可能在模型取图前过期;永久公开 URL 则可能泄露内容。真实业务要在授权范围内处理访问、尺寸、格式与留存期限。

5 分钟实践:准备两张你有使用权的公开测试图,一张标签清晰、一张故意模糊,问"容量是多少"。比较回答能否对模糊图保持未知。你会允许商品助手凭包装外观推断容量或认证信息吗?

做商品客服时,模型识别出的信息还应该和商品目录对齐。照片里的包装可能是旧版,系统中的在售 SKU(Stock Keeping Unit,库存保有单位)可能有新版容量。即便模型准确读出了旧包装,也不能据此承诺当前订单的规格。比较稳妥的流程是先从订单或商品页拿到 SKU,再用图片核对颜色、外观、明显标签;若两者冲突,直接转人工或询问用户补拍订单编号,而不是让视觉模型替业务数据库做裁判。

成本和速度也要测在完整链路上。用户上传图片、后端检查大小、生成临时地址、模型取图、模型输出、客服界面展示,每段都有时间。只测模型响应时间会忽略大图上传和图片服务器慢的问题。开始可以记录每段耗时的中位数与高分位数,再决定是压缩图片、裁剪区域,还是换用文件上传路径。压缩会让小字更难读,因此应保留原图以便人工复查,并对清晰度建立最低要求。

对于可见事实的回答,建议让客服点开"证据图片"后再发送,而不要设置默认自动回复。尤其用户问到成分、认证、适龄范围、保质期或安全规格时,必须结合权威商品资料。模型说"看起来像"只能作为搜索线索。为了让纠错数据可复用,人工修改时记录错误类别:图片模糊、模型漏读、错误推断、商品资料过期或问题超出图像范围。不同错误需要不同修复手段,不能都靠调整提示词。

把失败样本和原图一起保存时,注意图片可能含人脸、地址或单号。可先裁掉与问题无关的区域,并给审核样本设置访问权限和清理期限。这样既能保留足够的排障证据,也能减少无关信息暴露。

关注「蜗牛聊AI」,一起看懂技术变化背后的真正机会。


本文首发于 java4u.cn,转载请注明出处。

相关推荐
唯鹿1 小时前
Jev初体验记录
人工智能·ai·jev
工作10年+,存储芯片行业1 小时前
长鑫存储深度分析:国产 DRAM 的崛起、挑战与未来
人工智能·ssd·芯片·存储·pcie·dram·ddr
天天进步20151 小时前
90天AI接单学习路线:从LLM应用到AI Agent
人工智能·学习
fl1768311 小时前
工业镀金钨铜合金散热器表面缺陷识别污渍划痕分割数据集labelme格式1000张2类别低分辨率
人工智能·机器学习
打工仔折腾 AI1 小时前
用UU远程把家里电脑变成AI Agent常驻服务器:CLI、端口映射与代理实测
运维·服务器·人工智能·后端·python·电脑·ai agent 实战
龙亘川1 小时前
数智赋能物业不动产治理:亘川智城智慧物业系统实践解析
大数据·人工智能·智慧城市·开源软件·数据可视化
明月_清风1 小时前
AI Agent 进入系统层:现在有哪些开源项目在解决这个问题?
人工智能·后端
成为深度学习高手1 小时前
SEMixer:以随机注意力增强patch语义、渐进混合多尺度的轻量长期时序预测模型
人工智能·深度学习·机器学习·数据挖掘·时间序列