Spring AI调用通义千问模型输出流报错解决方案


最新版官方版本

Spring AI 的1.0.0-M7 已解决 1.0.0-M6 版本及之前的 Spring AI 的 OpenAi Model 和阿里云百炼的部分接口存在兼容性问题,直接调用官方的OpenAiChatModel类就可以:


一、问题原因

SpringAI对接阿里云百炼平台时不支持stream流模式,function calling的stream模式返回的函数参数格式有问题,与open AI接口不兼容。

二、问题解析

在处理大模型返回的结果时,尝试从中解析function calling信息,但由于参数格式问题导致解析失败。
open AI官方要求buildGeneration()方法只返回一条完整的结果,通义千问模型返回多条残缺不全的message,参数被分成了多段返回,导致生成的toolCalls对象残缺不全,无法调用。


三、解决方案

思想:使用map reduce方法,将buildGeneration()方法中传入的多个残缺不全的toolCall合并成一个完整的toolCall对象。
具体实现:由于OpenAiChatModel类中buildGeneration()方法私有,无法重写,我们需要配置新建的 AlibabaOpenAiChatModel 类,替换原生的OpenAiChatModel类,再修改buildGeneration()方法。

1. 新建 AlibabaOpenAiChatModel 类

遵循阿里巴巴百炼平台接口规范的ChatModel,其中大部分代码来自SpringAI的OpenAiChatModel,只需要重写接口协议不匹配的地方。

