Kimi,是月之暗面于2023年10月推出的一款智能助手,主要应用场景为专业学术论文的翻译和理解、辅助分析法律问题、快速理解API开发文档等,是全球首个支持输入20万汉字的智能助手产品
1.登录,获取API Key
打开网址Moonshot AI - 开放平台 ,可以看到官网比较简洁,有两部分构成,api文档和用户中心
点击用户中心,可以用微信登录,然后在API Key管理中新建一个key,记录下来
2.引入依赖
由于跟kimi对接需要用到SSE(Server Send Event),我们使用okhttp库
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
<version>4.10.0</version>
</dependency>
3.实现简单的对话
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class Message {
private String role;
private String content;
}
public enum RoleEnum {
system,
user,
assistant;
}
public class MoonshotAiUtils {
private static final String API_KEY = "api key";
private static final String MODELS_URL = "https://api.moonshot.cn/v1/models";
private static final String FILES_URL = "https://api.moonshot.cn/v1/files";
private static final String ESTIMATE_TOKEN_COUNT_URL = "https://api.moonshot.cn/v1/tokenizers/estimate-token-count";
private static final String CHAT_COMPLETION_URL = "https://api.moonshot.cn/v1/chat/completions";
public static String getModelList() {
return getCommonRequest(MODELS_URL)
.execute()
.body();
}
public static String uploadFile(@NonNull File file) {
return getCommonRequest(FILES_URL)
.method(Method.POST)
.header("purpose", "file-extract")
.form("file", file)
.execute()
.body();
}
public static String getFileList() {
return getCommonRequest(FILES_URL)
.execute()
.body();
}
public static String deleteFile(@NonNull String fileId) {
return getCommonRequest(FILES_URL + "/" + fileId)
.method(Method.DELETE)
.execute()
.body();
}
public static String getFileDetail(@NonNull String fileId) {
return getCommonRequest(FILES_URL + "/" + fileId)
.execute()
.body();
}
public static String getFileContent(@NonNull String fileId) {
return getCommonRequest(FILES_URL + "/" + fileId + "/content")
.execute()
.body();
}
public static String estimateTokenCount(@NonNull String model, @NonNull List<Message> messages) {
String requestBody = new JSONObject()
.putOpt("model", model)
.putOpt("messages", messages)
.toString();
return getCommonRequest(ESTIMATE_TOKEN_COUNT_URL)
.method(Method.POST)
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
.body(requestBody)
.execute()
.body();
}
@SneakyThrows
public static String chat(@NonNull String model, @NonNull List<Message> messages) {
StringBuilder sb = new StringBuilder();
String requestBody = new JSONObject()
.putOpt("model", model)
.putOpt("messages", messages)
.putOpt("stream", true)
.toString();
Request okhttpRequest = new Request.Builder()
.url(CHAT_COMPLETION_URL)
.post(RequestBody.create(requestBody, MediaType.get(ContentType.JSON.getValue())))
.addHeader("Authorization", "Bearer " + API_KEY)
.build();
Call call = new OkHttpClient().newCall(okhttpRequest);
Response okhttpResponse = call.execute();
BufferedReader reader = new BufferedReader(okhttpResponse.body().charStream());
try {
String line;
while ((line = reader.readLine()) != null) {
if (StrUtil.isBlank(line)) {
continue;
}
if (JSONUtil.isTypeJSON(line)) {
Optional.of(JSONUtil.parseObj(line))
.map(x -> x.getJSONObject("error"))
.map(x -> x.getStr("message"))
.ifPresent(x -> System.out.println("error: " + x));
JSONObject jsonObject = JSONUtil.parseObj(line);
throw new ServiceFailException(jsonObject.getJSONObject("error").getStr("message"));
}
line = StrUtil.replace(line, "data: ", StrUtil.EMPTY);
if (StrUtil.equals("[DONE]", line) || !JSONUtil.isTypeJSON(line)) {
return sb.toString();
}
Optional.of(JSONUtil.parseObj(line))
.map(x -> x.getJSONArray("choices"))
.filter(CollUtil::isNotEmpty)
.map(x -> (JSONObject) x.get(0))
.map(x -> x.getJSONObject("delta"))
.map(x -> x.getStr("content"))
.ifPresent(x -> sb.append(x));
}
return sb.toString();
} finally {
IoUtil.close(reader);
}
}
private static HttpRequest getCommonRequest(@NonNull String url) {
return HttpRequest.of(url).header(Header.AUTHORIZATION, "Bearer " + API_KEY);
}
}
@RestController
@RequestMapping("/kimi")
@Tag(name = "kimi管理")
public class KimiController extends BaseController {
// 演示用,实际要存入用户session
private List<Message> messages = new ArrayList<>();
@Operation(summary = "聊天")
@GetMapping("chat")
public Result chat(String content) {
Message message = new Message(RoleEnum.user.name(), content);
messages.add(message);
// 模型列表 https://platform.moonshot.cn/docs/intro#%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8
String result = MoonshotAiUtils.chat("moonshot-v1-8k", messages);
return resultOk(result);
}
}
代码都挺简单的,需要注意的是用户的多次问答需要放到一个list中一起发送给kimi,这样kimi才能根据上下文回答问题
4.提取文档摘要
提取文档摘要我们先要把文档上传到kimi获取一个content,再调用聊天接口给提示词就行了
@Operation(summary = "提取文档摘要")
@GetMapping("docSummary")
public Result docSummary() {
String hint = "请简要描述文档中的内容";
String content = MoonshotAiUtils.uploadFile(new File("d:/read.docx"));
List<Message> messages = CollUtil.newArrayList(
new Message(RoleEnum.system.name(), content),
new Message(RoleEnum.user.name(), hint)
);
String result = MoonshotAiUtils.chat("moonshot-v1-32k", messages);
return resultOk(result);
}