鸿蒙 + Flutter 下如何管理 AI 会话——AgentService 设计解析

适合谁看

  • 正在给鸿蒙 Flutter 应用接 AI 会话的人

  • 想把模型调用从页面层抽出来的人

  • 想理解移动端(尤其是鸿蒙端)AI 会话管理该收在哪一层的人

  • 想了解 AgentService 如何和协调器、工具层、鸿蒙原生能力协作的人

问题背景

很多 AI 页面初版都会直接这样写:

  • 页面按钮点一下

  • 直接发请求

  • 把结果塞回状态

这种方式在 Demo 阶段很快,但一旦开始加入:

  • 工具调用

  • 流式输出

  • 会话记忆

  • 页面退出释放

  • 鸿蒙端的语音输入输出联动

  • Token 消耗统计和成本追踪

页面层就会很快变得过重。

真正的难点不是"怎么发一次聊天请求",而是:

怎么让移动端里的 AI 会话成为一个可管理对象------在鸿蒙设备上尤其如此,因为会话生命周期还要和鸿蒙原生引擎的生命周期对齐。

项目中的真实场景

食界探味当前专门抽了:

  • app/lib/core/ai/agent_service.dart --- AI 会话服务层

它上面连接:

  • app/lib/core/ai/agent_providers.dart --- 模型配置和 Provider

下面被:

  • app/lib/core/ai/ai_explore_coordinator.dart --- 协调器消费

同时它间接触发鸿蒙侧的能力:

  • SpeechRecognitionPlugin.ets --- 语音识别

  • TextToSpeechPlugin.ets --- TTS 播报

这说明当前项目并没有让页面直接依赖 AI Provider,而是先放了一层专门的会话服务。这个服务层不感知底层是鸿蒙还是 Android,但它的生命周期管理直接影响鸿蒙端的体验。

核心实现

先说结论:

AgentService 的价值不在"帮你少写几行调用代码",而在"把 AI 会话的创建、使用、续跑和释放都变成可管理的结构"。

一、它先把"当前会话对象"收成了显式状态

AgentService 里,最关键的一个字段是:

复制代码
class AgentService {
  final OpenAIProvider _provider;
  AIAgent? _currentAgent;

这说明项目当前对 AI 会话的理解不是"随手发一次模型请求",而是"当前有一个会话中的 agent"。这一步很重要,因为它让创建 agent、使用 agent、清掉 agent 这些动作都有了明确落点。

二、Provider 层:模型配置和成本追踪

在深入 AgentService 之前,先看它依赖的 Provider 层:

复制代码
// app/lib/core/ai/agent_providers.dart

final zhipuAIProvider = Provider<OpenAIProvider>((ref) {
  return OpenAIProvider(
    config: OpenAIConfig(
      baseUrl: '${EnvConfig.apiBaseUrl}/zhipu/paas/v4',
      apiKey: 'proxy-managed',
      model: 'glm-4.7',
    ),
  );
});

这里有几个值得注意的设计:

  1. API Key 由后端代理管理 --- 客户端持有的是 proxy-managed 占位符,真实的 API Key 在后端注入。这对鸿蒙端的安全性尤其重要,避免密钥暴露在客户端代码中

  2. 通过后端代理转发 --- 请求走 ${EnvConfig.apiBaseUrl}/zhipu/paas/v4,而不是直连智谱 API。这意味着鸿蒙设备即使在受限网络环境下也能正常访问

  3. 模型可切换 --- 项目维护了一个允许使用的模型列表:

    const zhipuAllowedModels = [
    'glm-4v-flash',
    'glm-4-flash',
    'glm-4v',
    'glm-4v-plus',
    'glm-4.5v',
    'glm-4.6',
    'glm-4.6v',
    'glm-4.7',
    ];

  4. Token 成本追踪 --- 每次对话完成后,AgentService 会记录消耗的 token 数量和费用:

