云
6 小时以前 95ae0c83d2aa58d86b4039e3f7f16ee93afb8f00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package cn.iocoder.yudao.module.ai.service.model;
 
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelUpdateStatusReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
import cn.iocoder.yudao.module.ai.dal.mysql.model.AiModelMapper;
import cn.iocoder.yudao.module.ai.enums.model.AiModelCapabilityEnum;
import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum;
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
 
@Service
@Validated
public class AiModelServiceImpl implements AiModelService {
 
    @Resource
    private AiApiKeyService apiKeyService;
    @Resource
    private AiModelMapper modelMapper;
    @Resource
    private AiModelFactory modelFactory;
    @Resource
    private YudaoAiProperties aiProperties;
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createModel(AiModelSaveReqVO createReqVO) {
        AiPlatformEnum.validatePlatform(createReqVO.getPlatform());
        apiKeyService.validateApiKey(createReqVO.getKeyId());
        AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class);
        model.setCapability(resolveCapability(createReqVO.getCapability()));
        if (CommonStatusEnum.isEnable(model.getStatus())) {
            disableOtherEnabledModels(model);
        }
        modelMapper.insert(model);
        return model.getId();
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateModel(AiModelSaveReqVO updateReqVO) {
        AiModelDO existing = validateModelExists(updateReqVO.getId());
        AiPlatformEnum.validatePlatform(updateReqVO.getPlatform());
        apiKeyService.validateApiKey(updateReqVO.getKeyId());
        AiModelDO model = BeanUtils.toBean(updateReqVO, AiModelDO.class);
        // 未传能力时沿用原值,避免老前端提交把视觉模型降级成文本能力
        model.setCapability(resolveCapability(
                updateReqVO.getCapability() == null || updateReqVO.getCapability().isBlank()
                        ? existing.getCapability() : updateReqVO.getCapability()));
        if (CommonStatusEnum.isEnable(model.getStatus())) {
            disableOtherEnabledModels(model);
        }
        modelMapper.updateById(model);
    }
 
    @Override
    public void deleteModel(Long id) {
        validateModelExists(id);
        modelMapper.deleteById(id);
    }
 
    @Override
    public AiModelDO getModel(Long id) {
        return modelMapper.selectById(id);
    }
 
    @Override
    public AiModelDO getRequiredDefaultModel(Integer type) {
        return getRequiredDefaultModel(type, AiModelCapabilityEnum.defaultCapability());
    }
 
    @Override
    public AiModelDO getRequiredDefaultModel(Integer type, String capability) {
        AiModelDO model = modelMapper.selectFirstByTypeAndCapabilityAndStatus(
                type, resolveCapability(capability), CommonStatusEnum.ENABLE.getStatus());
        if (model == null) {
            throw exception(MODEL_NOT_ENABLED,
                    AiModelTypeEnum.getName(type), AiModelCapabilityEnum.getName(resolveCapability(capability)));
        }
        return model;
    }
 
    @Override
    public PageResult<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO) {
        return modelMapper.selectPage(pageReqVO);
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateModelStatus(AiModelUpdateStatusReqVO updateReqVO) {
        AiModelDO model = validateModelExists(updateReqVO.getId());
        model.setStatus(updateReqVO.getStatus());
        if (CommonStatusEnum.isEnable(updateReqVO.getStatus())) {
            // 启用:自动关闭同「类型 + 能力」下的其他模型,保证同组只有一个启用
            disableOtherEnabledModels(model);
        }
        modelMapper.updateById(model);
    }
 
    @Override
    public AiModelDO validateModel(Long id) {
        AiModelDO model = validateModelExists(id);
        if (CommonStatusEnum.isDisable(model.getStatus())) throw exception(MODEL_DISABLE, model.getName());
        return model;
    }
 
    @Override
    public List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, String platform) {
        return modelMapper.selectListByStatusAndType(status, type, platform);
    }
 
    /**
     * 关闭同「类型 + 能力」下已启用的其他模型(不含自身)
     */
    private void disableOtherEnabledModels(AiModelDO model) {
        List<AiModelDO> sameGroup = modelMapper.selectListByTypeAndCapabilityAndStatus(
                model.getType(), resolveCapability(model.getCapability()), CommonStatusEnum.ENABLE.getStatus());
        for (AiModelDO other : sameGroup) {
            if (other.getId().equals(model.getId())) {
                continue;
            }
            AiModelDO update = new AiModelDO();
            update.setId(other.getId());
            update.setStatus(CommonStatusEnum.DISABLE.getStatus());
            modelMapper.updateById(update);
        }
    }
 
    private static String resolveCapability(String capability) {
        return capability == null || capability.isBlank()
                ? AiModelCapabilityEnum.defaultCapability() : capability;
    }
 
    private AiModelDO validateModelExists(Long id) {
        AiModelDO model = modelMapper.selectById(id);
        if (model == null) throw exception(MODEL_NOT_EXISTS);
        return model;
    }
 
    // ========== 与 Spring AI 集成 ==========
 
    @Override
    public ChatModel getChatModel(Long id) {
        AiModelDO model = validateModel(id);
        AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
        AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
        // temperature / maxTokens 是 AI 模型表上一直存在、却从没人读过的两列:
        // 不读 temperature,库里配的值对调用方是假的;不读 maxTokens,长输出会被静默截断
        return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel(),
                model.getTemperature(), model.getMaxTokens(), aiProperties.getTimeout());
    }
 
    @Override
    public VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields) {
        AiModelDO model = validateModel(id);
        AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
        AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
 
        EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel(
                platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel());
 
        return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields);
    }
 
}