通义千问3-VL-Plus - 界面交互(本地图片改进)

一、前言

在上一篇文章 通义千问3-VL-Plus - 界面交互(本地图片)-CSDN博客 中,我搞定了对桌面软件的定位信息(交互的核心),但是发现输出的内容有残缺,如下:

我分析了一下原因可能如下:

从返回结果能看出 JSON 格式不完整(x 值数组截断、缺少 y 值、大括号未闭合),核心原因主要有 3 类:

  1. 模型输出截断 :GUI-Plus 默认输出长度有限,未配置max_tokens参数,导致长 JSON 被截断;
  2. 提示词约束不足:原提示词对「JSON 完整性」「参数必填性(如 CLICK 必须有 x/y 整数)」的约束不够明确,模型生成时遗漏字段;
  3. 提示词格式问题:原提示词中 JSON 模板的转义 / 排版混乱,模型理解规则时出错,生成不完整 JSON。

根据以上原因我们开始来修改代码内容

二、代码修改

1. 紧急修复:配置模型输出长度,避免截断

在构建MultiModalConversationParam时,新增maxTokens参数(设置足够大的值,如 2048),确保 JSON 完整输出:

java 复制代码
// 非流式调用的参数构建处(streamCall同理)
MultiModalConversationParam param = MultiModalConversationParam.builder()
        .apiKey(apiKey)
        .model(modelName)
        .messages(Arrays.asList(systemMsg, userMessage))
        .maxTokens(2048) // 新增:设置最大输出长度,避免截断
        .build();
2. 核心优化:强化提示词,强制 JSON 完整 + 参数必填

修改buildSystemPrompt()方法,重点补充「JSON 完整性约束」「参数必填规则」:

java 复制代码
private String buildSystemPrompt() {
    return """
            ## 1. 核心角色 (Core Role)
            你是一个顶级的AI视觉操作代理。你的任务是分析电脑屏幕截图,理解用户的指令,然后将任务分解为单一、精确的GUI原子操作。
            ## 2. [CRITICAL] JSON Schema & 绝对规则(必须严格遵守)
            你的输出必须是一个**完整、合法、可直接解析**的JSON对象,任何情况下都不能截断、遗漏字段、缺少闭合符号。
            ### 强制规则
            - [R1] 纯JSON输出:回复只能是JSON对象,无任何前缀、后缀、注释、解释性文字。
            - [R2] 字段必填性:
              - 所有Action的parameters字段必须包含模板中**所有必填键**(如CLICK必须有x、y整数,缺一不可);
              - x/y必须是**单个整数**(禁止数组/空值),代表屏幕坐标(像素);
              - thought字段必须是一句话,描述思考过程,不能为空。
            - [R3] Action值规范:只能是 CLICK/TYPE/SCROLL/KEY_PRESS/FINISH/FAIL(大写、无空格)。
            - [R4] JSON格式校验:生成后必须自检------确保大括号闭合、逗号正确、字符串用双引号、数值无引号。
            ## 3. 工具集 (Available Actions)
            ### CLICK(必填x、y,可选description)
            - 功能: 单击屏幕。
            - 必须返回如下完整JSON结构(x/y为非空整数):
            {
              "thought": "一句话描述思考过程",
              "action": "CLICK",
              "parameters": {
                "x": 1205,
                "y": 800,
                "description": "描述点击的对象(可选)"
              }
            }
            ### TYPE(必填text、needs_enter)
            - 功能: 输入文本。
            - 必须返回如下完整JSON结构:
            {
              "thought": "一句话描述思考过程",
              "action": "TYPE",
              "parameters": {
                "text": "要输入的文本",
                "needs_enter": true/false
              }
            }
            ### SCROLL(必填direction、amount)
            - 功能: 滚动窗口。
            - 必须返回如下完整JSON结构:
            {
              "thought": "一句话描述思考过程",
              "action": "SCROLL",
              "parameters": {
                "direction": "up/down",
                "amount": "small/medium/large"
              }
            }
            ### KEY_PRESS(必填key)
            - 功能: 按下功能键。
            - 必须返回如下完整JSON结构:
            {
              "thought": "一句话描述思考过程",
              "action": "KEY_PRESS",
              "parameters": {
                "key": "enter/esc/alt+f4等"
              }
            }
            ### FINISH(必填message)
            - 功能: 任务成功完成。
            - 必须返回如下完整JSON结构:
            {
              "thought": "一句话描述思考过程",
              "action": "FINISH",
              "parameters": {
                "message": "总结任务完成情况"
              }
            }
            ### FAIL(必填reason)
            - 功能: 任务无法完成。
            - 必须返回如下完整JSON结构:
            {
              "thought": "一句话描述思考过程",
              "action": "FAIL",
              "parameters": {
                "reason": "清晰解释失败原因"
              }
            }
            ## 4. 思维与决策框架
            1. 目标分析: 用户的最终目标是什么?
            2. 屏幕观察: 仅基于截图中的视觉证据决策,看不见的元素不交互。
            3. 行动决策: 选择最合适的Action,确保parameters字段完整。
            4. 最终校验: 检查JSON是否完整闭合、字段是否必填、格式是否合法,再输出。
            """;
}
3. 兜底处理:代码层修复不完整 JSON(应急方案)

