Go 语言实现本地大模型聊天机器人:从推理到 Web UI 的全流程

发布于:2025-07-08 ⋅ 阅读:(12) ⋅ 点赞:(0)

接续 Go-LLM-CPP 专案,继续扩充前端聊天室功能

一. 专案目录架构:

go-llm-cpp/
├── bin/                        # 第三方依赖
│   ├── go-llama.cpp/           # 封裝 GGUF 模型推理(CGo)
│   └── llm-go/                 # prompt 构建 + 回合管理(Go)
│
├── cmd/                        # 可执行应用
│   └── main.go                 # CLI / HTTP server 入口点
│
├── config/
│   └── persona.yaml            # 人格模板(系统 prompt + 选项)
│
├── llm/                        # LLM 封装与上下文逻辑
│   ├── model.go                # go-llama.cpp 模型初始化与推理
│   ├── session.go              # prompt 组装、上下文构建、角色管理
│   └── memory.go               # 对话记忆保存(快取 / SQLite)
│
├── api/                        # Web API 接口逻辑
│   ├── server.go               # FastAPI / Echo router 定义
│   └── handler.go              # 对话处理器(chat/infer endpoint)
│
├── web-ui/                     # Nuxt3 + Tailwind 前端聊天室(独立模组)
│   ├── pages/index.vue         # 主对话页
│   ├── components/             # Chat bubble / RoleCard 元件
│   └── composables/            # API 对接与 socket 管理
│
├── swagger/                    # OpenAPI v3 文件
│   └── swagger.yaml            # chat 接口规格与参数说明
│
├── go.mod                      # Go 模组依赖
├── go.sum
└── README.md

要模组功能解说

模组 功能描述
llm/model.go 初始化 GGUF 模型、推理 token(封装 go-llama.cpp)
llm/session.go 管理上下文、构造完整 prompt、切换角色模板
config/persona.yaml 配置角色语气、风格、禁止词与偏好用语
api/ 提供 /chat、/roles、/history 等 Web API 接口
web-ui/ Vue + Tailwind 结合 Nuxt 开发的聊天室介面(支援 streaming、socket typing 效果)

本篇专案新增功能
• ✅ 地部署 LLM,无需外部 API
• ✅ GGUF 格式(Gemma / Llama2 / Mistral 等模型)
• ✅ 格对话模板切换(配置于 persona.yaml)
• ✅ Prompt 管理结构化(使用 llm-go 思路)
• ✅ CLI + Web API + Web UI 三端整合
• ✅ 支援 SQLite 快取历史与上下文

二. 前端模組

Vue Chat UI

<template>
  <div class="chat-container">
    <div class="chat-header">Go LLM Chatbot</div>
    <div class="chat-messages" ref="messages">
      <div v-for="(msg, index) in messages" :key="index" :class="['chat-message', msg.role]">
        <strong>{{ msg.role === 'user' ? '你' : '機器人' }}</strong> {{ msg.content }}
      </div>
    </div>
    <div class="chat-input">
      <input v-model="input" @keyup.enter="sendMessage" placeholder="請輸入訊息..." />
      <button @click="sendMessage">發送</button>
    </div>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'ChatApp',
  data() {
    return {
      input: '',
      messages: []
    }
  },
  methods: {
    async sendMessage() {
      if (!this.input.trim()) return;

      const userInput = this.input;
      this.messages.push({ role: 'user', content: userInput });
      this.input = '';

      try {
        const res = await axios.post('/api/chat', { message: userInput });
        const reply = res.data.reply;
        this.messages.push({ role: 'assistant', content: reply });
        this.$nextTick(() => {
          const msgBox = this.$refs.messages;
          msgBox.scrollTop = msgBox.scrollHeight;
        });
      } catch (err) {
        this.messages.push({ role: 'assistant', content: '出現錯誤,請稍後再試。' });
      }
    }
  }
}
</script>

