HarmonyOS NEXT AI 智能生活助手:PromptManager 设计与实现

HarmonyOS NEXT AI 智能生活助手:PromptManager 设计与实现

前言

在之前的文章中,AI 的 Prompt 是直接写在代码中的------这在企业级项目中是"坏味道"。一旦需要修改 Prompt,就必须修改源代码、重新编译、重新发布。

本文将实现完整的 PromptManager

  1. Prompt 存储:独立文件管理,支持 Markdown 格式
  2. 模板引擎 :参数注入 {``{variable}}
  3. 版本控制:版本号、变更历史、回滚
  4. 动态加载:运行时热加载,无需重新编译
  5. Prompt 分类:按 AI 能力模块划分

一、Prompt 独立管理架构

1.1 整体设计

复制代码
prompt/
├── chat.md           # 聊天系统 Prompt
├── translate.md      # 翻译 Prompt
├── flower.md         # 每日花语
├── summary.md        # 文章总结
├── todo.md           # 待办生成
├── schedule.md       # 日程规划
├── code.md           # 代码解释
├── system.md         # 系统全局 Prompt
└── templates/        # Prompt 模板
    ├── chat.default.md
    └── chat.creative.md

PromptManager
    ↓
loadPrompt(name) → 读取 .md 文件
    ↓
buildPrompt(name, params) → 注入参数
    ↓
AIService → 组装 messages
    ↓
LLM Provider

1.2 Prompt 设计原则

原则 说明 实现方式
模块化 每个 AI 能力独立 Prompt 按文件拆分
参数化 动态内容通过 {``{变量}} 注入 模板引擎替换
版本化 每个 Prompt 有版本号和变更记录 文件头元数据
可测试 支持独立测试和对比 A/B 测试接口
可回滚 快速回退到上一版本 版本号管理

二、Prompt 文件格式

2.1 文件结构规范

每个 Prompt 文件使用 Markdown + YAML front matter 格式:

markdown 复制代码
---
name: chat
version: 2.1.0
description: AI 聊天系统 Prompt
author: HarmonyAI Team
created: 2025-01-01
updated: 2025-06-15
changelog:
  - 2.1.0: 优化角色设定,增加工具使用指导
  - 2.0.0: 重构 Prompt 结构,增强上下文管理
  - 1.0.0: 初始版本
tags:
  - chat
  - system
  - conversation
---

# 角色设定

你是一个名为 **HarmonyAI** 的智能生活助手,由 HarmonyOS NEXT 平台驱动。

## 核心能力

- 多轮对话:{{history}}
- 当前语言:{{language}}
- 用户偏好:{{preferences}}

## 回答规范

