HarmonyOS 接入 AI 完整教程

目录

  1. 核心概念

  2. 接入步骤

  3. 代码实现详解

  4. 关键技术点

  5. 常见问题


核心概念

什么是 AI API?

AI API 是一个 HTTP 接口,你发送文本请求,AI 返回生成的回复。就像调用天气 API 一样简单。

DeepSeek API 工作原理

你的应用 → HTTP POST 请求 → DeepSeek 服务器 → AI 处理 → 返回回复 → 你的应用显示

     如果我要在应用中接入ai,如:deepseek

  1. 首先要获取deepseek的apikey,知道请求的端点是什么
  2. 然后,构建请求格式,封装请求头、请求体,响应体的接口对象
  3. 再进行实现时,要创建消息模型的枚举对象,并且封装消息实体,接着,需要去创建api服务,设置api,发送对话请求,解析响应
  4. 在发送对话请求时,首先需要构建请求体,然后使用http.createHttp().request()异步获取相应对象,设置请求方法、请求头、请求超时时间等参数
  5. 然后在解析响应时,根据状态码返回不同的业务,当状态码为200时,反序列化这个响应对象,并将其断言为string类型,将result返回给调用处
  6. 在单次对话结束后,释放资源。
  7. UI层点击发送消息时,构建对话历史对象,并且获取到对话历史的每一条消息,同时,通过deepseekService.chat方法可以获取到每一条历史记录的结果,如果要提取ai的回复,则可以从封装的消息对象中获取到消息的内容,和ai的思考的内容,通过构建消息对象,并为该对象添加思考过程,然后可以将这个对象添加到消息数组中。
  8. 若要对消息进行持久化存储,则封装了rdb关系型数据库,通过relationalStore.getRdbStore传入上下文和config构建数据库对象,通过executeSQL创建表结构,保存消息时,通过封装saveMessage进行消息的保存,主要就是进行了一个插入操作,查询消息时,通过query传入谓词对象可以获去一个结果集,当结果集存在时,可以通过goToNextRow获取到每行的信息,最后将查询到的消息push到初始的数组对象中。对于流式响应的实现,在获取到响应后,需要对数据进行解析,对数据进行过滤,在反序列化,最后通过实时回调更新ui

接入步骤

第 1 步:获取 API Key

  1. 访问 https://platform.deepseek.com/

  2. 注册账号并登录

  3. 进入 API Keys 页面

  4. 创建新的 API Key(类似:sk-xxxxxxxxxxxxx

  5. 保存好这个 Key(只显示一次)

第 2 步:理解 API 请求格式

请求端点
POST https://api.deepseek.com/v1/chat/completions
请求头
{
  "Content-Type": "application/json",
  "Authorization": "Bearer sk-your-api-key"
}
请求体
{
  "model": "deepseek-chat",
  "messages": [
    {"role": "user", "content": "你好"}
  ],
  "stream": false
}
响应体
{
  "id": "chatcmpl-xxx",
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "你好!有什么我可以帮助你的吗?",
        "reasoning_content": "用户打招呼,我应该礼貌回应"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 20,
    "total_tokens": 30
  }
}

代码实现详解

架构设计

UI 层 (AiChatPage.ets)
    ↓
服务层 (DeepSeekService.ets)
    ↓
网络层 (HTTP Request)
    ↓
数据层 (AiDatabase.ets)

1. 创建数据模型

文件:model/AiMessage.ets

// 消息角色
export enum MessageRole {
  USER = 'user',        // 用户消息
  ASSISTANT = 'assistant', // AI 回复
  SYSTEM = 'system'     // 系统提示(可选)
}
​
// 消息实体
export class AiMessage {
  id: string = '';
  role: MessageRole = MessageRole.USER;
  content: string = '';              // 消息内容
  thinkingProcess?: string = '';     // AI 思考过程
  timestamp: number = Date.now();
  conversationId: string = '';       // 所属会话
​
  constructor(role: MessageRole, content: string, conversationId: string) {
    this.id = `${Date.now()}_${Math.random()}`;
    this.role = role;
    this.content = content;
    this.conversationId = conversationId;
  }
}