<style scoped>
.chat-container {
  max-width: 600px;
  margin: auto;
  border: 1px solid #ccc;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  height: 80vh;
  background: #fff;
}
.chat-header {
  padding: 16px;
  font-weight: bold;
  background: #42b983;
  color: white;
  border-bottom: 1px solid #ccc;
}
.chat-messages {
  flex: 1;
  padding: 16px;
  overflow-y: auto;
  background: #f9f9f9;
}
.chat-message {
  margin-bottom: 12px;
}
.chat-message.user {
  text-align: right;
  color: #333;
}
.chat-message.assistant {
  text-align: left;
  color: #2c3e50;
}
.chat-input {
  display: flex;
  border-top: 1px solid #ccc;
  padding: 10px;
}
.chat-input input {
  flex: 1;
  padding: 8px;
  font-size: 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
}
.chat-input button {
  margin-left: 10px;
  padding: 8px 16px;
  background: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

nuxt.config.ts

export default defineNuxtConfig({
  css: ['@/assets/chat.css'],
  modules: ['@pinia/nuxt'],
  runtimeConfig: {
    public: {
      apiBase: '/api'
    }
  }
})

三. API 接口 - Nuxt 3 与 Vue 3 和后端 go-llm-cpp 串接

于 Nuxt 3 与 Vue 3 所建构的完整聊天室前端架构,整合角色选择、历史保存与 API 对接逻辑,并配合后端 go-llm-cpp 提供的 Web API 使用方式

  1. 页面:pages/index.vue
<template>
  <div class="min-h-screen bg-gray-100 p-6">
    <div class="max-w-3xl mx-auto">
      <div class="mb-4">
        <label class="block font-bold">選擇角色</label>
        <select v-model="persona" class="mt-1 block w-full border rounded p-2">
          <option value="default">默認</option>
          <option value="engineer">工程師</option>
          <option value="teacher">老師</option>
        </select>
      </div>

      <div class="bg-white rounded shadow p-4 h-[60vh] overflow-y-auto">
        <div v-for="(msg, index) in history" :key="index" class="mb-2">
          <div class="font-bold" :class="msg.role === 'user' ? 'text-blue-600' : 'text-green-600'">
            {{ msg.role === 'user' ? '你:' : '助手:' }}
          </div>
          <div class="whitespace-pre-wrap">{{ msg.content }}</div>
        </div>
      </div>

      <form @submit.prevent="send" class="mt-4 flex gap-2">
        <input v-model="input" class="flex-1 border rounded p-2" placeholder="輸入訊息..." />
        <button class="bg-blue-500 text-white px-4 py-2 rounded" :disabled="loading">
          {{ loading ? '回應中...' : '發送' }}
        </button>
      </form>
    </div>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'
import { useChatStore } from '@/stores/chat'

const chat = useChatStore()
const input = ref('')
const persona = ref('default')
const loading = ref(false)

watch(persona, () => {
  chat.setPersona(persona.value)
})

const send = async () => {
  if (!input.value) return
  loading.value = true
  await chat.sendMessage(input.value)
  input.value = ''
  loading.value = false
}

const history = computed(() => chat.history)
</script>
  1. 状态管理::stores/chat.ts
// 使用 Pinia 作为状态管理工具。
// stores/chat.ts
import { defineStore } from 'pinia'

interface Message {
  role: 'user' | 'assistant'
  content: string
}

export const useChatStore = defineStore('chat', {
  state: () => ({
    persona: 'default',
    history: [] as Message[]
  }),

  actions: {
    setPersona(p: string) {
      this.persona = p
      this.history = [] // 切換角色時清空對話
    },

    async sendMessage(content: string) {
      this.history.push({ role: 'user', content })

      const res = await $fetch<{ message: string }>('/api/chat', {
        method: 'POST',
        body: {
          persona: this.persona,
          history: this.history,
          message: content
        }
      })

      this.history.push({ role: 'assistant', content: res.message })
    }
  }
})
  1. API 對接邏輯:server/api/chat.ts
// 使用 Nuxt 3 的 server API 机制连接 go-llm-cpp 提供的后端服务。
// server/api/chat.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)

  const res = await $fetch('http://localhost:11434/api/chat', {
    method: 'POST',
    body: {
      persona: body.persona,
      history: body.history,
      message: body.message
    }
  })

  return {
    message: res.message || '(沒有回應)'
  }
})

