为系统接入 Kimi AI 智能大模型(附源码)

火眼9988 2024-08-25 08:01:02 阅读 80

前言

Kimi 是月之暗面公司出品的人工智能大模型助手,在国内中文大模型中算还是不错的。接下来我们将详细介绍,如何接入 Kimi AI 智能大模型,并提供可使用的生产代码(Java)。

Kimi 官方的 API 文档有点过于简单了,不过它的 API 可兼容 OpenAI 的 SDK,使用支持 OpenAI 的 SDK 可以直接使用 Kimi API 的基本能力。但我们接下来介绍如何直接与 Kimi API 交互,以及介绍它的主要服务接口。

获取令牌

要使用 Kimi 的 API,首先需要注册并获取 API Key。注册账号非常简单,就不过多赘述了。

注册完成后,进入控制台,在 API Key 管理中新建 Key,输入一个名字。

在这里插入图片描述

注意自己保存好 Key,控制台上不能再次获取到,如果丢失了只能重新生成一个。

这个 Key 就是我们调用 Kimi API 的令牌。

使用

Kimi API 支持 Restful 请求,我们可以包装构建自己的客户端,并像 Kimi 服务器发起请求。

客户端

首先,我们用 OKHttp 来构建一个请求客户端。

<code>public class MoonShotClient { -- -->

public static final String MOONSHOT_BASE = "https://api.moonshot.cn/v1";

private final OkHttpClient httpClient;

// 服务端基础路径

private final String baseUrl;

// API Key

private final String accessToken;

// json 序列化

private final ObjectMapper objectMapper;

public MoonShotClient(String baseUrl, String accessToken, OkHttpClient httpClient) {

this.baseUrl = baseUrl;

this.accessToken = accessToken;

if (Objects.isNull(httpClient)) {

// 初始化 okhttp 客户端,可自定义配置参数,或从其他配置文件获取

this.httpClient =

new OkHttpClient.Builder()

.connectTimeout(30, TimeUnit.SECONDS)

.readTimeout(30, TimeUnit.SECONDS)

.callTimeout(30, TimeUnit.SECONDS)

.build();

} else {

this.httpClient = httpClient;

}

this.objectMapper = new ObjectMapper();

this.objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);

}

public MoonShotClient(String accessToken, OkHttpClient httpClient) {

this(MOONSHOT_BASE, accessToken, httpClient);

}

public MoonShotClient(String accessToken) {

this(accessToken, null);

}

}

单轮对话

单轮对话就是一次性的问题,需要 Kimi 返回问题的答案。

请求路径是 /chat/completions,请求体是一个 JSON 对象,类似下面这样:

{

model: "moonshot-v1-8k",

messages: [

{ "role": "system", "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。"},

{ "role": "user", "content": "你好,我叫李雷,1+1等于多少?"}

],

temperature: 0.3,

}

其中,model 是模型 ID,目前支持 moonshot-v1-8k,moonshot-v1-32k,moonshot-v1-128k。

messages 是对话的消息数组,其中的 role 是角色,只支持 system,user,assistant,content 是消息内容,不能为空。

有个 stream 控制返回类型,默认 false,是非流式返回,设置为 true 则采用流式返回。

只有 model 和 messages 是必选参数,其他的都是可选参数,详细的参数说明可参考字段说明。

我们先来构建请求体对象(下面使用了 Lombok 简化代码)。

@Data

@ToString

@AllArgsConstructor

@NoArgsConstructor

public class ChatMessage {

private String role;

private String content;

}

@Data

@ToString

@AllArgsConstructor

@NoArgsConstructor

public class ChatCompletionsRequestBody {

private String model;

private List<ChatMessage> messages;

private double temperature;

// ... 其他参数

}

Kimi 返回的响应体也是一个 JSON 对象。

对于非流式返回,一次返回完整的数据,结构如下所示:

{

"id": "cmpl-04ea926191a14749b7f2c7a48a68abc6",

"object": "chat.completion",

"created": 1698999496,

"model": "moonshot-v1-8k",

"choices": [

{

"index": 0,

"message": {

"role": "assistant",

"content": " 你好,李雷!1+1等于2。如果你有其他问题,请随时提问!"

},

"finish_reason": "stop"

}

],

"usage": {

"prompt_tokens": 19,

"completion_tokens": 21,

"total_tokens": 40

}

}

对于流式返回,结果会一条条返回,每条是按顺序的几个词,适合于生产环境中较大的结果。

data: { "id":"cmpl-1305b94c570f447fbde3180560736287","object":"chat.completion.chunk","created":1698999575,"model":"moonshot-v1-8k","choices":[{ "index":0,"delta":{ "role":"assistant","content":""},"finish_reason":null}]}

data: { "id":"cmpl-1305b94c570f447fbde3180560736287","object":"chat.completion.chunk","created":1698999575,"model":"moonshot-v1-8k","choices":[{ "index":0,"delta":{ "content":"你好"},"finish_reason":null}]}

...

data: [DONE]

我们也构建一个 Java 对象来接收响应体。

@Data

@ToString

public class ChatCompletionsResponseBody {

private String id;

private String object;

private long created;

private String model;

private List<Choice> choices;

private Usage usage;

@Data

@ToString

public static class Choice {

private long index;

private ChatMessage message;

private String finishReason;

}

@Data

@ToString

public static class Usage {

private long promptTokens;

private long completionTokens;

private long totalTokens;

}

}

