前提
1、OpenAi API是收费的,但是价格还是比较划算的,而批处理是OpenAi在四月刚出的,价格是单个处理的一半,也就是便宜了50%,下面会讲到价格
2、需要一个api key,申请key也很简单,在OpenAi后台生成一个key即可,还需要绑定银行卡,用于API的扣费
3、批处理的对接也比较简单,不需要高版本的JDK和高版本的SpringBoot版本,直接进行http请求就可以,都不需要依赖任何第三方
3、本文出现的网址,需要科学上网才可访问,同理,在你的项目中如果是国内ip是访问不了的
4、想尝试则可以买一个国外的代理服务,科学上网的方法有很多,这里就不做过多阐述
价格对比
官网给出的价格图:https://openai.com/api/pricing/
gpt-4o-mini也是最近才出的,价格比GPT-3.5还要便宜,建议都用gpt-4o-mini,非常划算
Token计算
官网给出一个在线计算token数的网址,输入文本就可以知道token信息
https://platform.openai.com/tokenizer
这次OpenAi出的批处理,还有个优势就是不需要自己去依赖第三方去计算token数量,批处理每次都会返回用到的token数量
你也可以利用三方的依赖去计算所用到的token,只需要引入以下依赖
<!-- 解析Ai模型词组(token)-->
<dependency>
<groupId>com.knuddels</groupId>
<artifactId>jtokkit</artifactId>
<version>1.1.0</version>
</dependency>
使用方法也很简单
// 计算输入的token
EncodingRegistry registry = Encodings.newDefaultEncodingRegistry();
int requestTokenCount = registry.getEncoding(EncodingType.CL100K_BASE).countTokensOrdinary(使用到的提示词字符串 + 消息字符串);
// 计算输出的token
int resultTokenCount = registry.getEncodingForModel(模型名称,例如gpt-40-mini).countTokensOrdinary(ai返回的结果字符串);
官网已给出1k的token所需要的价格,所以我们如果需要计算价格,只需要计算出我们所用的token然后乘以相应的价格即可
批处理
介绍
官网介绍:https://platform.openai.com/docs/guides/batch
躺平玩家看下面,已经为各位翻译好了
简单来说,以下是对比
优势
- 更高的成本效益:与同步 API 相比,成本可享受 50% 的折扣
- 更高的速率限制:与同步 API 相比,具有更大的裕量
- 快速完成时间:每批在 24 小时内完成(通常更快)
劣势
- 非实时
哈哈哈,我觉得优势非常明显哇,经过我的尝试,批处理的速度很快,我差不多500个文章的简介进行判断文章的文章类型,就几分钟,量更大的还没尝试,价格更是便宜了一半,太香了
流程
提前给各位大佬梳理一下使用的流程
1、将我们需要处理的数据组装成一个jsonl文件,jsonl文件就是多个json用换行分割,将这个jsonl文件上传到OpenAi服务器上,会返回你一个输入文件标识(input_file_id)
2、创建一个批次任务,这里需要用到第一步的input_file_id,然后会返回一个批次id
3、循环的去根据批次id去检索执行的状态是否完成(status=completed),完成之后拿到输出文件标识(output_file_id)
4、根据文件id去拿到检索文件内容,返回的结果也是一个jsonl,这个jsonl对应着我们输入文件
API介绍
官网:https://platform.openai.com/docs/api-reference/batch
1、上传文件
上传可跨各种端点使用的文件。单个文件的最大大小为 512 MB,一个组织上传的所有文件的大小最大为 100 GB
API:
参数:
file:仅支持最大 100 MB 的 .jsonl 文件,需要为以下格式
{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-mini", "messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is 2+2?"}]}} # 字段解释 custom_id:开发人员提供的每个请求 ID,用于将输出与输入匹配。对于批处理中的每个请求必须是唯一的。 method:要用于请求的 HTTP 方法。目前仅POST受支持。 model:ai语言模型 url:要用于请求的 OpenAI API 相对 URL。现在/v1/chat/completions, /v1/embeddings和/v1/completions受到支持。
purpose:上传文件的预期用途。 使用“assistants”表示助手和消息文件,“vision”表示助手图像文件输入,“batch”表示 Batch API,“fine-tune”表示微调.
返回:
{
"id": "input_file_id",
"object": "file",
"bytes": 120000,
"created_at": 1677610602,
"filename": "file.jsonl",
"purpose": "batch",
}
2、创建批处理
从上传的请求文件创建并执行批处理
API:
POST:https://api.openai.com/v1/batches
参数:
input_file_id:上传文件的 ID
endpoint:现在/v1/chat/completions, /v1/embeddings和/v1/completions受到支持
completion_window:应处理批次的时间范围。目前仅24h受支持
返回:
{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/chat/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "validating",
"output_file_id": null,
"error_file_id": null,
"created_at": 1711471533,
"in_progress_at": null,
"expires_at": null,
"finalizing_at": null,
"completed_at": null,
"failed_at": null,
"expired_at": null,
"cancelling_at": null,
"cancelled_at": null,
"request_counts": {
"total": 0,
"completed": 0,
"failed": 0
},
"metadata": {
"customer_id": "user_123456789",
"batch_description": "Nightly eval job",
}
}
3、检索批次
检索批处理的进度和状态
API:
GET:https://api.openai.com/v1/batches/{batch_id}
参数:
batch_id:要检索的批处理的 ID
返回:
{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "completed",
"output_file_id": "file-cvaTdG",
"error_file_id": "file-HOWS94",
"created_at": 1711471533,
"in_progress_at": 1711471538,
"expires_at": 1711557933,
"finalizing_at": 1711493133,
"completed_at": 1711493163,
"failed_at": null,
"expired_at": null,
"cancelling_at": null,
"cancelled_at": null,
"request_counts": {
"total": 100,
"completed": 95,
"failed": 5
},
"metadata": {
"customer_id": "user_123456789",
"batch_description": "Nightly eval job",
}
}
4、检索文件
API:
GET:https://api.openai.com/v1/files/{file_id}
参数:
file_id:要用于此请求的文件的 ID。
返回:
多个json的jsonl文件,单个json文件格式如下
{
"id": "batch_req_wnaDys",
"custom_id": "request-2",
"response": {
"status_code": 200,
"request_id": "req_c187b3",
"body": {
"id": "chatcmpl-9758Iw",
"object": "chat.completion",
"created": 1711475054,
"model": "gpt-4o-mini",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "2 + 2 equals 4."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 24,
"completion_tokens": 15,
"total_tokens": 39
},
"system_fingerprint": null
}
},
"error": null
}
5、取消批处理
取消正在进行的批处理。批处理将处于cancelling状态长达 10 分钟,然后更改为已cancelled,此时输出文件中将提供部分结果(如果有)
API:
POST:https://api.openai.com/v1/batches/{batch_id}/cancel
参数:
batch_id:要取消的批次的 ID。
返回:
{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/chat/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "cancelling",
"output_file_id": null,
"error_file_id": null,
"created_at": 1711471533,
"in_progress_at": 1711471538,
"expires_at": 1711557933,
"finalizing_at": null,
"completed_at": null,
"failed_at": null,
"expired_at": null,
"cancelling_at": 1711475133,
"cancelled_at": null,
"request_counts": {
"total": 100,
"completed": 23,
"failed": 1
},
"metadata": {
"customer_id": "user_123456789",
"batch_description": "Nightly eval job",
}
}
API使用示例代码
上传批次文件
/**
* 上传批次文件
*/
public String uploadBatchFile(String jsonStr, String fileName) {
File file = new File(System.getProperty("java.io.tmpdir") + "/" + fileName);
try (FileWriter writer = new FileWriter(file)) {
writer.write(jsonStr);
} catch (IOException e) {
log.error("openai创建jsonl文件异常,", e);
return null;
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.set("Authorization", "Bearer " + openAiKey);
headers.set("Connection", "keep-alive");
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("purpose", "batch");
body.add("file", new FileSystemResource(file));
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
try {
ResponseEntity<String> response = new RestTemplate().postForEntity(openAiUrl + "/v1/files", requestEntity, String.class);
log.info("openai上传批次文件返回:fileName:{},result:{}", fileName, response.getBody());
OpenAiUploadDTO uploadDTO = JSONObject.parseObject(response.getBody(), OpenAiUploadDTO.class);
if (uploadDTO != null && OpenAiStatusEnum.PROCESSED.getName().equals(uploadDTO.getStatus())) {
return uploadDTO.getId();
}
log.error("openai上传批次文件返回失败,result:{}", response.getBody());
} catch (Exception e) {
log.error("openai上传批次文件返回异常,", e);
}
return "";
}
创建批次
/**
* 创建批次
*/
public String createBatch(String fileId) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + openAiKey);
headers.set("Connection", "keep-alive");
// 创建批次
Map<String, String> map = new HashMap<>();
map.put("input_file_id", fileId);
map.put("endpoint", "/v1/chat/completions");
map.put("completion_window", "24h");
HttpEntity<Map<String, String>> entity = new HttpEntity<>(map, headers);
try {
ResponseEntity<String> response = new RestTemplate().postForEntity(openAiUrl + "/v1/batches", entity, String.class);
log.info("open-ai创建批次返回:fileId:{},result:{}", fileId, response.getBody());
OpenAiBatchDTO openAiBatchDTO = JSONObject.parseObject(response.getBody(), OpenAiBatchDTO.class);
if (openAiBatchDTO != null) {
return openAiBatchDTO.getId();
}
} catch (Exception e) {
log.error("open-ai创建批次返回异常,", e);
}
return "";
}
检索批次
/**
* 检索批次
*/
public OpenAiBatchDTO retrieveBatch(String batchId) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + openAiKey);
headers.set("Connection", "keep-alive");
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = new RestTemplate().exchange(openAiUrl + "/v1/batches/" + batchId, HttpMethod.GET, entity, String.class);
log.info("openai检索批次返回:batchId:{},result:{}", batchId, response.getBody());
return JSONObject.parseObject(response.getBody(), OpenAiBatchDTO.class);
} catch (NullPointerException e) {
log.error("openai检索批次返回异常,", e);
}
return null;
}
检索文件内容
/**
* 检索文件内容
*/
public List<OpenAiBatchContentDTO> retrieveFileContent(String outFileId) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + openAiKey);
headers.set("Connection", "keep-alive");
HttpEntity<String> entity = new HttpEntity<>(headers);
List<OpenAiBatchContentDTO> resultList = new ArrayList<>();
try {
ResponseEntity<String> response = new RestTemplate().exchange(openAiUrl + "/v1/files/" + outFileId + "/content", HttpMethod.GET, entity, String.class);
String body = response.getBody();
log.info("openai检索文件内容返回:outFiledId:{},result:{}", outFileId, body);
if (!StringUtils.hasText(body)) {
return null;
}
String[] lines = body.split("\n");
ObjectMapper objectMapper = new ObjectMapper();
for (String line : lines) {
OpenAiBatchContentDTO contentDTO = objectMapper.convertValue(objectMapper.readValue(line, Map.class), OpenAiBatchContentDTO.class);
resultList.add(contentDTO);
}
} catch (Exception e) {
log.error("openai检索文件内容返回异常,", e);
}
return resultList;
}
取消批处理
/**
* 取消批处理
*/
public String cancelBatch(String batchId) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + openAiKey);
headers.set("Connection", "keep-alive");
// 创建批次
HttpEntity<String> entity = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = new RestTemplate().postForEntity(openAiUrl + "/v1/batches/" + batchId + "/cancel", entity, String.class);
log.info("openai取消批处理返回:batchId:{},result:{}", batchId, response.getBody());
return response.getBody();
} catch (Exception e) {
log.error("openai取消批处理返回异常,", e);
}
return null;