Bối cảnh: Tại sao đội ngũ của tôi chuyển sang HolySheep AI
Cuối năm 2024, đội ngũ dev của tôi vận hành một Discord bot phục vụ cộng đồng game với hơn 50.000 người dùng hàng tháng. Bot sử dụng multi-turn conversation và function calling để xử lý quest tracking, inventory lookup và social features. Chi phí hàng tháng tại thời điểm đó là $847 — quá cao cho một dự án indie.
Sau khi thử nghiệm
HolySheep AI với tỷ giá ¥1=$1 và chi phí DeepSeek V3.2 chỉ $0.42/MTok (so với $8/MTok của GPT-4.1), đội ngũ tiết kiệm được 78% chi phí trong tháng đầu tiên — từ $847 xuống còn $186. Độ trễ trung bình thực tế đo được: 47ms cho requests từ Việt Nam.
Bài viết này là playbook chi tiết về cách tôi migrate toàn bộ hệ thống Discord bot từ relay trung gian sang HolySheep AI trong 3 ngày cuối tuần.
Kiến trúc Discord Bot với HolySheep AI
Sơ đồ luồng dữ liệu
Discord User → Discord.js → Node.js Server → HolySheep API
↓
Session Manager (Redis)
↓
Tool Executor (Local)
↓
Response → Discord Channel
File cấu trúc dự án
discord-ai-bot/
├── src/
│ ├── index.js # Entry point
│ ├── services/
│ │ ├── holySheepClient.js # HolySheep API wrapper
│ │ ├── sessionManager.js # Multi-turn session
│ │ └── toolExecutor.js # Function calling
│ ├── commands/
│ │ ├── chat.js # /chat command
│ │ └── reset.js # /reset command
│ └── utils/
│ └── rateLimiter.js # Rate limiting
├── .env
├── package.json
└── README.md
Cài đặt và Cấu hình
Khởi tạo dự án Node.js
npm init -y
npm install [email protected]
npm install [email protected]
npm install [email protected]
npm install [email protected]
npm install [email protected]
Cấu hình biến môi trường
# .env
DISCORD_BOT_TOKEN=your_discord_bot_token
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
REDIS_HOST=localhost
REDIS_PORT=6379
MAX_SESSION_TURNS=20
SESSION_TTL_SECONDS=3600
HolySheep AI Client — Core Implementation
HolySheepClient.js
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
class HolySheepClient {
constructor(apiKey, baseUrl = 'https://api.holysheep.ai/v1') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.client = axios.create({
baseURL: baseUrl,
headers: {
'Authorization': Bearer ${apiKey},
'Content-Type': 'application/json'
},
timeout: 30000
});
}
async chat(messages, tools = null, sessionId = null) {
const requestBody = {
model: 'deepseek-v3.2',
messages: messages,
stream: false,
temperature: 0.7,
max_tokens: 2048
};
if (tools && tools.length > 0) {
requestBody.tools = tools;
requestBody.tool_choice = 'auto';
}
if (sessionId) {
requestBody.session_id = sessionId;
}
const startTime = Date.now();
try {
const response = await this.client.post('/chat/completions', requestBody);
const latencyMs = Date.now() - startTime;
console.log([HolySheep] Latency: ${latencyMs}ms | Model: ${response.data.model});
return {
content: response.data.choices[0].message.content,
toolCalls: response.data.choices[0].message.tool_calls || [],
usage: response.data.usage,
latencyMs: latencyMs,
sessionId: response.data.session_id
};
} catch (error) {
if (error.response) {
console.error([HolySheep] API Error: ${error.response.status}, error.response.data);
} else {
console.error([HolySheep] Network Error: ${error.message});
}
throw error;
}
}
async streamChat(messages, tools = null, onChunk) {
const requestBody = {
model: 'deepseek-v3.2',
messages: messages,
stream: true,
temperature: 0.7,
max_tokens: 2048
};
if (tools && tools.length > 0) {
requestBody.tools = tools;
}
try {
const response = await this.client.post('/chat/completions', requestBody, {
responseType: 'stream'
});
let fullContent = '';
let buffer = '';
return new Promise((resolve, reject) => {
response.data.on('data', (chunk) => {
buffer += chunk.toString();
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
resolve({ content: fullContent });
return;
}
try {
const parsed = JSON.parse(data);
if (parsed.choices[0].delta.content) {
const token = parsed.choices[0].delta.content;
fullContent += token;
if (onChunk) onChunk(token);
}
} catch (e) {
// Skip malformed JSON
}
}
}
});
response.data.on('error', reject);
response.data.on('end', () => resolve({ content: fullContent }));
});
} catch (error) {
console.error([HolySheep] Stream Error: ${error.message});
throw error;
}
}
}
module.exports = HolySheepClient;
Session Manager cho Multi-turn Conversation
const Redis = require('ioredis');
const { v4: uuidv4 } = require('uuid');
class SessionManager {
constructor(redisConfig, maxTurns = 20, ttlSeconds = 3600) {
this.redis = new Redis(redisConfig);
this.maxTurns = maxTurns;
this.ttlSeconds = ttlSeconds;
}
async getOrCreateSession(discordUserId, channelId) {
const sessionKey = discord:session:${discordUserId}:${channelId};
let sessionId = await this.redis.get(sessionKey);
if (!sessionId) {
sessionId = uuidv4();
await this.redis.setex(sessionKey, this.ttlSeconds, sessionId);
console.log([Session] Created new session: ${sessionId});
}
return sessionId;
}
async getConversationHistory(sessionId) {
const historyKey = holySheep:history:${sessionId};
const history = await this.redis.lrange(historyKey, 0, -1);
return history.map(msg => JSON.parse(msg));
}
async addToHistory(sessionId, role, content) {
const historyKey = holySheep:history:${sessionId};
const message = JSON.stringify({ role, content });
await this.redis.rpush(historyKey, message);
await this.redis.expire(historyKey, this.ttlSeconds);
const currentLength = await this.redis.llen(historyKey);
if (currentLength > this.maxTurns * 2) {
await this.redis.lpop(historyKey);
await this.redis.lpop(historyKey);
}
}
async clearSession(discordUserId, channelId) {
const sessionId = await this.redis.get(discord:session:${discordUserId}:${channelId});
if (sessionId) {
await this.redis.del(holySheep:history:${sessionId});
await this.redis.del(discord:session:${discordUserId}:${channelId});
console.log([Session] Cleared session: ${sessionId});
}
}
async getSessionStats(sessionId) {
const history = await this.getConversationHistory(sessionId);
const totalTokens = history.reduce((sum, msg) => {
return sum + (msg.content?.length || 0) / 4;
}, 0);
return {
turns: Math.floor(history.length / 2),
estimatedTokens: Math.round(totalTokens),
ttl: await this.redis.ttl(holySheep:history:${sessionId})
};
}
}
module.exports = SessionManager;
Tool Executor — Function Calling
const axios = require('axios');
class ToolExecutor {
constructor() {
this.tools = new Map();
this.registerDefaultTools();
}
registerDefaultTools() {
this.registerTool('get_quest_status', this.getQuestStatus.bind(this));
this.registerTool('get_player_inventory', this.getPlayerInventory.bind(this));
this.registerTool('search_marketplace', this.searchMarketplace.bind(this));
this.registerTool('send_dm', this.sendDM.bind(this));
}
registerTool(name, handler) {
this.tools.set(name, handler);
}
async executeToolCall(toolCall) {
const { function: func, arguments: argsStr } = toolCall;
const toolName = func.name;
let args = {};
try {
args = JSON.parse(argsStr);
} catch (e) {
args = {};
}
const tool = this.tools.get(toolName);
if (!tool) {
return {
success: false,
error: Unknown tool: ${toolName}
};
}
try {
console.log([Tool] Executing: ${toolName}, args);
const result = await tool(args);
console.log([Tool] Success: ${toolName});
return { success: true, result };
} catch (error) {
console.error([Tool] Error in ${toolName}:, error.message);
return { success: false, error: error.message };
}
}
async executeToolCalls(toolCalls) {
const results = [];
for (const toolCall of toolCalls) {
const result = await this.executeToolCall(toolCall);
results.push({
call_id: toolCall.id,
function: toolCall.function.name,
result: result
});
}
return results;
}
async getQuestStatus({ player_id, quest_id }) {
return {
quest_id: quest_id,
player_id: player_id,
status: 'in_progress',
progress: 67,
time_remaining: '2h 34m',
rewards: {
gold: 1500,
exp: 500,
items: ['Rare Sword Fragment', 'Health Potion x3']
}
};
}
async getPlayerInventory({ player_id, category }) {
return {
player_id: player_id,
category: category || 'all',
items: [
{ id: 'item_001', name: 'Dragon Slayer Sword', rarity: 'legendary', quantity: 1 },
{ id: 'item_002', name: 'Health Potion', rarity: 'common', quantity: 47 },
{ id: 'item_003', name: 'Gold Coins', rarity: 'currency', quantity: 125000 }
],
capacity: { used: 78, total: 100 }
};
}
async searchMarketplace({ query, min_price, max_price, category }) {
return {
query: query,
results: [
{ item_id: 'mkt_001', name: 'Phoenix Feather', price: 2500, seller: 'ProGamer#1234' },
{ item_id: 'mkt_002', name: 'Enchanted Armor', price: 15000, seller: 'EliteMerchant#5678' }
],
total_found: 2,
filters: { min_price, max_price, category }
};
}
async sendDM({ user_id, message }) {
return {
success: true,
user_id: user_id,
message_id: msg_${Date.now()},
delivered: true
};
}
}
module.exports = ToolExecutor;
Main Bot Implementation — Entry Point
require('dotenv').config();
const { Client, GatewayIntentBits, SlashCommandBuilder, REST, Routes } = require('discord.js');
const HolySheepClient = require('./services/holySheepClient');
const SessionManager = require('./services/sessionManager');
const ToolExecutor = require('./services/toolExecutor');
const discordClient = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
const holySheep = new HolySheepClient(
process.env.HOLYSHEEP_API_KEY,
process.env.HOLYSHEEP_BASE_URL
);
const sessionManager = new SessionManager({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
}, 20, 3600);
const toolExecutor = new ToolExecutor();
const SYSTEM_PROMPT = `Bạn là trợ lý game chuyên nghiệp trong Discord.
Bạn có quyền truy cập các công cụ sau:
- get_quest_status: Lấy thông tin nhiệm vụ của người chơi
- get_player_inventory: Xem túi đồ của người chơi
- search_marketplace: Tìm kiếm vật phẩm trên marketplace
- send_dm: Gửi tin nhắn riêng cho người chơi
Luôn trả lời bằng tiếng Việt, thân thiện và hữu ích.`;
const TOOLS_DEFINITION = [
{
type: 'function',
function: {
name: 'get_quest_status',
description: 'Lấy thông tin tiến độ nhiệm vụ của người chơi',
parameters: {
type: 'object',
properties: {
player_id: { type: 'string', description: 'ID người chơi' },
quest_id: { type: 'string', description: 'ID nhiệm vụ' }
},
required: ['player_id']
}
}
},
{
type: 'function',
function: {
name: 'get_player_inventory',
description: 'Xem túi đồ của người chơi',
parameters: {
type: 'object',
properties: {
player_id: { type: 'string', description: 'ID người chơi' },
category: { type: 'string', description: 'Danh mục items (weapon/armor/potion/etc)' }
},
required: ['player_id']
}
}
},
{
type: 'function',
function: {
name: 'search_marketplace',
description: 'Tìm kiếm vật phẩm trên marketplace',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Từ khóa tìm kiếm' },
min_price: { type: 'number', description: 'Giá tối thiểu' },
max_price: { type: 'number', description: 'Giá tối đa' },
category: { type: 'string', description: 'Danh mục' }
},
required: ['query']
}
}
}
];
discordClient.on('ready', async () => {
console.log([Discord] Bot logged in as ${discordClient.user.tag});
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN);
try {
await rest.put(Routes.applicationGuildCommands(
discordClient.application.id,
process.env.GUILD_ID
), { body: [
new SlashCommandBuilder()
.setName('chat')
.setDescription('Chat với AI assistant')
.addStringOption(opt =>
opt.setName('message').setDescription('Tin nhắn của bạn').setRequired(true)
),
new SlashCommandBuilder()
.setName('reset')
.setDescription('Reset cuộc trò chuyện'),
new SlashCommandBuilder()
.setName('stats')
.setDescription('Xem thống kê phiên làm việc')
]});
console.log('[Discord] Slash commands registered');
} catch (error) {
console.error('[Discord] Command registration failed:', error.message);
}
});
discordClient.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const userId = interaction.user.id;
const channelId = interaction.channelId;
if (interaction.commandName === 'reset') {
await sessionManager.clearSession(userId, channelId);
await interaction.reply('✅ Đã reset cuộc trò chuyện. Bắt đầu lại từ đầu nhé!');
return;
}
if (interaction.commandName === 'stats') {
const sessionId = await sessionManager.getOrCreateSession(userId, channelId);
const stats = await sessionManager.getSessionStats(sessionId);
await interaction.reply(📊 **Session Stats:**\n- Số lượt hội thoại: ${stats.turns}\n- Ước tính tokens: ${stats.estimatedTokens}\n- TTL còn lại: ${stats.ttl}s);
return;
}
if (interaction.commandName === 'chat') {
const userMessage = interaction.options.getString('message');
await interaction.deferReply();
try {
const sessionId = await sessionManager.getOrCreateSession(userId, channelId);
await sessionManager.addToHistory(sessionId, 'system', SYSTEM_PROMPT);
const history = await sessionManager.getConversationHistory(sessionId);
history.push({ role: 'user', content: userMessage });
await sessionManager.addToHistory(sessionId, 'user', userMessage);
let response = await holySheep.chat(history, TOOLS_DEFINITION, sessionId);
let finalResponse = response.content;
if (response.toolCalls && response.toolCalls.length > 0) {
console.log([Bot] Executing ${response.toolCalls.length} tool calls);
const toolResults = await toolExecutor.executeToolCalls(response.toolCalls);
for (const toolResult of toolResults) {
await sessionManager.addToHistory(sessionId, 'assistant', response.content);
await sessionManager.addToHistory(sessionId, 'tool',
JSON.stringify({ name: toolResult.function, result: toolResult.result })
);
}
const followUpHistory = await sessionManager.getConversationHistory(sessionId);
const followUpResponse = await holySheep.chat(followUpHistory, null, sessionId);
finalResponse = followUpResponse.content;
console.log([Bot] Tool execution latency: ${response.latencyMs}ms);
}
await sessionManager.addToHistory(sessionId, 'assistant', finalResponse);
const stats = await sessionManager.getSessionStats(sessionId);
console.log([Bot] Response sent | Latency: ${response.latencyMs}ms | Session turns: ${stats.turns});
await interaction.editReply(finalResponse);
} catch (error) {
console.error('[Bot] Error:', error.message);
await interaction.editReply('❌ Đã xảy ra lỗi khi xử lý yêu cầu. Vui lòng thử lại
Tài nguyên liên quan
Bài viết liên quan