KMP全栈开发:从Android到AI Agent的技术演进与实践

一、 引言:KMP技术栈的演进与全栈愿景

Kotlin Multiplatform (KMP) 作为 JetBrains 推出的跨平台解决方案,正从移动端(Android/iOS)向更广阔的全栈领域迈进。本文将探讨如何利用 KMP 构建从移动端到服务端,乃至新兴 AI Agent 应用的全栈技术体系。

二、 KMP 核心能力与生态现状

  • 共享业务逻辑: 在 Android、iOS、Web、桌面端复用核心代码。
  • 原生 UI 体验: 各平台使用原生 UI 框架,保持最佳用户体验。
  • 日渐成熟的生态: Compose Multiplatform、Ktor、SQLDelight 等框架的支持。
  • 与现有技术的融合: 如何与 Spring Boot、Node.js 等后端技术栈协同。

三、 第一站:巩固移动端根基 (Android/iOS)

  • 架构设计: 使用 KMP 构建跨平台的 ViewModel、Repository、数据模型。
  • 网络与数据层共享: 使用 Ktor Client 和 Kotlinx.serialization 实现统一的网络请求与序列化。
  • 状态管理: 在共享模块中管理应用状态,通过 expect/actual 对接平台特性。
  • 实战案例: 一个简单的跨平台待办事项应用 (Todo App) 的 KMP 实现。

实战案例:跨平台待办事项应用 (Todo App) 核心代码示例

以下是一个 Todo App 的 KMP 共享模块核心代码骨架,展示了数据模型、Repository 和 ViewModel 的 expect/actual 定义。

