feat: M0-08 — SiliconFlow provider + Content Safety integration + dynamic config
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 17s
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 17s
This commit is contained in:
parent
577b2c7abe
commit
22132410a2
@ -13,6 +13,7 @@ import { LearningTrendWorkflow } from './workflows/learning-trend.workflow';
|
|||||||
import { AdminAiGatewayController } from './ai.controller';
|
import { AdminAiGatewayController } from './ai.controller';
|
||||||
import { MockAiProvider } from './providers/mock-ai.provider';
|
import { MockAiProvider } from './providers/mock-ai.provider';
|
||||||
import { DeepSeekProvider } from './providers/deepseek.provider';
|
import { DeepSeekProvider } from './providers/deepseek.provider';
|
||||||
|
import { SiliconFlowProvider } from './providers/siliconflow.provider';
|
||||||
import { MiniMaxProvider } from './providers/minimax.provider';
|
import { MiniMaxProvider } from './providers/minimax.provider';
|
||||||
import type { AiProvider } from './providers/ai-provider.interface';
|
import type { AiProvider } from './providers/ai-provider.interface';
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ModelRouter } from '../model-router';
|
|||||||
import { PromptTemplateService } from '../prompts/prompt-template.service';
|
import { PromptTemplateService } from '../prompts/prompt-template.service';
|
||||||
import { AiCostCalculatorService } from '../usage/ai-cost-calculator.service';
|
import { AiCostCalculatorService } from '../usage/ai-cost-calculator.service';
|
||||||
import { AiUsageLogService } from '../usage/ai-usage-log.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 { AiProvider } from '../providers/ai-provider.interface';
|
||||||
import type { GatewayRequest, GatewayResponse, ModelTier } from './ai-gateway.types';
|
import type { GatewayRequest, GatewayResponse, ModelTier } from './ai-gateway.types';
|
||||||
|
|
||||||
@ -17,7 +18,9 @@ export class AiGatewayService {
|
|||||||
private readonly promptTemplate: PromptTemplateService,
|
private readonly promptTemplate: PromptTemplateService,
|
||||||
private readonly costCalculator: AiCostCalculatorService,
|
private readonly costCalculator: AiCostCalculatorService,
|
||||||
private readonly usageLog: AiUsageLogService,
|
private readonly usageLog: AiUsageLogService,
|
||||||
|
private readonly contentSafety?: ContentSafetyService,
|
||||||
private readonly providers: Map<string, AiProvider>,
|
private readonly providers: Map<string, AiProvider>,
|
||||||
|
private readonly contentSafety?: ContentSafetyService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async generate(request: GatewayRequest, timeoutMs = this.DEFAULT_TIMEOUT_MS): Promise<GatewayResponse> {
|
async generate(request: GatewayRequest, timeoutMs = this.DEFAULT_TIMEOUT_MS): Promise<GatewayResponse> {
|
||||||
@ -47,6 +50,9 @@ export class AiGatewayService {
|
|||||||
signal: controller.signal,
|
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 parsed = this.parseJson(output.rawText, request.outputSchema);
|
||||||
const estimatedCost = this.costCalculator.calculate(
|
const estimatedCost = this.costCalculator.calculate(
|
||||||
target.provider,
|
target.provider,
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import type { AiProvider, AiGenerateInput, AiGenerateOutput } from './ai-provide
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeepSeekProvider implements AiProvider {
|
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';
|
readonly name = 'deepseek';
|
||||||
private readonly logger = new Logger(DeepSeekProvider.name);
|
private readonly logger = new Logger(DeepSeekProvider.name);
|
||||||
private readonly apiKey: string;
|
private readonly apiKey: string;
|
||||||
|
|||||||
40
src/modules/ai/providers/siliconflow.provider.ts
Normal file
40
src/modules/ai/providers/siliconflow.provider.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user