feat: support course-specific Qwen endpoints
This commit is contained in:
@@ -66,6 +66,7 @@ export class AgentService {
|
||||
provider: setting.provider as AiProvider,
|
||||
model: setting.model,
|
||||
apiKey: this.credentialService.decrypt(setting.encryptedApiKey),
|
||||
endpointUrl: setting.endpointUrl,
|
||||
json: true,
|
||||
system: this.systemPrompt(),
|
||||
messages: [{
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('AiProviderService', () => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('calls the Qwen OpenAI-compatible chat endpoint', async () => {
|
||||
it('prefers the course Qwen endpoint over the server default', async () => {
|
||||
const fetchMock = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
@@ -20,20 +20,21 @@ describe('AiProviderService', () => {
|
||||
}),
|
||||
});
|
||||
global.fetch = fetchMock as typeof fetch;
|
||||
const config = { get: jest.fn().mockReturnValue('https://example.test/compatible-mode/v1') } as unknown as ConfigService;
|
||||
const config = { get: jest.fn().mockReturnValue('https://server-default.test/compatible-mode/v1') } as unknown as ConfigService;
|
||||
const service = new AiProviderService(config);
|
||||
|
||||
const result = await service.complete({
|
||||
provider: 'QWEN',
|
||||
model: 'qwen3.7-plus',
|
||||
apiKey: 'qwen-secret',
|
||||
endpointUrl: 'https://course-endpoint.test/compatible-mode/v1',
|
||||
system: 'system prompt',
|
||||
messages: [{ role: 'user', content: 'hello' }],
|
||||
json: true,
|
||||
});
|
||||
|
||||
expect(result.content).toBe('{"ok":true}');
|
||||
expect(fetchMock).toHaveBeenCalledWith('https://example.test/compatible-mode/v1/chat/completions', expect.objectContaining({
|
||||
expect(fetchMock).toHaveBeenCalledWith('https://course-endpoint.test/compatible-mode/v1/chat/completions', expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({ Authorization: 'Bearer qwen-secret' }),
|
||||
}));
|
||||
|
||||
@@ -7,6 +7,7 @@ type CompletionInput = {
|
||||
provider: AiProvider;
|
||||
model: string;
|
||||
apiKey: string;
|
||||
endpointUrl?: string | null;
|
||||
system: string;
|
||||
messages: Array<{ role: 'user' | 'assistant'; content: string }>;
|
||||
json?: boolean;
|
||||
@@ -105,7 +106,7 @@ export class AiProviderService {
|
||||
}
|
||||
|
||||
private async qwen(input: CompletionInput): Promise<AiCompletionResult> {
|
||||
const configuredUrl = this.configService.get<string>('QWEN_API_BASE_URL')?.trim();
|
||||
const configuredUrl = input.endpointUrl?.trim() || this.configService.get<string>('QWEN_API_BASE_URL')?.trim();
|
||||
const normalizedUrl = configuredUrl?.replace(/\/+$/, '');
|
||||
const endpoint = normalizedUrl
|
||||
? `${normalizedUrl}${normalizedUrl.endsWith('/chat/completions') ? '' : '/chat/completions'}`
|
||||
|
||||
@@ -14,6 +14,11 @@ export class UpdateCourseAiSettingDto {
|
||||
@MaxLength(120)
|
||||
model: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
endpointUrl?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
|
||||
Reference in New Issue
Block a user