最近做项目,接入GPT,接口是流式返回的,于是对流式接口请求做了下总结。项目中采用的OKHttp实现网络请求、
接口中,数据是一行一行返回的,每行数据的返回格式如下:
{
"code": 0,
"message": "",
"data": "{\"text\":\"\",\"auxiliary_tips\":[]}"
}
流式接口应该一行一行处理,因为返回的内容数据会被拆成一段一段的,放到text字段下,我们的业务比较复杂,在返回的数据中还会有一些特殊标识需要解析,是否需要展示按钮之类的,数据如下:
"此次GPT生成完成!<_action_data_> {\"action\": \"confirm\"}"
因为后面的标识可能会被GPT拆成两个,因此需要拼装后进行解析。
核心代码如下:
companion object {
const val MSG_END_TAG = "<_action_data_>"
const val DATA_DONE_TAG = "data: [[DONE]]"
const val RETRY_TAG = "retry: "
}
suspend fun requestByStream(
ur: String,
taskType: String? = null,
content: String? = null,
onStart: () -> Unit,
onEnd: (String?) -> Unit,
onNetworkError: (Int?, String?) -> Unit,
onError: ((Throwable) -> Unit)? = null,
onCollect: (String?, Choices?, Int?, String?) -> Unit,
) {
val body = JsonUtils.toJson(GenerateBody(task_type = taskType, content = content))
.toRequestBody()
val request =
Request.Builder().url(url).post(body)
.build()
val call = RetrofitClient.client.newCall(request)
val gson = Gson()
withContext(Dispatchers.IO) {
val response = call.execute()
if (response.isSuccessful) {
try {
response.body?.byteStream()?.bufferedReader()?.use { reader ->
val originData = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
try {
if (line?.startsWith("data: ") == true) {
if (line?.startsWith(DATA_DONE_TAG) == true) {
withContext(Dispatchers.Main) {
onEnd(originData.toString())
}
} else {
val resp = gson.fromJson<BaseResponse<Choices>>(
line?.replaceFirst("data: ", ""),
object : TypeToken<BaseResponse<Choices>>() {}.type
)
resp.data?.choices?.let {
originData.append(it)
val parts = originData.split(MSG_END_TAG)
if (parts.size == 2) {
resp.data?.choices = parts[0]
runCatching {
val data = gson.fromJson(
parts[1], Choices::class.java
)
resp.data?.action = data.action
resp.data?.suggest_answers =
data.suggest_answers
}
} else {
resp.data?.choices = parts[0]
}
}
withContext(Dispatchers.Main) {
onCollect(
originData.toString(),
resp.data,
resp.code,
resp.msg
)
}
}
} else {
withContext(Dispatchers.Main) {
if (line?.startsWith(RETRY_TAG) == true) {
onStart()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
} catch (e: IOException) {
e.printStackTrace()
withContext(Dispatchers.Main) {
onError?.invoke(e)
}
}
} else {
withContext(Dispatchers.Main) {
onNetworkError(response.code, response.message)
}
}
}
}
采用Retrofit的@Streaming注解
Retrofit框架@Streaming注解返回ResponseBody,代码如下:
@Streaming
@GET("接口地址")
suspend fun requestByStream(): ResponseBody
接下来就是利用**ResponseBody.byteStream()**方法流式传输数据,不用等待整个响应返回。
感谢大家的支持,如有错误请指正,如需转载请标明原文出处!