HarmonyOS NEXT AI 智能生活助手:统一 AIService 封装

HarmonyOS NEXT AI 智能生活助手:统一 AIService 封装

图1:AIService 统一架构图

前言

第 08 篇第 09 篇中,我们分别实现了 PromptManagerProvider 。本文将设计统一 AIService,作为所有 AI 能力的统一入口,整合 PromptManager、CacheManager 和 LLM Provider。

AIService 是连接 UI 层和 AI 能力的桥梁。所有 AI 功能------聊天、翻译、OCR、花语、总结------都通过 AIService 统一调用,无需直接操作 Provider 或 PromptManager。PromptManager 独立管理,支持模板注入和版本控制。


一、AIService 架构

1.1 分层架构图

text 复制代码
UI (Pages)
  ↓ ViewModel/Manager
AIService(统一入口)
  ├── PromptManager → 构建 Prompt(支持模板注入和版本控制)
  ├── CacheManager → 缓存结果(内存 + 持久化)
  └── LLM Provider → 调用 AI API(OpenAI/DeepSeek/Qwen/智谱/豆包)

1.2 架构职责表

层级 职责 技术 关键特性
UI 页面和组件 ArkUI 声明式语法
Manager AI 能力管理者 Chat/Translate/Flower... 业务封装
AIService 统一入口 路由 + 构建 + 调用 单例模式
PromptManager Prompt 管理 模板引擎 版本控制、热加载
LLM Provider 模型接口 OpenAI/DeepSeek/Qwen/智谱/豆包 统一接口

二、AIService 实现

2.1 核心服务类

typescript 复制代码
// service/AIService.ts
export class AIService {
  private static instance: AIService;
  private provider!: LLMProvider;
  private promptManager = PromptManager.getInstance();
  private cacheManager = CacheManager.getInstance();

  static getInstance(): AIService {
    if (!AIService.instance) {
      AIService.instance = new AIService();
    }
    return AIService.instance;
  }

  // 设置 Provider
  setProvider(provider: LLMProvider): void {
    this.provider = provider;
  }

  getProvider(): LLMProvider {
    return this.provider;
  }

  // 统一聊天接口(非流式)
  async chat(messages: Message[], options?: {
    promptName?: string;
    promptParams?: Record<string, string>;
    useCache?: boolean;
  }): Promise<ChatResponse> {
    // 1. 构建消息(注入系统 Prompt)
    const builtMessages = await this.buildMessages(messages, options);

    // 2. 缓存检查
    const cacheKey = JSON.stringify(builtMessages);
    if (options?.useCache !== false) {
      const cached = await this.cacheManager.get<ChatResponse>(cacheKey);
      if (cached) return cached;
    }

    // 3. 调用 Provider
    const response = await this.provider.chat({ messages: builtMessages });

    // 4. 写入缓存
    await this.cacheManager.set(cacheKey, response, 5 * 60 * 1000);

    // 5. 统计
    this.recordUsage(response.usage);

    return response;
  }

  // 流式聊天接口
  async *chatStream(messages: Message[], options?: {
    promptName?: string;
    promptParams?: Record<string, string>;
  }): AsyncGenerator<StreamChunk> {
    const builtMessages = await this.buildMessages(messages, options);
    const stream = this.provider.chatStream({ messages: builtMessages });

    for await (const chunk of stream) {
      yield chunk;
      if (chunk.isEnd) break;
    }
  }

  // 构建完整消息(注入系统 Prompt)
  private async buildMessages(
    messages: Message[],
    options?: { promptName?: string; promptParams?: Record<string, string> }
  ): Promise<Message[]> {
    const promptName = options?.promptName || 'chat';
    const promptParams = options?.promptParams || {};

    const systemPrompt = await this.promptManager.buildPrompt(promptName, promptParams);

    return [
      { role: 'system', content: systemPrompt },
      ...messages
    ];
  }

  // 测试连接
  async testConnection(): Promise<ConnectionTestResult> {
    return this.provider.testConnection();
  }

  // 用量统计
  private usageStats = { totalTokens: 0, totalCost: 0, requestCount: 0 };

  private recordUsage(usage?: TokenUsage): void {
    if (usage) {
      this.usageStats.totalTokens += usage.totalTokens;
      this.usageStats.totalCost += usage.cost || 0;
      this.usageStats.requestCount++;
    }
  }

