mobile-use 使用与接入第三方 LLM 网关完整指南OC
1. 拉取代码
bash
git clone https://github.com/minitap-ai/mobile-use
cd mobile-use
2. 初始化 .env
项目根目录里有 .env.example,复制为 .env:
bash
cp .env.example .env
3. 配置 .env(接入第三方网关)
mobile-use 通过环境变量支持多家厂商(实现见 minitap/mobile_use/services/llm.py),
包括 MINIMAX_API_KEY、OPEN_ROUTER_API_KEY、ANTHROPIC_API_KEY 等。
如果厂商不在支持列表里------或者像本教程这样想接自建 / 第三方 OpenAI 兼容网关 ------
可以走 OpenAI 协议开放的 OPENAI_BASE_URL 自定义端点这条路:
dotenv
# .env
OPENAI_API_KEY='sk-bzi5jUbedDQKqtKJm******'
OPENAI_BASE_URL="https://token.*****.com/v1"
复制粘贴时注意行尾不要带多余空格。
4. 兼容层补丁(关键步骤)
本教程的第三方网关只实现了 OpenAI 协议的"一半"------忽略 response_format 与
tool_choice,需要打一个本地补丁。
4.1 新增导入
打开 minitap/mobile_use/services/llm.py,在文件顶部加:
python
import json
from langchain_core.messages import HumanMessage
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.runnables import RunnableLambda
from pydantic import BaseModel
4.2 新增兼容类与辅助函数
在 get_openai_llm 定义前插入:
python
def _json_format_instructions(schema: Any) -> str | None:
"""为给定 schema 构造一条显式的"请按 JSON 输出"指令。"""
if schema is None:
return None
if isinstance(schema, dict):
return (
"Respond with a single valid JSON object matching this JSON Schema. "
"Output the raw JSON only: no markdown fences, no explanations.\n"
f"{json.dumps(schema, ensure_ascii=False)}"
)
if isinstance(schema, type) and issubclass(schema, BaseModel):
return PydanticOutputParser(pydantic_object=schema).get_format_instructions()
return None
def _append_instruction(payload: Any, instruction: str) -> Any:
"""把指令消息追加到当前输入,保持原输入形态。"""
if isinstance(payload, list):
return [*payload, HumanMessage(content=instruction)]
if isinstance(payload, str):
return [HumanMessage(content=payload), HumanMessage(content=instruction)]
return payload
class CompatChatOpenAI(ChatOpenAI):
"""针对只支持部分 OpenAI 协议的网关定制的 ChatOpenAI。
langchain-openai 的 ``with_structured_output`` 默认走 ``json_schema``(依赖
``response_format``)。很多 OpenAI 兼容网关(包 MiniMax / Anthropic / Google
模型的代理)会静默忽略 ``response_format`` *和* ``tool_choice``:调用成功、
模型用散文或 JSON 块回复、不发 tool call,结果 parsed 为 ``None``,下游节点
崩溃。
本类先走 tool calling 路径(网关真支持时一次即中),拿不到结果时再追加一条
显式 JSON 格式指令重试 ``json_mode``。在原生 OpenAI 上首次必中,行为与
未替换时完全一致。
"""
def with_structured_output(
self,
schema=None,
*,
method: str = "function_calling",
**kwargs,
):
kwargs.pop("include_raw", None)
primary = super().with_structured_output(schema, method=method, include_raw=True, **kwargs)
instruction = _json_format_instructions(schema)
try:
secondary = super().with_structured_output(schema, method="json_mode", **kwargs)
except ValueError:
secondary = None
def _unwrap(out: Any) -> Any:
if isinstance(out, dict) and "raw" in out:
return out.get("parsed")
return out
async def _acall(input_, config=None):
parsed = _unwrap(await primary.ainvoke(input_, config))
if parsed is not None or secondary is None or instruction is None:
return parsed
llm_logger.warning(
"Structured output returned nothing via tool calling; retrying with an "
"explicit JSON instruction."
)
return await secondary.ainvoke(_append_instruction(input_, instruction), config)
return RunnableLambda(_acall)
4.3 替换 get_openai_llm 里的客户端类
python
def get_openai_llm(
model_name: str = "o3",
temperature: float = 1,
) -> ChatOpenAI:
assert settings.OPENAI_API_KEY is not None
client = CompatChatOpenAI( # ← 原先是 ChatOpenAI
model=model_name,
api_key=settings.OPENAI_API_KEY,
base_url=settings.OPENAI_BASE_URL,
temperature=temperature,
)
return client
⚠️ 这是对源码的本地补丁 :
git pull、重建 Docker 镜像、切分支都会覆盖,届时需要重新打。建议正式提个 PR 合入上游。
5. 配置 llm-config.override.jsonc
复制模板:
bash
cp llm-config.override.template.jsonc llm-config.override.jsonc
本教程的第三方网关只暴露 MiniMax 系列模型 (MiniMax-M3 / MiniMax-M2.7 /
MiniMax-M2.7-highspeed),全部走 provider: "openai",按节点能力分配主备:
能力型节点(planner / orchestrator / cortex / hopper)走 MiniMax-M3,
高频型节点(executor / contextor / outputter)走更便宜的
MiniMax-M2.7-highspeed。
jsonc
// llm-config.override.jsonc
//
// Custom OpenAI-compatible gateway: https://token.rainbowcn.com/v1
// Provider "openai" is used for every node; the base URL / API key come from
// OPENAI_BASE_URL / OPENAI_API_KEY in .env.
//
// Models exposed by this gateway (MiniMax series only):
// - MiniMax-M3 (most capable, preferred everywhere)
// - MiniMax-M2.7 (200k context, reasoning model)
// - MiniMax-M2.7-highspeed (fast / cheap variant)
{
"planner": {
"provider": "openai",
"model": "MiniMax-M3",
"fallback": {
"provider": "openai",
"model": "MiniMax-M2.7-highspeed"
}
},
"orchestrator": {
"provider": "openai",
"model": "MiniMax-M3",
"fallback": {
"provider": "openai",
"model": "MiniMax-M2.7-highspeed"
}
},
"cortex": {
"provider": "openai",
"model": "MiniMax-M3",
"fallback": {
"provider": "openai",
"model": "MiniMax-M2.7"
}
},
"executor": {
"provider": "openai",
"model": "MiniMax-M2.7-highspeed",
"fallback": {
"provider": "openai",
"model": "MiniMax-M3"
}
},
"contextor": {
"provider": "openai",
"model": "MiniMax-M2.7-highspeed",
"fallback": {
"provider": "openai",
"model": "MiniMax-M2.7"
}
},
"utils": {
"hopper": {
// Needs at least a 256k context window.
"provider": "openai",
"model": "MiniMax-M3",
"fallback": {
"provider": "openai",
"model": "MiniMax-M2.7"
}
},
"outputter": {
"provider": "openai",
"model": "MiniMax-M2.7-highspeed",
"fallback": {
"provider": "openai",
"model": "MiniMax-M3"
}
}
// video_analyzer is optional - only needed when using video recording tools.
// The gateway exposes no video-capable model, so it is left unconfigured.
}
}
fallback不能留空字符串 ,否则会被判为非法配置。
video_analyzer网关无视频模型,不配(项目对它为 optional)。
6. 安装 uv
uv 是用 Rust 写的 Python 包与项目管理工具,比 pip 快一个量级。
macOS / Linux
bash
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows(PowerShell)
powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
也可以用 pip install uv / winget install astral-sh.uv / scoop install uv。
详见 uv 官方安装文档。
7. 创建并激活虚拟环境
bash
# 读取 .python-version 创建 .venv
uv venv
# 激活环境
# macOS / Linux
source .venv/bin/activate
# Windows
.venv\Scripts\activate
8. 安装依赖
bash
uv sync
这一步会根据 uv.lock 同时安装项目依赖与开发依赖。
9. 跑起来
bash
python ./minitap/mobile_use/main.py "Go to settings and tell me my current battery level"
python ./minitap/mobile_use/main.py \
"Open Gmail, find all unread emails, and list their sender and subject line" \
--output-description "A JSON list of objects, each with 'sender' and 'subject' keys"

首次运行会要求连接 Android / iOS 设备(USB 调试开启 / 模拟器在跑),
具体可参考根目录的 mobile-use.sh / mobile-use.ps1。