跳转到内容

$ai - AI 推理引擎

更新: 2026/9/2 字数: 0 字 时长: 0 分钟

$ai 提供基于 llama.cpp 的本地大语言模型推理能力,支持文本生成、多模态(图像/音频/视频)理解、对话历史管理、采样参数调节等功能。

$ai.load(options[, callback])

  • options {Object} 模型加载配置
    • path {string} 模型文件路径(.gguf 格式)
      • 单文件:直接传入文件绝对路径。
      • 分片模型:若模型被分割为多个文件(如 model-00001-of-00002.gguf, model-00002-of-00002.gguf),只需传入第一个分片(即序号最小的文件,如 …-00001-of-….gguf)的绝对路径。加载器会自动扫描同一目录下的其余分片并按序加载。
      • 合并参考:若需将分片合并为单文件,请参考 llama.cpp 分片合并指南
    • nGpuLayers {number} GPU 层数,-1 表示全部使用 GPU,默认 -1
    • splitMode {number} 模型分割模式:0=按层,1=按行,默认 0
    • mainGpu {number} 主 GPU 索引,默认 0
    • tensorSplit {number[]} 各 GPU 的权重比例
    • loadMode {number} 加载模式:-1=自动,0=无,1=内存映射,2=锁定内存,3=映射+锁定,4=直接 IO,默认 1
    • tensorReadLazy {number} 张量延迟读取模式:-1=自动,0=禁用,1=启用,默认 1
    • vocabOnly {boolean} 仅加载词汇表,默认 false
    • checkTensors {boolean} 检查张量完整性,默认 false
    • useExtraBufts {boolean} 使用额外缓冲区,默认 true
    • noHost {boolean} 不使用主机内存缓冲,默认 false
  • callback {Object} 加载回调
    • onload(instance, success) 加载完成时调用
      • instance {Ai} 返回的 AI 实例
      • success {boolean} 是否加载成功
  • 返回 {Ai} 成功返回 Ai 实例,失败返回 null

加载模型文件,并返回 Ai 实例。

js
// 加载模型
const ai = $ai.load(
    {
        path: "/sdcard/models/Qwen2.5-0.5B-Instruct_q8_0.gguf",
        nGpuLayers: -1,
        loadMode: 1
    },
    {
        onload: function(instance, success) {
            if (success) {
                console.log("✅ 模型加载成功");
            } else {
                console.error("❌ 模型加载失败");
            }
        }
    }
);

// 加载实例创建失败
if (!ai) {
    console.error("❌ 模型加载失败");
    exit();
}

// 使用完成后释放实例
ai.release();

Ai

$ai.load() 返回的 Ai 实例,封装了模型推理的所有操作。

Ai.getMetadata()

获取当前加载模型的 GGUF 元数据,包含模型名称、架构、量化类型、上下文长度、聊天模板等信息。