1. **简洁准确**:优先用简短的语言回答问题
2. **结构化**:复杂回答使用 Markdown 格式
3. **代码展示**:使用 ```language 标注代码块
4. **中文优先**:默认使用中文回答
5. **友好亲切**:使用 {{tone}} 的语气

## 工具使用

当用户需要以下功能时,主动推荐对应工具:
- 翻译 → 使用翻译功能
- 图片识别 → 使用 OCR 功能
- 代码解释 → 使用代码分析功能

2.2 各能力模块 Prompt 示例

translate.md(翻译 Prompt):

markdown 复制代码
---
name: translate
version: 1.3.0
description: AI 翻译系统 Prompt
---

你是一个专业的翻译助手。请将以下文本从 {{sourceLang}} 翻译为 {{targetLang}}。

## 翻译规范

1. 保持原文的语气和风格
2. 专业术语准确翻译
3. 长句适当拆分
4. 文化差异适当本地化

## 输出格式

{ "translated": "翻译结果", "pronunciation": "发音(可选)", "explanation": "翻译说明" }

## 示例

**输入:** Hello, how are you?
**输出:** { "translated": "你好,最近怎么样?", "pronunciation": "Nǐ hǎo, zuìjìn zěnme yàng?", "explanation": "日常问候的友好表达" }

flower.md(花语 Prompt):

markdown 复制代码
---
name: flower
version: 1.1.0
description: AI 每日花语查询
---

你是一位花语专家。请查询「{{flowerName}}」的花语和寓意。

## 输出格式

请用以下 JSON 格式回答:

{
  "name": "花名",
  "language": "花语(一句话)",
  "meaning": "详细寓意",
  "suggestion": "送花建议",
  "story": "相关的历史故事或传说",
  "poem": "与花相关的诗句"
}

三、PromptManager 核心实现

3.1 Prompt 元数据

typescript 复制代码
// prompt/PromptMeta.ts
export interface PromptMeta {
  name: string;          // Prompt 名称
  version: string;       // 版本号 (semver)
  description: string;   // 描述
  author: string;        // 作者
  created: string;       // 创建日期
  updated: string;       // 更新日期
  changelog: string[];  // 变更记录
  tags: string[];       // 标签
}

export interface PromptTemplate {
  meta: PromptMeta;
  content: string;       // 原始内容
  compiled: string;      // 编译后的内容(无 front matter)
}

export class PromptError extends Error {
  constructor(message: string, public code: string) {
    super(message);
    this.name = 'PromptError';
  }
}

// Prompt 未找到
export class PromptNotFoundError extends PromptError {
  constructor(name: string) {
    super(`Prompt "${name}" not found`, 'PROMPT_NOT_FOUND');
  }
}

// Prompt 版本冲突
export class PromptVersionConflictError extends PromptError {
  constructor(name: string, expected: string, actual: string) {
    super(
      `Prompt "${name}" version conflict: expected ${expected}, got ${actual}`,
      'VERSION_CONFLICT'
    );
  }
}

3.2 PromptManager 实现

typescript 复制代码
// prompt/PromptManager.ts
import resourceManager from '@ohos.resourceManager';

export class PromptManager {
  private static instance: PromptManager;
  private prompts: Map<string, PromptTemplate> = new Map();
  private resourceManager: resourceManager.ResourceManager | null = null;

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

  // 初始化
  async init(context: Context): Promise<void> {
    this.resourceManager = context.resourceManager;
    // 预加载所有 Prompt
    await this.loadAllPrompts();
  }

  // 加载所有 Prompt
  async loadAllPrompts(): Promise<void> {
    const promptNames = [
      'chat', 'translate', 'flower', 'summary',
      'todo', 'schedule', 'code', 'system'
    ];

    for (const name of promptNames) {
      await this.loadPrompt(name);
    }
    hilog.info(0x0000, 'PromptManager',
      'Loaded %{public}d prompts', this.prompts.size);
  }

  // 加载指定 Prompt
  async loadPrompt(name: string): Promise<PromptTemplate> {
    try {
      // 从 resources/rawfile 读取
      const content = await this.readPromptFile(`${name}.md`);
      const parsed = this.parsePromptFile(content);
      this.prompts.set(name, parsed);
      return parsed;
    } catch (error) {
      hilog.error(0x0000, 'PromptManager',
        'Failed to load prompt %{public}s: %{public}s',
        name, error.message);
      throw new PromptNotFoundError(name);
    }
  }

  // 从 rawfile 读取 Prompt 文件
  private async readPromptFile(fileName: string): Promise<string> {
    if (!this.resourceManager) {
      throw new PromptError('ResourceManager not initialized', 'NO_RESOURCE');
    }

    try {
      // HarmonyOS 读取 rawfile
      const data = await this.resourceManager.getRawFileContent(fileName);
      const decoder = util.TextDecoder.create('utf-8');
      return decoder.decodeWithStream(data);
    } catch (e) {
      // fallback: 尝试从本地文件系统读取
      return this.readPromptFromFiles(fileName);
    }
  }

  // 从 files 目录读取(开发/调试用)
  private async readPromptFromFiles(fileName: string): Promise<string> {
    const context = getContext();
    const filePath = `${context.filesDir}/prompts/${fileName}`;
    try {
      const file = await fs.open(filePath, fs.OpenMode.READ_ONLY);
      const stat = await fs.stat(filePath);
      const buf = new ArrayBuffer(stat.size);
      await fs.read(file.fd, buf);
      fs.close(file);
      return util.TextDecoder.create('utf-8').decodeWithStream(buf);
    } catch {
      // 返回默认 Prompt
      return this.getDefaultPrompt(fileName.replace('.md', ''));
    }
  }

  // 解析 Prompt 文件(提取 front matter 和内容)
  private parsePromptFile(content: string): PromptTemplate {
    const meta: PromptMeta = {
      name: '',
      version: '1.0.0',
      description: '',
      author: 'HarmonyAI',
      created: new Date().toISOString().split('T')[0],
      updated: new Date().toISOString().split('T')[0],
      changelog: [],
      tags: []
    };

    let body = content;

    // 解析 YAML front matter (--- xxx ---)
    const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
    if (frontMatterMatch) {
      const yaml = frontMatterMatch[1];
      // 简单解析 key: value
      yaml.split('\n').forEach(line => {
        const [key, ...values] = line.split(':');
        const value = values.join(':').trim();
        switch (key.trim()) {
          case 'name': meta.name = value; break;
          case 'version': meta.version = value; break;
          case 'description': meta.description = value.replace(/^["']|["']$/g, ''); break;
          case 'author': meta.author = value; break;
          case 'created': meta.created = value; break;
          case 'updated': meta.updated = value; break;
          case 'tags': meta.tags = value.replace(/[[\]"]/g, '').split(',').map(t => t.trim()); break;
        }
      });
      body = content.slice(frontMatterMatch[0].length);
    }

    return {
      meta,
      content: body.trim(),
      compiled: body.trim()
    };
  }

  // 构建 Prompt(注入参数)
  buildPrompt(name: string, params: Record<string, string> = {}): string {
    const template = this.prompts.get(name);
    if (!template) {
      throw new PromptNotFoundError(name);
    }

    // 参数替换 {{variable}}
    let compiled = template.compiled;
    for (const [key, value] of Object.entries(params)) {
      const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
      compiled = compiled.replace(regex, value);
    }

    return compiled;
  }

  // 获取 Prompt 元数据
  getPromptMeta(name: string): PromptMeta | null {
    const template = this.prompts.get(name);
    return template ? template.meta : null;
  }

  // 获取所有 Prompt 名称
  getPromptNames(): string[] {
    return Array.from(this.prompts.keys());
  }

  // 获取所有 Prompt 元数据
  getAllMetas(): PromptMeta[] {
    return Array.from(this.prompts.values()).map(t => t.meta);
  }

  // 重新加载指定 Prompt
  async reloadPrompt(name: string): Promise<void> {
    await this.loadPrompt(name);
  }

  // 重新加载所有 Prompt
  async reloadAllPrompts(): Promise<void> {
    await this.loadAllPrompts();
  }

  // 默认 Prompt(兜底)
  private getDefaultPrompt(name: string): string {
    const defaults: Record<string, string> = {
      chat: `你是一个智能生活助手,名叫 HarmonyAI。
