
1. 从零跑通 React-Native AI为什么你的 Expo 项目一接大模型就崩如果你正在用 Expo 管理的 React-Native 项目做移动端 AI 应用大概率会遇到这几个场景聊天界面流式输出变成一坨乱码、真机跑起来内存一路飙红、想接本地大模型却发现 Expo Go 根本加载不了原生推理库。这不是你代码写得差而是移动端 AI 开发和 Web 端 AI 开发在底层约束上完全是两回事。React-Native 本身是跨平台移动端框架一套代码同时输出 Android 和 iOS而 Expo 是它上面的一层工作流封装把原生构建、依赖管理、热更新这些脏活都包掉了。把 AI 能力接进来之后你要面对的是三件事的叠加大模型 API 的网络调用、流式数据的渲染、以及端侧推理对原生模块的依赖。这三件事在 Web 上都有成熟方案搬到 RN 上就各有各的坑。这篇内容面向的是已经会写基础 React-Native、想在自己的 Expo 项目里落地第一个 AI 功能的开发者。我会用一条统一的 Key/API 通道把云端大模型和本地模型调用串起来交付可以直接复制的app.json、settings.json和config.toml骨架再给出真机联调和错误排查的验证动作。你跟着走完能跑通一个带流式对话和工具调用雏形的移动端 AI 功能。先说清楚两条技术路线后面所有配置都围绕它们展开。第一条是纯云端调用移动端只负责发请求、渲染结果Agent 循环和工具执行全部放在后端Expo Go 就能直接预览开发效率最高。第二条是端侧本地推理模型跑在手机上隐私优先、可离线但必须用npx expo run:android或run:ios做原生构建Expo Go 不支持带原生 C 推理库的模块。绝大多数商业项目最终走的是混合架构有网走云端无网降级到端侧小模型。我试过把 Web 端的 AI 逻辑直接复制到 RN 项目里结果流式输出在 Hermes 引擎上丢 token、dangerouslyAllowBrowser没开导致请求直接被拦、上下文无限追加把内存撑爆。这些坑后面会逐个给排查动作。现在先把开发环境和统一接入通道搭起来。2. TaoToken 前置准备统一 Key 打通云端与本地大模型调用在动手写代码之前先把接入通道这件事定下来。移动端 AI 项目最忌讳的就是把 API Key 硬编码进前端因为 RN 打包后的产物完全可以被逆向Key 一旦泄露就是被人盗刷。所以正确的做法是移动端只请求一个统一的 API 通道Key 的保管、鉴权、限流、模型路由全部放在这个通道后面。TaoToken 在这里扮演的就是这个统一通道的角色。它提供 OpenAI 兼容的接口协议意味着你在 RN 里用的openaiSDK 不用改任何调用方式只需要把baseURL指向它就能同时调用云端大模型和转发到本地/自建的推理服务。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。你需要先拿到一个 API Key。进入控制台创建密钥的地址是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 创建完之后把 Key 复制出来后面配置里会用到。如果你还不确定该选哪个模型可以先去模型对话页面试一下效果https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 在网页上验证通了再写进代码能省掉很多来回调试的时间。这里要强调一个工程原则移动端前端永远不直接持有长期有效的 Key。开发阶段可以用EXPO_PUBLIC_前缀的环境变量临时调试但正式打包前必须换成自建后端代理移动端只请求你自己的后端由后端去调 TaoToken。这样即使前端被逆向泄露的也只是你后端的地址而不是能直接盗刷的密钥。对于长期做编码类、Agent 类项目的同学可以考虑 Coding Plan它更适合持续性的开发调用场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到协议细节问题可以对照查。环境准备清单如下版本不对会直接导致构建失败别跳过依赖版本要求作用Node.js20 LTS ~ 22 LTSJS 运行时不要用 23JDK17强制Android 编译版本错直接报错Android Studio最新稳定版SDK、模拟器、NDKXcodemacOS 16iOS 编译与模拟器Watchman最新文件监听macOS 推荐 brew 安装macOS 上装基础依赖brew install node watchman node -v npm install -g expoWindows 用户注意只能编译 Android要做 iOS 必须 macOS。装好 Node 20 LTS、JDK 17 并配置JAVA_HOMEAndroid Studio 里配好ANDROID_HOME。另外 Windows 不要用 WSL2 做 Expo 开发会出现 adb 设备识别异常。校验环境是否正常node -v expo --version adb devicesadb devices能列出模拟器或真机说明 Android 环境通了。这一步没过后面所有原生构建都会失败。3. 可复制配置app.json、settings.json 与 config.toml 骨架这一节直接给可复制的配置骨架。创建项目用 TypeScript 模板npx create-expo-app rn-ai-demo --template expo-template-typescript cd rn-ai-demo先装依赖。云端调用用openaiSDK它兼容 OpenAI 协议、支持流式输出本地存储对话历史用 AsyncStorage语音输入可选装 expo-audio 和 expo-speech-recognitionnpm install openai npx expo install react-async-storage/async-storage npx expo install expo-audio expo-speech-recognition3.1 app.json 配置骨架app.json是 Expo 项目的核心配置AI 项目要额外注意权限声明和原生模块的 plugin 注册。下面这份可以直接改{ expo: { name: rn-ai-demo, slug: rn-ai-demo, version: 1.0.0, orientation: portrait, scheme: rnaidemo, userInterfaceStyle: automatic, newArchEnabled: true, ios: { supportsTablet: true, bundleIdentifier: com.demo.rnaidemo, infoPlist: { NSMicrophoneUsageDescription: 用于语音输入对话内容, NSCameraUsageDescription: 用于拍照识别场景 } }, android: { package: com.demo.rnaidemo, permissions: [ RECORD_AUDIO, INTERNET ], adaptiveIcon: { foregroundImage: ./assets/adaptive-icon.png, backgroundColor: #ffffff } }, plugins: [ expo-router, [ expo-audio, { microphonePermission: 允许 $(PRODUCT_NAME) 访问麦克风 } ] ], extra: { eas: { projectId: your-eas-project-id } } } }newArchEnabled设为 true 是因为新架构对原生模块的调用性能更好端侧推理场景尤其明显。plugins里注册的每个原生模块都意味着你不能再用 Expo Go 预览必须走开发构建。3.2 settings.json 与本地模型配置如果你走端侧本地推理路线模型文件的管理需要一个配置。这里给一份settings.json骨架放在项目根目录用于描述本地模型的下载源和缓存策略{ localModel: { enabled: true, provider: executorch, modelName: llama-3.2-1b-instruct, quantization: q4, downloadUrl: https://your-cdn.example.com/models/llama-3.2-1b-q4.pte, cacheDir: models, maxContextTokens: 2048, temperature: 0.7 }, cloudModel: { baseUrl: https://taotoken.net/api, modelId: deepseek-chat, stream: true, timeoutMs: 30000 }, agent: { maxLoopSteps: 5, toolCallTimeoutMs: 15000, enableMcpBridge: true, mcpBridgeUrl: wss://your-backend.example.com/mcp } }注意cloudModel.baseUrl指向的是 TaoToken 的 API 入口modelId按你实际要用的模型填。agent段里的mcpBridgeUrl是移动端 Agent 的关键手机端不能直接跑 STDIO 传输的 MCP Server必须通过后端 WebSocket 桥接。3.3 config.toml 配置骨架如果你用 EAS Build 做云构建eas.json之外还可以用config.toml管理构建 profile。下面这份覆盖开发、预览、生产三档[build.development] distribution internal developmentClient true android { buildType apk } ios { simulator true } [build.preview] distribution internal android { buildType apk } ios { simulator false } [build.production] android { buildType app-bundle } ios { simulator false } [submit.production] android { serviceAccountKeyPath ./secrets/play-service-account.json } ios { appleId your-apple-idexample.com, ascAppId 1234567890 }developmentClient true是本地模型调试的前提它生成的是带开发客户端的构建能加载原生推理模块。生产档用app-bundle而不是 apk是为了上架 Google Play。3.4 环境变量与安全红线新建.env文件开发阶段临时用EXPO_PUBLIC_AI_BASE_URLhttps://taotoken.net/api EXPO_PUBLIC_AI_API_KEYsk-你的开发密钥 EXPO_PUBLIC_AI_MODELdeepseek-chat注意EXPO_PUBLIC_前缀的变量会被打进前端产物任何人都能逆向拿到。这只适合开发调试正式打包前必须换成自建后端代理移动端只请求你自己的服务地址。到这里配置骨架就齐了。下一步是把这些配置真正用起来写一个能跑的流式对话。4. 验证请求流式对话与 Agent 工具调用的成功结果配置写完不验证等于没写。这一节给一个最小可跑的流式聊天实现再扩展到 Agent 工具调用最后给出成功结果的判断标准。4.1 流式对话核心代码在app/(tabs)/chat.tsx里写import { View, Text, TextInput, Button, ScrollView } from react-native; import AsyncStorage from react-async-storage/async-storage; import OpenAI from openai; import { useState, useEffect } from react; const openai new OpenAI({ baseURL: process.env.EXPO_PUBLIC_AI_BASE_URL, apiKey: process.env.EXPO_PUBLIC_AI_API_KEY, dangerouslyAllowBrowser: true, }); type MsgItem { role: user | assistant; content: string }; export default function ChatPage() { const [msgList, setMsgList] useStateMsgItem[]([]); const [inputText, setInputText] useState(); useEffect(() { (async () { const raw await AsyncStorage.getItem(chat_history); if (raw) setMsgList(JSON.parse(raw)); })(); }, []); const sendMessage async () { if (!inputText.trim()) return; const userMsg: MsgItem { role: user, content: inputText.trim() }; const all [...msgList, userMsg]; setMsgList(all); setInputText(); const stream await openai.chat.completions.create({ model: process.env.EXPO_PUBLIC_AI_MODEL || deepseek-chat, messages: all, stream: true, }); let fullResp ; for await (const chunk of stream) { const delta chunk.choices[0]?.delta?.content || ; fullResp delta; setMsgList([...all, { role: assistant, content: fullResp }]); } await AsyncStorage.setItem( chat_history, JSON.stringify([...all, { role: assistant, content: fullResp }]) ); }; return ( View style{{ flex: 1, padding: 16 }} ScrollView style{{ flex: 1 }} {msgList.map((m, i) ( Text key{i} style{{ marginVertical: 4 }} {m.role user ? 用户 : AI}{m.content} /Text ))} /ScrollView TextInput value{inputText} onChangeText{setInputText} style{{ borderWidth: 1, padding: 8 }} / Button title发送 onPress{sendMessage} / /View ); }dangerouslyAllowBrowser: true在 RN 环境里是必须的否则 SDK 会拒绝在非 Node 环境发起请求。流式循环里每次拿到 delta 就更新 state这样界面能逐字渲染。4.2 Agent 工具调用扩展移动端做 Agent核心原则是前端只做 UI 和会话管理Agent Loop 和工具执行全部下沉到后端。前端通过 WebSocket 连接后端的 MCP 桥接服务拿到工具列表把用户输入发给后端后端跑完循环再把结果流式回传。const ws new WebSocket(process.env.EXPO_PUBLIC_MCP_BRIDGE_URL!); ws.onopen () { ws.send(JSON.stringify({ type: list_tools })); }; ws.onmessage (event) { const data JSON.parse(event.data); if (data.type tool_list) { console.log(可用工具, data.tools); } if (data.type agent_delta) { setMsgList((prev) { const last prev[prev.length - 1]; if (last?.role assistant) { return [...prev.slice(0, -1), { ...last, content: last.content data.delta }]; } return [...prev, { role: assistant, content: data.delta }]; }); } };后端收到用户消息后负责调用大模型、解析工具调用意图、执行 MCP 工具、把结果再喂回模型整个循环在后端完成。前端只负责把agent_delta渲染出来。4.3 成功结果的判断标准跑通之后你应该看到这些现象输入一句话AI 回复逐字出现而不是等半天一次性弹出杀掉 APP 重进历史对话还在后端日志里能看到工具调用记录前端收到的是流式增量。如果这三点都满足说明云端链路和 Agent 桥接都通了。端侧本地模型验证方式不同。执行npx expo run:android构建后在 APP 里触发本地推理观察日志里模型加载耗时和首 token 延迟。1B 量化模型在中端 Android 机上首 token 延迟通常在几百毫秒到两秒之间如果超过十秒或者直接闪退多半是内存不够或模型文件损坏。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth这一节按真实报错来。每个错误给出触发条件和排查动作你对照自己的日志找。5.1 401 Unauthorized最常见。触发原因是 Key 无效、过期、或者请求头没带上。排查顺序先确认.env里的EXPO_PUBLIC_AI_API_KEY没有多余空格和换行再确认baseURL指向的是https://taotoken.net/api而不是别的地址最后去控制台确认这个 Key 还在有效期内。如果是在 Expo Go 里跑改完.env必须重启 dev server环境变量不会热更新。5.2 local proxy failed这个报错通常出现在你配置了本地代理或者后端转发但转发目标不可达。检查settings.json里的cloudModel.baseUrl是否写成了内网地址而真机不在同一网段。真机联调时手机和电脑要在同一个 Wi-Fi 下且后端服务监听的是0.0.0.0而不是127.0.0.1。用curl在电脑上先验证后端通不通再让手机请求。5.3 reading choices of undefined流式解析时chunk.choices为 undefined。原因通常是返回的不是标准 OpenAI 格式或者请求被拦截返回了错误 JSON。排查动作把stream临时设为 false打印完整响应体看结构确认model字段填的模型 ID 在 TaoToken 侧是存在的检查是不是把baseURL末尾多写了/v1导致路径拼接错误。TaoToken 的 API 入口是https://taotoken.net/apiSDK 会自动拼/v1/chat/completions不要手动重复。5.4 OAuth 相关报错如果你在用 Claude Code 或类似的编码工具接入可能会遇到 OAuth 认证失败。这类工具通常需要三件套配齐Base URL、API Key、Model ID。Base URL 填https://taotoken.net/apiKey 用控制台创建的密钥Model ID 按工具要求填。三者缺一或者 Model ID 写错都会报 OAuth 或认证类错误。Claude Code 的接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 对照检查配置项。5.5 其他高频坑JDK 版本不对会直接导致 Android 编译失败必须是 17。Expo Go 跑不了本地推理库报错通常是 native module not found换成npx expo run:android即可。iOS 上pod install超时可以执行pod cache clean --all后重试。流式输出丢 token 或乱码优先升级 Hermes 引擎版本并确认dangerouslyAllowBrowser已开启。内存持续上涨是因为消息列表无限追加必须做上下文截断只保留最近 N 轮对话。6. 语义一致 CTA把这条链路用到你的项目里走到这里你已经有了一个能跑的 Expo AI 移动端骨架统一通道打通了云端和本地模型调用流式对话验证通过Agent 工具调用通过后端桥接落地常见报错也有了排查路径。接下来最值得做的一件事是把你自己的业务场景接进去。如果你还在选模型阶段先去模型对话页面把几个候选模型都试一遍看哪个在移动端场景下响应质量和速度更合适https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。确定之后回到控制台创建正式密钥https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 把开发用的临时 Key 换掉。如果你的项目是长期迭代的编码类或 Agent 类应用Coding Plan 会比按量调用更省心https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。协议细节和参数说明随时查接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后一个实操建议在正式打包前务必把前端直连改成后端代理。移动端只保留你后端服务的地址Key 全部收进后端环境变量。这一步做完你的移动端 AI 项目才算真正具备上线条件。