refactor(server): config system (#11081)

This commit is contained in:
forehalo
2025-03-27 12:32:28 +00:00
parent 7091111f85
commit 0ea38680fa
274 changed files with 7583 additions and 5841 deletions
@@ -1 +0,0 @@
export const gql = '/graphql';
@@ -1,160 +1,11 @@
import { randomBytes } from 'node:crypto';
import {
DEFAULT_DIMENSIONS,
OpenAIProvider,
} from '../../plugins/copilot/providers/openai';
import {
CopilotCapability,
CopilotChatOptions,
CopilotEmbeddingOptions,
CopilotImageToImageProvider,
CopilotImageToTextProvider,
CopilotProviderType,
CopilotTextToEmbeddingProvider,
CopilotTextToImageProvider,
CopilotTextToTextProvider,
PromptConfig,
PromptMessage,
} from '../../plugins/copilot/types';
import { PromptConfig, PromptMessage } from '../../plugins/copilot/providers';
import { NodeExecutorType } from '../../plugins/copilot/workflow/executor';
import {
WorkflowGraph,
WorkflowNodeType,
WorkflowParams,
} from '../../plugins/copilot/workflow/types';
import { gql } from './common';
import { TestingApp } from './testing-app';
import { sleep } from './utils';
// @ts-expect-error no error
export class MockCopilotTestProvider
extends OpenAIProvider
implements
CopilotTextToTextProvider,
CopilotTextToEmbeddingProvider,
CopilotTextToImageProvider,
CopilotImageToImageProvider,
CopilotImageToTextProvider
{
static override readonly type = CopilotProviderType.Test;
override readonly availableModels = [
'test',
'gpt-4o',
'gpt-4o-2024-08-06',
'fast-sdxl/image-to-image',
'lcm-sd15-i2i',
'clarity-upscaler',
'imageutils/rembg',
];
static override readonly capabilities = [
CopilotCapability.TextToText,
CopilotCapability.TextToEmbedding,
CopilotCapability.TextToImage,
CopilotCapability.ImageToImage,
CopilotCapability.ImageToText,
];
constructor() {
super({ apiKey: '1' });
}
override getCapabilities(): CopilotCapability[] {
return MockCopilotTestProvider.capabilities;
}
static override assetsConfig(_config: any) {
return true;
}
override get type(): CopilotProviderType {
return CopilotProviderType.Test;
}
override async isModelAvailable(model: string): Promise<boolean> {
return this.availableModels.includes(model);
}
// ====== text to text ======
override async generateText(
messages: PromptMessage[],
model: string = 'test',
options: CopilotChatOptions = {}
): Promise<string> {
this.checkParams({ messages, model, options });
// make some time gap for history test case
await sleep(100);
return 'generate text to text';
}
override async *generateTextStream(
messages: PromptMessage[],
model: string = 'gpt-4o-mini',
options: CopilotChatOptions = {}
): AsyncIterable<string> {
this.checkParams({ messages, model, options });
// make some time gap for history test case
await sleep(100);
const result = 'generate text to text stream';
for (const message of result) {
yield message;
if (options.signal?.aborted) {
break;
}
}
}
// ====== text to embedding ======
override async generateEmbedding(
messages: string | string[],
model: string,
options: CopilotEmbeddingOptions = { dimensions: DEFAULT_DIMENSIONS }
): Promise<number[][]> {
messages = Array.isArray(messages) ? messages : [messages];
this.checkParams({ embeddings: messages, model, options });
// make some time gap for history test case
await sleep(100);
return [Array.from(randomBytes(options.dimensions)).map(v => v % 128)];
}
// ====== text to image ======
override async generateImages(
messages: PromptMessage[],
model: string = 'test',
_options: {
signal?: AbortSignal;
user?: string;
} = {}
): Promise<Array<string>> {
const { content: prompt } = messages[0] || {};
if (!prompt) {
throw new Error('Prompt is required');
}
// make some time gap for history test case
await sleep(100);
// just let test case can easily verify the final prompt
return [`https://example.com/${model}.jpg`, prompt];
}
override async *generateImagesStream(
messages: PromptMessage[],
model: string = 'dall-e-3',
options: {
signal?: AbortSignal;
user?: string;
} = {}
): AsyncIterable<string> {
const ret = await this.generateImages(messages, model, options);
for (const url of ret) {
yield url;
}
}
}
export const cleanObject = (
obj: any[] | undefined,
@@ -342,7 +193,7 @@ export async function addContextFile(
content: Buffer
): Promise<{ id: string }> {
const res = await app
.POST(gql)
.POST('/graphql')
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
.field(
'operations',
@@ -41,20 +41,17 @@ export async function createTestingApp(
moduleDef: TestingAppMetadata = {}
): Promise<TestingApp> {
const module = await createTestingModule(moduleDef, false);
const logger = new AFFiNELogger();
logger.setLogLevels([TEST_LOG_LEVEL]);
const app = module.createNestApplication<NestExpressApplication>({
cors: true,
bodyParser: true,
rawBody: true,
logger,
});
app.useBodyParser('raw', { limit: 1 * OneMB });
const logger = new AFFiNELogger();
logger.setLogLevels([TEST_LOG_LEVEL]);
app.useLogger(logger);
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
app.use(
graphqlUploadExpress({
@@ -8,9 +8,10 @@ import {
} from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import { AppModule, FunctionalityModules } from '../../app.module';
import { AFFiNELogger, JobQueue, Runtime } from '../../base';
import { buildAppModule, FunctionalityModules } from '../../app.module';
import { AFFiNELogger, JobQueue } from '../../base';
import { GqlModule } from '../../base/graphql';
import { ServerConfigModule } from '../../core';
import { AuthGuard, AuthModule } from '../../core/auth';
import { Mailer, MailModule } from '../../core/mail';
import { ModelsModule } from '../../models';
@@ -63,16 +64,18 @@ export async function createTestingModule(
autoInitialize = true
): Promise<TestingModule> {
// setting up
let imports = moduleDef.imports ?? [AppModule];
let imports = moduleDef.imports ?? [buildAppModule(globalThis.env)];
imports =
imports[0] === AppModule
? [AppModule]
// @ts-expect-error
imports[0].module?.name === 'AppModule'
? imports
: dedupeModules([
...FunctionalityModules,
ModelsModule,
AuthModule,
GqlModule,
MailModule,
ServerConfigModule,
...imports,
]);
@@ -101,10 +104,6 @@ export async function createTestingModule(
testingModule.initTestingDB = async () => {
await initTestingDB(module);
const runtime = module.get(Runtime);
// by pass password min length validation
await runtime.set('auth/password.min', 1);
};
testingModule.create = createFactory(
@@ -1,6 +1,7 @@
import { INestApplicationContext, LogLevel } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { PrismaClient } from '@prisma/client';
import whywhywhy from 'why-is-node-running';
import { RefreshFeatures0001 } from '../../data/migrations/0001-refresh-features';
@@ -32,3 +33,21 @@ export async function initTestingDB(context: INestApplicationContext) {
export async function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function debugProcessHolding(ignorePrismaStack = true) {
setImmediate(() => {
whywhywhy({
error: message => {
// ignore prisma error
if (
ignorePrismaStack &&
(message.includes('Prisma') || message.includes('prisma'))
) {
return;
}
console.error(message);
},
});
});
}