- 人工智能
- 大模型
- 本地部署
- AI 应用
- 移动开发
- AI Agent
- AI 技能
- MCP Clients
【免费下载链接】gallery
A gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally.
query-wikipedia 是 Android Gallery 项目内置的一个端侧 Agent Skill,它通过run_js工具驱动一段 JavaScript 脚本,从指定语言的 Wikipedia 拉取词条的简介(summary)与信息框(infobox),帮助端侧大模型在不联网搜索的情况下快速获取"某人 / 某事件 / 某作品"的权威概述。本文以 SKILL.md 为主线,完整讲解其参数规范、调用步骤、回答约束,并结合scripts/index.html与 Android 端RunJsTool的源码,剖析一条 Agent 调用从指令解析、WebView 执行到结果回传的完整链路。
一、Skill 是什么:一段可被 Agent 调用的"查询工具说明书"
在 Gallery 的架构中,一个 Skill 由两部分组成:
- SKILL.md:面向 LLM 的指令文件,描述何时调用、传什么参数、如何回答,本质是"工具说明书";
- scripts/index.html:真正执行逻辑的 WebView 页面,通过全局入口
window["ai_edge_gallery_get_result"]接收 JSON 参数并返回结果。
query-wikipedia 位于 skills/built-in/query-wikipedia/,同时在 Android 应用的 assets 中也有一份镜像副本 Android/src/app/src/main/assets/skills/query-wikipedia/。其 frontmatter 定义如下:
--- name: query-wikipedia description: Query summary from Wikipedia for a given topic. ---从源码结构看,Skill对象的name与description会被 SkillExtensions.kt 中的模板SKILL_INSTRUCTIONS_TEMPLATE拼装进注入给模型的多行指令:
const val SKILL_INSTRUCTIONS_TEMPLATE = "---\nname: %s\ndescription: %s\n---\n\n%s"也就是说,SKILL.md 的 frontmatter 并不是摆设——load_skill工具在把 Skill 内容交给模型时,会完整带上name、description与instructions(即本文件正文部分),见 LoadSkillTool.kt。query-wikipedia 的存在意义在于:端侧模型对"事实性、时效性"问题的内部知识可能过时或缺失,而通过该 Skill 可以实时拿到维基百科的结构化摘要作为可信上下文。
二、调用方式:run_js工具 + data JSON 参数
SKILL.md 明确规定,Agent 应按如下方式调用:
Call the
run_jstool usingindex.htmland a JSON string fordatawith the following fields.
对应到 Android 端,run_js是 RunJsTool.kt 中声明的工具,其三个参数为:
| 参数 | 说明 | 本 Skill 的取值 |
|---|---|---|
skillName | 要调用的 Skill 名称 | "query-wikipedia" |
scriptName | 要执行的脚本名 | "index.html"(未提供时默认) |
data | 传给脚本的 JSON 字符串 | {"topic": "...", "lang": "..."} |
data中必须包含两个字段:topic(必填)与lang(必填)。
topic:只提取主体实体
Extract ONLY the primary entity, person, or event (e.g., "2026 Oscars", "Albert Einstein"). You MUST REMOVE all specific question details, action words, or conversational text (e.g., do NOT include words like "winner", "best picture", "who won", "history of"). Search for the broad subject so the tool can return the main article.
即把用户问题中的疑问词、动作词、限定细节全部剥离,只留核心词条。例如:
| 用户提问 | topic 取值 | 理由 |
|---|---|---|
| 谁赢得了 2026 年奥斯卡最佳影片? | "2026 Oscars" | 去掉 "winner / best picture / who won" |
| 爱因斯坦的出生年份是什么? | "Albert Einstein" | 去掉 "history of" 等描述性词 |
| 法国的首都是什么? | "France" | 只保留国家实体 |
设计意图在 index.html 中可以得到印证:脚本第一步用的是generator: "search"的模糊搜索,gsrsearch直接拿topic全文去匹配维基标题。搜索词越"干净",命中的就越是词条主页,返回的 extract 质量越高。
lang:与 topic 保持同语言的 2 字母代码
The 2-letter language code. This code MUST match the language of the keywords you provided in the
topicfield.
支持的常见代码:
en(English)、es(Spanish)、zh(Chinese)、fr(French)、de(German)ja(Japanese)、ko(Korean)、it(Italian)、pt(Portuguese)ru(Russian)、ar(Arabic)、hi(Hindi)
lang直接决定请求的维基域名:脚本用模板字符串拼接https://${lang}.wikipedia.org/w/api.php(见 index.html),所以zh会访问中文维基、de会访问德语维基。若 topic 用了德语关键词却传en,模糊搜索基本无法命中正确词条——这就是"lang 必须与 topic 语言一致"的原因。
三、回答约束:省上下文、保语言一致、兜底时效
SKILL.md 的 Constraints 部分定义了模型拿到结果后的回答规范,这是该 Skill 的输出侧协议,需要与输入侧参数一同遵守:
1. 摘要要短,且必须句子完整。
Provide a concise summary (1-3 complete sentences) to conserve context. Always ensure your response ends with a finished sentence.
端侧上下文窗口有限,维基正文很长,模型应把脚本返回的 extract 压缩成 1~3 个完整句子,不得在半句话处截断。
2. 回答语言与用户原问题一致。
your response MUST BE written in the SAME language as the user's original prompt.
注意:这里绑定的是"用户 prompt 的语言",而非lang字段。用户用中文提问,即使查询的lang是en,回答也要用中文。
3. 周期性事件 / 时效性事实,必须锁定具体届次。
For recurring events or time-sensitive facts, query the specific iteration (e.g., "2026 Oscars"). If the user omits the year, default to the current year.
例如"奥斯卡"是年度重复事件,topic 应为"2026 Oscars";若用户没说年份,则默认取当前年份。这与维基百科"词条按届次独立成页"的排版方式对应——只有带上届次才能命中正确的词条主页。
4. 找不到答案时,先说明,再给相关事实。
If the exact answer to the user's question is not found in the extract, briefly state this, then proactively offer a related piece of information thatwasfound in the text.
这是"诚实 + 有用"的兜底策略:不编造,明确告知 extract 中未命中,同时把文本中确实存在的邻近信息作为替代价值提供给用户。
四、底层实现:两段式维基 API 查询管线
scripts/index.html 的核心是fetchWikiFuzzyAndInfobox(topic, lang),整条管线分为四个阶段:
阶段 1:模糊搜索 + 取词条简介(extract)
const searchParams = new URLSearchParams({ action: "query", format: "json", generator: "search", gsrsearch: topic, gsrlimit: "1", prop: "extracts", explaintext: "1", exintro: "1", origin: "*", });关键参数含义:
generator: "search"+gsrsearch: topic:以 topic 做模糊搜索,gsrlimit: "1"只取第一个结果;prop: "extracts"+exintro: "1":只返回条目的引言段(intro),这是被判定为最干净的"摘要";explaintext: "1":返回纯文本而非 HTML,便于 LLM 直接阅读;origin: "*":允许跨域请求(WebView 页面发起 fetch 必需)。
如果searchData.query.pages为空,脚本直接返回错误对象:No Wikipedia articles found matching '<topic>' in language '<lang>'.
阶段 2:拉取第 0 节 HTML 解析信息框(infobox)
const parseParams = new URLSearchParams({ action: "parse", page: title, section: "0", prop: "text", format: "json", origin: "*", });action: "parse"+section: "0":只取页面顶部(含标题、摘要和右侧 infobox)的 HTML;- 用
DOMParser解析后,querySelector("table.infobox")找到信息框表格; - 遍历每一行,取
th(键)与td(值),并用正则replace(/\[\d+\]/g, "")清除[1]、[2]这类引用角标,多行值用" | "连接。
这一步是整个 Skill 的价值增量:除了段落摘要,还拿到出生日期、获奖年份、国籍等结构化键值对,这正是回答"谁获奖了"这类问题的关键证据。
阶段 3:合并 INFOBOX 与 SUMMARY
if (infoboxText) finalResult += `--- INFOBOX ---\n${infoboxText}\n\n`; if (extract) finalResult += `--- SUMMARY ---\n${extract}`;最终返回结构为{ title, result },result以--- INFOBOX ---与--- SUMMARY ---两个分区组织,方便 LLM 快速定位两类信息。若两者皆空则返回错误Found page '<title>' but no text or infobox was available.
阶段 4:按语言设置安全截断上限
switch (lang.toLowerCase()) { case "zh": maxChars = 1500; break; case "fr": maxChars = 4300; break; case "es": maxChars = 4500; break; case "en": default: maxChars = 5000; break; } if (finalResult.length > maxChars) { finalResult = finalResult.substring(0, maxChars) + "\n\n... [TRUNCATED TO SAVE CONTEXT]"; }中文单字信息密度高,所以截断阈值最低(1500 字符);英文默认 5000。超限部分追加... [TRUNCATED TO SAVE CONTEXT]标记,配合 SKILL.md 中"1~3 句摘要"的约束,共同把注入模型的 token 量控制在预算内。
统一入口:ai_edge_gallery_get_result
window["ai_edge_gallery_get_result"] = async (data) => { ... }WebView 端通过这个全局函数接收 JSON 字符串:缺topic返回No topic provided to search.,缺lang返回No language code (lang) provided.,任何异常兜底返回Failed to query Wikipedia: <message>。
五、端到端运行链路:从指令到结果的五次跳转
综合 Android 端源码,一次 query-wikipedia 调用在运行时经历如下链路:
- 加载 Skill 指令:Agent(或用户)触发
load_skill工具,SkillsProvider.loadSkill("query-wikipedia")取出 Skill,getSkillContent()按---\nname: ...\ndescription: ...\n---\n\n<instructions>模板格式化后注入对话,见 LoadSkillTool.kt 与 SkillExtensions.kt; - 解析脚本地址:
run_js内部调用skill.getJsSkillUrl("index.html")。built-in Skill 的importDirName非空,于是拼出$LOCAL_URL_BASE/query-wikipedia/scripts/index.html(逻辑见 SkillExtensions.kt); - 发送执行动作:
CallJsToolAction(url, data, secret)通过ToolExecutionContext.actionChannel发出(RunJsTool.kt),此时工具会在聊天面板中展示"Calling JS script..."的进度条目; - WebView 执行:页面加载
index.html后,以dataJSON 调用ai_edge_gallery_get_result,内部完成上文四阶段查询,返回{title, result}或{error}; - 结果回传解析:
run_js用 Moshi 把返回值解析为CallJsSkillResult,有error则标记status: "failed",否则以mapOf("result" to ..., "status" to "succeeded")交还给模型(RunJsTool.kt)。模型随后按 SKILL.md 的约束生成 1~3 句摘要。
需要补充的是:若 Skill 声明了requireSecret(例如调用需要 API Key 的外部服务),run_js会先弹窗向用户索要密钥并写入 DataStore,而query-wikipedia 走的是公开的 Wikipedia API,不需要任何密钥,因此该 Skill 无此环节(逻辑见 RunJsTool.kt)。
六、边界情况与可观测性设计
脚本对异常做了多层防护,值得在实际开发中复用:
- HTTP 非 2xx:
if (!searchRes.ok) throw new Error(...),进入外层 catch; - 无匹配词条:返回
{ error: "No Wikipedia articles found..." }; - infobox 解析失败:单独
try/catch包裹,仅console.warn后静默跳过,不影响 extract 返回——"部分可用"优于"整体失败"; - 结果为空:返回
{ error: "Found page ... but no text or infobox was available." }; - 参数缺失:在
ai_edge_gallery_get_result入口分别校验topic、lang。
同时run_js端也会对返回结果做"双重校验":若返回的 JSON 既没有result、webview也没有image,就把整个原始字符串当作结果返回(RunJsTool.kt),确保 WebView 端任何未预期的返回格式都不会导致 Agent 死循环或空响应。
七、给 Skill 作者与使用者的实践建议
综合 SKILL.md 与源码,可以提炼出几条可直接落地的经验:
- 参数语义即质量:
topic越"纯"、越贴近维基词条标题,模糊搜索命中率越高。凡是在指令中对 LLM 强调"剥离疑问词与动作词"的 Skill,其搜索类脚本都应采用generator: search+gsrlimit: 1的"取首个结果"策略; - 语言代码对齐:
lang决定域名与截断阈值,务必与 topic 语言一致;对多语言场景,可在脚本内用switch维护每语言的字符上限; - 结构化信息优先:先用
action: parse拉第 0 节 HTML、再正则清洗引用角标,比直接要整页正文更省 token 且更利于回答"事实型"问题; - 诚实的兜底协议:SKILL.md 中"未命中即说明 + 给邻近信息"的约束,配合脚本
{ error }返回协议,能有效避免端侧模型在事实问题上编造答案。
以上规范与实现路径同样适用于本仓库其他基于run_js的 built-in Skill(如calculate-hash、text-spinner等,参见 skills/built-in/),可作为理解 Gallery 端侧技能系统的通用参考。
- 人工智能
- 大模型
- 本地部署
- AI 应用
- 移动开发
- AI Agent
- AI 技能
- MCP Clients
【免费下载链接】gallery
A gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally.
相关推荐
AI Edge Gallery 端侧技能解析:基于 run_js 的 query-wikipedia 维基百科查询技能实现原理与实战指南
AI Edge Gallery 端侧技能解析:基于 run_js 的 query wikipedia 维基百科查询技能实现原理与实战指南 本篇技术指南以 AI
人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP ClientsHive Aden Tools 的 Wikipedia 搜索工具实战指南:免 API Key 接入维基百科检索与摘要
Hive Aden Tools 的 Wikipedia 搜索工具实战指南:免 API Key 接入维基百科检索与摘要 导读 本文面向在 Hive 生产级多 Ag
人工智能AI Agent多智能体MCP 服务工具调用浏览器控制compromise-wikipedia 插件实战:基于 efrt 压缩词库的维基百科实体识别
compromise wikipedia 插件实战:基于 efrt 压缩词库的维基百科实体识别 导读 本文以 compromise 生态中的实验性插件 comp
NLP人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考