$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,默认-1splitMode{number} 模型分割模式:0=按层,1=按行,默认0mainGpu{number} 主 GPU 索引,默认0tensorSplit{number[]} 各 GPU 的权重比例loadMode{number} 加载模式:-1=自动,0=无,1=内存映射,2=锁定内存,3=映射+锁定,4=直接 IO,默认1tensorReadLazy{number} 张量延迟读取模式:-1=自动,0=禁用,1=启用,默认1vocabOnly{boolean} 仅加载词汇表,默认falsecheckTensors{boolean} 检查张量完整性,默认falseuseExtraBufts{boolean} 使用额外缓冲区,默认truenoHost{boolean} 不使用主机内存缓冲,默认false
callback{Object} 加载回调- 返回 {
Ai} 成功返回Ai实例,失败返回null。
加载模型文件,并返回 Ai 实例。
// 加载模型
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()
- 返回 {
GgufMetadata} 模型元数据对象
获取当前加载模型的 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)
获取当前采样配置。
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,默认512temperature{number} 温度,范围 0.0~2.0,默认0.8topK{number} Top-K,范围 1~100,默认40topP{number} Top-P,范围 0.1~1.0,默认0.9minP{number} Min-P,范围 0.0~1.0,默认0.05xtcThreshold{number} XTC 阈值,范围 0.0~1.0,默认0.10xtcProb{number} XTC 概率,范围 0.0~1.0,默认0.0typicalP{number} Typical-P,范围 0.0~1.0,默认1.0penaltyLastN{number} 惩罚作用的 Token 数量,默认64penaltyRepeat{number} 重复惩罚系数,范围 0.0~2.0,默认1.0penaltyFreq{number} 频率惩罚系数,范围 0.0~2.0,默认0.0penaltyPresent{number} 存在惩罚系数,范围 0.0~2.0,默认0.0mirostat{number} Mirostat 模式:0=关闭,1=v1,2=v2,默认0mirostatTau{number} Mirostat Tau,默认5.0mirostatEta{number} Mirostat Eta,默认0.1seed{number} 随机种子,-1表示随机,默认-1dryMultiplier{number} DRY 惩罚倍数,0.0表示禁用,默认0.0dryBase{number} DRY 底数,默认1.75dryAllowedLength{number} DRY 允许长度,默认2dryPenaltyLastN{number} DRY 惩罚作用范围,-1表示整个上下文,默认-1dynatempRange{number} 动态温度范围,0.0表示禁用,默认0.0dynatempExponent{number} 动态温度指数,默认1.0adaptiveTarget{number} 自适应采样目标,-1.0表示禁用,默认-1.0adaptiveDecay{number} 自适应衰减率,范围 0.0~0.99,默认0.9topNSigma{number} Top-n-sigma,-1.0表示禁用,默认-1.0ignoreEos{boolean} 是否忽略 EOS 标记,默认false
设置采样参数(仅传入需要修改的字段,未传入的保持原值)。
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(默认) |
minP | Min-P 采样阈值(相对于最高概率),过滤低质量 token | 0.05(默认) |
dryMultiplier | DRY 惩罚强度,检测到模型“原地打转”时介入,0.0 表示禁用 | 0.5 ~ 1.0 |
dryBase | DRY 指数惩罚底数 | 1.75(默认) |
dryAllowedLength | 允许重复的最大长度,短于此长度不惩罚 | 2(默认) |
dryPenaltyLastN | DRY 扫描范围,-1 表示整个上下文 | -1(默认) |
temperature | 适当降低温度可使输出更稳定 | 0.6 ~ 0.7 |
完整示例:
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() 获取内置模板,根据需要修改后重新设置。
// 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),会清空之前的对话历史。
var ok = ai.setSystemPrompt("你是一个中文助手,请用简洁的语言回答问题。");
console.log(ok ? "✅ 设置成功" : "❌ 设置失败");Ai.chat(prompt[, callback[, options]])
prompt{string} 用户消息内容callback{Object} 流式回调(可选),仅用于流式输出 token,不改变chat()的同步阻塞特性options{Object} 本次请求的临时配置(可选)maxTokens{number} 本次生成的最大 token 数(覆盖全局配置)
- 返回 {
AIResult|null}:- 正常完成:返回
AIResult对象 - 发生错误:返回
null(错误详情通过callback.onError获取) - 被取消:返回
AIResult对象(可通过callback.onCancelled获取),此时result.text包含已生成的部分文本
- 正常完成:返回
纯文本聊天,发送用户消息并触发 AI 回复。此方法为同步阻塞调用,生成完成后方才返回。callback 仅用于流式输出 token,并不改变调用的阻塞特性。
// 官方用法:通过回调获取流式结果
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()的同步阻塞特性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 格式,具体支持的音频格式取决于底层多模态模型及解码器。
ai.chat(
"请描述这张图片的内容。",
["/sdcard/photo.jpg"],
{
onToken: function (token) {
console.log(token);
},
onComplete: function (result) {
console.log("\n描述:", result.text);
}
},
{ maxTokens: 128 }
);ai.chat(
"比较这两张图片的异同。",
["/sdcard/img1.png", "/sdcard/img2.png"],
{
onToken: function (token) {
console.log(token);
},
onComplete: function (result) {
console.log("\n比较结果:", result.text);
}
},
{ maxTokens: 128 }
);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(); // 使用后回收 BitmapAi.chatStop()
取消当前正在进行的生成。
ai.chatStop();Ai.chatUndo()
- 返回 {boolean} 撤销成功返回
true
撤销上一轮对话(删除最后一组用户+AI 消息),回滚到用户提问前的状态。
注意事项
chatUndo 需要底层推理引擎支持 KV 缓存回滚功能。若当前模型或推理后端不支持该功能,方法将返回 false,请查看日志确认具体原因。
// 撤销前
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,请查看日志确认具体原因。
// 丢弃前
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 缓存)。
ai.chatReset();
console.log("✅ 对话已重置");Ai.chatReplay(role, content)
role{string} 消息角色:"system","user","assistant"content{string} 消息内容
向对话历史中插入一条消息(用于恢复之前的对话状态)。
当 role 为 "system" 时,会向当前对话历史中添加一条系统消息。这与 Ai.setChatTemplate([template])(设置消息格式模板)是完全不同的功能,请勿混淆。
// 手动构建对话历史
ai.chatReplay("user", "我的名字叫 Bruce");
ai.chatReplay("assistant", "你好 Bruce,很高兴认识你。");
// 之后发送的消息会基于此历史继续
ai.chat("你还记得我的名字吗?", {
onComplete: function(result) { console.log(result.text); }
});注意事项
chatReplay会将消息插入到当前对话上下文中,后续生成时模型可通过注意力机制访问这些历史信息。是否能有效利用取决于模型本身的能力、上下文窗口大小以及聊天模板是否正确。不同模型对上下文信息的利用能力存在差异,若测试效果不符合预期,请检查:
- 历史消息是否确实进入了 KV Cache(可通过
Ai.getChatHistory()确认) - 上下文窗口是否足够容纳所有历史消息
- 聊天模板是否与模型匹配
- 历史消息是否确实进入了 KV Cache(可通过
Ai.getChatHistory()
- 返回 {Array<Object>} 对话历史数组,每个元素包含
role和content字段
获取当前对话历史(包括系统提示词,若有)。
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 位置。
console.log("当前位置:", ai.getTokenPosition());Ai.loadMultimodal(options)
options{Object} 配置path{string}mmproj投影仪文件路径device{number} 推理设备:0=CPU,1=GPU,默认0(建议使用 GPU 以提升推理速度)nThreads{number} 推理线程数,默认自动printTimings{boolean} 是否打印耗时,默认falsewarmup{boolean} 是否预热,默认falseimageMinTokens{number} 图像最小 token 数,默认-1imageMaxTokens{number} 图像最大 token 数,默认-1flashAttnType{number} Flash Attention 类型:0=禁用,1=启用,2=自动,默认2batchMaxTokens{number} 批处理最大 token 数,默认1024
- 返回 {boolean} 加载成功返回
true
加载多模态投影仪,使模型支持图像/音频/视频输入。
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
var path = ai.getMultimodalPath();
console.log("当前 mmproj:", path || "(未加载)");Ai.getMultimodalMetadata()
- 返回
GgufMetadata多模态投影仪的 GGUF 元数据 IOException:未加载多模态投影仪或文件不存在时抛出
获取当前加载的多模态投影仪(mmproj)文件的 GGUF 元数据,包括模型名称、架构、量化类型、上下文长度等信息。
此方法需在成功调用 Ai.loadMultimodal(options) 加载多模态投影仪后使用。
Ai.unloadMultimodal()
释放多模态投影仪资源。
ai.unloadMultimodal();
console.log("✅ 多模态已释放");Ai.benchmark([options])
options{Object} 可选promptTokens{number} 提示词 token 数,默认512generatedTokens{number} 生成 token 数,默认128
- 返回 {Object} 包含以下字段:
promptSpeed{number} 提示处理速度(tokens/秒)generationSpeed{number} 生成速度(tokens/秒)
执行简单基准测试,返回提示处理速度和生成速度。同步阻塞调用,执行完整测试后返回结果。
var result = ai.benchmark({ promptTokens: 512, generatedTokens: 128 });
console.log("Prompt 速度:", result.promptSpeed, "t/s");
console.log("生成速度:", result.generationSpeed, "t/s");Ai.isGenerating()
- 返回 {boolean} 是否正在生成
if (ai.isGenerating()) {
console.log("⏳ 生成中...");
} else {
console.log("✅ 空闲");
}Ai.release()
释放当前实例占用的所有资源(包括 Native 句柄、多模态投影仪、采样器等),释放后实例不可再用。若当前正在生成回复,会等待生成完成后再执行释放。
// 主动释放
ai.release();
// 建议在脚本退出时自动释放
events.on("exit", function () {
if (ai) {
ai.release();
ai = null;
}
});AIResult
由 Ai.chat(prompt[, callback[, options]]) 回调返回的结果对象。
AIResult.text
- {string} 生成的完整文本
AIResult.metrics
- {
AIMetrics} 性能指标
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() 返回。
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);
}完整示例
纯文本对话
// 模型下载地址:
// 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();多模态(图片理解)
// 模型下载地址:
// 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();// 模型下载地址:
// 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();多模态(音频理解)
// 模型下载地址:
// 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();