Android应用集成AI:在移动端调用Nanbeige 4.1-3B模型API实践
Android应用集成AI在移动端调用Nanbeige 4.1-3B模型API实践最近在做一个智能笔记App想加入一个“一键润色”的功能让用户写的草稿能变得更通顺、更有文采。一开始想的是在App里直接塞个小模型但试了几个要么效果一般要么对手机性能要求太高发热耗电都成了问题。后来团队讨论决定换个思路为什么不把复杂的推理工作交给云端的大模型App只负责发送请求和展示结果呢这样一来用户能享受到更强大的AI能力App本身也能保持轻量流畅。我们最终选择了在星图GPU平台上部署的Nanbeige 4.1-3B模型通过API的方式集成到Android应用里。这篇文章我就来分享一下我们是怎么做的。整个过程不复杂核心就是用好OkHttp这个网络库处理好移动端网络的各种“小脾气”比如信号不稳、请求超时这些。如果你也在琢磨怎么给App加上AI大脑希望这篇实践能给你一些参考。1. 为什么选择云端API方案在移动端集成AI通常有两条路端侧部署和云端调用。我们一开始也纠结过但仔细对比后云端API方案的优势对我们这个场景更明显。端侧部署就是把模型文件直接打包进APK或者让用户下载。好处是离线可用响应快隐私性好。但缺点也很突出模型大小动辄几个G会极大增加App安装包体积影响下载和安装意愿推理过程消耗大量CPU/GPU和内存手机发热、耗电会非常明显而且模型一旦固化在App里后续想升级、优化或者切换模型都得靠发版更新不够灵活。而云端API方案就像是把AI大脑放在了云端服务器上。我们的Android App只需要通过网络把用户输入的文字“送过去”等云端大脑处理完再把结果“拿回来”展示。这么做App本身非常轻量用户手机的压力小。更重要的是云端的模型可以随时更新、替换或扩容我们甚至可以根据不同用户的需求动态分配不同的模型服务灵活性和可维护性大大提升。当然这个方案依赖网络这是它的主要考量点。但现在的移动网络覆盖已经相当不错而且我们可以通过一些技术手段比如请求队列、缓存、优雅降级来优化弱网甚至断网下的体验。综合来看对于需要较强AI能力、且对模型效果和迭代速度有要求的应用云端API是目前更务实的选择。2. 准备工作获取API访问凭证在写代码之前你得先有一个能调用的API服务。我们用的是星图GPU平台上面提供了预置的Nanbeige 4.1-3B模型镜像一键部署后就能获得一个HTTP API端点。这个过程在平台上有详细指引简单来说就是选择Nanbeige 4.1-3B镜像 - 配置资源比如GPU型号- 启动服务。服务启动成功后你会得到几个关键信息API Endpoint (URL) 这是你发送请求的地址比如https://your-service-id.star-map.cloud/v1/chat/completions。API Key 相当于访问密码用于鉴权确保只有你的App能调用。安全提醒这个API Key非常敏感绝对不能硬编码在App的代码里更不能提交到Git等版本控制系统。一旦泄露别人就可以用你的Key疯狂调用产生高额费用。正确的做法是把它放在后端服务器由App向自己的服务器申请临时令牌或者使用移动端安全存储方案。为了演示清晰下文代码中会以变量形式出现但在真实项目中请务必妥善处理。3. 构建Android网络请求层Android端与云端AI服务通信本质上就是发起HTTP请求。我们选用OkHttp Retrofit这套经典组合因为它稳定、高效而且生态丰富。3.1 添加依赖首先在App模块的build.gradle.kts(或build.gradle) 文件中添加必要的库。dependencies { // OkHttp 核心库 implementation(com.squareup.okhttp3:okhttp:4.12.0) // OkHttp 日志拦截器便于调试 implementation(com.squareup.okhttp3:logging-interceptor:4.12.0) // Retrofit用于将HTTP API定义为Kotlin接口 implementation(com.squareup.retrofit2:retrofit:2.9.0) // Retrofit的Gson转换器用于自动序列化/反序列化JSON implementation(com.squareup.retrofit2:converter-gson:2.9.0) // Kotlin协程支持用于异步操作 implementation(org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3) }3.2 定义数据模型和API接口根据Nanbeige模型的API文档通常遵循OpenAI兼容格式我们需要定义请求体和响应体的数据结构。请求体 (Request) 告诉模型我们要干什么。data class ChatCompletionRequest( val model: String nanbeige-4.1-3b, // 指定模型名称 val messages: ListChatMessage, // 对话消息列表 val stream: Boolean false, // 我们先用非流式简单些 val max_tokens: Int? 512, // 限制生成的最大长度 val temperature: Float? 0.7f // 控制随机性0.7比较平衡 ) data class ChatMessage( val role: String, // “system”, “user”, “assistant” val content: String // 消息内容 )响应体 (Response) 接收模型返回的结果。data class ChatCompletionResponse( val id: String, val choices: ListChoice, val usage: Usage? ) data class Choice( val index: Int, val message: ChatMessage, // 这里包含模型生成的回复 val finish_reason: String? ) data class Usage( val prompt_tokens: Int, val completion_tokens: Int, val total_tokens: Int )定义Retrofit接口 用Kotlin接口来描述这个网络调用非常清晰。interface AIService { POST(/v1/chat/completions) // 替换成你的实际路径 suspend fun createChatCompletion( Header(Authorization) authorization: String, Body request: ChatCompletionRequest ): ChatCompletionResponse }3.3 配置OkHttpClient与创建Service实例这里是核心我们要配置一个健壮的OkHttpClient专门应对移动网络环境。import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit object ApiClient { // 你的API基础地址和Key此处仅为示例生产环境需安全存储 private const val BASE_URL https://your-service-id.star-map.cloud private const val API_KEY your-api-key-here private fun createOkHttpClient(): OkHttpClient { val loggingInterceptor HttpLoggingInterceptor().apply { // 开发时用BODY级别看详细日志发布时改为NONE level HttpLoggingInterceptor.Level.BODY } return OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) // 连接超时 .readTimeout(60, TimeUnit.SECONDS) // 读取超时生成文本可能较慢 .writeTimeout(30, TimeUnit.SECONDS) // 写入超时 .addInterceptor(loggingInterceptor) .addInterceptor { chain - // 统一添加认证Header val originalRequest chain.request() val requestWithAuth originalRequest.newBuilder() .header(Authorization, Bearer $API_KEY) .header(Content-Type, application/json) .build() chain.proceed(requestWithAuth) } .addInterceptor { chain - // 简单的重试拦截器示例生产环境建议用更完善的策略 var response: okhttp3.Response? null var lastException: IOException? null for (attempt in 1..3) { try { response chain.proceed(chain.request()) if (response.isSuccessful) { returnaddInterceptor response } else if (response.code 429) { // 频率限制 // 可以等待一段时间后重试 Thread.sleep(1000L * attempt) response.close() continue } else { // 其他错误不重试 break } } catch (e: IOException) { lastException e if (attempt 3) break Thread.sleep(1000L * attempt) // 延迟重试 } } throw lastException ?: IOException(Request failed after retries) } .build() } val aiService: AIService by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(createOkHttpClient()) .addConverterFactory(GsonConverterFactory.create()) .build() .create(AIService::class.java) } }这段配置做了几件关键事设置超时移动网络不稳定连接和读取超时设得宽松些特别是读取因为AI生成文本需要时间。统一添加请求头自动给每个请求加上认证Authorization和内容类型头。加入日志方便调试能看到请求和响应的原始数据。实现简单重试当遇到网络IO异常或服务端限流429时自动重试最多3次每次间隔递增。这能有效应对网络抖动。4. 在业务中调用AI服务网络层准备好后在ViewModel或Repository中调用就非常直观了。我们以“文本润色”功能为例。import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class ContentRepository { private val aiService ApiClient.aiService suspend fun polishText(userText: String): ResultString { return withContext(Dispatchers.IO) { try { // 1. 构建请求 val request ChatCompletionRequest( messages listOf( ChatMessage(role system, content 你是一个专业的文本润色助手请将用户输入的文字修改得更流畅、优美但不要改变原意。), ChatMessage(role user, content userText) ), max_tokens 1024, temperature 0.8f // 润色可以稍有些创造性 ) // 2. 发起网络请求 val response aiService.createChatCompletion(request) // 3. 处理响应 val polishedText response.choices.firstOrNull()?.message?.content if (polishedText.isNullOrEmpty()) { Result.failure(Exception(AI服务返回内容为空)) } else { Result.success(polishedText) } } catch (e: Exception) { // 4. 错误处理 Result.failure(e) } } } }在UI层如Compose或Activity中结合协程和状态管理来调用// 在ViewModel中 fun onPolishClicked(originalText: String) { viewModelScope.launch { _uiState.update { it.copy(isLoading true) } val result repository.polishText(originalText) _uiState.update { it.copy( isLoading false, polishedText result.getOrNull(), errorMessage result.exceptionOrNull()?.message ) } } }5. 移动端专项优化实践把API调通只是第一步要让它在真实的移动环境下好用还得做不少优化。5.1 网络状态感知与优雅降级用户可能在电梯、地铁等网络差的地方使用。我们可以先检查网络状态再决定是否发起请求。// 使用 ConnectivityManager 检查网络连接需权限 val connectivityManager context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val activeNetwork connectivityManager.activeNetwork val isConnected activeNetwork ! null if (!isConnected) { // 提示用户检查网络或展示本地缓存的旧结果 showMessage(网络不可用请检查连接) return } // 网络良好继续发起AI请求5.2 响应缓存策略对于某些场景比如用户反复润色相似的文本或者查看历史记录我们可以缓存AI的响应减少不必要的网络请求和等待也能在离线时提供一些内容。OkHttp本身就支持缓存只需配置一个Cache对象。但要注意AI生成的文本往往具有时效性和唯一性缓存需要精心设计Key比如对用户输入做MD5哈希并设置合理的缓存时间Cache-Control头或者实现自己的内存/磁盘缓存逻辑。5.3 用户体验优化取消请求如果用户等不及或者离开了当前页面应该能取消正在进行的AI请求。Retrofit配合协程可以通过viewModelScope或lifecycleScope自动管理生命周期launch的Job可以取消。进度提示AI生成需要时间一定要有加载中的状态提示ProgressBar或Skeleton Screen。部分展示流式响应上面的例子是等完整文本生成完再返回。更优体验是使用API的流式stream模式让文本像打字一样一个个词地返回。这需要处理Server-Sent Events (SSE)实现起来稍复杂但体验提升巨大。错误友好提示不要直接把“HTTP 500”抛给用户。将错误分类转换成友好的提示如“服务繁忙请稍后再试”、“网络连接超时请重试”、“输入内容过长”等。6. 总结这次把Nanbeige 4.1-3B的云端API集成到Android App里的过程比预想的要顺利。核心工作其实就是围绕OkHttpClient做文章把超时、重试、鉴权这些琐碎但关键的事情配置好剩下的业务调用反而很清晰。最大的感受是云端方案把复杂度从移动端转移到了云端让我们能更专注于App本身的业务逻辑和用户体验。用户不需要强大的手机也能用上效果不错的AI功能。当然这也对后端服务的稳定性、成本和API设计提出了要求。如果你也想尝试建议先从一两个核心功能点切入比如智能回复或者文本摘要。把网络层的基础设施搭稳固处理好各种边缘情况。等跑通了再扩展到更复杂的场景比如流式输出、多模态交互等。这条路走下来你会发现给App注入AI能力并没有那么遥不可及。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。