请友好地回答用户的问题,使用 Markdown 格式。`,
      translate: `请将以下文本翻译为目标语言。`,
      flower: `请查询指定花的花语和寓意。`,
      summary: `请总结以下文本的核心内容。`,
      todo: `请从以下自然语言中提取待办事项。`,
      schedule: `请根据以下描述生成日程安排。`,
      code: `请解释以下代码的功能和实现原理。`,
      system: `你是 HarmonyAI,一个运行在 HarmonyOS NEXT 上的智能助手。`
    };
    return defaults[name] || '';
  }
}

关键设计 :使用 YAML front matter 管理 Prompt 元数据(版本、标签、变更记录),与正文分离。buildPrompt 方法使用正则替换实现参数注入,支持灵活的 {``{variable}} 语法。

3.3 PromptManager 核心方法汇总

方法 参数 返回值 功能说明
init(context) Context Promise<void> 初始化 ResourceManager
loadAllPrompts() Promise<void> 预加载所有 Prompt 文件
loadPrompt(name) string Promise<PromptTemplate> 加载指定 Prompt
buildPrompt(name, params) string, Record<string, string> string 构建并注入参数
getPromptMeta(name) string `PromptMeta null`
getPromptNames() string[] 获取所有 Prompt 名称
getAllMetas() PromptMeta[] 获取所有元数据
reloadPrompt(name) string Promise<void> 重新加载指定 Prompt
reloadAllPrompts() Promise<void> 重新加载所有 Prompt

四、Prompt 模板引擎

4.1 增强模板引擎

typescript 复制代码
// prompt/TemplateEngine.ts
export class TemplateEngine {
  // 高级模板替换
  static compile(template: string, params: Record<string, any>): string {
    let result = template;

    // 1. 简单变量替换 {{name}}
    result = result.replace(/\{\{(\w+)\}\}/g, (_, key) => {
      return params[key] !== undefined ? String(params[key]) : `{{${key}}}`;
    });

    // 2. 条件渲染 {{#if condition}}content{{/if}}
    result = result.replace(
      /\{\{#if (\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
      (_, key, content) => {
        return params[key] ? content : '';
      }
    );

    // 3. 循环渲染 {{#each items}}item{{/each}}
    result = result.replace(
      /\{\{#each (\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
      (_, key, template) => {
        const items = params[key] as any[];
        if (!items || !Array.isArray(items)) return '';
        return items.map(item => {
          return template.replace(/\{\{this\}\}/g, String(item));
        }).join('\n');
      }
    );

    // 4. 默认值 {{name:default}}
    result = result.replace(/\{\{(\w+):([^}]+)\}\}/g, (_, key, defaultValue) => {
      return params[key] !== undefined ? String(params[key]) : defaultValue;
    });

    return result;
  }

  // 验证参数完整性
  static validateParams(template: string, params: Record<string, any>): string[] {
    const missing: string[] = [];
    const regex = /\{\{(\w+)\}\}/g;
    let match;
    while ((match = regex.exec(template)) !== null) {
      const key = match[1];
      if (params[key] === undefined && !key.startsWith('#')) {
        missing.push(key);
      }
    }
    return missing;
  }
}

4.2 A/B 测试支持

typescript 复制代码
// prompt/PromptABTest.ts
export class PromptABTest {
  private experiments: Map<string, Experiment> = new Map();

  // 注册实验
  register(name: string, variants: string[]) {
    this.experiments.set(name, {
      name,
      variants,
      assignments: new Map()
    });
  }

  // 为用户分配变体
  assign(userId: string, experimentName: string): string {
    const experiment = this.experiments.get(experimentName);
    if (!experiment) throw new Error(`Experiment ${experimentName} not found`);

    // 一致性哈希:同一用户始终看到同一变体
    const hash = this.hashCode(userId + experimentName);
    const index = Math.abs(hash) % experiment.variants.length;
    const variant = experiment.variants[index];

    experiment.assignments.set(userId, variant);
    return variant;
  }

  // 获取用户的变体
  getVariant(userId: string, experimentName: string): string | undefined {
    const experiment = this.experiments.get(experimentName);
    return experiment?.assignments.get(userId);
  }

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

interface Experiment {
  name: string;
  variants: string[];
  assignments: Map<string, string>;
}

五、Prompt 管理页面

5.1 Prompt 设置页面

typescript 复制代码
// pages/SettingPage.ets (Prompt 管理部分)
@Component
struct PromptSettings {
  @State prompts: PromptMeta[] = [];
  @State selectedPrompt: PromptMeta | null = null;
  @State promptContent: string = '';
  @StorageLink('statusBarHeight') statusBarHeight: number = 32;
  @StorageLink('navBarHeight') navBarHeight: number = 24;
  private promptManager = PromptManager.getInstance();

  aboutToAppear() {
    this.prompts = this.promptManager.getAllMetas();
  }

  build() {
    Column() {
      // 顶部安全区占位
      Row().width('100%').height(this.statusBarHeight);

      // Prompt 列表
      List() {
        ForEach(this.prompts, (meta: PromptMeta) => {
          ListItem() {
            Column() {
              Row() {
                Text(meta.name).fontSize(16).fontWeight(FontWeight.Bold);
                Blank();
                Text(`v${meta.version}`)
                  .fontSize(12).fontColor('#6C5CE7')
                  .backgroundColor('#F0F0FF')
                  .borderRadius(8).padding({ left: 8, right: 8 });
              }
              Text(meta.description).fontSize(13).fontColor(Color.Gray);
              Row() {
                ForEach(meta.tags, (tag: string) => {
                  Text(tag).fontSize(11).fontColor('#0984E3')
                    .backgroundColor('#E8F4FD')
                    .borderRadius(4).padding({ left: 6, right: 6, top: 2, bottom: 2 });
                }, (tag: string) => tag);
              }
              .margin({ top: 6 });
            }
            .padding(12)
            .backgroundColor(Color.White)
            .borderRadius(12)
            .margin({ bottom: 8 })
            .onClick(() => {
              this.selectedPrompt = meta;
            });
          }
        }, (meta: PromptMeta) => meta.name);
      }
      .layoutWeight(1);

      // 底部安全区占位
      Row().width('100%').height(this.navBarHeight);
    }
    .padding(16);
  }
}

六、Prompt 优化策略

6.1 历史对话注入

typescript 复制代码
// 构建聊天消息时注入历史对话
async buildChatMessages(text: string): Promise<Message[]> {
  const systemPrompt = this.promptManager.buildPrompt('chat', {
    history: this.formatHistory(),
    language: this.getLanguage(),
    preferences: this.getUserPreferences(),
    tone: '友好亲切'
  });

  return [
    { role: 'system', content: systemPrompt },
    ...this.getRecentHistory(10),  // 最近 10 轮对话
    { role: 'user', content: text }
  ];
}

// 格式化历史对话
private formatHistory(): string {
  return this.messages.slice(-20).map(m =>
    `${m.role === 'user' ? '用户' : '助手'}: ${m.content.slice(0, 100)}`
  ).join('\n');
}

6.2 Few-shot 示例注入

typescript 复制代码
// 在 Prompt 中注入示例
buildPromptWithExamples(name: string, examples: Example[]): string {
  const base = this.promptManager.buildPrompt(name, {});

  const exampleSection = examples.map((ex, i) =>
    `## 示例 ${i + 1}\n\n**输入:** ${ex.input}\n\n**输出:** ${ex.output}`
  ).join('\n\n');

  return `${base}\n\n---\n\n## 参考示例\n\n${exampleSection}`;
}

