目录
引言
代码完整地址
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);
}
}
发表评论