    if (chunk.isDone && chunk.usage != null) {
    AppLogger.info(
    '[AgentService] 消耗: ${ZhipuPricing.formatCostLog(chunk.usage!)}',
    );
    }

成本计算逻辑:

复制代码
class ZhipuPricing {
  static const double inputPricePerMillion = 3.0;   // 输入 ¥3/百万 token
  static const double outputPricePerMillion = 14.0;  // 输出 ¥14/百万 token

  static String formatCostLog(TokenUsage usage) {
    final inputCost = usage.promptTokens * inputPricePerMillion / 1000000;
    final outputCost = usage.completionTokens * outputPricePerMillion / 1000000;
    final totalCost = inputCost + outputCost;
    return '输入: ${usage.promptTokens} tokens (¥${inputCost.toStringAsFixed(6)}), '
        '输出: ${usage.completionTokens} tokens (¥${outputCost.toStringAsFixed(6)}), '
        '总计: ${usage.totalTokens} tokens (¥${totalCost.toStringAsFixed(6)})';
  }
}

在鸿蒙端调试时,这些日志能帮你快速判断:模型调用是否正常、token 消耗是否合理、成本是否可控。

三、createAgent() 让会话初始化集中发生

createAgent() 当前做的事情并不只是"new 一个对象",而是把一组会话初始条件都收在了一起:

复制代码
AIAgent createAgent({
  required String systemPrompt,
  List<Tool>? tools,
  String? model,
  int maxMessages = 50,
  bool enableAutoToolExecution = false,
}) {
  final agent = AIAgent(
    provider: _provider,
    config: AIAgentConfig(
      systemPrompt: systemPrompt,
      model: model,
      enableAutoToolExecution: enableAutoToolExecution,
      additionalParams: {'thinking': {'type': 'disabled'}},
    ),
    memoryManager: ConversationMemory(maxMessages: maxMessages),
  );

  if (tools != null) {
    for (final tool in tools) {
      agent.addTool(tool);
    }
  }

  _currentAgent = agent;
  return agent;
}

各参数的职责:

参数 作用 为什么放在服务层
systemPrompt 定义 AI 的角色和行为规范 避免每个页面重复写 prompt
tools 注册业务工具(搜索菜品、详情等) 工具注册逻辑统一收口
model 指定模型名称,默认用 Provider 配置 支持按场景切换模型
maxMessages 记忆窗口大小,控制历史消息保留数量 移动端内存有限,必须主动控制
enableAutoToolExecution 是否自动执行工具调用 避免协调器手动管理工具执行

注意 additionalParams: {'thinking': {'type': 'disabled'}} --- 这禁用了模型的思考链输出。在移动端,思考链会增加延迟和 token 消耗,禁用后响应更快。

这说明 AgentService 当前其实已经在承担 agent 工厂 + 会话策略入口 的职责。页面层和协调器层不再需要自己拼这些初始条件。

四、为什么记忆窗口要留在这层

createAgent() 里,ConversationMemory(maxMessages: maxMessages) 是一个很值得注意的点。

它说明项目已经意识到:移动端 AI 会话不是无限上下文,而应该主动控制会话窗口大小和历史消息保留数量。

食界探味在协调器中实际使用的配置:

复制代码
// ai_explore_coordinator.dart

_agentService.createAgent(
  systemPrompt: _systemPrompt,
  tools: [...],
  enableAutoToolExecution: true,
  maxMessages: 30,  // 最多保留 30 条历史消息
);

为什么是 30?因为:

  1. 鸿蒙端设备内存有限,过多历史消息会占用大量内存

  2. 菜品推荐场景不需要太长的对话历史