七、性能与缓存

7.1 Prompt 缓存

typescript 复制代码
// LRU 缓存 Prompt 编译结果
private promptCache: Map<string, { result: string; timestamp: number }> = new Map();
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 分钟

buildPromptWithCache(name: string, params: Record<string, string>): string {
  const cacheKey = `${name}:${JSON.stringify(params)}`;
  const cached = this.promptCache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
    return cached.result;
  }

  const result = this.buildPrompt(name, params);
  this.promptCache.set(cacheKey, { result, timestamp: Date.now() });
  return result;
}

7.2 批量预加载

typescript 复制代码
// 在应用启动时预加载所有 Prompt
onCreate(want, launchParam) {
  PromptManager.getInstance().init(this.context);
  // 不需要等待,后台加载
}

7.3 Prompt 加载策略对比

策略 加载时机 优点 缺点 适用场景
启动预加载 App 启动时 首次使用无延迟 增加启动时间 小型 Prompt 文件
懒加载 首次使用时 启动速度快 首次有短暂延迟 大型 Prompt 文件
混合模式 核心预载 + 其他懒加载 平衡性能 实现稍复杂 企业级应用(推荐)

推荐方案 :HarmonyAI 采用混合模式,chatsystem 在启动时预加载,其他能力模块按需加载。


