feat: AI岗位专区 — 5个AI岗位置顶 + 首页分组展示
- schema: HotPosition 新增 category 字段 (ai/traditional)
- positions: 5 AI岗位 (AI算法/大模型应用/Prompt/AI产品/AI运维) + 7传统岗位
- frontend: 首页拆分 "🔥 AI热门岗位" 置顶高亮 + "更多岗位" 折叠
- ai服务: 新增 primaryFallbackModel (sensenova-6.7-flash-lite) 降级链路
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import axios from 'axios'
|
||||
import https from 'https'
|
||||
import { Injectable, Logger } from "@nestjs/common"
|
||||
import axios from "axios"
|
||||
import https from "https"
|
||||
|
||||
interface AiCallOptions {
|
||||
systemPrompt: string
|
||||
@@ -15,26 +15,35 @@ const httpAgent = new https.Agent({ rejectUnauthorized: true, keepAlive: true })
|
||||
export class AiService {
|
||||
private readonly logger = new Logger(AiService.name)
|
||||
|
||||
private readonly primaryUrl = process.env.AI_PRIMARY_URL || 'https://token.sensenova.cn/v1'
|
||||
private readonly primaryKey = process.env.AI_PRIMARY_KEY || ''
|
||||
private readonly primaryModel = process.env.AI_PRIMARY_MODEL || 'deepseek-v4-flash'
|
||||
private readonly primaryUrl = process.env.AI_PRIMARY_URL || "https://token.sensenova.cn/v1"
|
||||
private readonly primaryKey = process.env.AI_PRIMARY_KEY || ""
|
||||
private readonly primaryModel = process.env.AI_PRIMARY_MODEL || "deepseek-v4-flash"
|
||||
private readonly primaryFallbackModel = process.env.AI_PRIMARY_FALLBACK_MODEL || "sensenova-6.7-flash-lite"
|
||||
|
||||
private readonly backupUrl = process.env.AI_BACKUP_URL || 'https://integrate.api.nvidia.com/v1'
|
||||
private readonly backupKey = process.env.AI_BACKUP_KEY || ''
|
||||
private readonly backupModel = process.env.AI_BACKUP_MODEL || 'stepfun-ai/step-3.5-flash'
|
||||
private readonly backupUrl = process.env.AI_BACKUP_URL || "https://integrate.api.nvidia.com/v1"
|
||||
private readonly backupKey = process.env.AI_BACKUP_KEY || ""
|
||||
private readonly backupModel = process.env.AI_BACKUP_MODEL || "stepfun-ai/step-3.5-flash"
|
||||
|
||||
async call(options: AiCallOptions): Promise<string> {
|
||||
const { systemPrompt, userMessage, temperature = 0.7, maxTokens = 2048 } = options
|
||||
|
||||
// Try primary AI
|
||||
// Try primary AI (deepseek-v4-flash on sensenova)
|
||||
try {
|
||||
const result = await this.callApi(this.primaryUrl, this.primaryKey, this.primaryModel, systemPrompt, userMessage, temperature, maxTokens)
|
||||
if (result) return result
|
||||
} catch (e) {
|
||||
this.logger.warn(`Primary AI failed: ${(e as Error).message}, trying backup...`)
|
||||
this.logger.warn(`Primary AI failed: ${(e as Error).message}, trying primary fallback...`)
|
||||
}
|
||||
|
||||
// Try backup AI
|
||||
// Try primary fallback model (sensenova-6.7-flash-lite, same provider)
|
||||
try {
|
||||
const result = await this.callApi(this.primaryUrl, this.primaryKey, this.primaryFallbackModel, systemPrompt, userMessage, temperature, maxTokens)
|
||||
if (result) return result
|
||||
} catch (e) {
|
||||
this.logger.warn(`Primary fallback AI also failed: ${(e as Error).message}, trying backup...`)
|
||||
}
|
||||
|
||||
// Try backup AI (NVIDIA)
|
||||
try {
|
||||
const result = await this.callApi(this.backupUrl, this.backupKey, this.backupModel, systemPrompt, userMessage, temperature, maxTokens)
|
||||
if (result) return result
|
||||
@@ -43,7 +52,7 @@ export class AiService {
|
||||
}
|
||||
|
||||
// Final fallback
|
||||
throw new Error('AI 服务暂时不可用,请稍后重试')
|
||||
throw new Error("AI \u670d\u52a1\u6682\u65f6\u4e0d\u53ef\u7528\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5")
|
||||
}
|
||||
|
||||
private async callApi(
|
||||
@@ -56,16 +65,16 @@ export class AiService {
|
||||
{
|
||||
model,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userMessage },
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: userMessage },
|
||||
],
|
||||
temperature,
|
||||
max_tokens: maxTokens,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 60000,
|
||||
httpsAgent: httpAgent,
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
|
||||
import { Document } from 'mongoose'
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"
|
||||
import { Document } from "mongoose"
|
||||
|
||||
export type HotPositionDocument = HotPosition & Document
|
||||
|
||||
export type PositionCategory = "ai" | "traditional"
|
||||
|
||||
@Schema({ timestamps: true })
|
||||
export class HotPosition {
|
||||
@Prop({ required: true })
|
||||
name: string
|
||||
|
||||
@Prop({ default: '' })
|
||||
@Prop({ default: "" })
|
||||
salary?: string
|
||||
|
||||
@Prop({ default: '' })
|
||||
@Prop({ default: "" })
|
||||
company?: string
|
||||
|
||||
@Prop({ default: '' })
|
||||
@Prop({ default: "" })
|
||||
icon?: string
|
||||
|
||||
@Prop({ default: 0 })
|
||||
@@ -22,7 +24,11 @@ export class HotPosition {
|
||||
|
||||
@Prop({ default: true })
|
||||
active: boolean
|
||||
|
||||
@Prop({ type: String, enum: ["ai", "traditional"], default: "traditional" })
|
||||
category: PositionCategory
|
||||
}
|
||||
|
||||
export const HotPositionSchema = SchemaFactory.createForClass(HotPosition)
|
||||
HotPositionSchema.index({ sort: 1 })
|
||||
HotPositionSchema.index({ category: 1, active: 1 })
|
||||
|
||||
Reference in New Issue
Block a user