4、大模型API接入:OpenAI API调用基础、请求与响应解析、Token管理
好,咱们进入正题。这一章要聊的是怎么让Ghidra插件真正跟大模型“说上话”。说白了,就是API调用。我刚开始做这个的时候,以为就是发个HTTP请求那么简单,结果踩了不少坑。尤其是Token管理,稍不注意,你的API账单就能让你肉疼。
咱们一步步来。先搞清楚OpenAI API怎么调,再解析它返回什么,最后聊聊怎么管好你的Token。
4.1 OpenAI API调用基础
调用OpenAI的API,本质上就是发一个HTTP POST请求。你给它一段文本(Prompt),它给你返回一段文本(Completion)。在Ghidra插件里,我们通常用Java的HttpURLConnection或者OkHttp库来做这件事。
我个人习惯用OkHttp,因为它更简洁,处理异步也方便。但如果你不想引入额外依赖,Java原生的HttpURLConnection也完全够用。
先看一个最基础的调用示例:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class OpenAIClient {
private static final String API_URL = "https://api.openai.com/v1/chat/completions";
private static final String API_KEY = "sk-your-api-key-here"; // 千万别硬编码!
public static String callGPT(String prompt) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 构造请求体
JsonObject requestBody = new JsonObject();
requestBody.addProperty("model", "gpt-4");
requestBody.addProperty("temperature", 0.3);
JsonObject message = new JsonObject();
message.addProperty("role", "user");
message.addProperty("content", prompt);
JsonObject messages = new JsonObject();
messages.add("messages", message);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
}
嗯,这里要注意一点:model参数决定了你用哪个模型。我个人建议在逆向工程场景下,用gpt-4或者gpt-4-turbo,它们的理解能力更强。虽然贵一点,但准确率高,省得你反复调。
4.2 请求与响应解析
发请求只是第一步。真正干活的是解析响应。OpenAI返回的JSON结构长这样:
{
"id": "chatcmpl-123abc",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-4",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "这个函数的功能是计算两个整数的和。建议重命名为 `add_two_numbers`。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 56,
"completion_tokens": 20,
"total_tokens": 76
}
}
你真正关心的,是choices[0].message.content这个字段。里面就是大模型给你的建议。至于usage字段,那是你的钱包在哭泣——它告诉你这次调用花了多少Token。
解析代码也很直接:
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class ResponseParser {
public static String extractSuggestion(String jsonResponse) {
JsonObject root = JsonParser.parseString(jsonResponse).getAsJsonObject();
String content = root.getAsJsonArray("choices")
.get(0).getAsJsonObject()
.getAsJsonObject("message")
.get("content").getAsString();
return content.trim();
}
public static int getTotalTokens(String jsonResponse) {
JsonObject root = JsonParser.parseString(jsonResponse).getAsJsonObject();
return root.getAsJsonObject("usage").get("total_tokens").getAsInt();
}
}
你想想看,如果每次调用都返回这么一大坨JSON,你只取其中一小段,是不是有点浪费?这就是为什么我们要关注Token管理。
4.3 Token管理
Token是OpenAI计费的基本单位。一个Token大约对应0.75个英文单词,或者1-2个中文字符。你发送的Prompt和模型返回的Completion都会消耗Token。
为什么会这样?因为模型内部把文本切成了小块(Token)来处理。你输入的每个字符、每个标点,都会被算进去。
我刚开始做的时候,没注意Token消耗。有一次批量处理100个函数,每个函数我都把整个反汇编代码扔进去,结果一次调用就花了3000多Token。算下来,100个函数就是30万Token,按GPT-4的价格,一次就要烧掉6美元。心疼啊。
所以,Token管理有几个核心原则:
- 精简Prompt:只传必要的信息。比如函数名、参数类型、局部变量,别把整个函数的汇编代码都塞进去。
- 控制输出长度:设置
max_tokens参数,限制模型返回的长度。对于函数重命名,一般50-100个Token就够。 - 缓存结果:同一个函数不要重复调用。把结果存到本地,下次直接读缓存。
来看一个带Token控制的调用示例:
public static String callGPTWithTokenControl(String prompt, int maxTokens) throws Exception {
HttpClient client = HttpClient.newHttpClient();
JsonObject requestBody = new JsonObject();
requestBody.addProperty("model", "gpt-4");
requestBody.addProperty("temperature", 0.3);
requestBody.addProperty("max_tokens", maxTokens); // 控制输出长度
requestBody.addProperty("max_prompt_tokens", 500); // 控制输入长度(部分模型支持)
// ... 其余代码同上
}
这里我推荐一个工具:OpenAI Tokenizer(在线工具,不需要网络图片)。你可以把Prompt贴进去,它会告诉你精确的Token数量。我在开发插件时,每次写Prompt都会先测一下,确保不超过预算。
另外,usage字段里的prompt_tokens和completion_tokens要分开看。前者是你花的,后者是模型花的。如果你发现prompt_tokens特别高,说明你的Prompt太啰嗦了,需要精简。
最后,我建议你在插件里加一个Token统计功能。每次调用后,把消耗的Token累加起来,显示在Ghidra的日志或者状态栏里。这样你心里有数,不会等到月底收到账单才傻眼。
这张图把整个流程串起来了。从构造Prompt开始,到发送请求、解析响应,每一步都跟Token管理挂钩。你可以在插件里照着这个流程实现,每一步都加上Token统计,这样就不会超支了。