feat: M0-08 — SiliconFlow provider + Content Safety integration + dynamic config
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 17s

This commit is contained in:
WangDL 2026-05-23 09:38:09 +08:00
parent 577b2c7abe
commit 22132410a2
4 changed files with 49 additions and 0 deletions

View File

@ -13,6 +13,7 @@ import { LearningTrendWorkflow } from './workflows/learning-trend.workflow';
import { AdminAiGatewayController } from './ai.controller';
import { MockAiProvider } from './providers/mock-ai.provider';
import { DeepSeekProvider } from './providers/deepseek.provider';
import { SiliconFlowProvider } from './providers/siliconflow.provider';
import { MiniMaxProvider } from './providers/minimax.provider';
import type { AiProvider } from './providers/ai-provider.interface';

View File

@ -4,6 +4,7 @@ import { ModelRouter } from '../model-router';
import { PromptTemplateService } from '../prompts/prompt-template.service';
import { AiCostCalculatorService } from '../usage/ai-cost-calculator.service';
import { AiUsageLogService } from '../usage/ai-usage-log.service';
import { ContentSafetyService } from '../../content-safety/content-safety.service';
import type { AiProvider } from '../providers/ai-provider.interface';
import type { GatewayRequest, GatewayResponse, ModelTier } from './ai-gateway.types';
@ -17,7 +18,9 @@ export class AiGatewayService {
private readonly promptTemplate: PromptTemplateService,
private readonly costCalculator: AiCostCalculatorService,
private readonly usageLog: AiUsageLogService,
private readonly contentSafety?: ContentSafetyService,
private readonly providers: Map<string, AiProvider>,
private readonly contentSafety?: ContentSafetyService,
) {}
async generate(request: GatewayRequest, timeoutMs = this.DEFAULT_TIMEOUT_MS): Promise<GatewayResponse> {
@ -47,6 +50,9 @@ export class AiGatewayService {
signal: controller.signal,
});
const safetyCheck = await this.contentSafety?.check(output.rawText, { contentType: 'ai_output' }).catch(() => ({ safe: true }));
if (!safetyCheck.safe) throw new Error('AI output blocked by content safety');
const parsed = this.parseJson(output.rawText, request.outputSchema);
const estimatedCost = this.costCalculator.calculate(
target.provider,

View File

@ -4,6 +4,8 @@ import type { AiProvider, AiGenerateInput, AiGenerateOutput } from './ai-provide
@Injectable()
export class DeepSeekProvider implements AiProvider {
private baseUrl = process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com/v1';
private apiKey = process.env.DEEPSEEK_API_KEY || '';
readonly name = 'deepseek';
private readonly logger = new Logger(DeepSeekProvider.name);
private readonly apiKey: string;

View File

@ -0,0 +1,40 @@
import { Injectable } from '@nestjs/common';
import type { AiProvider, AiGenerateInput, AiGenerateOutput } from './ai-provider.interface';
@Injectable()
export class SiliconFlowProvider implements AiProvider {
readonly name = 'siliconflow';
private getConfig() {
return {
baseUrl: process.env.SILICONFLOW_BASE_URL || 'https://api.siliconflow.cn/v1',
apiKey: process.env.SILICONFLOW_API_KEY || '',
};
}
async generate(input: AiGenerateInput): Promise<AiGenerateOutput> {
const { baseUrl, apiKey } = this.getConfig();
const start = Date.now();
const resp = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({
model: input.model,
messages: input.messages,
temperature: input.temperature ?? 0.3,
max_tokens: input.maxTokens ?? 4096,
}),
signal: input.signal ?? AbortSignal.timeout(60_000),
});
if (!resp.ok) throw new Error(`SiliconFlow ${resp.status}: ${await resp.text().slice(0, 200)}`);
const data = await resp.json();
const choice = data.choices?.[0];
return {
rawText: choice?.message?.content || '',
usage: { inputTokens: data.usage?.prompt_tokens || 0, outputTokens: data.usage?.completion_tokens || 0 },
latencyMs: Date.now() - start,
};
}
}