1. 共享数据模型 (commonMain)
kotlin 复制代码
// commonMain/kotlin/com/example/todo/shared/model/TodoItem.kt
package com.example.todo.shared.model
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
/**
待办事项数据模型,在共享模块中定义,Android/iOS 通用。
*/
@Serializable
data class TodoItem(
val id: String,
val title: String,
val description: String? = null,
val isCompleted: Boolean = false,
val createdAt: Instant = Instant.now(),
val updatedAt: Instant = Instant.now()
)
2. 共享 Repository 接口 (commonMain)
kotlin 复制代码
// commonMain/kotlin/com/example/todo/shared/repository/TodoRepository.kt
package com.example.todo.shared.repository
import com.example.todo.shared.model.TodoItem
import kotlinx.coroutines.flow.Flow
/**
待办事项仓库接口(expect)。
各平台需提供 actual 实现,处理平台特定的数据持久化(如 SQLite、UserDefaults 等)。
*/
expect class TodoRepository {
suspend fun getAllTodos(): Flow<List<TodoItem>>
suspend fun getTodoById(id: String): TodoItem?
suspend fun insertTodo(todo: TodoItem)
suspend fun updateTodo(todo: TodoItem)
suspend fun deleteTodo(id: String)
suspend fun toggleTodoCompletion(id: String)
}
3. Android 平台 Repository 实现 (androidMain)
kotlin 复制代码
// androidMain/kotlin/com/example/todo/shared/repository/TodoRepository.kt
package com.example.todo.shared.repository
import android.content.Context
import com.example.todo.shared.model.TodoItem
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.datetime.Instant
/**
Android 平台的 TodoRepository 实际实现。
此处使用内存存储简化示例,实际项目可替换为 Room、SQLDelight 等。
*/
actual class TodoRepository actual constructor() {
private val _todos = MutableStateFlow<List<TodoItem>>(emptyList())
actual override suspend fun getAllTodos(): Flow<List<TodoItem>> = _todos.asStateFlow()
actual override suspend fun getTodoById(id: String): TodoItem? {
return _todos.value.find { it.id == id }
}
actual override suspend fun insertTodo(todo: TodoItem) {
_todos.value = _todos.value + todo
}
actual override suspend fun updateTodo(todo: TodoItem) {
_todos.value = _todos.value.map { if (it.id == todo.id) todo else it }
}
actual override suspend fun deleteTodo(id: String) {
_todos.value = _todos.value.filterNot { it.id == id }
}
actual override suspend fun toggleTodoCompletion(id: String) {
_todos.value = _todos.value.map { todo ->
if (todo.id == id) todo.copy(isCompleted = !todo.isCompleted, updatedAt = Instant.now())
else todo
}
}
}
4. iOS 平台 Repository 实现 (iosMain)
kotlin 复制代码
// iosMain/kotlin/com/example/todo/shared/repository/TodoRepository.kt
package com.example.todo.shared.repository
import com.example.todo.shared.model.TodoItem
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.datetime.Instant
/**
iOS 平台的 TodoRepository 实际实现。
同样使用内存存储简化,实际可使用 SQLDelight(通过 CocoaPods)或 UserDefaults。
*/
actual class TodoRepository actual constructor() {
private val _todos = MutableStateFlow<List<TodoItem>>(emptyList())
actual override suspend fun getAllTodos(): Flow<List<TodoItem>> = _todos.asStateFlow()
actual override suspend fun getTodoById(id: String): TodoItem? {
return _todos.value.find { it.id == id }
}
actual override suspend fun insertTodo(todo: TodoItem) {
_todos.value = _todos.value + todo
}
actual override suspend fun updateTodo(todo: TodoItem) {
_todos.value = _todos.value.map { if (it.id == todo.id) todo else it }
}
actual override suspend fun deleteTodo(id: String) {
_todos.value = _todos.value.filterNot { it.id == id }
}
actual override suspend fun toggleTodoCompletion(id: String) {
_todos.value = _todos.value.map { todo ->
if (todo.id == id) todo.copy(isCompleted = !todo.isCompleted, updatedAt = Instant.now())
else todo
}
}
}
5. 共享 ViewModel (commonMain)
kotlin 复制代码
// commonMain/kotlin/com/example/todo/shared/viewmodel/TodoViewModel.kt
package com.example.todo.shared.viewmodel
import com.example.todo.shared.model.TodoItem
import com.example.todo.shared.repository.TodoRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/**
待办事项的共享 ViewModel。
业务逻辑(状态管理、事件处理)在共享模块中编写,与平台 UI 框架解耦。
*/
class TodoViewModel(private val repository: TodoRepository) {
private val _uiState = MutableStateFlow(TodoUiState())
val uiState: StateFlow<TodoUiState> = _uiState.asStateFlow()
private val coroutineScope = CoroutineScope(Dispatchers.Default)
init {
loadTodos()
}
private fun loadTodos() {
coroutineScope.launch {
repository.getAllTodos().collect { todos ->
_uiState.update { it.copy(todos = todos) }
}
}
}
fun addTodo(title: String, description: String? = null) {
coroutineScope.launch {
val newTodo = TodoItem(
id = System.currentTimeMillis().toString(),
title = title,
description = description
)
repository.insertTodo(newTodo)
}
}
fun toggleTodoCompletion(id: String) {
coroutineScope.launch {
repository.toggleTodoCompletion(id)
}
}
fun deleteTodo(id: String) {
coroutineScope.launch {
repository.deleteTodo(id)
}
}
}
/**
UI 状态数据类,同样定义在共享模块中。
*/
data class TodoUiState(
val todos: List<TodoItem> = emptyList(),
val isLoading: Boolean = false,
val errorMessage: String? = null
)

架构说明

  • 数据模型 (TodoItem) :使用 kotlinx.serialization 序列化,确保在 Android/iOS 间传输时类型安全。
  • Repository 层 :通过 expect/actual 机制,共享接口,隔离平台具体实现(如 Android 用 Room,iOS 用 SQLDelight 或 Core Data)。
  • ViewModel 层 :纯 Kotlin 协程实现,管理状态和业务逻辑,可在 Android 的 ViewModel 和 iOS 的 @State 对象中复用。
  • 平台 UI :Android 端使用 Jetpack Compose 或 View 系统,iOS 端使用 SwiftUI 或 UIKit,分别观察 TodoViewModel.uiState 并调用其方法。

四、 进军服务端:KMP 作为后端技术栈

  • 为什么选择 KMP 做后端?: 代码复用、团队技能统一、性能考量。
  • 技术选型: Ktor Server、Exposed ORM、Koin/Dagger 依赖注入。

下表对比了 KMP 后端开发中几个核心技术的选型考量:

技术选项 优点 缺点 适用场景
Ktor Server * 轻量级、高性能,专为 Kotlin 协程设计 * 与 KMP 生态无缝集成,代码复用度高 * DSL 风格配置,开发体验流畅 * 模块化设计,可按需引入功能 * 生态相对 Spring Boot 等仍较小 * 企业级功能(如监控、链路追踪)需自行集成或选择第三方 * 学习曲线对传统 Java EE 开发者可能较陡 * 中小型项目、微服务、API Gateway * 需要快速原型或对性能有较高要求的服务 * 团队已熟悉 Kotlin 协程和函数式编程
Exposed ORM * 纯 Kotlin DSL,类型安全,编译期检查 * 轻量,无反射,与 KMP 共享模块兼容性好 * 支持 SQL 和 DAO 两种模式,灵活度高 * JetBrains 官方维护,与 IntelliJ IDEA 集成好 * 高级 ORM 特性(如懒加载、复杂关联映射)较弱 * 社区插件和工具链不如 Hibernate 丰富 * 对复杂查询的 DSL 学习有一定成本 * 中小型项目,数据结构相对简单 * 追求轻量、无反射、类型安全的数据库访问层 * 需要在 KMP 共享模块中复用数据模型定义
Koin * 纯 Kotlin,轻量级,无代码生成,无反射 * DSL 简洁直观,学习成本低 * 与 KMP 和 Kotlin 协程天然契合 * 启动快,运行时开销小 * 功能相对 Dagger 较简单,缺少编译时严格检查 * 复杂依赖图或动态依赖场景下可能不够灵活 * 生态和社区规模小于 Dagger * 中小型 KMP 项目,追求开发效率和简洁性 * 团队偏好纯 Kotlin 解决方案,避免注解处理器 * 依赖关系相对简单,不需要复杂的生命周期管理
Dagger/Hilt * 编译时依赖注入,性能好,错误提前暴露 * 功能强大,支持复杂的依赖图和生命周期 * 生态成熟,社区资源丰富,最佳实践多 * Android 官方推荐,与 Jetpack 集成度高 * 配置复杂,学习曲线陡峭 * 依赖注解处理器,可能增加构建时间 * 在纯 Kotlin/非 Android 环境中配置稍显繁琐 * 大型、长期维护的项目,需要严格的架构约束 * Android 平台为主或需要与现有 Android 项目深度集成 * 团队已熟悉 Dagger/Hilt,且需要其强大功能
  • 构建 RESTful API: 设计、实现并部署一个为移动端提供服务的 KMP 后端。
  • 数据库操作: 使用 SQLDelight 或 Exposed 进行跨平台的数据持久化设计。
  • 身份认证与授权: JWT 等认证机制在 KMP 全栈中的实现。

五、 全栈贯通:前后端一体化开发实践

  • 共享数据模型与 API 契约: 使用 Kotlin 定义 DTO,并通过共享模块确保前后端类型安全。
  • 端到端类型安全: 告别手写 API 文档和客户端代码,实现编译期检查。
  • 开发效率提升: 修改共享业务逻辑,自动同步到所有平台。
  • 调试与联调: 在全栈 KMP 项目中的调试技巧与工具链。

六、 迈向未来:KMP 在 AI Agent 开发中的角色

  • AI Agent 是什么?: 简述自主性、工具使用、长期记忆等核心概念。
  • KMP 的优势: 为何适合作为 AI Agent 的"大脑"或"协调层"?
  • 架构设想: 使用 KMP 共享模块封装 Agent 核心逻辑(工作流、记忆、工具调用)。
  • 平台适配 :
    • 移动端 Agent: 作为本地智能助手,处理设备传感器数据。
    • 服务端 Agent: 作为后端服务,处理复杂、耗时的自动化任务。
    • 桌面端 Agent: 提供强大的本地计算和交互界面。
  • 技术集成: 如何将 LangChain/Kotlin、OpenAI API 等 AI 能力封装进 KMP 共享模块。

技术集成:在 KMP 共享模块中封装 AI 能力

将 AI 能力(如 LangChain/Kotlin 或 OpenAI API)集成到 KMP 共享模块的核心思想是:在共享代码中定义统一的 AI 服务接口(expect),然后在各平台(Android、iOS、JVM)提供具体的实现(actual)。这样,业务逻辑(如 Agent 的工作流、工具调用)就可以在共享模块中编写,而平台特定的网络、安全或 UI 交互细节则被隔离。