  3. 30 条足够覆盖 10-15 轮对话,满足"探索 → 细化 → 推荐"的典型流程

这类决策如果直接塞进页面层,后面会很难统一。放在 AgentService 里就更合理,因为它更接近"会话策略",而不是"页面渲染策略"。

五、为什么流式对话也应该收在这层

chatWithToolsStream() 是这份代码里最关键的方法之一:

复制代码
Future<void> chatWithToolsStream({
  required String message,
  AIAgent? agent,
  void Function(String)? onThinking,
  void Function(String)? onContent,
  void Function(ToolCall)? onToolCall,
  void Function(String)? onComplete,
}) async {
  final targetAgent = agent ?? _currentAgent;
  if (targetAgent == null) {
    throw StateError('没有可用的 Agent,请先调用 createAgent()');
  }

  await for (final chunk in targetAgent.chatStreamRaw(message)) {
    // 1. 思考内容(已禁用,但保留接口)
    if (chunk.reasoningContent != null && onThinking != null) {
      onThinking(chunk.reasoningContent!);
    }
    // 2. 增量文本(流式输出给页面展示)
    if (chunk.content != null && onContent != null) {
      onContent(chunk.content!);
    }
    // 3. 工具调用(模型决定调用业务工具)
    if (chunk.toolCalls != null &&
        chunk.toolCalls!.isNotEmpty &&
        onToolCall != null) {
      for (final toolCall in chunk.toolCalls!) {
        onToolCall(toolCall);
      }
    }
    // 4. 完成时记录 token 消耗
    if (chunk.isDone && chunk.usage != null) {
      AppLogger.info(
        '[AgentService] 消耗: ${ZhipuPricing.formatCostLog(chunk.usage!)}',
      );
    }
  }

这段代码把流式的四个阶段统一收口了:

复制代码
用户消息 → [reasoningContent] → [content] → [toolCalls] → [isDone + usage]
             ↑ 思考(已禁用)    ↑ 文本输出    ↑ 工具调用     ↑ 完成统计

协调器只需要关注回调,不需要理解底层 chunk 结构:

复制代码
// ai_explore_coordinator.dart

await _agentService.chatWithToolsStream(
  message: text,
  onContent: (chunk) {
    // 只关心:文本增量来了,更新页面
    buffer.write(chunk);
    state = state.copyWith(
      status: AiSessionStatus.responding,
      streamingText: buffer.toString(),
    );
  },
  onToolCall: (toolCall) {
    // 只关心:模型在调用工具,更新状态为"搜索中"
    state = state.copyWith(status: AiSessionStatus.searching);
  },
  onComplete: (full) {
    // 只关心:回复完成
    state = state.copyWith(status: AiSessionStatus.idle, streamingText: full);
  },
);

这样做的好处是:协调器层不需要直接理解底层流对象细节,页面层更不需要直接碰这些底层 chunk 结构。

六、工具调用为什么也放在这里处理

chatWithToolsStream() 在流式对话结束后,还会自动检测并执行待执行的工具调用:

复制代码
// 如果有待执行的工具调用,自动执行并继续对话
if (targetAgent.pendingToolCalls != null &&
    targetAgent.pendingToolCalls!.isNotEmpty) {
  await for (final chunk in targetAgent.executeToolsAndContinue()) {
    if (chunk.content != null && onContent != null) {
      onContent(chunk.content!);
    }
    if (chunk.isDone && chunk.fullContent != null && onComplete != null) {
      onComplete(chunk.fullContent!);
    }
    if (chunk.isDone && chunk.usage != null) {
      AppLogger.info(
        '[AgentService] 工具回调消耗: ${ZhipuPricing.formatCostLog(chunk.usage!)}',
      );
    }
  }
}

这意味着一次完整的 AI 交互可能是这样的:

复制代码
用户: "推荐牛肉吃法"
  ↓
AgentService: 调用 chatWithToolsStream
  ↓
模型: 返回 tool_call: search_dishes({ingredients: ["牛肉"]})
  ↓
AgentService: 检测到 pendingToolCalls,自动执行 executeToolsAndContinue
  ↓
工具: 查询数据库,返回 5 道牛肉菜品
  ↓
模型: 基于工具结果生成推荐文案
  ↓
AgentService: 通过 onContent 回调把文案流式输出
  ↓
协调器: 收到文案 + matchedDishes,更新页面状态

关键在于:工具执行和继续对话的逻辑完全在 AgentService 内部完成,协调器不需要手动管理这个循环。如果这部分留在页面层或者每个协调器里各自拼,后面很容易变成每个页面有自己的一套工具续跑逻辑。

七、为什么 clearCurrentAgent() 很关键

很多移动端 AI 功能一开始都会忽略这一点:页面退出了,会话要不要清。

食界探味当前已经明确给了:

复制代码
void clearCurrentAgent() {
  _currentAgent?.clearHistory();  // 清除对话历史
  _currentAgent = null;           // 释放引用
}

协调器在两个场景下会调用它:

  1. 重置会话 --- 用户点击"新对话"按钮:

    // ai_explore_coordinator.dart

    void reset() {
    _agentService.clearCurrentAgent();
    _agentInitialized = false;
    if (mounted) {
    state = const AiSessionState();
    }
    }

  2. 重新初始化 --- 每个 coordinator 实例首次调用时,先清掉旧 agent 再创建新的:

    void _ensureAgent() {
    if (_agentInitialized) return;
    _agentInitialized = true;

    _agentService.clearCurrentAgent(); // 先清旧的
    _agentService.createAgent( // 再建新的
    systemPrompt: _systemPrompt,
    tools: [...],
    enableAutoToolExecution: true,
    maxMessages: 30,
    );
    }

这说明当前项目并没有把 AI 会话默认当成永久全局状态,而是允许某个页面会话结束后重置。这对鸿蒙端来说尤其重要,因为:

  • 鸿蒙设备内存管理更严格,长期悬挂的会话对象会占用不可回收的内存

  • 鸿蒙的页面生命周期和 Android 不完全一致,需要更主动地管理资源

  • 用户在鸿蒙设备上切换应用再回来时,会话状态应该被正确恢复或清理

八、为什么 dispose() 必须放在服务层

在当前代码里,dispose() 最后还会调用 _provider.dispose()

复制代码
void dispose() {
  _currentAgent = null;
  _provider.dispose();  // 释放底层模型 Provider
}

同时 Riverpod Provider 也在 onDispose 时触发了这件事:

复制代码
final agentServiceProvider = Provider<AgentService>((ref) {
  final provider = ref.watch(zhipuAIProvider);
  final service = AgentService(provider);
  ref.onDispose(() => service.dispose());  // Provider 销毁时自动释放
  return service;
});

这说明当前结构非常明确地把 AI 资源释放责任收到了服务层,而不是页面层自己猜什么时候释放。这一步很重要,因为 AI Provider、会话对象和流式任务都可能比普通页面状态更重。

在鸿蒙端,资源释放尤其关键:

  1. 鸿蒙的内存回收机制和 Android 不同 --- 不能依赖 GC 自动回收大对象

  2. 流式任务必须显式取消 --- 如果页面退出时还有 pending 的流式请求,必须中止

  3. Provider 的 dispose 时机和页面对齐 --- Riverpod 的 autoDispose 会自动处理,但前提是服务层的 dispose() 实现正确

九、协调器如何消费 AgentService

理解了 AgentService 的设计后,再看协调器是怎么用它的:

复制代码
class AiExploreCoordinator extends StateNotifier<AiSessionState> {
  final AgentService _agentService;
  final FoodRepository _foodRepository;

  // ... 初始化 ...

  void _ensureAgent() {
    if (_agentInitialized) return;
    _agentInitialized = true;

    _agentService.clearCurrentAgent();
    _agentService.createAgent(
      systemPrompt: _systemPrompt,
      tools: [
        SearchDishesTool(_foodRepository, onDishesFound: _onDishesFound),
        GetDishDetailTool(_foodRepository),
        GetRandomDishTool(_foodRepository, onDishesFound: _onDishesFound),
        GetDishesByIngredientTool(_foodRepository, onDishesFound: _onDishesFound),
      ],
      enableAutoToolExecution: true,
      maxMessages: 30,
    );
  }

协调器向 AgentService 注册了 4 个业务工具:

工具 功能 回调
SearchDishesTool 根据食材/口味/地域搜索菜品 onDishesFound → 更新 matchedDishes
GetDishDetailTool 获取某道菜的详细信息 无(直接返回文本)
GetRandomDishTool 随机推荐一道菜 onDishesFound → 更新 matchedDishes
GetDishesByIngredientTool 按食材查同食材的其他吃法 onDishesFound → 更新 matchedDishes

注意工具的 onDishesFound 回调------它直接把查询到的菜品数据推给协调器的状态,协调器再通过 Riverpod 通知页面渲染菜品卡片。这就是"AI 推荐 + 业务卡片"联动的关键链路。

十、AgentService 在整体架构中的位置

从整体架构看,AgentService 的位置非常清晰:

复制代码
┌─────────────────────────────────────────────────┐
│                   页面层                          │
│  AiAssistantScreen                               │
│       │                                          │
│       ▼                                          │
│               协调器层                            │
│  AiExploreCoordinator                            │
│       │                                          │
│       ▼                                          │
│               服务层                              │
│  AgentService ← agentServiceProvider             │
│       │                                          │
│       ▼                                          │
│               Provider 层                        │
│  zhipuAIProvider → OpenAIProvider                 │
│       │                                          │
│       ▼                                          │
│               工具层                              │
│  SearchDishesTool / GetDishDetailTool / ...       │
│                                                  │
├─────────────────────────────────────────────────┤
│               鸿蒙原生层(间接)                   │
│  SpeechRecognitionPlugin  TextToSpeechPlugin     │
│  ← 通过 Channel 被协调器调用,AgentService 不直接 │
│     感知,但会话生命周期影响语音体验                │
└─────────────────────────────────────────────────┘

核心要点:

  1. AgentService 不感知鸿蒙 --- 它是纯 Flutter/AI 服务层,不 import 任何鸿蒙相关代码

  2. 协调器是鸿蒙和 AI 的桥梁 --- 它同时调用 AgentService(AI 能力)和 Channel(鸿蒙能力)

  3. 页面层最轻 --- 只负责 UI 渲染和用户交互,所有 AI/语音逻辑都委托给下层

  4. Provider 层管理依赖 --- Riverpod 自动处理创建、缓存、销毁的生命周期

关键代码位置

文件 作用
app/lib/core/ai/agent_service.dart AI 会话服务层
app/lib/core/ai/agent_providers.dart 模型配置、Provider、成本追踪
app/lib/core/ai/ai_explore_coordinator.dart 协调器,消费 AgentService
app/lib/core/ai/models/ai_session_state.dart 会话状态模型
app/lib/core/ai/tools/ 4 个业务工具
app/lib/core/platform/speech_recognition_channel.dart 语音识别通道(协调器调用)
app/lib/core/platform/text_to_speech_channel.dart TTS 通道(协调器调用)

鸿蒙侧与 AgentService 的协作关系

虽然 AgentService 本身是纯 Flutter 层,但它在鸿蒙端的行为有特殊意义:

生命周期对齐

复制代码
鸿蒙页面创建
  → AiAssistantScreen initState
    → coordinator 创建(Riverpod autoDispose)
      → AgentService 创建(Provider)
        → zhipuAIProvider 创建
          → OpenAIProvider 初始化

鸿蒙页面销毁
  → AiAssistantScreen dispose
    → coordinator dispose(自动)
      → AgentService dispose
        → _currentAgent = null
        → _provider.dispose()

语音联动时序

复制代码
用户按住语音按钮
  → coordinator.startVoiceInput()
    → SpeechRecognitionChannel.startListening()  [鸿蒙原生]
      → 用户说话...
      → 鸿蒙识别完成,返回文本
    → coordinator.submitQuery(text)
      → AgentService.chatWithToolsStream()
        → 模型推理 + 工具调用
        → 流式输出
  → coordinator.speakText(response)
    → TextToSpeechChannel.speak()  [鸿蒙原生]
      → 鸿蒙 TTS 引擎播报
  → 用户退出页面
    → coordinator dispose
      → TextToSpeechChannel.stop()  [鸿蒙原生]
        → 鸿蒙 TTS 引擎停止

在这个流程中,AgentService 不直接调用任何鸿蒙 API,但它的 chatWithToolsStream 是整个链路的核心------所有 AI 推理和工具调用都在这里完成。协调器负责把鸿蒙的语音输入喂给 AgentService,再把 AgentService 的输出喂给鸿蒙的 TTS。

资源管理对比

场景 Android 行为 鸿蒙行为 AgentService 的应对
页面退出 Activity 销毁,GC 回收 Ability 组件销毁,内存管理更严格 dispose() 显式释放
流式中断 网络断开,chunk 停止 网络切换(WiFi→移动数据),chunk 可能中断 mounted 检查 + 异常捕获
后台切回 onResume,状态恢复 前后台切换,Ability 状态恢复 Riverpod autoDispose 自动管理
内存不足 系统杀进程 系统回收 Ability clearCurrentAgent() 主动释放

常见坑

  • 页面层直接 new agent,导致会话生命周期四处散落 → 一定要通过 AgentService 集中管理

  • 工具调用逻辑分散在多个页面或协调器里 → 统一注册到 AgentService,通过 enableAutoToolExecution 自动执行

  • 流式输出直接在页面层消费底层 chunk → 用 AgentService 的回调封装,页面只关心文本增量

  • 没有显式清理当前会话 ,导致状态长期悬挂 → 调用 clearCurrentAgent(),尤其是页面退出时

  • 鸿蒙端不主动释放 Provider ,导致内存泄漏 → 确保 ref.onDispose 正确注册

  • API Key 硬编码在客户端 → 走后端代理,客户端只持占位符

  • 记忆窗口设得太大,鸿蒙设备内存吃紧 → 移动端建议 30-50 条,按场景调整

  • 流式任务没有 mounted 检查 ,页面退出后继续更新状态 → 协调器每个回调开头加 if (!mounted) return

可复用模板

如果你要在自己的鸿蒙 + Flutter 项目里做类似的 AI 会话管理,可以参考这个结构:

AgentService 模板

复制代码
class AgentService {
  final OpenAIProvider _provider;
  AIAgent? _currentAgent;

  AgentService(this._provider);

  AIAgent? get currentAgent => _currentAgent;

  AIAgent createAgent({
    required String systemPrompt,
    List<Tool>? tools,
    String? model,
    int maxMessages = 50,
    bool enableAutoToolExecution = false,
  }) {
    final agent = AIAgent(
      provider: _provider,
      config: AIAgentConfig(
        systemPrompt: systemPrompt,
        model: model,
        enableAutoToolExecution: enableAutoToolExecution,
      ),
      memoryManager: ConversationMemory(maxMessages: maxMessages),
    );

    if (tools != null) {
      for (final tool in tools) {
        agent.addTool(tool);
      }
    }

    _currentAgent = agent;
    return agent;
  }

  Future<void> chatWithToolsStream({
    required String message,
    AIAgent? agent,
    void Function(String)? onContent,
    void Function(ToolCall)? onToolCall,
    void Function(String)? onComplete,
  }) async {
    final targetAgent = agent ?? _currentAgent;
    if (targetAgent == null) {
      throw StateError('没有可用的 Agent,请先调用 createAgent()');
    }

    await for (final chunk in targetAgent.chatStreamRaw(message)) {
      if (chunk.content != null && onContent != null) {
        onContent(chunk.content!);
      }
      if (chunk.toolCalls != null && chunk.toolCalls!.isNotEmpty) {
        for (final toolCall in chunk.toolCalls!) {
          onToolCall?.call(toolCall);
        }
      }
    }

    // 自动执行工具调用并继续对话
    if (targetAgent.pendingToolCalls != null &&
        targetAgent.pendingToolCalls!.isNotEmpty) {
      await for (final chunk in targetAgent.executeToolsAndContinue()) {
        if (chunk.content != null && onContent != null) {
          onContent(chunk.content!);
        }
        if (chunk.isDone && chunk.fullContent != null) {
          onComplete?.call(chunk.fullContent!);
        }
      }
    }
  }

  void clearCurrentAgent() {
    _currentAgent?.clearHistory();
    _currentAgent = null;
  }

  void dispose() {
    _currentAgent = null;
    _provider.dispose();
  }
}

Riverpod Provider 模板

复制代码
final agentServiceProvider = Provider<AgentService>((ref) {
  final provider = ref.watch(zhipuAIProvider);
  final service = AgentService(provider);
  ref.onDispose(() => service.dispose());
  return service;
});

协调器消费模板

复制代码
class AiCoordinator extends StateNotifier<AiSessionState> {
  final AgentService _agentService;
  bool _agentInitialized = false;

  void _ensureAgent() {
    if (_agentInitialized) return;
    _agentInitialized = true;

    _agentService.clearCurrentAgent();
    _agentService.createAgent(
      systemPrompt: '你的系统提示词...',
      tools: [/* 你的工具列表 */],
      enableAutoToolExecution: true,
      maxMessages: 30,
    );
  }

  Future<void> submitQuery(String text) async {
    _ensureAgent();

    final buffer = StringBuffer();
    await _agentService.chatWithToolsStream(
      message: text,
      onContent: (chunk) {
        buffer.write(chunk);
        state = state.copyWith(streamingText: buffer.toString());
      },
      onToolCall: (toolCall) {
        state = state.copyWith(status: AiSessionStatus.searching);
      },
      onComplete: (full) {
        state = state.copyWith(status: AiSessionStatus.idle);
      },
    );
  }

  void reset() {
    _agentService.clearCurrentAgent();
    _agentInitialized = false;
    state = const AiSessionState();
  }
}

本篇总结

AgentService 在食界探味里的价值,是把 AI 从"页面里发一次请求"提升成了"移动端里一个可管理的会话对象"。

这层一旦单独成立:

  • 页面层只关心 UI 渲染,不碰模型细节

  • 协调器层专注于产品流程编排,通过 AgentService 的回调驱动状态

  • 工具层统一注册到 AgentService,不分散在各处

  • 鸿蒙原生能力由协调器直接调用,AgentService 保持平台无关

  • 资源生命周期由 Riverpod Provider 统一管理,确保鸿蒙端不泄漏

在鸿蒙设备上,这套分层让 AI 助手既能享受 Flutter 的跨平台 UI 能力,又能无缝接入鸿蒙的语音识别和 TTS 能力,同时保持了清晰的职责边界和资源管理。

相关推荐
Token炼金师37 分钟前
去噪扩散:从随机噪声到高保真图像的数学之路
人工智能·aigc
这个DBA有点耶38 分钟前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
阿里云大数据AI技术1 小时前
阿里云 EMR AI 助手正式发布:从问答工具到全栈智能运维助手
运维·人工智能
Larcher2 小时前
从零搭建 MCP 服务——让 AI 拥有无限扩展能力
人工智能·程序员
zzzzzz3102 小时前
你的 AI 写的 React 烂透了?这个 8000+ Star 的开源工具能揪出 90% 的「Agent 屎山」
人工智能
小星AI2 小时前
MCP协议超详细教程,从入门到实战
人工智能
小星AI2 小时前
Kimi Code CLI 超详细教程,附源码
人工智能·agent
牧艺3 小时前
Cursor Rules / Skills 分层设计:让 Agent 像「团队新同事」
前端·人工智能·cursor
shepherd1113 小时前
一文带你掌握 LLM、Token、Context、Prompt、RAG、MCP、Skill、Agent 等 AI 核心概念
人工智能·后端·ai编程
小林ixn3 小时前
MCP 保姆级入门指南:AI 的“万能充电口”到底怎么玩?
人工智能