  getUsageStats() {
    return { ...this.usageStats };
  }
}

2.2 Manager 注册机制

typescript 复制代码
// service/AIManagerRegistry.ts
export class AIManagerRegistry {
  private static managers: Map<string, AIManager> = new Map();

  static register(name: string, manager: AIManager): void {
    this.managers.set(name, manager);
  }

  static get<T extends AIManager>(name: string): T {
    const manager = this.managers.get(name);
    if (!manager) throw new Error(`Manager not found: ${name}`);
    return manager as T;
  }

  static getAll(): AIManager[] {
    return Array.from(this.managers.values());
  }

  // 初始化所有 Manager
  static async initAll(): Promise<void> {
    const aiService = AIService.getInstance();
    for (const [name, manager] of this.managers) {
      await manager.init(aiService);
      hilog.info(0x0000, 'AIManager', 'Initialized: %{public}s', name);
    }
  }
}

export interface AIManager {
  name: string;
  init(service: AIService): Promise<void>;
}

// 注册示例
AIManagerRegistry.register('chat', ChatManager.getInstance());
AIManagerRegistry.register('translate', TranslateManager.getInstance());
AIManagerRegistry.register('flower', FlowerManager.getInstance());
AIManagerRegistry.register('summary', SummaryManager.getInstance());
AIManagerRegistry.register('code', CodeManager.getInstance());
AIManagerRegistry.register('todo', TodoManager.getInstance());
AIManagerRegistry.register('schedule', ScheduleManager.getInstance());

2.3 Manager 映射表

Manager 注册名 AIService 方法 Prompt 模板 功能说明
ChatManager chat chat / chatStream chat.md 智能对话
TranslateManager translate chat translate.md 多语言翻译
FlowerManager flower chat flower.md 花语查询
SummaryManager summary chat summary.md 文章摘要
CodeManager code chat code.md 代码分析
TodoManager todo chat todo.md 待办生成
ScheduleManager schedule chat schedule.md 日程规划

统一路由:所有 AI 能力通过 AIService 单一路径调用。新增 Manager 只需注册到 Registry,无需修改 AIService 核心代码。


三、错误处理

3.1 统一错误处理器

typescript 复制代码
// service/AIServiceErrorHandler.ts
export class AIServiceErrorHandler {
  static handle(error: Error): AppError {
    if (error.name === 'ProviderError') {
      return { code: 'PROVIDER_ERROR', message: 'AI 服务异常', retryable: true };
    }
    if (error.name === 'PromptError') {
      return { code: 'PROMPT_ERROR', message: 'Prompt 模板错误', retryable: false };
    }
    if (error.message?.includes('timeout')) {
      return { code: 'TIMEOUT', message: '请求超时,请重试', retryable: true };
    }
    if (error.message?.includes('401') || error.message?.includes('unauthorized')) {
      return { code: 'AUTH_ERROR', message: 'API Key 无效', retryable: false };
    }
    if (error.message?.includes('429')) {
      return { code: 'RATE_LIMIT', message: '请求过于频繁,请稍后重试', retryable: true };
    }
    return { code: 'UNKNOWN', message: '未知错误', retryable: true };
  }
}

interface AppError {
  code: string;
  message: string;
  retryable: boolean;
}

3.2 错误码对照表

错误码 场景 用户提示 是否可重试
PROVIDER_ERROR AI 服务异常 服务暂时不可用
PROMPT_ERROR 模板渲染失败 请检查 Prompt 配置
TIMEOUT 请求超时 网络较慢,请重试
AUTH_ERROR API Key 无效 请检查 API Key
RATE_LIMIT 频率限制 请求过于频繁
UNKNOWN 未知错误 发生未知错误

四、使用示例

4.1 初始化流程

typescript 复制代码
// EntryAbility.ts
async onCreate() {
  // 1. 创建 Provider(支持 OpenAI/DeepSeek/Qwen/智谱/豆包)
  const provider = ProviderFactory.create({
    provider: 'OpenAI',
    apiKey: await PreferenceUtil.get('api_key', '')
  });

  // 2. 设置到 AIService
  AIService.getInstance().setProvider(provider);

  // 3. 初始化 PromptManager(支持模板注入和版本控制)
  await PromptManager.getInstance().init(this.context);

  // 4. 初始化 CacheManager
  await CacheManager.getInstance().init(this.context);

  // 5. 初始化所有 Manager
  await AIManagerRegistry.initAll();

  // 6. 加载首页
  windowStage.loadContent('pages/HomePage');
}