八、完整使用流程

8.1 Prompt 文件存放位置

复制代码
resources/
├── rawfile/
│   └── prompts/
│       ├── chat.md
│       ├── translate.md
│       ├── flower.md
│       ├── summary.md
│       ├── todo.md
│       ├── schedule.md
│       ├── code.md
│       └── system.md
└── ...

8.2 AIService 调用 Prompt

typescript 复制代码
// service/AIService.ts
async chat(messages: Message[]): Promise<ChatResponse> {
  // 1. 构建系统 Prompt
  const systemPrompt = PromptManager.getInstance().buildPrompt('chat', {
    role: '智能生活助手',
    language: 'zh-CN',
    tone: '友好亲切'
  });

  // 2. 组装消息
  const builtMessages = [
    { role: 'system', content: systemPrompt },
    ...messages
  ];

  // 3. 调用 Provider
  return this.provider.chat({ messages: builtMessages });
}

九、常见问题

9.1 Prompt 参数未替换

typescript 复制代码
// 错误:参数名拼写不一致
// template: "你的角色是 {{role}}"
// buildPrompt('chat', { roles: '助手' }); // 拼写错误

// 正确:参数名一致
buildPrompt('chat', { role: '助手' }); // role 匹配

9.2 文件编码问题

markdown 复制代码
// 错误:Prompt 文件编码不正确导致解析失败
// 解决方案:确保 .md 文件使用 UTF-8 编码保存