package com.itheima.ai.model;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.ai.chat.messages.ToolResponseMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.metadata.*;
import org.springframework.ai.chat.model.*;
import org.springframework.ai.chat.observation.ChatModelObservationContext;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.chat.observation.ChatModelObservationDocumentation;
import org.springframework.ai.chat.observation.DefaultChatModelObservationConvention;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackResolver;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.tool.LegacyToolCallingManager;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.model.tool.ToolExecutionResult;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
import org.springframework.ai.openai.metadata.support.OpenAiResponseHeaderExtractor;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class AlibabaOpenAiChatModel extends AbstractToolCallSupport implements ChatModel {
   
   

    private static final Logger logger = LoggerFactory.getLogger(AlibabaOpenAiChatModel.class);

    private static final ChatModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultChatModelObservationConvention();

    private static final ToolCallingManager DEFAULT_TOOL_CALLING_MANAGER = ToolCallingManager.builder().build();

    /**
     * The default options used for the chat completion requests.
     */
    private final OpenAiChatOptions defaultOptions;

    /**
     * The retry template used to retry the OpenAI API calls.
     */
    private final RetryTemplate retryTemplate;

    /**
     * Low-level access to the OpenAI API.
     */
    private final OpenAiApi openAiApi;

    /**
     * Observation registry used for instrumentation.
     */
    private final ObservationRegistry observationRegistry;

    private final ToolCallingManager toolCallingManager;

    /**
     * Conventions to use for generating observations.
     */
    private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;

    /**
     * Creates an instance of the AlibabaOpenAiChatModel.
     * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI
     * Chat API.
     * @throws IllegalArgumentException if openAiApi is null
     * @deprecated Use AlibabaOpenAiChatModel.Builder.
     */
    @Deprecated
    public AlibabaOpenAiChatModel(OpenAiApi openAiApi) {
   
   
        this(openAiApi, OpenAiChatOptions.builder().model(OpenAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build());
    }

    /**
     * Initializes an instance of the AlibabaOpenAiChatModel.
     * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI
     * Chat API.
     * @param options The OpenAiChatOptions to configure the chat model.
     * @deprecated Use AlibabaOpenAiChatModel.Builder.
     */
    @Deprecated
    public AlibabaOpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options) {
   
   
        this(openAiApi, options, null, RetryUtils.DEFAULT_RETRY_TEMPLATE);
    }

    /**
     * Initializes a new instance of the AlibabaOpenAiChatModel.
     * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI
     * Chat API.
     * @param options The OpenAiChatOptions to configure the chat model.
     * @param functionCallbackResolver The function callback resolver.
     * @param retryTemplate The retry template.
     * @deprecated Use AlibabaOpenAiChatModel.Builder.
     */
    @Deprecated
    public AlibabaOpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options,
                           @Nullable FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate) {
   
   
        this(openAiApi, options, functionCallbackResolver, List.of(), retryTemplate);
    }

    /**
     * Initializes a new instance of the AlibabaOpenAiChatModel.
     * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI
     * Chat API.
     * @param options The OpenAiChatOptions to configure the chat model.
     * @param functionCallbackResolver The function callback resolver.
     * @param toolFunctionCallbacks The tool function callbacks.
     * @param retryTemplate The retry template.
     * @deprecated Use AlibabaOpenAiChatModel.Builder.
     */
    @Deprecated
    public AlibabaOpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options,
                           @Nullable FunctionCallbackResolver functionCallbackResolver,
                           @Nullable List<FunctionCallback> toolFunctionCallbacks, RetryTemplate retryTemplate) {
   
   
        this(openAiApi, options, functionCallbackResolver, toolFunctionCallbacks, retryTemplate,
                ObservationRegistry.NOOP);
    }

    /**
     * Initializes a new instance of the AlibabaOpenAiChatModel.
     * @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI
     * Chat API.
     * @param options The OpenAiChatOptions to configure the chat model.
     * @param functionCallbackResolver The function callback resolver.
     * @param toolFunctionCallbacks The tool function callbacks.
     * @param retryTemplate The retry template.
     * @param observationRegistry The ObservationRegistry used for instrumentation.
     * @deprecated Use AlibabaOpenAiChatModel.Builder or AlibabaOpenAiChatModel(OpenAiApi,
     * OpenAiChatOptions, ToolCallingManager, RetryTemplate, ObservationRegistry).
     */
    @Deprecated
    public AlibabaOpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options,
                           @Nullable FunctionCallbackResolver functionCallbackResolver,
                           @Nullable List<FunctionCallback> toolFunctionCallbacks, RetryTemplate retryTemplate,
                           ObservationRegistry observationRegistry) {
   
   
        this(openAiApi, options,
                LegacyToolCallingManager.builder()
                        .functionCallbackResolver(functionCallbackResolver)
                        .functionCallbacks(toolFunctionCallbacks)
                        .build(),
                retryTemplate, observationRegistry);
        logger.warn("This constructor is deprecated and will be removed in the next milestone. "
                + "Please use the AlibabaOpenAiChatModel.Builder or the new constructor accepting ToolCallingManager instead.");
    }

    public AlibabaOpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions defaultOptions, ToolCallingManager toolCallingManager,
                           RetryTemplate retryTemplate, ObservationRegistry observationRegistry) {
   
   
        // We do not pass the 'defaultOptions' to the AbstractToolSupport,
        // because it modifies them. We are using ToolCallingManager instead,
        // so we just pass empty options here.
        super(null, OpenAiChatOptions.builder().build(), List.of());
        Assert.notNull(openAiApi, "openAiApi cannot be null");
        Assert.notNull(defaultOptions, "defaultOptions cannot be null");
        Assert.notNull(toolCallingManager, "toolCallingManager cannot be null");
        Assert.notNull(retryTemplate, "retryTemplate cannot be null");
        Assert.notNull(observationRegistry, "observationRegistry cannot be null");
        this.openAiApi = openAiApi;
        this.defaultOptions = defaultOptions;
        this.toolCallingManager = toolCallingManager;
        this.retryTemplate = retryTemplate;
        this.observationRegistry = observationRegistry;
    }

    @Override
    public ChatResponse call(Prompt prompt) {
   
   
        // Before moving any further, build the final request Prompt,
        // merging runtime and default options.
        Prompt requestPrompt = buildRequestPrompt(prompt);
        return this.internalCall(requestPrompt, null);
    }

    public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
   
   

        OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false);

        ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
                .prompt(prompt)
                .provider(OpenAiApiConstants.PROVIDER_NAME)
                .requestOptions(prompt.getOptions())
                .build();

        ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
                .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
                        this.observationRegistry)
                .observe(() -> {
   
   

                    ResponseEntity<OpenAiApi.ChatCompletion> completionEntity = this.retryTemplate
                            .execute(ctx -> this.openAiApi.chatCompletionEntity(request, getAdditionalHttpHeaders(prompt)));

                    var chatCompletion = completionEntity.getBody();

                    if (chatCompletion == null) {
   
   
                        logger.warn("No chat completion returned for prompt: {}", prompt);
                        return new ChatResponse(List.of());
                    }

                    List<OpenAiApi.ChatCompletion.Choice> choices = chatCompletion.choices();
                    if (choices == null) {
   
   
                        logger.warn("No choices returned for prompt: {}", prompt);
                        return new ChatResponse(List.of());
                    }

                    List<Generation> generations = choices.stream(
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值