4.2 调用 AI 能力

typescript 复制代码
// 基础聊天
const response = await AIService.getInstance().chat([
  { role: 'user', content: 'Hello' }
]);

// 翻译(使用 Prompt 模板)
const translated = await AIService.getInstance().chat(
  [{ role: 'user', content: 'Hello' }],
  {
    promptName: 'translate',
    promptParams: { sourceLang: '英文', targetLang: '中文' }
  }
);

// 流式聊天
const stream = AIService.getInstance().chatStream([
  { role: 'user', content: '写一首诗' }
]);
for await (const chunk of stream) {
  console.log(chunk.content);
}

五、性能测试

5.1 请求延迟对比

typescript 复制代码
// benchmark/AIServiceBenchmark.ts
export class AIServiceBenchmark {
  static async run(): Promise<BenchmarkResult> {
    const service = AIService.getInstance();
    const results: BenchmarkResult = { avgLatency: 0, p95Latency: 0, throughput: 0 };

    const latencies: number[] = [];
    const startTime = Date.now();
    const requestCount = 10;

    for (let i = 0; i < requestCount; i++) {
      const reqStart = Date.now();
      await service.chat([{ role: 'user', content: 'Hello' }], {
        promptName: 'chat',
        useCache: false
      });
      latencies.push(Date.now() - reqStart);
    }

    const totalTime = Date.now() - startTime;
    latencies.sort((a, b) => a - b);

    results.avgLatency = latencies.reduce((a, b) => a + b, 0) / latencies.length;
    results.p95Latency = latencies[Math.floor(latencies.length * 0.95)];
    results.throughput = requestCount / (totalTime / 1000);

    return results;
  }
}

interface BenchmarkResult {
  avgLatency: number;
  p95Latency: number;
  throughput: number;
}

5.2 性能数据表

场景 平均延迟 P95 延迟 吞吐量
非缓存请求 800ms 1200ms 1.2 req/s
缓存命中 <5ms 10ms 200 req/s
流式输出(首字) 300ms 500ms ---
流式输出(完成) 2s 3s ---

性能数据:缓存命中后响应时间从 800ms 降低到 5ms,吞吐量提升 160 倍。流式输出首字延迟 300ms,用户体验流畅。

5.3 并发请求处理

typescript 复制代码
// service/ConcurrencyManager.ts
export class ConcurrencyManager {
  private activeRequests: number = 0;
  private readonly MAX_CONCURRENT = 5;
  private queue: Array<() => Promise<object>> = [];

  async execute<T>(task: () => Promise<T>): Promise<T> {
    if (this.activeRequests >= this.MAX_CONCURRENT) {
      return new Promise((resolve, reject) => {
        this.queue.push(async () => {
          try { resolve(await task()); }
          catch (e) { reject(e); }
        });
      });
    }

    this.activeRequests++;
    try {
      return await task();
    } finally {
      this.activeRequests--;
      if (this.queue.length > 0) {
        const next = this.queue.shift()!;
        next();
      }
    }
  }

  get activeCount(): number { return this.activeRequests; }
  get queueLength(): number { return this.queue.length; }
}

六、日志与监控

6.1 请求日志

typescript 复制代码
// service/AIServiceLogger.ts
export class AIServiceLogger {
  private logBuffer: LogEntry[] = [];
  private readonly MAX_BUFFER = 100;
  private readonly LOG_TAG = 'AIService';

  log(type: 'request' | 'response' | 'error' | 'cache', data: object): void {
    const entry: LogEntry = {
      type,
      timestamp: Date.now(),
      data: this.sanitize(data),
      duration: data['duration'] || 0
    };

    this.logBuffer.push(entry);
    if (this.logBuffer.length > this.MAX_BUFFER) {
      this.logBuffer.shift();
    }

    hilog.info(0x0000, this.LOG_TAG,
      '[%{public}s] %{public}ds: %{public}s',
      type, entry.duration, JSON.stringify(entry.data).slice(0, 200));
  }

  getRecentLogs(count: number = 20): LogEntry[] {
    return this.logBuffer.slice(-count);
  }

  getErrorRate(): number {
    const total = this.logBuffer.length;
    if (total === 0) return 0;
    const errors = this.logBuffer.filter(e => e.type === 'error').length;
    return errors / total;
  }

