目录
引言
代码完整地址
入参
出参
Controller
Service
Service实现类
模型Service
入参转换类
文心一言实现类
讯飞星火实现类
通义千问实现类
智谱清言实现类
引言
本文将介绍如何使用Java语言,结合Spring Boot框架,集成国内热门大模型API,包括文心一言、讯飞星火、通义千问、智谱清言。
在开始前,请确保您已经按照各模型官网的指引,完成了相应的资源申请和配置。这些资源是调用大模型API的必要凭证,务必妥善保管。接下来,我们将通过具体的代码示例和步骤说明,引导您完成Spring
Boot项目和大模型API集成。
代码完整地址
https://github.com/wangsilingwsl/model-integrate.git
入参
package com.wsl.model.llm.api.dto;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 聊天请求 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class ChatRequestDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "聊天上下文信息", notes = "(1)最后一个message为当前请求的信息,前面的message为历史对话信息\n" +
"(2)成员数目必须为奇数\n" +
"(3)示例中message中的role值分别为user、assistant;奇数位message中的role值为user;偶数位值为assistant",
example = "[{\"role\":\"user\",\"content\":\"你好\"},{\"role\":\"assistant\",\"content\":\"需要什么帮助\"},{\"role\":\"user\",\"content\":\"自我介绍下\"}]")
@NotNull(message = "聊天上下文信息不能为空")
private List<MessageDTO> messages;
@ApiModelProperty(value = "模型人设", notes = "主要用于人设设定,例如,你是xxx公司制作的AI助手,最大20000字符", example = "你是一名天气助手,需要提供天气查询服务")
private String system;
@ApiModelProperty(value = "请求参数", notes = "请求参数", example = "{\"key\":\"value\"}")
private JSONObject params;
}
package com.wsl.model.llm.api.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
/**
* 消息 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
@AllArgsConstructor
public class MessageDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "角色", notes = "说明: user-用户, assistant-助手", example = "user")
private String role;
@ApiModelProperty(value = "消息内容", notes = "说明: 消息内容", example = "你好")
private String content;
}
出参
package com.wsl.model.llm.api.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 聊天响应 VO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class ChatResponseVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "结果", notes = "结果")
private String result;
}
Controller
package com.wsl.model.llm.api.controller;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.service.IAiAppService;
import com.wsl.model.llm.api.vo.ChatResponseVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* AI应用Controller
*
* @author wsl
* @date 2024/02/19
*/
@Slf4j
@RestController
@Api(tags = "AI应用")
@RequestMapping("/llm/middle")
public class AiAppController {
@Autowired
private IAiAppService service;
@PostMapping("/chat-message")
@ApiOperation("向大模型发起对话请求")
public ChatResponseVO chatMessage(
@ApiParam(value = "模型类型(ErnieBot/SparkDesk/ChatGlm/QianWen)", required = true) @RequestParam String modelType,
@ApiParam(value = "消息参数", required = true) @RequestBody ChatRequestDTO dto) {
try {
return service.chatMessage(modelType, dto);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Service
package com.wsl.model.llm.api.service;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.vo.ChatResponseVO;
/**
* AI应用Service
*
* @author wsl
* @date 2024/02/19
*/
public interface IAiAppService {
/**
* 向大模型发起对话请求-根据模型编码、用户ID
*
* @param modelType 模型类型
* @param dto 消息参数
* @return ChatResponseVO
* @throws Exception 异常
*/
ChatResponseVO chatMessage(String modelType, ChatRequestDTO dto) throws Exception;
}
Service实现类
package com.wsl.model.llm.api.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.wsl.model.llm.api.constant.enums.ModelTypeEnum;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.dto.MessageDTO;
import com.wsl.model.llm.api.service.IAiAppService;
import com.wsl.model.llm.api.service.ModelService;
import com.wsl.model.llm.api.vo.ChatResponseVO;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* AI应用ServiceImpl
*
* @author wsl
* @date 2024/02/19
*/
@Service("aiAppService")
public class AiAppServiceImpl implements IAiAppService {
@Override
public ChatResponseVO chatMessage(String modelType, ChatRequestDTO dto) throws Exception {
this.checkMessages(dto.getMessages());
// 根据枚举类ModelTypeEnum中的枚举值判断模型类型,并调用对应的模型实现类的方法
ModelService modelService = getModelService(modelType);
return modelService.chatMessage(dto);
}
/**
* 检查消息参数是否符合规范
*
* @param messages 消息参数
*/
private void checkMessages(List<MessageDTO> messages) {
if (CollUtil.isNotEmpty(messages)) {
// messages参数个数必须为奇数并且奇数个数的消息role必须为user,偶数个数的消息role必须为assistant
if (messages.size() % 2 == 0) {
throw new RuntimeException("messages参数个数必须为奇数");
}
for (int i = 0; i < messages.size(); i++) {
if (i % 2 == 0) {
if (!"user".equals(messages.get(i).getRole())) {
throw new RuntimeException("messages奇数参数的role必须为user");
}
} else {
if (!"assistant".equals(messages.get(i).getRole())) {
throw new RuntimeException("messages偶数参数的role必须为assistant");
}
}
}
}
}
/**
* 根据模型类型获取对应的模型服务
*
* @param modelType 模型类型
* @return 模型服务
*/
private ModelService getModelService(String modelType) {
try {
// 将模型类型字符串转换为枚举值
ModelTypeEnum modelTypeEnum = ModelTypeEnum.valueOf(modelType);
// 根据枚举值获取对应的实现类Bean的名称
String beanName = modelTypeEnum.name();
beanName = StrUtil.toCamelCase(beanName) + "Service";
return SpringUtil.getBean(beanName);
} catch (IllegalArgumentException e) {
throw new RuntimeException("模型类型错误");
}
}
}
模型Service
package com.wsl.model.llm.api.service;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.vo.ChatResponseVO;
/**
* 模型服务
*
* @author wsl
* @date 2024/2/19
*/
public interface ModelService {
/**
* 发起请求
*
* @param dto 请求参数
* @return 返回值
* @throws Exception 异常
*/
ChatResponseVO chatMessage(ChatRequestDTO dto) throws Exception;
}
入参转换类
package com.wsl.model.llm.api.convert;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.wsl.model.llm.api.dto.*;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.ArrayList;
import java.util.List;
/**
* 聊天请求转换器
*
* @author wsl
* @date 2024/2/22
*/
@Mapper
public interface ChatRequestConvert {
ChatRequestConvert INSTANCE = Mappers.getMapper(ChatRequestConvert.class);
/**
* 通用请求转换为文心一言请求
*
* @param dto 通用请求
* @return 文心一言请求
*/
default JSONObject convertErnieBot(ChatRequestDTO dto) {
ErnieBotDTO ernieBotDTO = new ErnieBotDTO();
ernieBotDTO.setMessages(dto.getMessages());
ernieBotDTO.setSystem(dto.getSystem());
JSONObject jsonObject = new JSONObject();
BeanUtil.copyProperties(ernieBotDTO, jsonObject);
BeanUtil.copyProperties(dto.getParams(), jsonObject);
return jsonObject;
}
/**
* 通用请求转换为通义千问请求
*
* @param dto 通用请求
* @return 通义千问请求
*/
default QianWenDTO convertQianwen(ChatRequestDTO dto) {
QianWenDTO qianWenDTO = new QianWenDTO();
qianWenDTO.setModel("qwen-turbo");
QianWenInputDTO input = new QianWenInputDTO();
String system = dto.getSystem();
if (StrUtil.isNotBlank(system)) {
MessageDTO messageDTO = new MessageDTO("system", system);
dto.getMessages().add(0, messageDTO);
}
input.setMessages(dto.getMessages());
JSONObject parametersJsonObject = new JSONObject();
BeanUtil.copyProperties(dto.getParams(), parametersJsonObject);
qianWenDTO.setInput(input);
qianWenDTO.setParameters(parametersJsonObject);
return qianWenDTO;
}
/**
* 通用请求转换为智谱清言请求
*
* @param dto 通用请求
* @return 智谱清言请求
*/
default JSONObject convertChatGlm(ChatRequestDTO dto) {
ChatGlmDTO chatGlmDTO = new ChatGlmDTO();
String system = dto.getSystem();
if (StrUtil.isNotBlank(system)) {
MessageDTO messageDTO = new MessageDTO("system", system);
dto.getMessages().add(0, messageDTO);
}
chatGlmDTO.setMessages(dto.getMessages());
chatGlmDTO.setModel("glm-4");
JSONObject jsonObject = new JSONObject();
BeanUtil.copyProperties(chatGlmDTO, jsonObject);
BeanUtil.copyProperties(dto.getParams(), jsonObject);
return jsonObject;
}
/**
* 通用请求转换为讯飞星火请求
*
* @param dto 通用请求
* @return 讯飞星火请求
*/
default SparkDeskDTO convertSparkDesk(ChatRequestDTO dto) {
SparkDeskDTO sparkDeskDTO = new SparkDeskDTO();
SparkDeskPayloadDTO payload = new SparkDeskPayloadDTO();
SparkDeskPayloadMessageDTO payloadMessage = new SparkDeskPayloadMessageDTO();
String system = dto.getSystem();
if (StrUtil.isNotBlank(system)) {
MessageDTO messageDTO = new MessageDTO("system", system);
dto.getMessages().add(0, messageDTO);
}
payloadMessage.setText(dto.getMessages());
payload.setMessage(payloadMessage);
SparkDeskParameterChatDTO parameterChat = new SparkDeskParameterChatDTO();
parameterChat.setDomain("generalv3.5");
JSONObject parameterChatJsonObject = new JSONObject();
BeanUtil.copyProperties(parameterChat, parameterChatJsonObject);
BeanUtil.copyProperties(dto.getParams(), parameterChatJsonObject);
SparkDeskParameterDTO parameter = new SparkDeskParameterDTO();
parameter.setChat(parameterChatJsonObject);
sparkDeskDTO.setPayload(payload);
sparkDeskDTO.setParameter(parameter);
return sparkDeskDTO;
}
/**
* 通用请求转换为通义千问请求
*
* @param dto 通用请求
* @return 通义千问请求
*/
default FaRuiDTO convertFaRui(ChatRequestDTO dto) {
FaRuiDTO faRuiDTO = new FaRuiDTO();
List<MessageDTO> messages = dto.getMessages();
String prompt = messages.get(messages.size() - 1).getContent();
FaRuiInputDTO input = new FaRuiInputDTO();
if (messages.size() == 1) {
messages = new ArrayList<>();
}
if (CollUtil.isNotEmpty(messages)) {
messages.remove(messages.size() - 1);
List<FaRuiHistoryDTO> history = convertFaRuiHistory(messages);
input.setHistory(history);
}
input.setPrompt(prompt);
faRuiDTO.setParameters(dto.getParams());
faRuiDTO.setInput(input);
return faRuiDTO;
}
/**
* 通用消息转换为通义法睿历史消息
*
* @param messages 通用消息
* @return 通义法睿历史消息
*/
default List<FaRuiHistoryDTO> convertFaRuiHistory(List<MessageDTO> messages) {
List<FaRuiHistoryDTO> history = new ArrayList<>();
int size = messages.size();
for (int i = 0; i < size; i += 2) {
FaRuiHistoryDTO messagePair = new FaRuiHistoryDTO();
messagePair.setUser(messages.get(i).getContent());
if (i + 1 < size) {
messagePair.setBot(messages.get(i + 1).getContent());
}
history.add(messagePair);
}
return history;
}
}
文心一言实现类
package com.wsl.model.llm.api.service.impl;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wsl.model.llm.api.convert.ChatRequestConvert;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.service.ModelService;
import com.wsl.model.llm.api.vo.ChatResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 文心一言 大模型服务
*
* @author wsl
* @link https://console.bce.baidu.com/tools/?_=1708497709522&u=qfdc#/api?product=AI&project=%E5%8D%83%E5%B8%86%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%B9%B3%E5%8F%B0&parent=ERNIE-Bot&api=rpc%2F2.0%2Fai_custom%2Fv1%2Fwenxinworkshop%2Fchat%2Fcompletions&method=post
* @date 2024/2/19
*/
@Service("ErnieBotService")
@Slf4j
public class ErnieBotServiceImpl implements ModelService {
private String appSecret = "?";
private String apiKey = "?";
private static final String TOKEN_URL_TEMPLATE = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s";
private static final String CHAT_URL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=%s";
@Override
public ChatResponseVO chatMessage(ChatRequestDTO dto) {
JSONObject ernieBot = ChatRequestConvert.INSTANCE.convertErnieBot(dto);
String requestBody = JSONUtil.toJsonStr(ernieBot);
log.info("文心一言请求参数 ernieBot request:{}", requestBody);
HttpResponse response = HttpUtil.createPost(String.format(CHAT_URL, getAccessToken(apiKey, appSecret)))
.body(requestBody)
.header("Content-Type", "application/json")
.execute();
if (response == null) {
throw new RuntimeException("HTTP response is null");
}
log.info("文心一言返回结果 ernieBot response:{}", response.body());
if (response.body() == null || response.body().trim().isEmpty()) {
throw new RuntimeException("HTTP response body is null or empty");
}
JSONObject jsonObject = JSON.parseObject(response.body());
if (!jsonObject.containsKey("result")) {
throw new RuntimeException(JSONObject.toJSONString(jsonObject));
}
ChatResponseVO vo = new ChatResponseVO();
vo.setResult(jsonObject.getString("result"));
return vo;
}
/**
* 从用户的AK,SK生成鉴权签名(Access Token)
*
* @param appId 应用ID
* @param appSecret 应用密钥
* @return token
*/
public String getAccessToken(String appId, String appSecret) {
String url = String.format(TOKEN_URL_TEMPLATE, apiKey, appSecret);
try (HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.execute()) {
JSONObject jsonObject = JSON.parseObject(response.body());
String accessToken = jsonObject.getString("access_token");
return accessToken;
}
}
}
package com.wsl.model.llm.api.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 文心一言 请求 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class ErnieBotDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "聊天上下文信息", notes = "说明:\n" +
"(1)messages成员不能为空,1个成员表示单轮对话,多个成员表示多轮对话;例如:\n" +
"· 1个成员示例,\"messages\": [ {\"role\": \"user\",\"content\": \"你好\"}]\n" +
"· 3个成员示例,\"messages\": [ {\"role\": \"user\",\"content\": \"你好\"},{\"role\":\"assistant\",\"content\":\"需要什么帮助\"},{\"role\":\"user\",\"content\":\"自我介绍下\"}]\n" +
"(2)最后一个message为当前请求的信息,前面的message为历史对话信息\n" +
"(3)成员数目必须为奇数,成员中message的role值说明如下:奇数位messsage的role值必须为user或function,偶数位message的role值为assistant,第一个message的role不能是function。例如:\n" +
"示例中message中的role值分别为user、assistant、user、assistant、user;奇数位(红框)message中的role值为user,即第1、3、5个message中的role值为user;偶数位(蓝框)值为assistant,即第2、4个message中的role值为assistant",
example = "[{\"role\":\"system\",\"content\":\"您好,我是智谱清言,我可以帮您查询天气,您可以输入:查询{{destination}}的天气,查询{{destination}}未来{{num_day}}天的天气\"},{\"role\":\"user\",\"content\":\"查询三亚未来5天的天气\"},{\"role\":\"assistant\",\"content\":\"正在查询三亚未来5天的天气\"},{\"role\":\"assistant\",\"content\":\"三亚未来5天的天气是晴天\"}]")
@NotNull(message = "聊天上下文信息不能为空")
private List<MessageDTO> messages;
@ApiModelProperty(value = "模型人设", notes = "主要用于人设设定,例如,你是xxx公司制作的AI助手,最大20000字符", example = "qwen-turbo")
private String system;
@ApiModelProperty(value = "温度", notes = "较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。默认0.8,范围 [0, 1.0],不能为0", example = "0.8")
private Float temperature;
}
讯飞星火实现类
package com.wsl.model.llm.api.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.wsl.model.llm.api.convert.ChatRequestConvert;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.dto.SparkDeskDTO;
import com.wsl.model.llm.api.dto.SparkDeskHeaderDTO;
import com.wsl.model.llm.api.service.ModelService;
import com.wsl.model.llm.api.vo.ChatResponseVO;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.stereotype.Service;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* 讯飞星火 大模型服务
*
* @author wsl
* @link https://www.xfyun.cn/doc/spark/Web.html
* @date 2024/2/19
*/
@Service("SparkDeskService")
@Slf4j
public class SparkDeskServiceImpl implements ModelService {
private String appId = "?";
private String appSecret = "?";
private String appKey = "?";
public static final String HOST_URL = "https://spark-api.xf-yun.com/v3.5/chat";
@Override
public ChatResponseVO chatMessage(ChatRequestDTO dto) throws Exception {
ChatResponseVO vo = new ChatResponseVO();
SparkDeskDTO sparkDeskDTO = ChatRequestConvert.INSTANCE.convertSparkDesk(dto);
sparkDeskDTO.setHeader(new SparkDeskHeaderDTO(appId));
String authUrl = getAuthUrl(HOST_URL, appKey, appSecret).replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(authUrl).build();
OkHttpClient client = new OkHttpClient.Builder().build();
StringBuilder sb = new StringBuilder();
CompletableFuture<String> messageReceived = new CompletableFuture<>();
String body = JSON.toJSONString(sparkDeskDTO);
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
//发送消息
log.info("讯飞星火请求参数 sparkDesk request:{}", body);
webSocket.send(body);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
try {
JSONObject obj = JSON.parseObject(text);
// 使用Optional来避免空指针异常,并在内容不存在时抛出异常
Optional<String> contentOptional = Optional.ofNullable(obj)
.map(jsonObject -> jsonObject.getJSONObject("payload"))
.map(payload -> payload.getJSONObject("choices"))
.map(choices -> choices.getJSONArray("text"))
.map(jsonArray -> jsonArray.getJSONObject(0))
.map(jsonObject -> jsonObject.getString("content"));
String str = contentOptional.orElseThrow(() -> new RuntimeException(JSONObject.toJSONString(obj)));
sb.append(str);
// 检查header和status字段
Optional<Long> statusOptional = Optional.ofNullable(obj)
.map(jsonObject -> jsonObject.getJSONObject("header"))
.map(header -> header.getLong("status"));
// 如果status为2,则关闭WebSocket并完成CompletableFuture
if (statusOptional.isPresent() && statusOptional.get() == 2) {
webSocket.close(1000, "Closing WebSocket connection");
messageReceived.complete(text);
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
});
messageReceived.get(60, TimeUnit.SECONDS);
webSocket.close(1000, "Closing WebSocket connection");
log.info("讯飞星火返回结果 sparkDesk response:{}", sb);
vo.setResult(sb.toString());
return vo;
}
/**
* 鉴权方法
*
* @param hostUrl 服务地址
* @param apiKey apiKey
* @param apiSecret apiSecret
* @return 返回鉴权url
* @throws Exception 异常
*/
public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
URL url = new URL(hostUrl);
// 时间
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
// 拼接
String preStr = "host: " + url.getHost() + "\n" +
"date: " + date + "\n" +
"GET " + url.getPath() + " HTTP/1.1";
// SHA256加密
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
mac.init(spec);
byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
// Base64加密
String sha = Base64.getEncoder().encodeToString(hexDigits);
String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
// 拼接地址
HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().
addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).
addQueryParameter("date", date).
addQueryParameter("host", url.getHost()).
build();
return httpUrl.toString();
}
}
package com.wsl.model.llm.api.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 讯飞星火 请求 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class SparkDeskDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "头部", notes = "头部")
private SparkDeskHeaderDTO header;
@ApiModelProperty(value = "参数", notes = "参数")
private SparkDeskParameterDTO parameter;
@ApiModelProperty(value = "有效载荷", notes = "有效载荷")
private SparkDeskPayloadDTO payload;
}
package com.wsl.model.llm.api.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 讯飞星火 Header DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SparkDeskHeaderDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "应用appid", notes = "从开放平台控制台创建的应用中获取")
private String app_id;
}
package com.wsl.model.llm.api.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 讯飞星火 聊天 参数 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class SparkDeskParameterChatDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "指定访问的领域", notes = "generalv3指向V3版本;generalv3.5指向V3.5版本")
private String domain;
@ApiModelProperty(value = "温度", notes = "较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。默认0.8,范围 [0, 2.0],不能为0", example = "0.8")
private Float temperature;
@ApiModelProperty(value = "最大标记", notes = "模型回答的tokens的最大长度;取值范围[1,8192],默认为2048", example = "2048")
private Integer max_tokens;
}
package com.wsl.model.llm.api.dto;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 讯飞星火 参数 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class SparkDeskParameterDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "聊天参数", notes = "聊天参数")
private JSONObject chat;
}
package com.wsl.model.llm.api.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 讯飞星火 有效载荷 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class SparkDeskPayloadDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "消息", notes = "消息")
private SparkDeskPayloadMessageDTO message;
}
package com.wsl.model.llm.api.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 讯飞星火 有效载荷 消息 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class SparkDeskPayloadMessageDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull(message = "聊天上下文信息不能为空")
private List<MessageDTO> text;
}
通义千问实现类
package com.wsl.model.llm.api.service.impl;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wsl.model.llm.api.convert.ChatRequestConvert;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.dto.QianWenDTO;
import com.wsl.model.llm.api.service.ModelService;
import com.wsl.model.llm.api.vo.ChatResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Optional;
/**
* 通义千问 大模型服务
*
* @author wsl
* @link https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.6922140bTYj6qJ#602895ef3dtl1
* @date 2024/2/19
*/
@Slf4j
@Service("QianWenService")
public class QianWenServiceImpl implements ModelService {
private String apiKey = "?";
@Override
public ChatResponseVO chatMessage(ChatRequestDTO dto) {
QianWenDTO qianwen = ChatRequestConvert.INSTANCE.convertQianwen(dto);
String url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation";
String json = JSON.toJSONString(qianwen);
log.info("通义千问请求参数 qianwen request:{}", json);
String result = HttpRequest.post(url)
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.body(json)
.execute().body();
log.info("通义千问返回结果 qianwen response:{}", result);
ChatResponseVO vo = new ChatResponseVO();
JSONObject jsonObject = JSON.parseObject(result);
Optional<String> textOptional = Optional.ofNullable(jsonObject.getJSONObject("output"))
.map(output -> output.getString("text"));
if (!textOptional.isPresent()) {
throw new RuntimeException(JSONObject.toJSONString(jsonObject));
}
vo.setResult(textOptional.get());
return vo;
}
}
package com.wsl.model.llm.api.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 通义千问 输入 DTO
*
* @author wsl
* @date 2024/2/20
*/
@Data
public class QianWenInputDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "聊天上下文信息", notes = "说明:\n" +
"(1)messages成员不能为空,1个成员表示单轮对话,多个成员表示多轮对话;例如:\n" +
"· 1个成员示例,\"messages\": [ {\"role\": \"user\",\"content\": \"你好\"}]\n" +
"· 3个成员示例,\"messages\": [ {\"role\": \"user\",\"content\": \"你好\"},{\"role\":\"assistant\",\"content\":\"需要什么帮助\"},{\"role\":\"user\",\"content\":\"自我介绍下\"}]\n" +
"(2)最后一个message为当前请求的信息,前面的message为历史对话信息\n" +
"(3)成员数目必须为奇数,成员中message的role值说明如下:奇数位messsage的role值必须为user或function,偶数位message的role值为assistant,第一个message的role不能是function。例如:\n" +
"示例中message中的role值分别为user、assistant、user、assistant、user;奇数位(红框)message中的role值为user,即第1、3、5个message中的role值为user;偶数位(蓝框)值为assistant,即第2、4个message中的role值为assistant",
example = "[{\"role\":\"system\",\"content\":\"您好,我是智谱清言,我可以帮您查询天气,您可以输入:查询{{destination}}的天气,查询{{destination}}未来{{num_day}}天的天气\"},{\"role\":\"user\",\"content\":\"查询三亚未来5天的天气\"},{\"role\":\"assistant\",\"content\":\"正在查询三亚未来5天的天气\"},{\"role\":\"assistant\",\"content\":\"三亚未来5天的天气是晴天\"}]")
@NotNull(message = "聊天上下文信息不能为空")
private List<MessageDTO> messages;
}
智谱清言实现类
package com.wsl.model.llm.api.service.impl;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.wsl.model.llm.api.convert.ChatRequestConvert;
import com.wsl.model.llm.api.dto.ChatRequestDTO;
import com.wsl.model.llm.api.service.ModelService;
import com.wsl.model.llm.api.vo.ChatResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* 智谱清言 大模型服务
*
* @author wsl
* @link https://open.bigmodel.cn/dev/api#glm-4
* @date 2024/2/19
*/
@Slf4j
@Service("ChatGlmService")
public class ChatGlmServiceImpl implements ModelService {
private String apiKey = "?";
@Override
public ChatResponseVO chatMessage(ChatRequestDTO dto) throws Exception {
JSONObject chatGlm = ChatRequestConvert.INSTANCE.convertChatGlm(dto);
String url = "https://open.bigmodel.cn/api/paas/v4/chat/completions";
String requestBody = JSONUtil.toJsonStr(chatGlm);
log.info("智谱清言请求参数 chatGlm request:{}", requestBody);
HttpResponse response = HttpUtil.createPost(url).body(requestBody).header("Content-Type", "application/json").header("Authorization", "Bearer " + generateToken(apiKey, 3600)).execute();
log.info("智谱清言返回结果 chatGlm response:{}", Optional.ofNullable(response).map(HttpResponse::body).orElse(""));
ChatResponseVO vo = new ChatResponseVO();
Optional<JSONObject> jsonObject = Optional.ofNullable(JSON.parseObject(response.body()));
jsonObject.ifPresent(json -> {
Optional<JSONArray> choices = Optional.ofNullable(json.getJSONArray("choices"));
choices.ifPresent(choiceArray -> {
if (!choiceArray.isEmpty()) {
Optional<JSONObject> firstChoiceMessage = Optional.ofNullable(choiceArray.getJSONObject(0).getJSONObject("message"));
firstChoiceMessage.ifPresent(message -> {
String content = message.getString("content");
if (content != null) {
vo.setResult(content);
} else {
throw new RuntimeException(response.body());
}
});
}
});
throw new RuntimeException(response.body());
});
return vo;
}
/**
* 生成token
*
* @param apikey apikey
* @param expSeconds 过期时间
* @return token
* @throws Exception 异常
*/
public static String generateToken(String apikey, int expSeconds) throws Exception {
String[] parts = apikey.split("\\.");
if (parts.length != 2) {
throw new Exception("Invalid apikey");
}
String id = parts[0];
String secret = parts[1];
Map<String, Object> payload = new HashMap<>(16);
payload.put("api_key", id);
payload.put("exp", new Date(System.currentTimeMillis() + expSeconds * 1000));
payload.put("timestamp", new Date(System.currentTimeMillis()));
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create().withHeader(new HashMap<String, Object>(16) {{
put("alg", "HS256");
put("sign_type", "SIGN");
}}).withPayload(payload).sign(algorithm);
}
}
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
2024最新版CSDN大礼包:《AGI大模型学习资源包》免费分享!
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取