为什么需要这些字段?

  • role: 区分是用户说的还是 AI 说的

  • content: 消息的实际内容

  • thinkingProcess: DeepSeek Reasoner 模型会返回思考过程

  • conversationId: 支持多个对话会话

2. 创建 API 服务

文件:services/DeepSeekService.ets

import { http } from '@kit.NetworkKit';
​
export class DeepSeekService {
  private apiKey: string = '';
  private baseUrl: string = 'https://api.deepseek.com/v1';
​
  // 设置 API Key
  setApiKey(apiKey: string) {
    this.apiKey = apiKey;
  }
​
  // 发送对话请求
  async chat(
    messages: DeepSeekMessage[],
    model: string = 'deepseek-chat'
  ): Promise<DeepSeekResponse> {
    // 1. 构建请求体
    const requestBody = {
      model: model,
      messages: messages,
      stream: false  // 非流式响应
    };
​
    // 2. 创建 HTTP 请求
    const httpRequest = http.createHttp();
    
    try {
      // 3. 发送 POST 请求
      const response = await httpRequest.request(
        `${this.baseUrl}/chat/completions`,
        {
          method: http.RequestMethod.POST,
          header: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${this.apiKey}`  // 关键:API Key 认证
          },
          extraData: JSON.stringify(requestBody),
          expectDataType: http.HttpDataType.STRING,
          connectTimeout: 60000,  // 60秒超时
          readTimeout: 60000
        }
      );
​
      // 4. 解析响应
      if (response.responseCode === 200) {
        const result: DeepSeekResponse = JSON.parse(response.result as string);
        return result;
      } else {
        throw new Error(`API Error: ${response.responseCode}`);
      }
    } finally {
      // 5. 释放资源
      httpRequest.destroy();
    }
  }
}

关键点解析:

  1. API Key 认证

    'Authorization': `Bearer ${this.apiKey}`

    这是标准的 Bearer Token 认证方式

  2. 请求体格式

    {
      model: "deepseek-chat",      // 模型名称
      messages: [                   // 对话历史
        {role: "user", content: "你好"}
      ],
      stream: false                 // 是否流式返回
    }
  3. 超时设置

    connectTimeout: 60000,  // 连接超时
    readTimeout: 60000      // 读取超时

    AI 响应可能较慢,需要设置较长的超时时间

3. UI 层调用

文件:pages/AiChatPage.ets

async sendMessage() {
  // 1. 构建对话历史
  const historyMessages: DeepSeekMessage[] = [];
  this.messages.forEach((m: AiMessage) => {
    if (m.role !== MessageRole.SYSTEM) {
      historyMessages.push({
        role: m.role as 'user' | 'assistant',
        content: m.content
      });
    }
  });
​
  // 2. 调用 API
  try {
    const result = await this.deepSeekService.chat(
      historyMessages,
      this.selectedModel.id
    );
​
    // 3. 提取 AI 回复
    if (result.choices && result.choices.length > 0) {
      const aiReply = result.choices[0].message.content;
      const thinking = result.choices[0].message.reasoning_content;
      
      // 4. 显示在 UI 上
      const aiMessage = new AiMessage(MessageRole.ASSISTANT, aiReply, this.conversationId);
      aiMessage.thinkingProcess = thinking;
      this.messages.push(aiMessage);
    }
  } catch (error) {
    console.error('API Error:', error);
  }
}

为什么要传对话历史?

// 单轮对话(AI 没有记忆)
messages: [
  {role: "user", content: "我叫小明"}
]
​
// 多轮对话(AI 有上下文)
messages: [
  {role: "user", content: "我叫小明"},
  {role: "assistant", content: "你好小明!"},
  {role: "user", content: "我叫什么名字?"}  // AI 能记住你叫小明
]

4. 数据持久化

文件:database/AiDatabase.ets

export class AiDatabase {
  private store?: relationalStore.RdbStore;
​
  // 初始化数据库
  async init(): Promise<void> {
    const config: relationalStore.StoreConfig = {
      name: 'AiChat.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };
    this.store = await relationalStore.getRdbStore(this.context, config);
    
    // 创建表
    await this.store.executeSql(`
      CREATE TABLE IF NOT EXISTS messages (
        id TEXT PRIMARY KEY,
        role TEXT,
        content TEXT,
        thinkingProcess TEXT,
        timestamp INTEGER,
        conversationId TEXT
      )
    `);
  }
​
  // 保存消息
  async saveMessage(message: AiMessage): Promise<void> {
    const valueBucket: relationalStore.ValuesBucket = {
      id: message.id,
      role: message.role,
      content: message.content,
      thinkingProcess: message.thinkingProcess || '',
      timestamp: message.timestamp,
      conversationId: message.conversationId
    };
    await this.store?.insert('messages', valueBucket);
  }
​
  // 查询消息
  async getMessages(conversationId: string): Promise<AiMessage[]> {
    const predicates = new relationalStore.RdbPredicates('messages');
    predicates.equalTo('conversationId', conversationId);
    predicates.orderByAsc('timestamp');
    
    const resultSet = await this.store?.query(predicates);
    const messages: AiMessage[] = [];
    
    if (resultSet) {
      while (resultSet.goToNextRow()) {
        const msg = new AiMessage(MessageRole.USER, '', conversationId);
        msg.id = resultSet.getString(resultSet.getColumnIndex('id'));
        msg.role = resultSet.getString(resultSet.getColumnIndex('role')) as MessageRole;
        msg.content = resultSet.getString(resultSet.getColumnIndex('content'));
        msg.thinkingProcess = resultSet.getString(resultSet.getColumnIndex('thinkingProcess'));
        msg.timestamp = resultSet.getLong(resultSet.getColumnIndex('timestamp'));
        messages.push(msg);
      }
      resultSet.close();
    }
    return messages;
  }
}

关键技术点

1. 流式响应 vs 非流式响应

非流式(一次性返回)

// 请求
{ "stream": false }
​
// 响应(等待完整回复后一次性返回)
{
  "choices": [{
    "message": {"content": "完整的回复内容"}
  }]
}

流式(逐字返回,类似 ChatGPT)

// 请求
{ "stream": true }
​
// 响应(多次返回,每次一小段)
data: {"choices":[{"delta":{"content":"你"}}]}
data: {"choices":[{"delta":{"content":"好"}}]}
data: {"choices":[{"delta":{"content":"!"}}]}
data: [DONE]

流式响应实现:

async chatStream(
  messages: DeepSeekMessage[],
  onContent?: (content: string) => void
): Promise<void> {
  const response = await httpRequest.request(url, {
    extraData: JSON.stringify({ stream: true, messages })
  });
​
  // 解析流式数据
  const lines = (response.result as string).split('\n');
  let fullContent = '';
  
  for (const line of lines) {
    if (line.startsWith('data: ')) {
      const data = line.substring(6).trim();
      if (data === '[DONE]') break;
      
      const json = JSON.parse(data);
      const delta = json.choices[0].delta;
      
      if (delta.content) {
        fullContent += delta.content;
        onContent?.(fullContent);  // 实时回调,更新 UI
      }
    }
  }
}

2. 对话上下文管理

为什么需要上下文?

// ❌ 错误:每次只发送当前消息
await api.chat([
  {role: "user", content: "今天天气怎么样?"}
]);
​
// ✅ 正确:发送完整对话历史
await api.chat([
  {role: "user", content: "我在北京"},
  {role: "assistant", content: "好的,北京今天..."},
  {role: "user", content: "今天天气怎么样?"}  // AI 知道你在北京
]);

上下文截断(避免超出 Token 限制)

// 只保留最近 10 轮对话
const recentMessages = allMessages.slice(-20);  // 10轮 = 20条消息

3. Token 计算

什么是 Token?

  • 1 个中文字 ≈ 2-3 个 Token

  • 1 个英文单词 ≈ 1-2 个 Token

  • "你好" ≈ 4-6 个 Token

Token 限制:

// DeepSeek Chat: 最大 32K tokens
// 输入 + 输出 不能超过 32K
​
// 示例
{
  "usage": {
    "prompt_tokens": 100,      // 输入消耗
    "completion_tokens": 50,   // 输出消耗
    "total_tokens": 150        // 总消耗
  }
}

4. 错误处理

try {
  const result = await api.chat(messages);
} catch (error) {
  // 常见错误码
  if (error.code === 401) {
    // API Key 无效
  } else if (error.code === 429) {
    // 请求频率过高
  } else if (error.code === 500) {
    // 服务器错误
  }
}

5. 成本优化

减少 Token 消耗:

// 1. 精简系统提示
const systemPrompt = "你是助手";  // ✅ 简洁
// 而不是
const systemPrompt = "你是一个非常有帮助的AI助手...";  // ❌ 冗长
​
// 2. 限制对话历史长度
const recentMessages = messages.slice(-10);
​
// 3. 使用更便宜的模型
// deepseek-chat: ¥1/百万 tokens
// deepseek-reasoner: ¥14/百万 tokens

常见问题

Q1: 如何调试 API 调用?

添加日志:

console.info('Request:', JSON.stringify(requestBody));
console.info('Response:', JSON.stringify(response));

使用 Postman 测试:

POST https://api.deepseek.com/v1/chat/completions
Headers:
  Content-Type: application/json
  Authorization: Bearer sk-your-key
Body:
{
  "model": "deepseek-chat",
  "messages": [{"role": "user", "content": "你好"}]
}

Q2: 为什么 AI 没有记忆?

原因: 没有传递对话历史

解决:

// ❌ 错误
const messages = [{role: "user", content: currentInput}];
​
// ✅ 正确
const messages = this.allMessages.map(m => ({
  role: m.role,
  content: m.content
}));

Q3: 如何实现打字机效果?

使用流式响应:

let displayText = '';
await api.chatStream(messages, (chunk) => {
  displayText += chunk;
  this.aiReply = displayText;  // 实时更新 UI
});

Q4: API Key 如何安全存储?

// 1. 使用 Preferences 加密存储
import { preferences } from '@kit.ArkData';
​
// 2. 不要硬编码在代码中
const apiKey = 'sk-xxx';  // ❌ 危险
​
// 3. 从用户输入获取
TextInput({ text: $$this.apiKey, type: InputType.Password })

Q5: 如何支持多模型切换?

const models = [
  { id: 'deepseek-chat', name: 'Chat', price: '¥1/M' },
  { id: 'deepseek-reasoner', name: 'Reasoner', price: '¥14/M' }
];
​
// 用户选择模型
await api.chat(messages, selectedModel.id);

完整流程图

用户输入
  ↓
[UI 层] 收集消息
  ↓
[服务层] 构建请求
  ↓
[网络层] HTTP POST
  ↓
DeepSeek API
  ↓
[网络层] 接收响应
  ↓
[服务层] 解析 JSON
  ↓
[UI 层] 显示回复
  ↓
[数据层] 保存到数据库

总结

接入 AI 的核心步骤:

  1. 获取 API Key - 注册并获取认证凭证

  2. 理解 API 格式 - 请求/响应的 JSON 结构

  3. 发送 HTTP 请求 - 使用 @kit.NetworkKit

  4. 解析响应 - 提取 AI 回复内容

  5. 管理上下文 - 传递对话历史

  6. 持久化存储 - 保存对话记录

关键代码只有 3 行:

const response = await http.request(url, {
  header: { 'Authorization': `Bearer ${apiKey}` },
  extraData: JSON.stringify({ model, messages })
});
const result = JSON.parse(response.result);
const aiReply = result.choices[0].message.content;

就这么简单!🎉


参考资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