1. 定义共享的 AI 服务接口

首先,在 KMP 共享模块的 commonMain 源集中,定义一个 expect 类或接口,它抽象了 AI 服务的基本操作,例如聊天补全、工具调用等。

kotlin 复制代码
// commonMain/kotlin/com/example/ai/AIService.kt
import kotlinx.coroutines.flow.Flow
/**
在 KMP 共享模块中期望的 AI 服务接口。
各平台需提供 actual 实现。
*/
expect class AIService(apiKey: String) {
/**
发送聊天消息并获取流式响应。
@param messages 聊天消息历史
@param model 使用的模型标识符(如 "gpt-4o-mini")
@param temperature 温度参数
@return 响应内容的 Flow(用于流式输出)
*/
suspend fun chatCompletions(
messages: List<ChatMessage>,
model: String = "gpt-4o-mini",
temperature: Double = 0.7
): Flow<String>
/**
发送聊天消息并获取一次性完整响应。
*/
suspend fun chatCompletionsBlocking(
messages: List<ChatMessage>,
model: String = "gpt-4o-mini",
temperature: Double = 0.7
): String
// 可扩展更多方法,如工具调用、嵌入生成等
}
/**
聊天消息数据类(在共享模块中定义,前后端通用)。
*/
data class ChatMessage(
val role: String, // "system", "user", "assistant"
val content: String
)
2. 在各平台提供 actual 实现

接下来,在对应平台的源集(如 androidMainiosMainjvmMain)中,使用平台特定的 HTTP 客户端或 SDK 来实现上述接口。

Android/JVM 实现(使用 Ktor Client)

对于 Android 和 JVM(后端)平台,我们可以使用 Ktor Client 来调用 OpenAI 的 HTTP API。

kotlin 复制代码
// androidMain/kotlin/com/example/ai/AIService.kt
// jvmMain/kotlin/com/example/ai/AIService.kt (内容相同)
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.flow.flow
actual class AIService actual constructor(private val apiKey: String) {
// 使用 Ktor Client,需在对应平台的依赖中引入
private val client = HttpClient {
    expectSuccess = false
    defaultRequest {
        url("https://api.openai.com/v1/")
        header(HttpHeaders.Authorization, "Bearer $apiKey")
        header(HttpHeaders.ContentType, ContentType.Application.Json)
    }
}

actual override suspend fun chatCompletions(
    messages: List&lt;ChatMessage&gt;,
    model: String,
    temperature: Double
): Flow&lt;String&gt; = flow {
    val response: HttpResponse = client.post("chat/completions") {
        setBody(
            mapOf(
                "model" to model,
                "messages" to messages.map { it.toMap() },
                "temperature" to temperature,
                "stream" to true // 启用流式响应
            )
        )
    }
    // 简化处理:逐行读取 SSE 流并发射内容
    response.bodyAsChannel().toByteArray().decodeToString().lines().forEach { line ->
        if (line.startsWith("data: ")) {
            val data = line.removePrefix("data: ")
            if (data != "[DONE]") {
                // 解析 JSON 提取 content(此处简化)
                emit(data)
            }
        }
    }
}

actual override suspend fun chatCompletionsBlocking(
    messages: List&lt;ChatMessage&gt;,
    model: String,
    temperature: Double
): String {
    val response: HttpResponse = client.post("chat/completions") {
        setBody(
            mapOf(
                "model" to model,
                "messages" to messages.map { it.toMap() },
                "temperature" to temperature,
                "stream" to false
            )
        )
    }
    val json = response.body&lt;Map&lt;String, Any&gt;&gt;()
    // 简化解析,实际应使用 kotlinx.serialization
    return (json["choices"] as List&lt;Map&lt;String, Any&gt;&gt;).first()["message"] as Map&lt;String, String&gt;).getValue("content")
}

private fun ChatMessage.toMap() = mapOf("role" to role, "content" to content)
}
iOS 实现(使用 NSURLSession)

在 iOS 平台,我们可以使用 Foundation 的 NSURLSession 来实现网络请求,包括流式响应。