  private sanitize(data: object): object {
    const sanitized = { ...data };
    if (sanitized['apiKey']) sanitized['apiKey'] = '***';
    if (sanitized['messages']) {
      sanitized['messages'] = (sanitized['messages'] as object[]).map((m: object) => ({
        ...m,
        content: (m as Record<string, string>)['content']?.slice(0, 100)
      }));
    }
    return sanitized;
  }

  clear(): void { this.logBuffer = []; }
}

interface LogEntry {
  type: string;
  timestamp: number;
  data: object;
  duration: number;
}

6.2 灰度发布

typescript 复制代码
// service/GrayReleaseManager.ts
export class GrayReleaseManager {
  private static instance: GrayReleaseManager;
  private grayUsers: Set<string> = new Set();
  private readonly GRAY_RATIO = 0.1; // 10% 灰度

  static getInstance(): GrayReleaseManager {
    if (!GrayReleaseManager.instance) {
      GrayReleaseManager.instance = new GrayReleaseManager();
    }
    return GrayReleaseManager.instance;
  }

  isInGray(userId: string): boolean {
    // 一致性哈希决定灰度分组
    const hash = this.hash(userId);
    return hash % 100 < this.GRAY_RATIO * 100;
  }

  getProviderForUser(userId: string): string {
    if (this.isInGray(userId)) {
      return 'DeepSeek'; // 灰度新 Provider
    }
    return 'OpenAI'; // 稳定 Provider
  }

  private hash(str: string): number {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = ((hash << 5) - hash) + str.charCodeAt(i);
      hash |= 0;
    }
    return Math.abs(hash);
  }
}

七、Git 提交

bash 复制代码
git add .
git commit -m "feat(service): AIService 性能与监控

- 统一 AIService 入口封装
- Manager 注册机制(零侵入扩展)
- 统一错误处理与错误码
- 基准测试框架
- 并发请求管理器
- 请求日志与错误率监控
- 灰度发布机制
- 缓存命中率统计
- PromptManager 模板注入与版本控制

Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>"
git tag v0.2.1

总结

本文实现了 统一 AIService 封装。核心要点如下:

  1. 单一入口:所有 AI 能力通过 AIService 调用,屏蔽底层差异
  2. 自动注入:系统 Prompt 自动构建消息,PromptManager 支持模板注入和版本控制
  3. 缓存集成:CacheManager 提供内存缓存 + 持久化缓存,减少重复 API 调用
  4. Manager 注册:扩展新能力无需改核心,符合开闭原则
  5. 统一错误处理:标准化错误码和用户提示
  6. 多 Provider 支持:OpenAI、DeepSeek、Qwen、智谱、豆包统一通过 LLMProvider 接口
  7. 监控体系:日志、错误率、灰度发布全覆盖

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源


下一篇预告: 23-主题切换与玻璃拟态------ 实现 Light/Dark/Auto 三种主题模式,以及 GlassCard 玻璃拟态组件,打造通透现代的视觉效果。

相关推荐
星栈独行8 小时前
决定 Agent 交付下限的「操作系统」:Harness 六层架构拆解
人工智能·架构
AKAMAI8 小时前
实时可观测性:Akamai Cloud Pulse 警报功能正式发布
人工智能·云计算
Capricorn19889 小时前
解决全网抄 Karpathy 导致的 LLM Wiki 污染?基于知芽 Notebook Skill 的 raw/ 笔记法排障指南
大数据·论文阅读·人工智能·笔记·论文笔记
财经三剑客9 小时前
小赢科技发布2026年Q2财报:持续AI场景落地提效,夯实经营发展韧性
人工智能·科技
不开大的凯20779 小时前
开源、资本、落地、入口:AI正在同时打赢四场战争
人工智能·开源
神奇霸王龙9 小时前
Cursor 3 + Claude Opus 4.8 屠榜:5 编程基座 IDE 卡位
ide·人工智能·ai·aigc·agent·ai编程·ai写作
临沂GEO9 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
AI创界者9 小时前
IndexTTS 2.5 零样本语音克隆本地部署指南与实战避坑(附WebUI使用技巧)
人工智能·aigc
大模型码小白10 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
Zentceh10 小时前
AI-ISP在夜视机芯中的应用:从传统ISP到PixelClean全彩夜视的进化
人工智能·科技·算法·计算机视觉·车载系统·视频·智能硬件