// 检查编码
file --mime-encoding prompt/chat.md
// 输出:chat.md: utf-8

十、Git 提交

bash 复制代码
git add .
git commit -m "feat(prompt): 完成 PromptManager 设计与实现

- 设计 YAML front matter Prompt 文件格式
- 实现 PromptManager(加载/解析/编译/缓存)
- 实现 TemplateEngine(变量注入/条件/循环)
- 实现 Prompt 版本管理与变更记录
- 支持热加载和 A/B 测试
- 实现 Prompt 管理设置页面
- 8 个能力模块 Prompt 模板
- LRU 缓存优化

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

git tag v0.0.7

总结

本文实现了完整的 Prompt 独立管理体系,这是企业级 AI 应用的关键基础设施。核心要点:

  1. 独立文件存储 :每个 Prompt 独立 .md 文件,支持 YAML front matter
  2. 模板引擎{``{variable}} 参数注入、条件渲染、循环渲染
  3. 版本管理:semver 版本号、changelog、元数据追踪
  4. 动态加载:运行时加载和重新加载,无需重新编译
  5. 缓存优化:LRU 缓存 + TTL,减少重复编译
  6. A/B 测试:支持 Prompt 变体分配和效果对比
  7. 8 个 Prompt 模板:覆盖所有 AI 能力模块

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


上一篇: 会话管理与聊天记录保存

下一篇: Provider 抽象与模型切换

相关资源:

相关推荐
我是大卫1 小时前
从零到答辩全链路:花1小时用TRAE Work搞定100页案例报告+40页答辩PPT
人工智能·trae
极序时代GEO品牌优化1 小时前
杭州极序时代|面向地域、设备与用户画像的动态GEO
人工智能·python·算法·极序时代geo
崔高杰1 小时前
【自动阅读笔记】Inference-Time Scaling for Generalist Reward Modeling
人工智能·笔记·机器学习
风途科技~1 小时前
隧道氮氧化物检测仪|实时监测 NO、NO₂浓度,助力公路隧道通风智能调控
大数据·人工智能
senmuleishi1 小时前
持续发力嵌入式AI IDE,攻坚大科学装置电源解决方案
ide·人工智能
刘婉晴2 小时前
【大模型安全】OWASP 大语言模型十大风险
人工智能·安全·语言模型
mlidongfeng2 小时前
[AI][C++26] SIMD 编程模型思考
开发语言·c++·人工智能
关于不上作者榜就原神启动那件事2 小时前
从 MDC 到 Agent:我手搓的文档路由协议,在 Spring AI Alibaba 里找到了正式实现
java·人工智能·spring·ai·agent
大模型真好玩2 小时前
LangChain DeepAgents 速通指南(十二)——一文详解生产级智能体的命令体系和工程设计
人工智能·langchain·agent