kotlin 复制代码
// iosMain/kotlin/com/example/ai/AIService.kt
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import platform.Foundation.*

actual class AIService actual constructor(private val apiKey: String) {
    actual override suspend fun chatCompletions(
        messages: List<ChatMessage>,
        model: String,
        temperature: Double
    ): Flow<String> = callbackFlow {
        val url = NSURL.URLWithString("https://api.openai.com/v1/chat/completions")!!
        val request = NSMutableURLRequest.requestWithURL(url)
        request.setHTTPMethod("POST")
        request.setValue("Bearer $apiKey", forHTTPHeaderField = "Authorization")
        request.setValue("application/json", forHTTPHeaderField = "Content-Type")
        request.setValue("text/event-stream", forHTTPHeaderField = "Accept")

        // 构建请求体
        val requestBody = NSMutableDictionary()
        requestBody.setObject(model, forKey = "model")
        requestBody.setObject(messages.map { it.toMap() }, forKey = "messages")
        requestBody.setObject(temperature, forKey = "temperature")
        requestBody.setObject(true, forKey = "stream")

        val jsonData = try {
            NSJSONSerialization.dataWithJSONObject(requestBody, 0, null)
        } catch (e: NSException) {
            close(RuntimeException("Failed to serialize request body: ${e.description}"))
            return@callbackFlow
        }

        request.HTTPBody = jsonData

        // 创建 NSURLSessionDataTask 处理 SSE 流
        val task = NSURLSession.sharedSession.dataTaskWithRequest(request) { data, response, error ->
            try {
                if (error != null) {
                    val nsError = error as NSError
                    close(RuntimeException("Network error: ${nsError.localizedDescription}"))
                    return@dataTaskWithRequest
                }

                val httpResponse = response as? NSHTTPURLResponse
                if (httpResponse == null || httpResponse.statusCode.toInt() !in 200..299) {
                    val statusCode = httpResponse?.statusCode?.toInt() ?: 0
                    val responseBody = data?.let { NSString.create(it, NSUTF8StringEncoding) } ?: "No response body"
                    close(RuntimeException("HTTP error $statusCode: $responseBody"))
                    return@dataTaskWithRequest
                }

                if (data == null) {
                    close(RuntimeException("Empty response body"))
                    return@dataTaskWithRequest
                }

                // 解析 SSE 流式响应
                val responseString = NSString.create(data, NSUTF8StringEncoding) as String
                val lines = responseString.split("\n")

                for (line in lines) {
                    if (line.startsWith("data: ")) {
                        val dataLine = line.removePrefix("data: ").trim()
                        if (dataLine == "[DONE]") {
                            close()
                            break
                        }

                        if (dataLine.isNotEmpty()) {
                            try {
                                val jsonData = dataLine.toNSString().dataUsingEncoding(NSUTF8StringEncoding)!!
                                val json = NSJSONSerialization.JSONObjectWithData(jsonData, 0, null) as? Map<Any?, Any?>
                                
                                val choices = json?.get("choices") as? List<*>
                                val firstChoice = choices?.firstOrNull() as? Map<Any?, Any?>
                                val delta = firstChoice?.get("delta") as? Map<Any?, Any?>
                                val content = delta?.get("content") as? String
                                
                                if (content != null && content.isNotEmpty()) {
                                    trySend(content)
                                }
                            } catch (e: Throwable) {
                                // 忽略单个数据块的解析错误,继续处理后续数据
                                continue
                            }
                        }
                    }
                }
                
                close()
            } catch (e: Throwable) {
                close(e)
            }
        }

        task.resume()

        // 确保任务在流取消时被取消
        awaitClose {
            task.cancel()
        }
    }

    actual override suspend fun chatCompletionsBlocking(
        messages: List<ChatMessage>,
        model: String,
        temperature: Double
    ): String = suspendCancellableCoroutine { continuation ->
        val url = NSURL.URLWithString("https://api.openai.com/v1/chat/completions")!!
        val request = NSMutableURLRequest.requestWithURL(url)
        request.setHTTPMethod("POST")
        request.setValue("Bearer $apiKey", forHTTPHeaderField = "Authorization")
        request.setValue("application/json", forHTTPHeaderField = "Content-Type")

        val requestBody = NSMutableDictionary()
        requestBody.setObject(model, forKey = "model")
        requestBody.setObject(messages.map { it.toMap() }, forKey = "messages")
        requestBody.setObject(temperature, forKey = "temperature")
        requestBody.setObject(false, forKey = "stream")

        do {
            val jsonData = try {
                NSJSONSerialization.dataWithJSONObject(requestBody, 0, null)
            } catch (e: NSException) {
                continuation.resumeWithException(IllegalArgumentException("Failed to serialize request body", e))
                break
            }
            
            request.HTTPBody = jsonData
            
            val task = NSURLSession.sharedSession.dataTaskWithRequest(request) { data, response, error ->
                try {
                    if (error != null) {
                        val nsError = error as NSError
                        continuation.resumeWithException(RuntimeException("Network error: ${nsError.localizedDescription}"))
                        return@dataTaskWithRequest
                    }
                    
                    val httpResponse = response as? NSHTTPURLResponse
                    if (httpResponse == null || httpResponse.statusCode.toInt() !in 200..299) {
                        val statusCode = httpResponse?.statusCode?.toInt() ?: 0
                        val responseBody = data?.let { NSString.create(it, NSUTF8StringEncoding) } ?: "No response body"
                        continuation.resumeWithException(RuntimeException("HTTP error $statusCode: $responseBody"))
                        return@dataTaskWithRequest
                    }
                    
                    if (data == null) {
                        continuation.resumeWithException(RuntimeException("Empty response body"))
                        return@dataTaskWithRequest
                    }
                    
                    val jsonString = NSString.create(data, NSUTF8StringEncoding) as String
                    val json = try {
                        NSJSONSerialization.JSONObjectWithData(data, 0, null) as? Map<Any?, Any?>
                    } catch (e: NSException) {
                        continuation.resumeWithException(RuntimeException("Failed to parse JSON response", e))
                        return@dataTaskWithRequest
                    }
                    
                    val choices = json?.get("choices") as? List<*>
                    val firstChoice = choices?.firstOrNull() as? Map<Any?, Any?>
                    val message = firstChoice?.get("message") as? Map<Any?, Any?>
                    val content = message?.get("content") as? String
                    
                    if (content != null) {
                        continuation.resume(content)
                    } else {
                        continuation.resumeWithException(RuntimeException("Invalid response format: $jsonString"))
                    }
                } catch (e: Throwable) {
                    continuation.resumeWithException(e)
                }
            }
            
            task.resume()
            
            continuation.invokeOnCancellation {
                task.cancel()
            }
        } while (false)
    }

    private fun ChatMessage.toMap(): Map<String, Any> {
        return mapOf("role" to role, "content" to content)
    }
}
3. 架构图描述

