Giao diện
@digiforce-nc/ai
Framework quản lý năng lực AI cho Digiforce - tools, skills, chatbot, MCP, và tìm kiếm tài liệu.
typescript
import { AIManager } from '@digiforce-nc/ai';Tổng quan
Hãy hình dung hệ thống AI như một đội ngũ trợ lý:
- AIManager = quản lý đội - điều phối tất cả thành viên.
- Tools = kỹ năng đơn lẻ - mỗi tool làm một việc (tính toán, tìm kiếm, gửi email…).
- Skills = hướng dẫn nghiệp vụ - tài liệu mô tả cách xử lý tình huống (prompt + metadata).
- AI Bot = trợ lý ảo - kết hợp tools + skills + system prompt thành chatbot hoàn chỉnh.
- MCP = kết nối bên ngoài - giao thức chuẩn để AI gọi công cụ từ hệ thống khác.
- Document = bộ nhớ tìm kiếm - index full-text cho tra cứu nhanh.
Hai pha hoạt động
| Pha | Thời điểm | Hành động |
|---|---|---|
| 1. Load | Khi app khởi động | Loader quét filesystem → đăng ký tools/skills/bots/MCP vào manager (in-memory) |
| 2. Init | Sau khi DB sẵn sàng | Manager gọi init() → flush dữ liệu in-memory xuống DB (persistence) |
AIManager - trung tâm điều phối
typescript
export class AIManager {
documentManager: DocumentManager;
toolsManager: ToolsManager;
skillsManager: SkillsManager;
aibotManager: AIBotManager;
mcpManager: MCPManager;
mcpToolsManager: McpToolsManager;
constructor(app: Application) {
this.documentManager = new DocumentManager();
this.toolsManager = new DefaultToolsManager();
this.skillsManager = new DefaultSkillsManager(app);
this.aibotManager = new DefaultAIBotManager(app);
this.mcpManager = new DefaultMCPManager(app);
this.mcpToolsManager = new McpToolsManager();
}
}| Property | Implementation | Cần DB? | Mô tả |
|---|---|---|---|
toolsManager | DefaultToolsManager | Không | Registry tools + dynamic providers |
skillsManager | DefaultSkillsManager | Có | Skills in-memory → DB |
aibotManager | DefaultAIBotManager | Có | Bot config in-memory → DB |
mcpManager | DefaultMCPManager | Có | MCP server config → DB + LangChain client |
documentManager | DocumentManager | Không | FlexSearch index/document |
mcpToolsManager | McpToolsManager | Không | MCP server-side tools (cho plugin) |
AIManager không có method init() riêng - host application gọi init() trên từng sub-manager cần DB.
ToolsManager - đăng ký & gọi AI tools
Tool là function mà LLM có thể gọi (function calling). Mỗi tool có:
- definition - tên, mô tả, JSON schema cho tham số.
- invoke - hàm thực thi khi LLM quyết định gọi tool.
Cấu trúc ToolsOptions
typescript
interface ToolsOptions {
scope: 'SPECIFIED' | 'GENERAL' | 'CUSTOM';
from?: 'loader' | 'workflow' | 'mcp';
execution?: 'frontend' | 'backend';
defaultPermission?: 'ASK' | 'ALLOW';
silence?: boolean;
introduction?: {
title: string;
about?: string;
};
definition: {
name: string;
description: string;
schema?: object; // JSON Schema cho tham số
};
invoke: (ctx: Context, args: object, runtime: ToolsRuntime) => Promise<any>;
}| Field | Mặc định | Ý nghĩa |
|---|---|---|
scope | - | GENERAL = dùng cho mọi bot, SPECIFIED = chỉ bot được gán, CUSTOM = tùy chỉnh |
from | 'loader' | Nguồn đăng ký: loader (file), workflow, hoặc MCP |
execution | 'backend' | Nơi thực thi: server hoặc client |
defaultPermission | 'ASK' | ASK = hỏi user trước khi gọi, ALLOW = tự động |
silence | false | Nếu true, tool chạy ngầm không thông báo user |
Đăng ký tool
typescript
// Đăng ký trực tiếp
aiManager.toolsManager.registerTools({
scope: 'GENERAL',
definition: {
name: 'weather',
description: 'Tra cứu thời tiết theo thành phố',
schema: {
type: 'object',
properties: {
city: { type: 'string', description: 'Tên thành phố' },
},
required: ['city'],
},
},
invoke: async (ctx, { city }) => {
const data = await fetchWeather(city);
return { temperature: data.temp, condition: data.condition };
},
});
// Đăng ký nhiều tool cùng lúc
aiManager.toolsManager.registerTools([tool1, tool2, tool3]);Dynamic tools - provider pattern
Ngoài tool đăng ký tĩnh, có thể đăng ký provider - function được gọi khi cần liệt kê/tìm tool:
typescript
aiManager.toolsManager.registerDynamicTools(async (register) => {
const externalTools = await fetchToolsFromExternalService();
register.registerTools(externalTools);
});Khi listTools() hoặc getTools() được gọi, tất cả dynamic providers chạy vào một registry tạm → merge với tool tĩnh.
MCP tools sử dụng chính cơ chế này - mcpManager.getMCPToolsProvider() trả về một dynamic provider.
API tổng hợp
| Method | Mô tả |
|---|---|
registerTools(options) | Đăng ký một hoặc nhiều tool |
registerDynamicTools(provider) | Đăng ký provider tạo tool động |
getTools(name) | Tìm tool theo tên (tĩnh trước, rồi dynamic) |
listTools(filter?) | Liệt kê tất cả tool (tĩnh + dynamic), lọc theo scope, defaultPermission, silence |
Helper
typescript
import { defineTools } from '@digiforce-nc/ai';
// Ergonomic wrapper - chỉ trả lại object, giúp IDE autocomplete
export default defineTools({
scope: 'GENERAL',
definition: { name: 'myTool', description: '...' },
invoke: async (ctx, args) => { /* ... */ },
});SkillsManager - hướng dẫn nghiệp vụ cho AI
Skill là tài liệu hướng dẫn (prompt + metadata) cho AI bot biết cách xử lý tình huống. Khác tool (code thực thi), skill là nội dung text được inject vào context của LLM.
Cấu trúc SkillsOptions
typescript
interface SkillsOptions {
scope: 'SPECIFIED' | 'GENERAL' | 'CUSTOM';
name: string;
description: string; // Bắt buộc
content: string; // Nội dung skill (markdown)
tools?: string[]; // Tên tools liên quan
introduction?: {
title: string;
about?: string;
};
from?: string; // Nguồn: 'loader', 'workflow', etc.
}Skill được viết bằng Markdown + front matter
markdown
---
scope: SPECIFIED
name: order-support
description: Hướng dẫn hỗ trợ đơn hàng
tools:
- lookup-order
- cancel-order
introduction:
title: Hỗ trợ đơn hàng
about: Skill xử lý câu hỏi về đơn hàng của khách
---
## Quy trình xử lý
1. Hỏi mã đơn hàng
2. Tra cứu bằng tool `lookup-order`
3. Nếu khách muốn hủy → xác nhận → dùng tool `cancel-order`
4. Tóm tắt kết quả cho khách
## Lưu ý
- Luôn xác nhận lại trước khi hủy đơn
- Đơn đã giao không thể hủyFile này phải có tên SKILLS.md - SkillsLoader chỉ nhận file đúng tên.
Memory → Database
Trước init(), skill được lưu in-memory (Registry). Khi init() chạy, tất cả được flush xuống DB collection aiSkills. Sau đó, registerSkills() ghi trực tiếp vào DB.
API
| Method | Mô tả |
|---|---|
init() | Flush memory → DB, chuyển sang mode database |
registerSkills(options) | Đăng ký skill (memory hoặc DB tùy mode) |
getSkills(name) | Lấy skill theo tên (từ DB) |
listSkills(filter?) | Liệt kê skills, lọc theo scope, name (substring) |
AIBotManager - quản lý chatbot
AI Bot là trợ lý ảo hoàn chỉnh - kết hợp system prompt, skills, tools, và profile thành một chatbot sẵn sàng tương tác.
Cấu trúc AIBotOptions
typescript
interface AIBotOptions {
username: string; // ID duy nhất
description?: string;
// Profile hiển thị
avatar?: string;
displayname?: string;
position?: string; // Chức danh (ví dụ "Trợ lý CSKH")
bio?: string;
greeting?: string; // Lời chào khi bắt đầu hội thoại
// AI configuration
systemPrompt?: string | null; // System prompt cho LLM (có thể null)
skills?: string[]; // Tên skills được gán
tools?: AIBotToolSetting[]; // Tools với cấu hình
sort?: number; // Thứ tự hiển thị
}
interface AIBotToolSetting {
name: string;
autoCall?: boolean; // Tự động gọi không cần hỏi user
}
// Entry trả về từ DB có cấu trúc khác
interface AIBotEntry {
username: string;
description?: string;
avatar?: string;
displayname?: string;
position?: string;
bio?: string;
greeting?: string;
about?: string;
defaultPrompt?: string; // Lưu trong DB là defaultPrompt, không phải systemPrompt
skillSettings: { // Skills và tools được wrap trong skillSettings
skills: string[];
tools: AIBotToolSetting[];
};
sort?: number;
}Ví dụ đăng ký bot
typescript
aiManager.aibotManager.registerAIBot({
username: 'support-bot',
displayname: 'Trợ lý CSKH',
position: 'Customer Support',
greeting: 'Xin chào! Tôi có thể giúp gì cho bạn?',
avatar: '/avatars/support.png',
systemPrompt: `Bạn là trợ lý hỗ trợ khách hàng.
Luôn trả lời bằng tiếng Việt, lịch sự và chuyên nghiệp.`,
skills: ['order-support', 'product-info'],
tools: [
{ name: 'lookup-order', autoCall: true },
{ name: 'cancel-order', autoCall: false },
],
});Memory → Database (giống Skills)
Bot cũng dùng pattern memory → DB. Khi init(), bot được flush vào collection aiBots.
Khi update bot đã tồn tại, manager merge tools thông minh: giữ nguyên workflowAiTool-* tools do workflow tạo ra, union với tools mới từ config.
API
| Method | Mô tả |
|---|---|
init() | Flush memory → DB |
registerAIBot(options) | Đăng ký bot (create hoặc update) |
getAIBot(username) | Lấy bot config |
listAIBots(filter?) | Liệt kê bots |
MCPManager - kết nối MCP servers
Model Context Protocol (MCP) là giao thức chuẩn để LLM gọi công cụ từ hệ thống bên ngoài. MCPManager quản lý kết nối đến nhiều MCP server cùng lúc.
Transport types
| Transport | Mô tả | Ví dụ |
|---|---|---|
stdio | Chạy process con, giao tiếp qua stdin/stdout | Tool CLI local |
sse | Server-Sent Events qua HTTP | Remote MCP server |
http | HTTP streamable | Remote MCP server (mới hơn) |
Cấu trúc MCPOptions
typescript
interface MCPOptions {
transport: 'stdio' | 'sse' | 'http';
// stdio
command?: string; // Lệnh chạy (ví dụ 'npx')
args?: string[]; // Arguments
env?: Record<string, string>;
// sse / http
url?: string; // URL endpoint
headers?: Record<string, string>;
restart?: boolean;
}Ví dụ cấu hình MCP
typescript
// MCP server chạy local (stdio)
aiManager.mcpManager.registerMCP({
'github-tools': {
transport: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-github'],
env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN },
},
});
// MCP server remote (http)
aiManager.mcpManager.registerMCP({
'company-api': {
transport: 'http',
url: 'https://mcp.company.com/api',
headers: { Authorization: 'Bearer ...' },
},
});Luồng kết nối và tool discovery
Cầu nối MCP → ToolsManager
getMCPToolsProvider() trả về một DynamicToolsProvider - khi được đăng ký vào toolsManager, mỗi MCP tool được chuyển thành ToolsOptions:
- name:
mcp-${serverName}-${toolName}(ví dụmcp-github-tools-create_issue) - from:
'mcp' - invoke: gọi
tool.invoke(args)từ LangChain
Nhờ cầu nối này, LLM thấy MCP tools cùng danh sách với tool nội bộ - không cần phân biệt.
API
| Method | Mô tả |
|---|---|
init() | Flush to DB + rebuildClient() |
registerMCP(config) | Đăng ký MCP server config |
getMCP(name) | Lấy config MCP server |
listMCP(filter?) | Liệt kê MCP servers |
rebuildClient() | Đóng client cũ → kết nối lại tất cả server enabled |
getMCPToolsProvider() | Trả DynamicToolsProvider cho ToolsManager |
listMCPTools() | Liệt kê tools từ tất cả MCP server đã kết nối |
updateMCPToolPermission(tool, perm) | Cập nhật permission (ASK/ALLOW) cho MCP tool |
testConnection(options) | Test kết nối MCP server mới (timeout 60s) |
DocumentManager - tìm kiếm full-text
DocumentManager quản lý FlexSearch index - cho phép tìm kiếm full-text nhanh trên dữ liệu in-memory.
Không phải document loader
Tên "DocumentManager" có thể gây nhầm lẫn. Nó không load file PDF/Word - nó quản lý search index. Việc đọc và phân tích file thuộc về LangChain document loaders trong pipeline AI.
Hai loại storage
| Loại | Class | Dùng cho |
|---|---|---|
| Index | Index (wrapper FlexSearch) | Tìm kiếm text đơn giản: thêm text → search |
| Document | Document (FlexSearch native) | Tìm kiếm trên object có nhiều field |
Index API
typescript
// Tạo index
aiManager.documentManager.addIndex('knowledge-base');
const index = aiManager.documentManager.getIndex('knowledge-base');
// Thêm nội dung (ID có thể là string hoặc number)
await index.add('doc-1', 'Digiforce là nền tảng no-code...');
await index.add('doc-2', 'Plugin ACL quản lý phân quyền...');
await index.add('article-42', 'Hướng dẫn cài đặt Digiforce trên Docker...');
// Tìm kiếm
const results = await index.search('phân quyền');
// → ['doc-2']
// Xóa
await index.remove('doc-1');Bên trong, Index wrapper sử dụng IdMapper để chuyển đổi ID string/number ↔ ID số tuần tự nội bộ (FlexSearch yêu cầu ID số).
Document API
typescript
aiManager.documentManager.addDocument('products', {
document: {
id: 'id',
index: ['name', 'description'], // fields để index
},
});
const doc = aiManager.documentManager.getDocument('products');McpToolsManager - MCP server-side tools
Khác với MCPManager (kết nối đến MCP server), McpToolsManager quản lý tools cho Digiforce làm MCP server - phục vụ plugin plugin-mcp-server.
typescript
interface McpTool {
name: string;
description?: string;
inputSchema?: object;
resourceName?: string; // Liên kết resource
actionName?: string; // Liên kết action
call: (args: object, context?: McpToolCallContext) => Promise<any>;
}API
| Method | Mô tả |
|---|---|
registerTools(tools) | Đăng ký MCP server tools |
getTool(name) | Lấy tool theo tên |
listTools() | Liệt kê tất cả tools |
registerToolResultPostProcessor(resource, action, processor) | Đăng ký post-processor cho kết quả tool |
postProcessToolResult(tool, result, context) | Chạy post-processors đã đăng ký |
Post-processor
Post-processor cho phép biến đổi kết quả tool trước khi trả về client MCP:
typescript
aiManager.mcpToolsManager.registerToolResultPostProcessor(
'orders',
'list',
async ({ result, tool, context }) => {
// Lọc thông tin nhạy cảm trước khi trả về
return result.map(order => ({
id: order.id,
status: order.status,
// loại bỏ customerEmail, internalNotes
}));
},
);Loader system - tự động quét và đăng ký
Loader system quét filesystem theo convention, import module, và đăng ký vào manager tương ứng.
DirectoryScanner
typescript
import { DirectoryScanner } from '@digiforce-nc/ai';
const scanner = new DirectoryScanner({
basePath: '/path/to/ai-resources',
pattern: ['**/*.ts', '**/*.js', '**/*.md'],
});
const files = await scanner.scan();
// → FileDescriptor[]FileDescriptor:
typescript
interface FileDescriptor {
name: string; // Tên file (không có extension)
directory: string; // Thư mục chứa file
path: string; // Đường dẫn tuyệt đối
extname: string; // Extension (.ts, .js, .md)
basename: string; // Tên file (có extension)
}Loader base class
Tất cả loader kế thừa LoadAndRegister<TOptions>:
ToolsLoader
Quét thư mục tools, mỗi tool là một file .ts/.js hoặc thư mục có index.ts.
ai/tools/
├── weather.ts → tool name: "weather"
├── calculator/
│ ├── index.ts → tool name: "calculator"
│ └── description.md → override definition.description
└── send-email.ts → tool name: "send-email"Quy tắc:
- File đơn → tool name = tên file (không extension).
- Thư mục có
index.ts→ tool name = tên thư mục. - Nếu có
description.mdcùng cấp → nội dung ghi đèdefinition.description. - Module phải export default hoặc export object
ToolsOptions. - Trùng tên → bỏ qua (tool đầu tiên thắng).
SkillsLoader
Chỉ nhận file có tên SKILLS.md - parse front matter bằng gray-matter.
ai/skills/
├── order-support/
│ ├── SKILLS.md → skill definition
│ └── tools/
│ ├── lookup-order.ts → tool name tự merge vào skill.tools
│ └── cancel-order.ts
└── product-info/
└── SKILLS.mdQuy tắc:
- Front matter →
scope,name,description,tools,introduction. - Body markdown →
content. - Nếu thư mục có
tools/→ scan tool names và merge vàoskills.tools[].
AIBotLoader
Quét bot config - mỗi bot là file .ts/.js hoặc thư mục.
ai/bots/
├── support-bot/
│ ├── index.ts → AIBotOptions (phải có username)
│ ├── prompt.md → systemPrompt
│ ├── skills/
│ │ └── SKILLS.md → auto-discover skill
│ └── tools/
│ └── lookup.ts → auto-discover tool
└── simple-bot.ts → bot đơn fileQuy tắc:
index.tsexportAIBotOptions(phải cóusername).prompt.md→ full text trở thànhsystemPrompt.skills/→ quétSKILLS.md→ merge tên skill vào bot config.tools/→ quét.ts/.js→ merge tên tool vàobot.tools[].
MCPLoader
Mỗi file là config cho một MCP server.
ai/mcp/
├── github.ts → MCPOptions cho server "github"
└── company-api.ts → MCPOptions cho server "company-api"Tool name = tên file. Module export MCPOptions (transport, command/url, etc.).
LangChain dependencies
Package tích hợp sâu với hệ sinh thái LangChain:
| Package | Dùng cho |
|---|---|
@langchain/core | Interface StructuredToolInterface, base types |
@langchain/openai | OpenAI model integration |
@langchain/anthropic | Anthropic (Claude) model integration |
@langchain/google-genai | Google Gemini integration |
@langchain/deepseek | DeepSeek model integration |
@langchain/ollama | Ollama (local LLM) integration |
@langchain/community | Community model adapters |
@langchain/mcp-adapters | MultiServerMCPClient - kết nối MCP servers |
@langchain/langgraph | Graph-based agent orchestration |
@langchain/langgraph-checkpoint | Agent state checkpointing |
langchain | Document loaders, text splitters, chains |
Các thư viện parse tài liệu
| Package | Format |
|---|---|
pdf-parse | |
mammoth | DOCX (Word) |
word-extractor | DOC (Word cũ) |
officeparser | Office formats tổng quát |
gray-matter | YAML front matter trong Markdown |
flexsearch | Full-text search engine |
Kiến trúc tổng hợp - luồng xử lý AI
- User gửi tin nhắn cho bot.
- Bot kết hợp system prompt + skills + lịch sử hội thoại → gửi đến LLM.
- LLM quyết định gọi tool (function calling).
- ToolsManager tìm tool:
- Tool nội bộ → gọi
invoke()trực tiếp. - MCP tool → qua
MultiServerMCPClient→ gọi MCP server.
- Tool nội bộ → gọi
- Kết quả tool trả về LLM.
- LLM sinh response cuối cùng → trả về user.
Ví dụ tổng hợp - plugin AI đơn giản
typescript
import { Plugin } from '@digiforce-nc/server';
class MyAIPlugin extends Plugin {
async load() {
const ai = this.app.aiManager;
// Đăng ký tool
ai.toolsManager.registerTools({
scope: 'GENERAL',
definition: {
name: 'company-search',
description: 'Tìm kiếm thông tin công ty trong hệ thống',
schema: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
},
invoke: async (ctx, { query }) => {
const repo = ctx.db.getRepository('companies');
return repo.find({
filter: { name: { $includes: query } },
limit: 5,
});
},
});
// Đăng ký MCP server
ai.mcpManager.registerMCP({
'slack-tools': {
transport: 'stdio',
command: 'npx',
args: ['@anthropic/mcp-server-slack'],
env: { SLACK_TOKEN: process.env.SLACK_TOKEN },
},
});
// Kết nối MCP tools vào ToolsManager
ai.toolsManager.registerDynamicTools(
ai.mcpManager.getMCPToolsProvider(),
);
// Tạo search index
ai.documentManager.addIndex('help-articles');
}
}Troubleshooting - xử lý sự cố thường gặp
MCP connection failed
Triệu chứng: rebuildClient() throw error, MCP tools không xuất hiện trong listTools().
Nguyên nhân phổ biến:
- stdio transport - command không tồn tại hoặc thiếu quyền thực thi
- http/sse transport - URL sai, network timeout, hoặc server không chạy
- Environment variables - thiếu token/credentials trong
env
Debug steps:
typescript
// Test connection trước khi register
const testResult = await aiManager.mcpManager.testConnection({
transport: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-github'],
env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN },
});
if (!testResult.success) {
console.error('MCP test failed:', testResult.error);
// → Kiểm tra command path, env vars, network
}Giải pháp:
- Verify command tồn tại:
which npxhoặcwhere npx - Test manual: chạy command trực tiếp trong terminal
- Check logs:
app.log.errortrongrebuildClient()catch block - Timeout 60s - nếu server khởi động chậm, tăng timeout hoặc pre-warm server
Skills không load vào bot
Triệu chứng: Bot đã có skills: ['skill-name'] nhưng LLM không thấy skill content.
Nguyên nhân:
- Skill chưa được
init()→ vẫn nằm trong memory, chưa flush xuống DB - Skill name không khớp (typo, case-sensitive)
- Bot query DB nhưng skill chưa persist
Debug:
typescript
// Kiểm tra skill đã vào DB chưa
const skill = await aiManager.skillsManager.getSkills('skill-name');
console.log('Skill in DB:', skill);
// Kiểm tra bot config
const bot = await aiManager.aibotManager.getAIBot('bot-username');
console.log('Bot skills:', bot.skillSettings?.skills);Giải pháp:
- Đảm bảo
skillsManager.init()được gọi trước khi bot sử dụng - Verify skill name chính xác (check
SKILLS.mdfront matter) - Nếu update skill content → gọi lại
registerSkills()để upsert DB
Tool permission bị ASK khi mong đợi ALLOW
Triệu chứng: MCP tool luôn hỏi user confirm, dù tool name bắt đầu bằng get.
Nguyên nhân:
seedPermissions()chỉ chạy trongrebuildClient()- nếu tool được add sau đó, không có permission seed- Tool name không match pattern
^get(ví dụ:Get,GET,getUser→ OK;fetch,retrieve→ ASK)
Giải pháp:
typescript
// Manual update permission
await aiManager.mcpManager.updateMCPToolPermission(
'mcp-github-tools-create_issue',
'ALLOW'
);
// Hoặc customize seed logic
// Override seedPermissions() trong custom MCPManagerFlexSearch index quá lớn, memory leak
Triệu chứng: App memory tăng dần, GC không thu hồi được.
Nguyên nhân:
- FlexSearch index toàn bộ trong memory - không có disk persistence
- Add quá nhiều document mà không giới hạn
Giải pháp:
typescript
// Giới hạn số document
const MAX_DOCS = 10000;
let docCount = 0;
async function addWithLimit(id, content) {
if (docCount >= MAX_DOCS) {
// Xóa document cũ nhất hoặc clear index
await index.remove(oldestDocId);
}
await index.add(id, content);
docCount++;
}
// Hoặc dùng LRU cache pattern
// Hoặc chuyển sang vector DB (Pinecone, Weaviate) cho scale lớnBest practice:
- Index < 50MB text (~10k documents × 5KB)
- Nếu cần nhiều hơn → dùng external search service (Elasticsearch, Algolia)
- DocumentManager phù hợp cho in-app search, không phải production search engine
Workflow tools bị ghi đè khi update bot
Triệu chứng: Bot có tools từ workflow (workflowAiTool-*), sau khi update bot config, tools này mất.
Nguyên nhân: Code đã handle - registerAIBotInDatabase() merge tools thông minh, giữ workflowAiTool-*.
Verify:
typescript
// Check merge logic trong ai-bot-manager/index.ts:98-100
let { tools } = current?.skillSettings ?? { tools: [] };
tools = tools?.length ? tools.filter((s) => s.name?.startsWith('workflowAiTool-')) : [];
const mergedTools = new Set([...tools, ...aibot.tools]);Nếu vẫn mất → kiểm tra có override registerAIBotInDatabase() không.
Dynamic tools không xuất hiện trong listTools()
Triệu chứng: registerDynamicTools() đã gọi, nhưng listTools() không trả về tool.
Nguyên nhân:
- Dynamic provider throw error → bị skip
- Provider không gọi
register.registerTools()đúng cách
Debug:
typescript
// Wrap provider với error handling
aiManager.toolsManager.registerDynamicTools(async (register) => {
try {
const tools = await fetchExternalTools();
register.registerTools(tools);
} catch (e) {
console.error('Dynamic tools provider failed:', e);
// Provider fail không crash app, chỉ skip tools
}
});
// Test provider trực tiếp
const tools = await aiManager.toolsManager.listTools();
console.log('Total tools:', tools.length);Performance considerations
Tool execution timeout
Tool invoke() không có timeout mặc định - nếu tool gọi API chậm, LLM sẽ đợi mãi.
Best practice:
typescript
invoke: async (ctx, args, runtime) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000); // 30s
try {
const result = await fetch(url, { signal: controller.signal });
return result;
} catch (e) {
if (e.name === 'AbortError') {
throw new Error('Tool timeout after 30s');
}
throw e;
} finally {
clearTimeout(timeout);
}
}MCP connection pooling
Mỗi rebuildClient() tạo connection mới - nếu gọi liên tục, tốn tài nguyên.
Optimization:
- Chỉ gọi
rebuildClient()khi:- MCP config thay đổi (add/remove/update server)
- Connection bị đứt (detect qua error)
- Không gọi trong hot path (mỗi request)
Skills content size
Skill content được inject vào LLM context - nếu quá dài, tốn token và làm chậm response.
Guideline:
- Mỗi skill < 2000 tokens (~1500 words)
- Nếu cần nhiều hơn → chia thành nhiều skill nhỏ
- Bot chỉ load skills cần thiết, không load tất cả
Best practices
1. Init sequence đúng thứ tự
typescript
// ❌ SAI - bot dùng skill trước khi skill vào DB
await aiManager.aibotManager.init();
await aiManager.skillsManager.init();
// ✅ ĐÚNG
await aiManager.skillsManager.init(); // Skills trước
await aiManager.aibotManager.init(); // Bot sau
await aiManager.mcpManager.init(); // MCP cuối (cần DB)2. Tool naming convention
typescript
// ✅ TỐT - tên rõ ràng, mô tả chính xác
{
name: 'lookup-order-by-id',
description: 'Tra cứu thông tin đơn hàng theo mã đơn hàng',
}
// ❌ TỆ - tên mơ hồ, mô tả chung chung
{
name: 'get-data',
description: 'Get some data',
}LLM quyết định gọi tool dựa vào name + description - phải rõ ràng, cụ thể.
3. Error handling trong tool invoke
typescript
invoke: async (ctx, args) => {
try {
const result = await doSomething(args);
return result;
} catch (e) {
// ✅ Trả error message rõ ràng cho LLM
return {
error: true,
message: `Failed to lookup order: ${e.message}`,
suggestion: 'Please check if order ID is correct',
};
// ❌ Throw error → LLM không biết xử lý
// throw e;
}
}4. MCP tool permission strategy
typescript
// Read-only tools → ALLOW (tự động)
// Write/destructive tools → ASK (cần confirm)
// Nếu tool an toàn nhưng không match pattern 'get*'
await mcpManager.updateMCPToolPermission(
'mcp-server-list-files', // Read-only nhưng tên 'list'
'ALLOW'
);5. Knowledge base integration
typescript
// Kết hợp DocumentManager với bot
const index = aiManager.documentManager.addIndex('help-docs');
// Index tài liệu
await index.add('doc-1', helpArticle1);
await index.add('doc-2', helpArticle2);
// Bot skill tìm kiếm
const skill = {
name: 'search-help',
content: `
When user asks for help, search the knowledge base first:
1. Use tool 'search-help-docs' with user's query
2. Summarize relevant articles
3. Provide direct answer based on search results
`,
tools: ['search-help-docs'],
};
// Tool tìm kiếm
const tool = {
name: 'search-help-docs',
invoke: async (ctx, { query }) => {
const results = await index.search(query);
const docs = await Promise.all(
results.map(id => getDocContent(id))
);
return docs;
},
};Đọc thêm
- Server - Application - lifecycle nơi AIManager được khởi tạo
- FAQ - Core
- Mã nguồn