Ai.getConfig()

  • 返回 {Object} 当前生效的全部采样参数(包含 maxTokens, temperature, topK, topP, minP, xtcThreshold, xtcProb, typicalP, penaltyLastN, penaltyRepeat, penaltyFreq, penaltyPresent, mirostat, mirostatTau, mirostatEta, seed, dryMultiplier, dryBase, dryAllowedLength, dryPenaltyLastN, dynatempRange, dynatempExponent, adaptiveTarget, adaptiveDecay, topNSigma, ignoreEos

获取当前采样配置。

js
var config = ai.getConfig();
// 打印完整配置(JSON 格式)
console.log(JSON.stringify(config, null, 2));

// 也可单独访问
console.log("当前 maxTokens:", config.maxTokens);
console.log("temperature:", config.temperature);

Ai.setConfig(config)

  • config {Object} 采样参数对象(仅传入需要修改的字段,未传入的保持原值):
    • maxTokens {number} 最大生成 Token 数,范围 16~4096,默认 512
    • temperature {number} 温度,范围 0.0~2.0,默认 0.8
    • topK {number} Top-K,范围 1~100,默认 40
    • topP {number} Top-P,范围 0.1~1.0,默认 0.9
    • minP {number} Min-P,范围 0.0~1.0,默认 0.05
    • xtcThreshold {number} XTC 阈值,范围 0.0~1.0,默认 0.10
    • xtcProb {number} XTC 概率,范围 0.0~1.0,默认 0.0
    • typicalP {number} Typical-P,范围 0.0~1.0,默认 1.0
    • penaltyLastN {number} 惩罚作用的 Token 数量,默认 64
    • penaltyRepeat {number} 重复惩罚系数,范围 0.0~2.0,默认 1.0
    • penaltyFreq {number} 频率惩罚系数,范围 0.0~2.0,默认 0.0
    • penaltyPresent {number} 存在惩罚系数,范围 0.0~2.0,默认 0.0
    • mirostat {number} Mirostat 模式:0=关闭,1=v1,2=v2,默认 0
    • mirostatTau {number} Mirostat Tau,默认 5.0
    • mirostatEta {number} Mirostat Eta,默认 0.1
    • seed {number} 随机种子,-1 表示随机,默认 -1
    • dryMultiplier {number} DRY 惩罚倍数,0.0 表示禁用,默认 0.0
    • dryBase {number} DRY 底数,默认 1.75
    • dryAllowedLength {number} DRY 允许长度,默认 2
    • dryPenaltyLastN {number} DRY 惩罚作用范围,-1 表示整个上下文,默认 -1
    • dynatempRange {number} 动态温度范围,0.0 表示禁用,默认 0.0
    • dynatempExponent {number} 动态温度指数,默认 1.0
    • adaptiveTarget {number} 自适应采样目标,-1.0 表示禁用,默认 -1.0
    • adaptiveDecay {number} 自适应衰减率,范围 0.0~0.99,默认 0.9
    • topNSigma {number} Top-n-sigma,-1.0 表示禁用,默认 -1.0
    • ignoreEos {boolean} 是否忽略 EOS 标记,默认 false

设置采样参数(仅传入需要修改的字段,未传入的保持原值)。

js
ai.setConfig({
    maxTokens: 256,
    temperature: 0.8,
    topK: 50,
    topP: 0.95,
    penaltyRepeat: 1.1,
    seed: 42
});

防止内容重复

老旧小模型(如 0.5B~1.5B 参数)容易出现“车轱辘话”重复生成的问题。提供了以下关键参数来抑制重复:

参数说明推荐值
penaltyRepeat重复惩罚系数,1.0 表示禁用。惩罚最近出现过的 token,抑制重复1.1 ~ 1.3
penaltyLastN考虑惩罚的最近 token 数量64(默认)
minPMin-P 采样阈值(相对于最高概率),过滤低质量 token0.05(默认)
dryMultiplierDRY 惩罚强度,检测到模型“原地打转”时介入,0.0 表示禁用0.5 ~ 1.0
dryBaseDRY 指数惩罚底数1.75(默认)
dryAllowedLength允许重复的最大长度,短于此长度不惩罚2(默认)
dryPenaltyLastNDRY 扫描范围,-1 表示整个上下文-1(默认)
temperature适当降低温度可使输出更稳定0.6 ~ 0.7

完整示例:

js
ai.setConfig({
    maxTokens: 512,
    temperature: 0.6,              // 适当降低温度使输出更稳定
    topK: 40,
    topP: 0.95,
    minP: 0.05,                    // Min-P 过滤低质量 token

    // 重复惩罚
    penaltyRepeat: 1.1,            // 从 1.1 开始尝试,效果不佳可逐步提高至 1.2~1.3
    penaltyLastN: 64,

    // DRY 采样器(专门抑制“车轱辘话”)
    dryMultiplier: 0.8,            // 0.0 禁用,建议 0.5~1.0
    dryBase: 1.75,
    dryAllowedLength: 2,
    dryPenaltyLastN: -1
});

建议

优先尝试调整 penaltyRepeat,若效果不理想再启用 dryMultiplier。两者的组合通常能有效解决重复问题。

Ai.setChatTemplate([template])

  • template {string} Jinja2 格式的聊天模板字符串。不传参、传 null 或空字符串均恢复模型内置模板

设置自定义聊天模板,用于格式化多轮对话历史。模板通常定义在模型的 GGUF 元数据中,可通过 Ai.getMetadata() 获取内置模板,根据需要修改后重新设置。

js
// 1. 获取模型内置模板
var metadata = ai.getMetadata();
var builtinTemplate = metadata.getTokenizer().getChatTemplate();

// 2. 如需修改模板,请基于实际模板内容进行替换
// 注意:不同模型的模板格式差异较大,建议先打印查看
var customTemplate = builtinTemplate.replace("user", "用户");

// 3. 应用自定义模板
ai.setChatTemplate(customTemplate);

// 恢复模型内置模板
ai.setChatTemplate();        // 不传参

注意事项

  • 模板必须符合 Jinja2 语法,且模型需支持相应标记,错误的模板可能导致生成结果异常。
  • 不同模型的 Chat Template 格式差异较大,建议先通过 Ai.getMetadata().getTokenizer().getChatTemplate() 获取内置模板后再进行修改。

Ai.setSystemPrompt(prompt)

  • prompt {string} 系统提示词内容
  • 返回 {boolean} 设置成功返回 true

设置系统提示词(System Prompt),会清空之前的对话历史。

js
var ok = ai.setSystemPrompt("你是一个中文助手,请用简洁的语言回答问题。");
console.log(ok ? "✅ 设置成功" : "❌ 设置失败");

Ai.chat(prompt[, callback[, options]])

  • prompt {string} 用户消息内容
  • callback {Object} 流式回调(可选),仅用于流式输出 token,不改变 chat() 的同步阻塞特性
    • onPromptProcessed() Prompt 处理完成时调用
    • onToken(token) 每生成一个 token 时调用,token 为字符串片段
    • onComplete(result) 生成完成时调用,resultAIResult
    • onCancelled(result) 生成被取消时调用,resultAIResult
    • onError(error) 发生错误时调用
  • options {Object} 本次请求的临时配置(可选)
    • maxTokens {number} 本次生成的最大 token 数(覆盖全局配置)
  • 返回 {AIResult | null}:
    • 正常完成:返回 AIResult 对象
    • 发生错误:返回 null(错误详情通过 callback.onError 获取)
    • 被取消:返回 AIResult 对象(可通过 callback.onCancelled 获取),此时 result.text 包含已生成的部分文本

纯文本聊天,发送用户消息并触发 AI 回复。此方法为同步阻塞调用,生成完成后方才返回。callback 仅用于流式输出 token,并不改变调用的阻塞特性。

js

// 官方用法:通过回调获取流式结果

var result = ai.chat(
    "你好,请介绍一下自己。",
    {
        onPromptProcessed: function() {
            console.log("⏳ Prompt 处理完成,开始生成...");
        },
        onToken: function(token) {
            console.log(token);  // 逐字输出
        },
        onComplete: function(result) {
            console.log("\n✅ 生成完成");
            console.log("回复:", result.text);
            console.log("promptSpeed:", result.metrics.promptSpeed);
            console.log("generationSpeed:", result.metrics.generationSpeed);
        },
        onCancelled: function(result) {
            console.log("⚠️ 生成被取消,部分文本:", result.text);
        },
        onError: function(error) {
            console.error("❌ 生成错误:", error);
        }
    },
    {
        maxTokens: 64  // 本次覆盖全局设置
    }
);

// 如果不需要流式,可以省略 callback
console.log("chat() 返回 result:", result);

Ai.chat(prompt, media[, callback][, options])

  • prompt {string} 用户消息内容
  • media {Array} 媒体数据数组,元素可为以下类型之一:
    • string:文件路径(图片/音频/视频)
    • Bitmap:Android Bitmap 对象
    • byte[]:图片原始数据(JPEG/PNG 编码)
    • ImageWrapper:由 images.read() 返回的对象
  • callback {Object} 流式回调(可选),仅用于流式输出 token,不改变 chat() 的同步阻塞特性
    • onPromptProcessed() Prompt 处理完成时调用
    • onToken(token) 每生成一个 token 时调用,token 为字符串片段
    • onComplete(result) 生成完成时调用,resultAIResult
    • onCancelled(result) 生成被取消时调用,resultAIResult
    • onError(error) 发生错误时调用
  • options {Object} 本次请求的临时配置(可选)
    • maxTokens {number} 本次生成的最大 token 数(覆盖全局配置)
  • 返回 {AIResult | null}:
    • 正常完成:返回 AIResult 对象
    • 发生错误:返回 null(错误详情通过 callback.onError 获取)
    • 被取消:返回 AIResult 对象(可通过 callback.onCancelled 获取),此时 result.text 包含已生成的部分文本

多模态聊天,发送用户消息及媒体内容并触发 AI 回复。此方法为同步阻塞调用,生成完成后方才返回。callback 仅用于流式输出 token,并不改变调用的阻塞特性。

注意事项

  • 多模态功能需要先加载对应的 mmproj 投影仪文件(见 Ai.loadMultimodal(options))。
  • 图片建议尺寸控制在 640px 以内(不同模型对输入尺寸要求不同,此为移动端通用推荐值,有助于降低推理耗时和显存占用),可使用 images.resize(img, size[, interpolation]) 预先缩放。
  • 音频支持 MP3 格式,具体支持的音频格式取决于底层多模态模型及解码器。
js
ai.chat(
    "请描述这张图片的内容。",
    ["/sdcard/photo.jpg"],
    {
        onToken: function (token) {
            console.log(token);
        },
        onComplete: function (result) {
            console.log("\n描述:", result.text);
        }
    },
    { maxTokens: 128 }
);
js
ai.chat(
    "比较这两张图片的异同。",
    ["/sdcard/img1.png", "/sdcard/img2.png"],
    {
        onToken: function (token) {
            console.log(token);
        },
        onComplete: function (result) {
            console.log("\n比较结果:", result.text);
        }
    },
    { maxTokens: 128 }
);
js
var img = images.read("/sdcard/test.jpeg");
ai.chat(
    "图片里有什么?",
    [img],
    {
        onToken: function (token) {
            console.log(token);
        },
        onComplete: function (result) {
            console.log("\n描述:", result.text);
        }
    },
    { maxTokens: 128 }
);
img.recycle();  // 使用后回收 Bitmap

Ai.chatStop()

取消当前正在进行的生成。

js
ai.chatStop();

Ai.chatUndo()

  • 返回 {boolean} 撤销成功返回 true

撤销上一轮对话(删除最后一组用户+AI 消息),回滚到用户提问前的状态。

注意事项

chatUndo 需要底层推理引擎支持 KV 缓存回滚功能。若当前模型或推理后端不支持该功能,方法将返回 false,请查看日志确认具体原因。

js
// 撤销前
var history = ai.getChatHistory();
console.log("=== 撤销前 (" + history.length + " 条) ===");
for (var i = 0; i < history.length; i++) {
    var msg = history[i];
    console.log("[" + i + "] role=" + msg.role + ", content=" + msg.content);
}

var ok = ai.chatUndo();
console.log(ok ? "✅ 已撤销上一轮对话" : "❌ 撤销失败");

// 撤销后
history = ai.getChatHistory();
console.log("=== 撤销后 (" + history.length + " 条) ===");
for (var i = 0; i < history.length; i++) {
    var msg = history[i];
    console.log("[" + i + "] role=" + msg.role + ", content=" + msg.content);
}

Ai.chatDiscard()

  • 返回 {boolean} 丢弃成功返回 true

丢弃当前 AI 回复(回滚到用户提问完成后的状态),保留用户消息以便重新生成。

调用后可以再次调用 chat() 生成新的回复,适用于“对回答不满意,重新生成”的场景。

注意事项

chatDiscard 需要底层推理引擎支持 KV 缓存回滚功能。若当前模型或推理后端不支持该功能,方法将返回 false,请查看日志确认具体原因。

js
// 丢弃前
var history = ai.getChatHistory();
console.log("=== 丢弃前 (" + history.length + " 条) ===");
for (var i = 0; i < history.length; i++) {
    var msg = history[i];
    console.log("[" + i + "] role=" + msg.role + ", content=" + msg.content);
}

var ok = ai.chatDiscard();
console.log(ok ? "✅ 已丢弃当前回复" : "❌ 丢弃失败");

// 丢弃后
history = ai.getChatHistory();
console.log("=== 丢弃后 (" + history.length + " 条) ===");
for (var i = 0; i < history.length; i++) {
    var msg = history[i];
    console.log("[" + i + "] role=" + msg.role + ", content=" + msg.content);
}

Ai.chatReset()

重置整个对话(清空历史记录和 KV 缓存)。

js
ai.chatReset();
console.log("✅ 对话已重置");

Ai.chatReplay(role, content)

  • role {string} 消息角色:"system", "user", "assistant"
  • content {string} 消息内容

向对话历史中插入一条消息(用于恢复之前的对话状态)。

role"system" 时,会向当前对话历史中添加一条系统消息。这与 Ai.setChatTemplate([template])(设置消息格式模板)是完全不同的功能,请勿混淆。

js
// 手动构建对话历史
ai.chatReplay("user", "我的名字叫 Bruce");
ai.chatReplay("assistant", "你好 Bruce,很高兴认识你。");

// 之后发送的消息会基于此历史继续
ai.chat("你还记得我的名字吗?", {
    onComplete: function(result) { console.log(result.text); }
});

注意事项

  • chatReplay 会将消息插入到当前对话上下文中,后续生成时模型可通过注意力机制访问这些历史信息。是否能有效利用取决于模型本身的能力、上下文窗口大小以及聊天模板是否正确。

  • 不同模型对上下文信息的利用能力存在差异,若测试效果不符合预期,请检查:

    1. 历史消息是否确实进入了 KV Cache(可通过 Ai.getChatHistory() 确认)
    2. 上下文窗口是否足够容纳所有历史消息
    3. 聊天模板是否与模型匹配

Ai.getChatHistory()

  • 返回 {Array<Object>} 对话历史数组,每个元素包含 rolecontent 字段

获取当前对话历史(包括系统提示词,若有)。

js
var history = ai.getChatHistory();
for (var i = 0; i < history.length; i++) {
    console.log("[" + i + "] role=" + history[i].role + ", content=" + history[i].content);
}

Ai.getTokenPosition()

  • 返回 {number} 当前 KV Cache 中的 token 位置(即已处理的 token 数量)

获取当前 KV 缓存中的 token 位置。

js
console.log("当前位置:", ai.getTokenPosition());

Ai.loadMultimodal(options)

  • options {Object} 配置
    • path {string} mmproj 投影仪文件路径
    • device {number} 推理设备:0=CPU,1=GPU,默认 0(建议使用 GPU 以提升推理速度)
    • nThreads {number} 推理线程数,默认自动
    • printTimings {boolean} 是否打印耗时,默认 false
    • warmup {boolean} 是否预热,默认 false
    • imageMinTokens {number} 图像最小 token 数,默认 -1
    • imageMaxTokens {number} 图像最大 token 数,默认 -1
    • flashAttnType {number} Flash Attention 类型:0=禁用,1=启用,2=自动,默认 2
    • batchMaxTokens {number} 批处理最大 token 数,默认 1024
  • 返回 {boolean} 加载成功返回 true

加载多模态投影仪,使模型支持图像/音频/视频输入。

js
var ok = ai.loadMultimodal({
    path: "/sdcard/models/mmproj-SmolVLM2-500M-Video-Instruct-Q8_0.gguf",
    device: 1,
    nThreads: 4,
    warmup: true,
    flashAttnType: 2
});
console.log(ok ? "✅ 多模态加载成功" : "❌ 多模态加载失败");

Ai.getMultimodalPath()

  • 返回 {string | null} 当前加载的 mmproj 文件路径,未加载则返回 null
js
var path = ai.getMultimodalPath();
console.log("当前 mmproj:", path || "(未加载)");

Ai.getMultimodalMetadata()

  • 返回 GgufMetadata 多模态投影仪的 GGUF 元数据
  • IOException:未加载多模态投影仪或文件不存在时抛出

获取当前加载的多模态投影仪(mmproj)文件的 GGUF 元数据,包括模型名称、架构、量化类型、上下文长度等信息。

此方法需在成功调用 Ai.loadMultimodal(options) 加载多模态投影仪后使用。

Ai.unloadMultimodal()

释放多模态投影仪资源。

js
ai.unloadMultimodal();
console.log("✅ 多模态已释放");

Ai.benchmark([options])

  • options {Object} 可选
    • promptTokens {number} 提示词 token 数,默认 512
    • generatedTokens {number} 生成 token 数,默认 128
  • 返回 {Object} 包含以下字段:
    • promptSpeed {number} 提示处理速度(tokens/秒)
    • generationSpeed {number} 生成速度(tokens/秒)

执行简单基准测试,返回提示处理速度和生成速度。同步阻塞调用,执行完整测试后返回结果。

js
var result = ai.benchmark({ promptTokens: 512, generatedTokens: 128 });
console.log("Prompt 速度:", result.promptSpeed, "t/s");
console.log("生成速度:", result.generationSpeed, "t/s");

Ai.isGenerating()

  • 返回 {boolean} 是否正在生成
js
if (ai.isGenerating()) {
    console.log("⏳ 生成中...");
} else {
    console.log("✅ 空闲");
}

Ai.release()

释放当前实例占用的所有资源(包括 Native 句柄、多模态投影仪、采样器等),释放后实例不可再用。若当前正在生成回复,会等待生成完成后再执行释放。

js
// 主动释放
ai.release();

// 建议在脚本退出时自动释放
events.on("exit", function () {
    if (ai) {
        ai.release();
        ai = null;
    }
});

AIResult

Ai.chat(prompt[, callback[, options]]) 回调返回的结果对象。

AIResult.text

  • {string} 生成的完整文本

AIResult.metrics

AIResult.position

  • {number} 生成结束时的 token 游标位置

AIMetrics

性能指标对象。

AIMetrics.promptSpeed

  • {number} 提示处理速度(tokens/秒)

AIMetrics.generationSpeed

  • {number} 生成速度(tokens/秒)

AIMetrics.promptTokens

  • {number} 提示词 token 数

AIMetrics.generatedTokens

  • {number} 生成的 token 数

GgufMetadata

模型元数据对象,由 Ai.getMetadata()Ai.getMultimodalMetadata() 返回。

js
var meta = ai.getMetadata();

// 注意:部分字段可能为 null,访问前建议判空
// 注意:List 类型字段需要使用 Java 的 size() 和 get() 方法遍历

// 版本信息
console.log("GGUF 版本:", meta.version.label);

// 统计信息
console.log("Tensor 总数:", meta.tensorCount);
console.log("KV 总数:", meta.kvCount);

// 基础信息
console.log("名称:", meta.basic.name);
console.log("名称标签:", meta.basic.nameLabel);
console.log("大小标签:", meta.basic.sizeLabel);
console.log("UUID:", meta.basic.uuid);

// 架构与量化
console.log("架构:", meta.architecture.architecture);
console.log("量化类型:", meta.architecture.fileType);
console.log("词表大小:", meta.architecture.vocabSize);
console.log("微调信息:", meta.architecture.finetune);
console.log("量化版本:", meta.architecture.quantizationVersion);

// 维度
console.log("上下文长度:", meta.dimensions.contextLength);
console.log("嵌入维度:", meta.dimensions.embeddingSize);
console.log("Block 数量:", meta.dimensions.blockCount);
console.log("FFN 维度:", meta.dimensions.feedForwardSize);

// 分词器
if (meta.tokenizer) {
    console.log("分词器模型:", meta.tokenizer.model);
    console.log("BOS Token ID:", meta.tokenizer.bosTokenId);
    console.log("EOS Token ID:", meta.tokenizer.eosTokenId);
    console.log("未知 Token ID:", meta.tokenizer.unknownTokenId);
    console.log("填充 Token ID:", meta.tokenizer.paddingTokenId);
    console.log("是否添加 BOS:", meta.tokenizer.addBosToken);
    console.log("是否添加 EOS:", meta.tokenizer.addEosToken);
    console.log("聊天模板:", meta.tokenizer.chatTemplate);
}

// 作者与许可证
if (meta.author) {
    console.log("组织:", meta.author.organization);
    console.log("作者:", meta.author.author);
    console.log("DOI:", meta.author.doi);
    console.log("URL:", meta.author.url);
    console.log("仓库 URL:", meta.author.repoUrl);
    console.log("许可证:", meta.author.license);
    console.log("许可证链接:", meta.author.licenseLink);
}

// 附加信息(tags 和 languages 是 List<String>,需遍历)
if (meta.additional) {
    console.log("类型:", meta.additional.type);
    console.log("描述:", meta.additional.description);

    // 打印标签列表
    if (meta.additional.tags && meta.additional.tags.size() > 0) {
        var tagStr = "";
        for (var i = 0; i < meta.additional.tags.size(); i++) {
            if (i > 0) tagStr += ", ";
            tagStr += meta.additional.tags.get(i);
        }
        console.log("标签:", tagStr);
    }

    // 打印语言列表
    if (meta.additional.languages && meta.additional.languages.size() > 0) {
        var langStr = "";
        for (var i = 0; i < meta.additional.languages.size(); i++) {
            if (i > 0) langStr += ", ";
            langStr += meta.additional.languages.get(i);
        }
        console.log("语言:", langStr);
    }
}

// 基础模型列表(List<BaseModelInfo>)
if (meta.baseModels && meta.baseModels.size() > 0) {
    for (var i = 0; i < meta.baseModels.size(); i++) {
        var bm = meta.baseModels.get(i);
        console.log("基础模型[" + i + "]:", bm.name, "作者:", bm.author, "版本:", bm.version);
    }
}

// RoPE 参数
if (meta.rope) {
    console.log("频率基数:", meta.rope.frequencyBase);
    console.log("维度数量:", meta.rope.dimensionCount);
    console.log("缩放类型:", meta.rope.scalingType);
    console.log("缩放因子:", meta.rope.scalingFactor);
    console.log("注意力因子:", meta.rope.attnFactor);
    console.log("原始上下文长度:", meta.rope.originalContextLength);
    console.log("是否微调:", meta.rope.finetuned);
}

// 注意力配置
if (meta.attention) {
    console.log("头数:", meta.attention.headCount);
    console.log("KV 头数:", meta.attention.headCountKv);
    console.log("Key 长度:", meta.attention.keyLength);
    console.log("Value 长度:", meta.attention.valueLength);
    console.log("Layer Norm Epsilon:", meta.attention.layerNormEpsilon);
    console.log("RMS Norm Epsilon:", meta.attention.layerNormRmsEpsilon);
}

// MoE 专家
if (meta.experts) {
    console.log("专家总数:", meta.experts.count);
    console.log("使用专家数:", meta.experts.usedCount);
}

完整示例

纯文本对话

js
// 模型下载地址:
// https://hf-mirror.com/unsloth/Qwen3.5-2B-GGUF/blob/main/Qwen3.5-2B-Q6_K.gguf

var ai;

// 双保险:脚本退出时自动释放
events.on("exit", function () {
    if (ai) {
        ai.release();
        ai = null;
    }
});

// 同步加载模型
ai = $ai.load({
    path: "/sdcard/models/Qwen3.5-2B-Q6_K.gguf",
    nGpuLayers: -1,
    loadMode: 1
});

if (!ai) {
    console.error("模型加载失败");
    exit();
}

console.log("✅ 模型加载成功");

// 配置采样参数
ai.setConfig({
    maxTokens: 256,
    temperature: 0.7,
    topK: 40,
    topP: 0.9,
    penaltyRepeat: 1.1
});

// 设置系统提示
ai.setSystemPrompt("你是一个专业助手,回答简洁准确。");

// 发送消息
ai.chat(
    "请解释什么是大语言模型?",
    {
        onPromptProcessed: function() {
            console.log("⏳ Prompt 处理完成,开始生成...");
        },
        onToken: function(token) {
            console.log(token);  // 逐字输出
        },
        onComplete: function(result) {
            console.log("\n✅ 生成完成");
            console.log("回复:", result.text);
            console.log("promptSpeed:", result.metrics.promptSpeed);
            console.log("generationSpeed:", result.metrics.generationSpeed);
        },
        onCancelled: function(result) {
            console.log("⚠️ 生成被取消,部分文本:", result.text);
        },
        onError: function(error) {
            console.error("❌ 生成错误:", error);
        }
    },
    { maxTokens: 128 }
);
// 主动释放
ai.release();

多模态(图片理解)

js
// 模型下载地址:
// GGUF: https://hf-mirror.com/Qwen/Qwen3-VL-2B-Instruct-GGUF/blob/main/Qwen3VL-2B-Instruct-Q4_K_M.gguf
// mmproj: https://hf-mirror.com/Qwen/Qwen3-VL-2B-Instruct-GGUF/blob/main/mmproj-Qwen3VL-2B-Instruct-Q8_0.gguf
// 示例图片: https://docs.botjs.org/resources/ai/sample.jpg

var ai;

// 双保险:脚本退出时自动释放
events.on("exit", function () {
    if (ai) {
        ai.release();
        ai = null;
    }
});

// 同步加载模型
ai = $ai.load({
    path: "/sdcard/models/Qwen3VL-2B-Instruct-Q4_K_M.gguf",
    nGpuLayers: -1,
    loadMode: 1
});

if (!ai) {
    console.error("模型加载失败");
    exit();
}

console.log("✅ 模型加载成功");

// 加载多模态投影仪
var mmOk = ai.loadMultimodal({
    path: "/sdcard/models/mmproj-Qwen3VL-2B-Instruct-Q8_0.gguf",
    device: 1
});

if (!mmOk) {
    console.error("多模态投影仪加载失败");
    exit();
}

// 配置采样参数
ai.setConfig({
    maxTokens: 256,
    temperature: 0.7,
    topK: 40,
    topP: 0.9
});

// 图片路径数组(支持 jpg/png/webp)
// 
// 尺寸建议:
// - 推荐图片短边 ≥ 64px,长边 ≤ 640px
// - 过小(< 64px)可能影响识别效果,过大(> 640px)会增加推理耗时和显存占用
// - 为获得最佳效果,建议提前将图片缩放到长边不超过 640px
// - 示例中使用 640px 作为参考值,实际最佳尺寸取决于所用多模态模型,建议查阅模型文档确认。
//
// 缩放示例(等比例缩放,长边缩放到 640):
//   var w = img.getWidth(), h = img.getHeight();
//   var maxSize = 640;
//   var scale = Math.min(maxSize / w, maxSize / h);
//   var resized = images.resize(img, [Math.round(w * scale), Math.round(h * scale)]);
//   images.save(resized, "/sdcard/resized.jpg");
//   //然后将 resized 路径传入 media 数组


// 发送图片 + 文本
ai.chat(
    "请描述这张图片的内容。",
    ["/sdcard/sample.jpg"], 
    {
        onToken: function(token) {
            console.log(token);
        },
        onComplete: function(result) {
            console.log("\n✅ 图片描述:", result.text);
        },
        onError: function(error) {
            console.error("错误:", error);
        }
    },
    { maxTokens: 128 }
);

// 主动释放
ai.release();
js
// 模型下载地址:
// GGUF: https://hf-mirror.com/Qwen/Qwen3-VL-2B-Instruct-GGUF/blob/main/Qwen3VL-2B-Instruct-Q4_K_M.gguf
// mmproj: https://hf-mirror.com/Qwen/Qwen3-VL-2B-Instruct-GGUF/blob/main/mmproj-Qwen3VL-2B-Instruct-F16.gguf

var ai;

// 双保险:脚本退出时自动释放
events.on("exit", function () {
    if (ai) {
        ai.release();
        ai = null;
    }
});

// 请求截图权限
requestScreenCapture();

// 同步加载模型
ai = $ai.load({
    path: "/sdcard/models/Qwen3VL-2B-Instruct-Q4_K_M.gguf",
    nGpuLayers: -1,
    loadMode: 1
});

if (!ai) {
    console.error("模型加载失败");
    exit();
}

console.log("✅ 模型加载成功");

// 加载多模态投影仪
var mmOk = ai.loadMultimodal({
    path: "/sdcard/models/mmproj-Qwen3VL-2B-Instruct-F16.gguf",
    device: 1
});

if (!mmOk) {
    console.error("多模态投影仪加载失败");
    exit();
}

// 配置采样参数
ai.setConfig({
    maxTokens: 256,
    temperature: 0.7,
    topK: 40,
    topP: 0.9
});

// ---------- 截屏并压缩 ----------
var img = captureScreen();
if (!img) {
    console.error("截图失败");
    ai.release();
    exit();
}

var w = img.getWidth();
var h = img.getHeight();
var maxSize = 640;  // 进一步缩小到 640px,提升推理速度

var resized = img;
var needRecycle = false;

if (w > maxSize || h > maxSize) {
    var scale = Math.min(maxSize / w, maxSize / h);
    var newW = Math.round(w * scale);
    var newH = Math.round(h * scale);
    resized = images.resize(img, [newW, newH]);
    needRecycle = true;
    console.log("📐 图片已缩放: " + w + "x" + h + " → " + newW + "x" + newH);
} else {
    console.log("📐 图片尺寸 " + w + "x" + h + ",无需缩放");
}

// 压缩为 JPEG 格式(质量 75%,减少文件体积,提升传输/处理速度)
var compressedBytes = images.toBytes(resized, "jpg", 75);
var compressedImg = images.fromBytes(compressedBytes);
if (compressedImg) {
    // 用压缩后的图片替换原图
    if (needRecycle && resized) {
        resized.recycle();
    }
    resized = compressedImg;
    needRecycle = true;
    console.log("📦 图片已压缩为 JPEG(质量 75%)");
}

// 发送图片 + 文本
ai.chat(
    "请描述这张截图中显示的内容。",
    [resized],
    {
        onToken: function(token) {
            console.log(token);
        },
        onComplete: function(result) {
            console.log("\n✅ 截图描述:", result.text);

        },
        onError: function(error) {
            console.error("错误:", error);

        }
    },
    { maxTokens: 128 }
);

// 图片数据已在 Native 层拷贝,可立即回收
// captureScreen() 返回的 img 无需手动回收,仅回收 resize/compress 产生的新图片
if (needRecycle && resized) {
    resized.recycle();
}

// 主动释放
ai.release();

多模态(音频理解)

js
// 模型下载地址:
// GGUF: https://hf-mirror.com/ggml-org/Qwen2.5-Omni-3B-GGUF/blob/main/Qwen2.5-Omni-3B-Q4_K_M.gguf
// mmproj: https://hf-mirror.com/ggml-org/Qwen2.5-Omni-3B-GGUF/blob/main/mmproj-Qwen2.5-Omni-3B-Q8_0.gguf
// 示例音频: https://docs.botjs.org/resources/ai/sample.mp3

var ai;

// 双保险:脚本退出时自动释放
events.on("exit", function () {
    if (ai) {
        ai.release();
        ai = null;
    }
});

// 同步加载模型
ai = $ai.load({
    path: "/sdcard/models/Qwen2.5-Omni-3B-Q4_K_M.gguf",
    nGpuLayers: -1,
    loadMode: 1
});

if (!ai) {
    console.error("模型加载失败");
    exit();
}

console.log("✅ 模型加载成功");

// 加载多模态投影仪(音频模型也使用 mmproj)
var mmOk = ai.loadMultimodal({
    path: "/sdcard/models/mmproj-Qwen2.5-Omni-3B-Q8_0.gguf",
    device: 1
});

if (!mmOk) {
    console.error("多模态投影仪加载失败");
    exit();
}

// 配置采样参数
ai.setConfig({
    maxTokens: 256,
    temperature: 0.7,
    topK: 40,
    topP: 0.9
});

// 发送音频 + 文本
ai.chat(
    "请总结这段音频的内容。",
    ["/sdcard/sample.mp3"],
    {
        onToken: function(token) {
            console.log(token);
        },
        onComplete: function(result) {
            console.log("\n✅ 音频摘要:", result.text);
        },
        onError: function(error) {
            console.error("错误:", error);
        }
    },
    { maxTokens: 128 }
);
// 主动释放
ai.release();