Echo Server:本地 AI 聊天助手集成
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/stream5. 前端页面与 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();
}