以下 Mermaid 流程图描述了上述 AIService 在 KMP 全栈 AI Agent 中的架构位置与数据流:

flowchart TD subgraph "KMP 共享模块 (commonMain)" A[Agent 核心逻辑] --> B[调用 AIService.expect] B --> C[定义 ChatMessage 等共享数据模型] end subgraph "平台实现层" D[Android/JVM actual 实现] --> E[使用 Ktor Client 调用 OpenAI API] F[iOS actual 实现] --> G[使用 NSURLSession 调用 OpenAI API] end subgraph "外部 AI 服务" H[OpenAI API] I[LangChain/Kotlin 本地推理] end C --> D C --> F E --> H G --> H B -.-> I

架构说明

  • 共享模块 :包含 AI Agent 的核心工作流、记忆管理和工具调用逻辑。它依赖 AIService 接口来与 AI 模型交互,但不知道具体实现。
  • 平台实现层 :各平台(Android、iOS、JVM)提供 AIServiceactual 实现,处理平台特定的网络、安全策略(如证书锁定)和性能优化。
  • 外部 AI 服务:实现层最终调用 OpenAI API 或集成本地 LangChain/Kotlin 运行时。共享模块的业务逻辑可以无缝切换不同的 AI 后端。
4. 在共享业务逻辑中使用 AIService

定义好接口和实现后,在共享模块中就可以编写与平台无关的 AI Agent 逻辑了。

