Bỏ qua, đến nội dung

@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

PhaThời điểmHành động
1. LoadKhi app khởi độngLoader quét filesystem → đăng ký tools/skills/bots/MCP vào manager (in-memory)
2. InitSau khi DB sẵn sàngManager 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();
  }
}
PropertyImplementationCần DB?Mô tả
toolsManagerDefaultToolsManagerKhôngRegistry tools + dynamic providers
skillsManagerDefaultSkillsManagerSkills in-memory → DB
aibotManagerDefaultAIBotManagerBot config in-memory → DB
mcpManagerDefaultMCPManagerMCP server config → DB + LangChain client
documentManagerDocumentManagerKhôngFlexSearch index/document
mcpToolsManagerMcpToolsManagerKhôngMCP 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>;
}
FieldMặ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
silencefalseNế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

MethodMô 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ủy

File 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

MethodMô 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

MethodMô 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

TransportMô tảVí dụ
stdioChạy process con, giao tiếp qua stdin/stdoutTool CLI local
sseServer-Sent Events qua HTTPRemote MCP server
httpHTTP streamableRemote 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

MethodMô 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ạiClassDùng cho
IndexIndex (wrapper FlexSearch)Tìm kiếm text đơn giản: thêm text → search
DocumentDocument (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

MethodMô 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.md cù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.md

Quy 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ào skills.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 file

Quy tắc:

  • index.ts export AIBotOptions (phải có username).
  • prompt.md → full text trở thành systemPrompt.
  • skills/ → quét SKILLS.md → merge tên skill vào bot config.
  • tools/ → quét .ts/.js → merge tên tool vào bot.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:

PackageDùng cho
@langchain/coreInterface StructuredToolInterface, base types
@langchain/openaiOpenAI model integration
@langchain/anthropicAnthropic (Claude) model integration
@langchain/google-genaiGoogle Gemini integration
@langchain/deepseekDeepSeek model integration
@langchain/ollamaOllama (local LLM) integration
@langchain/communityCommunity model adapters
@langchain/mcp-adaptersMultiServerMCPClient - kết nối MCP servers
@langchain/langgraphGraph-based agent orchestration
@langchain/langgraph-checkpointAgent state checkpointing
langchainDocument loaders, text splitters, chains

Các thư viện parse tài liệu

PackageFormat
pdf-parsePDF
mammothDOCX (Word)
word-extractorDOC (Word cũ)
officeparserOffice formats tổng quát
gray-matterYAML front matter trong Markdown
flexsearchFull-text search engine

Kiến trúc tổng hợp - luồng xử lý AI

  1. User gửi tin nhắn cho bot.
  2. Bot kết hợp system prompt + skills + lịch sử hội thoại → gửi đến LLM.
  3. LLM quyết định gọi tool (function calling).
  4. ToolsManager tìm tool:
    • Tool nội bộ → gọi invoke() trực tiếp.
    • MCP tool → qua MultiServerMCPClient → gọi MCP server.
  5. Kết quả tool trả về LLM.
  6. 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:

  1. stdio transport - command không tồn tại hoặc thiếu quyền thực thi
  2. http/sse transport - URL sai, network timeout, hoặc server không chạy
  3. 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 npx hoặc where npx
  • Test manual: chạy command trực tiếp trong terminal
  • Check logs: app.log.error trong rebuildClient() 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.md front 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 trong rebuildClient() - 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 MCPManager

FlexSearch 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ớn

Best 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