Echo Server:本地 AI 聊天助手集成

第七阶段:在社区项目中接入 llama.cpp、Qwen 模型、多轮会话和 SSE 流式输出

AIllama.cppQwenSSE

0. 阶段目标

在本地 WSL 环境部署 llama.cpp 推理服务,让 Drogon 后端通过 HTTP 调用本地大模型,并保存 AI 会话历史。

项目说明
建议位置精选作品详情页 / 项目文档页
统一风格左侧导航、右侧正文、表格说明、折叠代码块、阶段总结。
阅读顺序建议从总览页进入,再按卡片顺序阅读。

1. 下载与编译 llama.cpp

在 echo-ai 目录中下载 llama.cpp,编译 llama-server 和 llama-cli。

cd ~/workspace/echo-ai
curl -L --http1.1 -o llama.cpp.zip   https://codeload.github.com/ggml-org/llama.cpp/zip/refs/heads/master
unzip llama.cpp.zip
mv llama.cpp-master llama.cpp

cd llama.cpp
cmake -B build
cmake --build build -j2 --target llama-server llama-cli

2. 下载并启动 Qwen 模型

使用 Qwen2.5-1.5B-Instruct-GGUF 的量化模型,降低本地运行门槛。

mkdir -p ./models/qwen2.5-1.5b
wget -c -O ./models/qwen2.5-1.5b/qwen2.5-1.5b-instruct-q4_k_m.gguf   https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf

./build/bin/llama-server   -m ./models/qwen2.5-1.5b/qwen2.5-1.5b-instruct-q4_k_m.gguf   --host 127.0.0.1   --port 18080   -c 2048

3. AI 会话表设计

用 ai_conversations 保存会话,用 ai_messages 保存用户和助手消息。

CREATE TABLE IF NOT EXISTS ai_conversations (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  title VARCHAR(100) NOT NULL DEFAULT 'AI Chat',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_ai_conversations_user_id (user_id)
);

CREATE TABLE IF NOT EXISTS ai_messages (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  conversation_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  role VARCHAR(20) NOT NULL,
  content TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_ai_messages_conversation_id (conversation_id)
);

4. Drogon 后端接口

提供 /api/ai/chat、/api/ai/conversations、/api/ai/messages 和 /api/ai/chat/stream。

g_aiClient = drogon::HttpClient::newHttpClient("http://127.0.0.1:18080");

POST /api/ai/chat
1. 鉴权
2. 解析用户消息
3. 新建或查询会话
4. 保存用户消息到 ai_messages
5. 调用 llama.cpp API
6. 保存 AI 回复并返回 JSON

GET /api/ai/conversations
GET /api/ai/messages?conversation_id=1
POST /api/ai/chat/stream

5. 前端页面与 SSE

前端使用侧边栏展示会话列表,聊天面板展示消息,并用 ReadableStream 实现流式输出。

async function typeAssistantReply(fullText) {
  state.aiMessages.push({ role: 'assistant', content: '' });
  const lastIndex = state.aiMessages.length - 1;

  for (let i = 0; i < fullText.length; i++) {
    state.aiMessages[lastIndex].content += fullText[i];
    renderAiChat();
    await new Promise(r => setTimeout(r, 18));
  }
}

// SSE / ReadableStream 版本:
const reader = response.body.getReader();
while (!done) {
  const { value, done: readerDone } = await reader.read();
  // 解析 data: {...} 并追加到消息列表
  renderAiChat();
}