如果模型仍偶尔返回不完整 JSON,可在结果解析处增加「JSON 修复逻辑」,示例:

java 复制代码
// 非流式调用的结果解析处
String resText = content != null && !content.isEmpty() 
        ? content.get(0).get("text").toString() 
        : "{}";

// 新增:修复不完整JSON(应急兜底)
resText = fixIncompleteJson(resText);
log.info("GUI-Plus非流式调用完成,修复后结果:{}", resText);
return resText;

// 新增JSON修复工具方法
private String fixIncompleteJson(String json) {
    try {
        // 1. 移除多余换行/空格
        json = json.trim().replaceAll("\\n", "").replaceAll("\\r", "");
        // 2. 检查大括号闭合
        int openBrace = countOccurrences(json, "{");
        int closeBrace = countOccurrences(json, "}");
        if (openBrace > closeBrace) {
            json += "}".repeat(openBrace - closeBrace);
        }
        // 3. 检查CLICK的x/y是否为数组,转为单个整数(针对你遇到的x是数组的问题)
        if (json.contains("\"action\":\"CLICK\"") && json.contains("\"x\":[")) {
            // 提取数组第一个值,替换数组为单个整数
            Pattern pattern = Pattern.compile("\"x\":\\[(\\d+),?.*?]");
            Matcher matcher = pattern.matcher(json);
            if (matcher.find()) {
                json = matcher.replaceFirst("\"x\":" + matcher.group(1));
            }
            // 补充缺失的y值(默认0,或提示模型重新生成)
            if (!json.contains("\"y\":")) {
                json = json.replace("\"parameters\": {", "\"parameters\": {\"y\":0,");
            }
        }
        // 4. 校验JSON合法性(不合法则返回空JSON)
        new com.alibaba.fastjson.JSONObject().parse(json);
        return json;
    } catch (Exception e) {
        log.error("修复JSON失败,返回空JSON", e);
        return "{}";
    }
}

// 辅助方法:统计字符出现次数
private int countOccurrences(String str, String target) {
    return (str.length() - str.replace(target, "").length()) / target.length();
}

三、额外优化建议

  1. 模型版本确认 :确保使用的gui-plus是最新版本(可在阿里云百炼控制台确认),旧版本可能存在输出不完整的 bug(目前来说版本并没有变动,可能后续会发生变动,其实目前这个模型对定位还是有所欠缺的);
  2. 测试提示词:先在百炼控制台「在线调试」功能中测试提示词,确认模型能生成完整 JSON 后,再接入代码;
  3. 参数校验强化:在代码中对返回的 JSON 做结构化校验(如用 FastJSON 解析为实体类),不合法则重试调用:
java 复制代码
// 定义GUI操作实体类
@Data
public class GuiOperation {
    private String thought;
    private String action;
    private Map<String, Object> parameters;
}

// 解析时校验
try {
    GuiOperation operation = new com.alibaba.fastjson.JSONObject().parseObject(resText, GuiOperation.class);
    // 校验CLICK必须有x/y
    if ("CLICK".equals(operation.getAction())) {
        if (operation.getParameters().get("x") == null || operation.getParameters().get("y") == null) {
            throw new IllegalArgumentException("CLICK操作缺少x/y参数");
        }
    }
} catch (Exception e) {
    log.error("JSON解析/校验失败,重试调用", e);
    // 可选:重试调用模型(避免单次截断)
    return conv.call(param).getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text").toString();
}

四、问题复现验证

问题解决,然而,我发现我的豆包的位置其实并不是模型返回的结果,我的豆包app的位置为:

所以我怀疑是不是因为需要其他参数的原因(比如缩放百分比,屏幕分辨率等)

由于篇幅的关系,我在下一篇文章改进看看。

如果觉得这份修改实用、总结清晰,别忘了动动小手点个赞👍,再关注一下呀~ 后续还会分享更多 AI 接口封装、代码优化的干货技巧,一起解锁更多好用的功能,少踩坑多提效!🥰 你的支持就是我更新的最大动力,咱们下次分享再见呀~🌟

相关推荐
Carl_奕然2 小时前
人工智能的幻觉问题:机理、挑战与缓解策略
人工智能·语言模型
LitchiCheng2 小时前
Mujoco 使用 Pinocchio 进行逆动力学及阻抗力矩控制维持当前位置
人工智能·python
ZCXZ12385296a2 小时前
面包类型识别与手部检测系统改进_yolov8-ADown实战
人工智能·yolo·目标跟踪
浔川python社2 小时前
《C++ 小程序编写系列》(第四部):实战:简易图书管理系统(类与对象篇)
java·开发语言·apache
undsky_2 小时前
n8n 重构镜像,开启无限可能
人工智能·ai·aigc·ai编程
北邮刘老师2 小时前
智能体,超越人类与机器的世界“理解者”
网络·人工智能·大模型·智能体·智能体互联网
paopao_wu2 小时前
声音克隆与情感合成:Dify接入IndexTTS2
人工智能·ai·tts
会挠头但不秃2 小时前
深度学习常用工具和库介绍
人工智能·深度学习
Coder_Boy_2 小时前
【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制
人工智能·小程序