feat: support course-specific Qwen endpoints

This commit is contained in:
2026-10-14 15:12:08 +09:00
parent e3f0073124
commit f43ecd0f2a
8 changed files with 38 additions and 5 deletions
+22
View File
@@ -118,6 +118,7 @@ export class InstructorService {
enabled: setting?.enabled ?? false,
provider: setting?.provider ?? 'OPENAI',
model: setting?.model ?? 'gpt-4.1-mini',
endpointUrl: setting?.provider === 'QWEN' ? setting.endpointUrl : null,
hasApiKey: Boolean(setting?.encryptedApiKey),
updatedAt: setting?.updatedAt ?? null,
};
@@ -138,6 +139,9 @@ export class InstructorService {
if (input.enabled && !apiKey && (!existing?.encryptedApiKey || existing.provider !== input.provider)) {
throw new BadRequestException('AI 에이전트를 사용하거나 공급자를 변경하려면 해당 공급자의 API 키를 입력하세요.');
}
const endpointUrl = input.provider === 'QWEN'
? this.normalizeQwenEndpoint(input.endpointUrl)
: null;
const setting = await this.prisma.courseAiSetting.upsert({
where: { courseId: course.id },
create: {
@@ -145,12 +149,14 @@ export class InstructorService {
enabled: input.enabled,
provider: input.provider,
model: input.model.trim(),
endpointUrl,
encryptedApiKey: apiKey ? this.aiCredentialService.encrypt(apiKey) : null,
},
update: {
enabled: input.enabled,
provider: input.provider,
model: input.model.trim(),
...(input.provider !== 'QWEN' || input.endpointUrl !== undefined ? { endpointUrl } : {}),
...(apiKey ? { encryptedApiKey: this.aiCredentialService.encrypt(apiKey) } : {}),
},
});
@@ -159,11 +165,27 @@ export class InstructorService {
enabled: setting.enabled,
provider: setting.provider,
model: setting.model,
endpointUrl: setting.provider === 'QWEN' ? setting.endpointUrl : null,
hasApiKey: Boolean(setting.encryptedApiKey),
updatedAt: setting.updatedAt,
};
}
private normalizeQwenEndpoint(value: string | undefined): string | null {
const endpoint = value?.trim();
if (!endpoint) return null;
let url: URL;
try {
url = new URL(endpoint);
} catch {
throw new BadRequestException('Qwen 엔드포인트 주소가 올바르지 않습니다.');
}
if (url.protocol !== 'https:' || url.username || url.password) {
throw new BadRequestException('Qwen 엔드포인트는 인증정보가 없는 HTTPS 주소여야 합니다.');
}
return endpoint.replace(/\/+$/, '');
}
async listCourses(accessToken: string | undefined) {
const { authUser, instructor } = await this.getInstructor(accessToken);
const organizationName = authUser.primaryAffiliation?.organizationName?.trim();