mirror of
https://github.com/nocobase/nocobase.git
synced 2026-09-19 02:23:00 +08:00
fix(ai): fixed breaking test of ai (#8941)
* fix(ai): fixed breaking test of ai * refactor(ai): move skill,ai-employee.mcp init action to ai plugin
This commit is contained in:
@@ -26,7 +26,7 @@ describe('AI employee loader test cases', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createMockServer({
|
||||
plugins: ['nocobase', 'field-sort', 'workflow'],
|
||||
plugins: ['nocobase'],
|
||||
});
|
||||
await app.pm.enable('ai');
|
||||
aiManager = app.aiManager;
|
||||
@@ -34,7 +34,13 @@ describe('AI employee loader test cases', () => {
|
||||
loader = new AIEmployeeLoader(aiManager, {
|
||||
scan: {
|
||||
basePath,
|
||||
pattern: ['**/ai-employees/*.ts', '**/ai-employees/*/index.ts', '!**/ai-employees/**/*.d.ts'],
|
||||
pattern: [
|
||||
'**/ai-employees/*.ts',
|
||||
'**/ai-employees/*/index.ts',
|
||||
'**/ai-employees/*.js',
|
||||
'**/ai-employees/*/index.js',
|
||||
'!**/ai-employees/**/*.d.ts',
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('MCP loader test cases', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createMockServer({
|
||||
plugins: ['nocobase', 'field-sort', 'workflow'],
|
||||
plugins: ['nocobase'],
|
||||
});
|
||||
await app.pm.enable('ai');
|
||||
aiManager = app.aiManager;
|
||||
@@ -41,7 +41,6 @@ describe('MCP loader test cases', () => {
|
||||
|
||||
it('should load mcp definitions in mcp root directory', async () => {
|
||||
await loader.load();
|
||||
await mcpManager.persistence();
|
||||
|
||||
const entry = await mcpManager.getMCP('weather');
|
||||
expect(entry).toBeDefined();
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('Skills loader test cases', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createMockServer({
|
||||
plugins: ['nocobase', 'field-sort', 'workflow'],
|
||||
plugins: ['nocobase'],
|
||||
});
|
||||
// with mysql add ai plugin in createMockServer will occur error with create usersAiEmployees collection at app startup
|
||||
await app.pm.enable('ai');
|
||||
|
||||
@@ -28,12 +28,13 @@ export class DefaultAIEmployeeManager implements AIEmployeeManager {
|
||||
|
||||
constructor(private readonly app: any) {
|
||||
this.provideCollectionManager = () => app.mainDataSource;
|
||||
this.app.on('afterStart', async () => {
|
||||
if (this.mode === 'memory') {
|
||||
await this.persistence();
|
||||
this.mode = 'database';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.mode === 'memory') {
|
||||
await this.persistence();
|
||||
this.mode = 'database';
|
||||
}
|
||||
}
|
||||
|
||||
async getEmployee(username: string): Promise<AIEmployeeEntry> {
|
||||
|
||||
@@ -55,6 +55,7 @@ export type AIEmployeeFilter = {
|
||||
};
|
||||
|
||||
export interface AIEmployeeManager {
|
||||
init(): Promise<void>;
|
||||
getEmployee(username: string): Promise<AIEmployeeEntry>;
|
||||
listEmployees(filter?: AIEmployeeFilter): Promise<AIEmployeeEntry[]>;
|
||||
registerEmployee(options: AIEmployeeOptions): Promise<void>;
|
||||
|
||||
@@ -92,11 +92,9 @@ export class AIEmployeeLoader extends LoadAndRegister<AIEmployeeLoaderOptions> {
|
||||
return;
|
||||
}
|
||||
const { employeeManager } = this.ai;
|
||||
await Promise.all(
|
||||
this.employeeDescriptors.map(async (descriptor) => {
|
||||
await employeeManager.registerEmployee(descriptor.options);
|
||||
}),
|
||||
);
|
||||
for (const descriptor of this.employeeDescriptors) {
|
||||
await employeeManager.registerEmployee(descriptor.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,18 +101,16 @@ export class SkillsLoader extends LoadAndRegister<SkillsLoaderOptions> {
|
||||
return;
|
||||
}
|
||||
const { skillsManager } = this.ai;
|
||||
await Promise.all(
|
||||
this.skillsDescriptors.map(async (descriptor) => {
|
||||
await skillsManager.registerSkills({
|
||||
scope: descriptor.scope,
|
||||
name: descriptor.name,
|
||||
description: descriptor.description,
|
||||
content: descriptor.content,
|
||||
tools: descriptor.tools,
|
||||
introduction: descriptor.introduction,
|
||||
});
|
||||
}),
|
||||
);
|
||||
for (const descriptor of this.skillsDescriptors) {
|
||||
await skillsManager.registerSkills({
|
||||
scope: descriptor.scope,
|
||||
name: descriptor.name,
|
||||
description: descriptor.description,
|
||||
content: descriptor.content,
|
||||
tools: descriptor.tools,
|
||||
introduction: descriptor.introduction,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,17 +26,18 @@ export class DefaultMCPManager implements MCPManager {
|
||||
|
||||
constructor(private readonly app: any) {
|
||||
this.provideCollectionManager = () => app.mainDataSource;
|
||||
this.app.on('afterStart', async () => {
|
||||
if (this.mode === 'memory') {
|
||||
await this.persistence();
|
||||
this.mode = 'database';
|
||||
}
|
||||
try {
|
||||
await this.rebuildClient();
|
||||
} catch (e) {
|
||||
this.app.log.error('fail to init mcp clients', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.mode === 'memory') {
|
||||
await this.persistence();
|
||||
this.mode = 'database';
|
||||
}
|
||||
try {
|
||||
await this.rebuildClient();
|
||||
} catch (e) {
|
||||
this.app.log.error('fail to init mcp clients', e);
|
||||
}
|
||||
}
|
||||
|
||||
async registerMCP(registration: { [key: string | symbol]: MCPOptions }): Promise<void> {
|
||||
@@ -46,7 +47,7 @@ export class DefaultMCPManager implements MCPManager {
|
||||
}
|
||||
} else {
|
||||
for (const [name, options] of Object.entries(registration)) {
|
||||
this.persistenceEntry({
|
||||
await this.persistenceEntry({
|
||||
name,
|
||||
...this.normalizeEntry(name, options),
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { MultiServerMCPClient } from '@langchain/mcp-adapters';
|
||||
import type { DynamicToolsProvider, Permission } from '../tools-manager/types';
|
||||
|
||||
export interface MCPManager extends MCPRegistration {
|
||||
init(): Promise<void>;
|
||||
getMCP(name: string): Promise<MCPEntry>;
|
||||
listMCP(filter: MCPFilter): Promise<MCPEntry[]>;
|
||||
testConnection(options: MCPOptions): Promise<MCPTestResult>;
|
||||
|
||||
@@ -20,12 +20,13 @@ export class DefaultSkillsManager implements SkillsManager {
|
||||
|
||||
constructor(private readonly app: any) {
|
||||
this.provideCollectionManager = () => app.mainDataSource;
|
||||
this.app.on('afterStart', async () => {
|
||||
if (this.mode === 'memory') {
|
||||
await this.persistence();
|
||||
this.mode = 'database';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.mode === 'memory') {
|
||||
await this.persistence();
|
||||
this.mode = 'database';
|
||||
}
|
||||
}
|
||||
|
||||
getSkills(name: string[]): Promise<SkillsEntry[]>;
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
*/
|
||||
|
||||
export interface SkillsManager {
|
||||
init(): Promise<void>;
|
||||
getSkills(name: string[]): Promise<SkillsEntry[]>;
|
||||
getSkills(name: string): Promise<SkillsEntry>;
|
||||
listSkills(filter?: SkillsFilter): Promise<SkillsEntry[]>;
|
||||
registerSkills(options: SkillsOptions): Promise<void>;
|
||||
persistence(): Promise<void>;
|
||||
}
|
||||
|
||||
export type SkillsOptions = {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { getMCPToolsByServer } from '../../ai-employees/admin/mcp/MCPToolsList';
|
||||
|
||||
describe('getMCPToolsByServer', () => {
|
||||
it('returns tools for the target MCP server only', () => {
|
||||
expect(
|
||||
getMCPToolsByServer(
|
||||
{
|
||||
weather: [
|
||||
{ name: 'mcp-weather-current', title: 'current', serverName: 'weather', permission: 'ALLOW' },
|
||||
{ name: 'mcp-weather-forecast', title: 'forecast', serverName: 'weather', permission: 'ASK' },
|
||||
],
|
||||
maps: [{ name: 'mcp-maps-search', title: 'search', serverName: 'maps', permission: 'ASK' }],
|
||||
},
|
||||
'weather',
|
||||
),
|
||||
).toEqual([
|
||||
{ name: 'mcp-weather-current', title: 'current', serverName: 'weather', permission: 'ALLOW' },
|
||||
{ name: 'mcp-weather-forecast', title: 'forecast', serverName: 'weather', permission: 'ASK' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an empty array for unknown servers', () => {
|
||||
expect(getMCPToolsByServer(undefined, 'weather')).toEqual([]);
|
||||
expect(getMCPToolsByServer({}, 'weather')).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -90,6 +90,11 @@ export class PluginAIServer extends Plugin {
|
||||
}
|
||||
},
|
||||
});
|
||||
this.app.on('afterStart', async () => {
|
||||
await this.ai.skillsManager.init();
|
||||
await this.ai.employeeManager.init();
|
||||
await this.ai.mcpManager.init();
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
|
||||
Reference in New Issue
Block a user