使用Azure openai java SDK在流式输出时处理functioncall
emmm,新的功能和api都出了,还在写旧格式的感觉有点过时。
之前都是用非流式的使用function, 刚好遇到这个处理了下顺便记录一下。
流式的function也是分段返回的, 和content类似, 举个例子:
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"role":"assistant","function_call":{"name":"urlContent","arguments":""}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":"{\n"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":" "}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":" \""}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":"url"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":"\":"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":" \""}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":"www"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":".baidu"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":".com"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":"\"\n"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"delta":{"function_call":{"arguments":"}"}}}]}
data:{"id":"chatcmpl-8KsmRHXFf9HQRr3xDpHzJK6C71hGX","created":1699987827.000000000,"choices":[{"index":0,"finish_reason":"function_call","delta":{}}]}
内容依旧在choices/0/delta
中,只不过从content变成了function_call.
content的例子:
data:{"id":"chatcmpl-8Krqrqqn1rKPPBatGpBhvOfN3sN7G","created":1699984257.000000000,"choices":[{"index":0,"delta":{"content":" today"}}]}
所以要处理就比较简单了, 在IterableStream<ChatCompletions>
的循环中去把function_call给拼出来,之前content只需要拼一个字段,这个拼两个字段即可.
代码如下:
String role = null;
StringBuilder contentBuilder = new StringBuilder();
StringBuilder functionCallNameBuilder = new StringBuilder();
StringBuilder functionCallArgumentsBuilder = new StringBuilder();
for (ChatCompletions e : stream) {
try {
final List<ChatChoice> choices = e.getChoices();
if (CollectionUtils.isEmpty(choices)) {
continue;
}
final ChatChoice chatChoice = choices.get(0);
if (chatChoice.getFinishReason() != null) {
break;
}
final ChatMessage chatMessage = chatChoice.getDelta();
if (chatMessage == null) {
continue;
}
if (chatMessage.getRole() != null) {
role = chatMessage.getRole().toString();
}
final FunctionCall functionCall = chatMessage.getFunctionCall();
if (functionCall != null) {
if (functionCall.getName() != null) {
functionCallNameBuilder.append(functionCall.getName());
}
if (functionCall.getArguments() != null) {
functionCallArgumentsBuilder.append(functionCall.getArguments());
}
}
// 这里不写else 不排除这两者都返回的情况
final String content = chatMessage.getContent();
if (content != null) {
contentBuilder.append(content);
}
} catch (IOException ex) {
log.error("chat - for loop", ex);
}
}
if (role == null) {
log.warn("chat - role is null");
return;
}
String content = null;
if (!contentBuilder.isEmpty()) {
content = contentBuilder.toString();
}
// 要用可以直接构造Map或者FunctionCall
String functionMap = null;
if (!functionCallNameBuilder.isEmpty()) {
Map<String, String> map = new HashMap<>();
map.put("name", functionCallNameBuilder.toString());
if (!functionCallArgumentsBuilder.isEmpty()) {
map.put("arguments", functionCallArgumentsBuilder.toString());
}
functionMap = JsonUtils.toString(map).orElseThrow();
}
拿到了content和function的完整结构后续逻辑就和非流式一样了