go-llm-cpp 的 Web API 请确保已支援 POST /api/chat 并接受 persona, history, message。

  1. 安裝依賴與配置
# 安装依赖
npm install
npm install @pinia/nuxt

# 启用 Pinia
# nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt']
})

四. 整合 Tailwind CSS 的 Nuxt 3 聊天室设计

  1. 安裝 Tailwind CSS
npx nuxi init chat-ui
cd chat-ui

npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
  1. 設定 tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./components/**/*.{js,vue,ts}",
    "./layouts/**/*.{js,vue,ts}",
    "./pages/**/*.{js,vue,ts}",
    "./app.vue",
    "./plugins/**/*.{js,ts}",
    "./nuxt.config.{js,ts}"
  ],
  theme: {
    extend: {}
  },
  plugins: []
}
  1. 在 assets/css/tailwind.css 新增
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. 在 nuxt.config.ts 加入 Tailwind CSS
export default defineNuxtConfig({
  css: ["@/assets/css/tailwind.css"],
  modules: ["@pinia/nuxt"]
})
  1. pages/index.vue 加入 Tailwind 樣式(含角色切換與聊天視窗)
<template>
  <div class="min-h-screen bg-gray-100 flex flex-col items-center p-6">
    <div class="w-full max-w-3xl space-y-4">
      <h1 class="text-2xl font-semibold">LLM 聊天機器人</h1>

      <div>
        <label class="block text-sm font-medium">選擇角色:</label>
        <select v-model="persona" class="mt-1 block w-full border border-gray-300 rounded p-2">
          <option v-for="opt in personaOptions" :key="opt" :value="opt">
            {{ opt }}
          </option>
        </select>
      </div>

      <div class="bg-white rounded-lg shadow p-4 h-[55vh] overflow-y-auto">
        <div v-for="(msg, i) in history" :key="i" class="mb-3">
          <div :class="msg.role === 'user' ? 'text-blue-600' : 'text-green-600'">
            <strong>{{ msg.role === 'user' ? '你' : '助手' }}</strong>
          </div>
          <p class="whitespace-pre-wrap">{{ msg.content }}</p>
        </div>
      </div>

      <form @submit.prevent="send" class="flex gap-2">
        <input v-model="input" class="flex-1 border border-gray-300 rounded p-2" placeholder="輸入訊息..." />
        <button class="bg-blue-600 text-white px-4 py-2 rounded" :disabled="loading">
          {{ loading ? "回應中..." : "發送" }}
        </button>
      </form>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from "vue"
import { useChatStore } from "@/stores/chat"

const input = ref("")
const persona = ref("default")
const loading = ref(false)
const chat = useChatStore()

const history = computed(() => chat.history)
const personaOptions = ["default", "engineer", "teacher"]

onMounted(() => {
  chat.setPersona(persona.value)
})

watch(persona, () => {
  chat.setPersona(persona.value)
})

const send = async () => {
  if (!input.value.trim()) return
  loading.value = true
  await chat.sendMessage(input.value)
  input.value = ""
  loading.value = false
}
</script>
  1. SQLite 快取 / 记忆优化扩展
    • 使用 @prisma/client 或 better-sqlite3 建立讯息表
    • 将 history 写入 messages.db
    • 搭配 embedding(例如 sentence-transformers)进行语义记忆检索

五. 打造本地 LLM 聊天机器人的未来展望

LLM 本地部署的門檻不斷降低,結合如 go-llama.cpp 與 llm-go 的專案已能讓開發者在無需倚賴雲端 API 的情況下,自行構建高效、安全、可擴展的聊天機器人系統。本專案透過模組化的架構設計,實現了 CLI、Web API 與 Vue 前端的完整整合,並支援角色切換、歷史對話保存與即時回應等功能。

参考链接:

本项目 README

🧾 GitCode 開源項目地址:

👉 GitCode - 全球开发者的开源社区,开源代码托管平台

我是一位独立开发者,加入使用者社群,一起讨论私有化 LLM 与 RAG 架构实践,欢迎 Star、Fork、Issue 交流。


网站公告

今日签到

点亮在社区的每一天
去签到