然后编写调用的方法,在客户端代码中添加对话的方法。

@Nullable

public ChatCompletionsResponseBody chatCompletions(@Nullable ChatCompletionsRequestBody body)

throws IOException {

if (Objects.isNull(body)) {

return null;

}

// 构建请求体

RequestBody requestBody =

RequestBody.create(

this.objectMapper.writeValueAsString(body), MediaType.parse("application/json;charset=utf-8"));

Request request =

new Request.Builder()

.url(this.baseUrl + "/chat/completions")

.addHeader("Authorization", "Bearer " + this.accessToken)

.post(requestBody)

.build();

// 发起请求

final Call call = this.httpClient.newCall(request);

Response response = call.execute();

if (response.isSuccessful()) {

// 请求成功

ResponseBody responseBody = response.body();

if (Objects.nonNull(responseBody)) {

String content = responseBody.string();

return this.objectMapper.readValue(content, ChatCompletionsResponseBody.class);

}

} else {

// 请求异常

ResponseBody responseBody = response.body();

if (Objects.nonNull(responseBody)) {

String content = responseBody.string();

throw new MoonShotRequestException(content);

}

}

return null;

}

返回的响应体就包含了 Kimi 给我们的答案。

多轮对话

多轮对话可以让 Kimi 掌握之前对话的上下文,提供更准备的回答。我们可以将之前对话的结果传递给下一个对话。

// history 是上一个请求响应体中 choices 的 message

ChatMessage history = new ChatMessage("system", "...");

ChatMessage chatMessage1 = new ChatMessage("system", "...");

ChatMessage chatMessage2 = new ChatMessage("user", "...");

ChatCompletionsRequestBody body = new ChatCompletionsRequestBody("moonshot-v1-8k", Arrays.asList(history, chatMessage1, chatMessage2), 0.3);

然后一起发送请求即可。

上传文件

Kimi 支持从文件中学习并回答问题,支持多种格式的文件,如 PDF、Word、Excel、PPT、图片文件、文本文件等,同时 Kimi 的 API 提供了强大的 OCR 能力,对于 PDF、图片等格式的文件可自动提取识别文字。然后就可以将提取的文字发送给对话接口作为上下文。上传文件的接口路径是 /files,通过 multipart 表单上传文件。我们在客户端类中添加上传文件的方法。

@Nullable

public FileUploadResponseBody uploadFile(byte[] bytes, String filename, String contentType)

throws IOException {

if (Objects.isNull(bytes) || !StringUtils.hasText(filename)) {

return null;

}

// 创建请求体

RequestBody fileBody = RequestBody.create(bytes, MediaType.parse(contentType));

MultipartBody requestBody =

new MultipartBody.Builder()

.setType(MultipartBody.FORM)

.addFormDataPart("file", filename, fileBody)

.addFormDataPart("purpose", "file-extract")

.build();

Request request =

new Request.Builder()

.url(this.baseUrl + "/files")

.addHeader("Authorization", "Bearer " + this.accessToken)

.header("Content-Type", "multipart/form-data")

.post(requestBody)

.build();

// 发起请求

final Call call = this.httpClient.newCall(request);

try (Response response = call.execute()) {

ResponseBody body = response.body();

if (response.isSuccessful()) {

// 请求成功

if (Objects.nonNull(body)) {

String content = body.string();

return this.objectMapper.readValue(content, FileUploadResponseBody.class);

}

} else {

// 请求失败

if (Objects.nonNull(body)) {

String content = body.string();

throw new MoonShotRequestException(content);

}

}

}

return null;

}

响应对象的类型如下,其中 id 为上传后生成的文件 ID。

@Data

@ToString

public class FileUploadResponseBody {

private String id;

private String object;

private long bytes;

private long createdAt;

private String filename;

private String purpose;

private String status;

private String status_details;

}

然后使用文件 ID 获取文件内容,接口路径是 /files/{file_id}/content。我们在客户端类中添加获取文件内容的方法。

@Nullable

public FileContentResponseBody getFileContent(String fileId) throws IOException {

if (!StringUtils.hasText(fileId)) {

return null;

}

// 构建请求体

Request request =

new Request.Builder()

.url(this.baseUrl + "/files/" + fileId + "/content")

.addHeader("Authorization", "Bearer " + this.accessToken)

.get()

.build();

// 发起请求

final Call call = this.httpClient.newCall(request);

try (Response response = call.execute()) {

ResponseBody body = response.body();

if (response.isSuccessful()) {

// 请求成功

if (Objects.nonNull(body)) {

String content = body.string();

return this.objectMapper.readValue(content, FileContentResponseBody.class);

}

} else {

// 请求失败

if (Objects.nonNull(body)) {

String content = body.string();

throw new MoonShotRequestException(content);

}

}

}

return null;

}

响应对象如下,其中 content 即是文件的内容。

@Data

@ToString

public class FileContentResponseBody {

private String content;

private String fileType;

private String filename;

private String title;

private String type;

}

另外也支持文件的 Restful 查询列表、详情和删除接口,路径分别是 GET /filesGET /files/{file_id}DELETE /files/{file_id}

上面提供了发起对话的接口对接,同时也支持文件上传,就可以自己实现一个和官方对话窗口一样的界面了。

在这里插入图片描述

最后,提醒大家注意 Kimi 的 API 已经开始收费了,具体的收费政策请参考官网。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。