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();
    }
}
警告:API Key绝对不能硬编码在代码里!我曾经见过有人把Key提交到GitHub,几分钟后就被爬虫扫到,损失了几百美元。建议用环境变量或者配置文件来管理。

嗯,这里要注意一点: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();
    }
}
小技巧:有时候模型返回的内容里会包含一些格式标记,比如代码块或者多余的空格。我习惯在提取后做一次清理,用正则去掉不必要的字符。不然你重命名出来的函数名可能带着换行符,Ghidra会报错。

你想想看,如果每次调用都返回这么一大坨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); // 控制输入长度(部分模型支持)

    // ... 其余代码同上
}
核心要点:Token管理不是省小钱,而是保证你的插件在实际使用中不会因为API费用过高而无法落地。我见过有人因为没控制好Token,一个月花了上千美元。记住:每次调用前,先算算你的Prompt有多少Token

这里我推荐一个工具:OpenAI Tokenizer(在线工具,不需要网络图片)。你可以把Prompt贴进去,它会告诉你精确的Token数量。我在开发插件时,每次写Prompt都会先测一下,确保不超过预算。

另外,usage字段里的prompt_tokenscompletion_tokens要分开看。前者是你花的,后者是模型花的。如果你发现prompt_tokens特别高,说明你的Prompt太啰嗦了,需要精简。

最后,我建议你在插件里加一个Token统计功能。每次调用后,把消耗的Token累加起来,显示在Ghidra的日志或者状态栏里。这样你心里有数,不会等到月底收到账单才傻眼。

OpenAI API调用流程与Token管理 1. 构造Prompt 2. 发送HTTP请求 3. 解析响应 Token管理核心 精简Prompt 只传必要信息 控制输出长度 设置max_tokens 缓存结果 避免重复调用

这张图把整个流程串起来了。从构造Prompt开始,到发送请求、解析响应,每一步都跟Token管理挂钩。你可以在插件里照着这个流程实现,每一步都加上Token统计,这样就不会超支了。

专注资料整理