kotlin 复制代码
// commonMain/kotlin/com/example/agent/EmailAgent.kt
class EmailClassificationAgent(private val aiService: AIService) {
suspend fun classifyAndReply(emailContent: String): String {
    val systemPrompt = "你是一个邮件分类助手,请判断邮件类型并生成简要回复。"
    val userMessage = "邮件内容:$emailContent"

    val messages = listOf(
        ChatMessage("system", systemPrompt),
        ChatMessage("user", userMessage)
    )

    // 调用共享的 AI 服务,实际执行由平台决定
    val reply = aiService.chatCompletionsBlocking(messages)
    return reply
}
}
// 在共享模块中可以通过依赖注入或工厂来获取平台特定的 AIService 实例
// 例如在 Android 的 ViewModel 或 iOS 的 ViewController 中初始化 EmailClassificationAgent

概念验证:智能邮件分类与回复助手的 KMP 全栈设计

基于上述 AIService 封装,我们可以设计一个全栈的"智能邮件分类与回复助手"Agent:

  • 共享模块 :包含 EmailClassificationAgent 类(如上)、邮件解析逻辑、分类规则和回复模板。
  • Android/iOS 移动端 :通过 AIService 的实际实现调用云端或本地 AI 模型,在移动设备上提供即时邮件分类和草稿回复功能,并可集成系统通知。
  • JVM 服务端 :将相同的 EmailClassificationAgent 部署为后端服务,处理批量邮件或为其他客户端提供 API。由于共享了核心逻辑,移动端和服务端的分类行为完全一致。
  • 桌面端:使用 Compose Multiplatform 构建跨平台桌面 UI,复用相同的 Agent 逻辑,提供更强大的邮件管理和编辑界面。

通过这种设计,AI 能力的集成点被统一在 AIService 接口,各平台只需关注网络和交互细节,而 Agent 的"大脑"(业务逻辑)则在 KMP 共享模块中一次编写,处处运行。

七、 挑战、最佳实践与学习路径

  • 面临的挑战: 包体积、原生互操作复杂性、社区资源相对较少、调试难度。
  • 最佳实践建议: 渐进式采用、清晰的模块边界、充分的测试(共享测试)、CI/CD 流水线。
  • 推荐学习路径: Kotlin 基础 → KMP 核心概念 → 移动端实践 → 后端 Ktor → 全栈项目 → 探索 AI 集成。
  • 资源推荐: 官方文档、开源项目、社区论坛、相关会议与文章。

八、 总结与展望

总结 KMP 实现全栈开发的核心价值,并展望其在云原生、边缘计算、物联网(IoT)以及更智能的 AI Agent 领域的潜在应用,鼓励开发者拥抱这一技术趋势。

相关推荐
●VON4 小时前
鸿蒙 PC Markdown 编辑器内核:在 ArkWeb 中离线运行 CodeMirror 6
安全·华为·编辑器·harmonyos·鸿蒙
YM52e6 小时前
鸿蒙Flutter Padding内边距:EdgeInsets详解
android·学习·flutter·华为·harmonyos·鸿蒙
摇滚侠6 小时前
AI 编程工具 《TRAE 官方手册》阅读笔记 AI 编程核心 上
人工智能·笔记
一个王同学6 小时前
从零到一 | CV转多模态大模型 | week19 | 基于 FastAPI 和 vLLM 的多模态大模型部署
人工智能·深度学习·计算机视觉·fastapi·改行学it·vllm
m沐沐7 小时前
【深度学习】YOLOv2目标检测算法——改进点、网络结构与聚类先验框解析
人工智能·pytorch·深度学习·算法·yolo·目标检测·transformer
企业老板ai培训7 小时前
破解中小企业AI变现难:2026年企业AI培训与陪跑行业趋势深度报告,为何从‘陪跑’到‘变现’才是关键?
大数据·人工智能
西安老张(AIGC&ComfyUI)8 小时前
第034章:ComfyUI&AIGC一阶段学习总结及下阶段学习安排
学习·aigc
神奇霸王龙8 小时前
国产音乐视频 Prompt 三段式屠夫榜:5 个国产视频模型实测对比
人工智能·ai·prompt·音视频·agent·ai编程·agi
OpenCSG8 小时前
Hugging Face遭遇AI Agent攻击:AI资产管理正在进入新阶段
人工智能·大模型