本周我的主要工作是结合DeepSeek API进行单词谐音梗功能的开发,选取单词后由DeepSeek生成响应单词的谐音梗,协助用户记忆。
一、DeepSeek API的调用
1. HTTP 客户端配置:创建了一个 OkHttpClient 实例,配置了连接超时:60秒,读取超时:60秒
private val client = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build()
2. 创建核心方法:streamResponse。 使用 callbackFlow 构建器创建一个冷流(cold flow),当收集者开始收集时才会执行。在 IO 调度器上下文中构建了HTTP请求,并且创建了 EventSource 工厂,初始化了一个 StringBuilder 用于累积最终响应。
val payload = JSONObject().apply {
put("model", model)
put("messages", JSONArray().apply {
put(JSONObject().apply {
put("role", "user")
put("content", prompt)
})
})
put("temperature", 0.7)
put("max_tokens", 1024)
put("stream", true)
}
val request = withContext(Dispatchers.IO) {
Request.Builder()
.url(url)
.header("Authorization", "Bearer $apiKey")
.header("Content-Type", "application/json")
.post(payload.toString().toRequestBody())
.build()
}
val eventSourceFactory = EventSources.createFactory(client)
val finalResponse = StringBuilder() // 存储最终回复
3. 定义了一个匿名类实现 EventSourceListener,用于处理接收到的服务器发送事件、请求失败情况和连接关闭事件。
val eventSourceListener = object : EventSourceListener() {
override fun onEvent(
eventSource: EventSource,
id: String?,
type: String?,
data: String
) {
val trimmedData = data.trim()
Log.d("DeepSeekDebug", "收到完整数据: $trimmedData")
if (trimmedData == "[DONE]") {
// 请求结束时打印最终结果
Log.d("DeepSeekDebug", "请求完成,最终回复: $finalResponse")
close()
return
}
try {
val jsonObject = JSONObject(trimmedData)
val choices = jsonObject.getJSONArray("choices")
if (choices.length() > 0) {
val delta = choices.getJSONObject(0).getJSONObject("delta")
if (delta.has("content")) {
val content = delta.getString("content")
// finalResponse.append(content) // 追加内容
trySend(content) // 发送部分内容
}
}
} catch (e: Exception) {
trySend("\n[解析错误] ${e.message}")
}
}
override fun onFailure(
eventSource: EventSource,
t: Throwable?,
response: Response?
) {
trySend("\n[API请求错误] ${t?.message}")
close(t)
}
override fun onClosed(eventSource: EventSource) {
close()
}
}
二、架构设计与实现
1. 功能流程图如下:
2. 通过 prompt tuning(加入定向描述)约束模型输出格式 :
onClick = {
if (selectedWords.value.isNotEmpty()) {
val prompt = if (selectedWords.value.size == 1) {
"角色:你是一个幽默的语言大师,能够根据用户提供的英文单词,输出谐音梗,中英结合 \n" +
"\n" +
"要求 \n" +
"1. 描述要详细、准确,充分展现单词意思。 \n" +
"2. 语言生动、有趣,富有表现力。 \n" +
"3. 输出中英文结合! \n" +
"\n" +
"限制 \n" +
"1.不添加无关内容。你只需要回答谐音梗就行了,其他的东西一个字也不要说\n " +
"如果你准备好了,请回答:\n"+
"${selectedWords.value.first()}"
}
else {
"角色:你是一个幽默的语言大师,能够根据用户提供的英文单词,输出谐音梗,中英结合 \n" +
"\n" +
"要求 \n" +
"1. 描述要详细、准确,充分展现单词意思。 \n" +
"2. 语言生动、有趣,富有表现力。 \n" +
"3. 输出中英文结合! \n" +
"\n" +
"限制 \n" +
"1.不添加无关内容。你只需要回答故事就行了,其他的东西一个字也不要说\n " +
"如果你准备好了,请回答"+
"${selectedWords.value.joinToString("、")}"
}
viewModel.sendPrompt(prompt)
selectedWords.value = emptySet() // 查询后清空选择
}
},
modifier = Modifier.weight(1f).padding(start = 4.dp),
enabled = selectedWords.value.isNotEmpty() && !viewModel.uiState.isLoading
) {
Text("查询")
}
}
3.最终实现效果如下: