Advisors API

Spring AI Advisors API 提供了一種靈活且強大的方式,用於攔截、修改和增強您 Spring 應用中的 AI 驅動互動。透過利用 Advisors API,開發者可以建立更復雜、可複用且更易於維護的 AI 元件。

其主要優勢包括封裝重複出現的生成式 AI 模式,轉換髮送到大型語言模型 (LLMs) 和從大型語言模型接收的資料,以及在各種模型和用例之間提供可移植性。

您可以使用 ChatClient API 配置現有的建議器,示例如下

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
        new QuestionAnswerAdvisor(vectorStore)    // RAG advisor
    )
    .build();

String response = this.chatClient.prompt()
    // Set advisor parameters at runtime
    .advisors(advisor -> advisor.param("chat_memory_conversation_id", "678")
            .param("chat_memory_response_size", 100))
    .user(userText)
    .call()
	.content();

建議在構建時使用構建器的 defaultAdvisors() 方法註冊建議器。

建議器也參與到可觀測性堆疊中,因此您可以檢視與其執行相關的指標和跟蹤資訊。

核心元件

該 API 包含用於非流式場景的 CallAroundAdvisorCallAroundAdvisorChain,以及用於流式場景的 StreamAroundAdvisorStreamAroundAdvisorChain。它還包括用於表示未密封的 Prompt 請求的 AdvisedRequest,以及用於 Chat Completion 響應的 AdvisedResponse。兩者都持有一個 advise-context,用於在建議器鏈中共享狀態。

Advisors API Classes

nextAroundCall()nextAroundStream() 是關鍵的建議器方法,通常執行諸如檢查未密封的 Prompt 資料、自定義和增強 Prompt 資料、呼叫建議器鏈中的下一個實體、可選地阻止請求、檢查聊天完成響應以及丟擲異常以指示處理錯誤等操作。

此外,getOrder() 方法決定了建議器在鏈中的順序,而 getName() 方法提供了建議器的唯一名稱。

由 Spring AI 框架建立的建議器鏈(Advisor Chain)允許按照建議器的 getOrder() 值順序呼叫多個建議器。值較低的建議器首先執行。最後一個自動新增的建議器會將請求傳送給 LLM。

以下流程圖說明了建議器鏈與聊天模型之間的互動

Advisors API Flow
  1. Spring AI 框架會根據使用者的 Prompt 建立一個 AdvisedRequest,同時建立一個空的 AdvisorContext 物件。

  2. 鏈中的每個建議器都會處理請求,並可能對其進行修改。或者,它也可以選擇透過不呼叫下一個實體來阻止請求。在後一種情況下,建議器負責填充響應。

  3. 由框架提供的最後一個建議器將請求傳送給聊天模型。

  4. 然後,聊天模型的響應會透過建議器鏈傳遞回來,並轉換為 AdvisedResponse。後者包含了共享的 AdvisorContext 例項。

  5. 每個建議器都可以處理或修改響應。

  6. 最終的 AdvisedResponse 透過提取 ChatCompletion 返回給客戶端。

建議器順序

建議器在鏈中的執行順序由 getOrder() 方法決定。需要理解的關鍵點:

  • order 值較低的建議器首先執行。

  • 建議器鏈作為棧執行

    • 鏈中的第一個建議器是第一個處理請求的。

    • 它也是最後一個處理響應的。

  • 要控制執行順序

    • 將 order 值設定為接近 Ordered.HIGHEST_PRECEDENCE,以確保建議器在鏈中首先執行(先處理請求,後處理響應)。

    • 將 order 值設定為接近 Ordered.LOWEST_PRECEDENCE,以確保建議器在鏈中最後執行(後處理請求,先處理響應)。

  • 值越高表示優先順序越低。

  • 如果多個建議器具有相同的 order 值,其執行順序不保證。

order 值與執行順序看似矛盾,原因在於建議器鏈的棧狀特性

  • 優先順序最高(order 值最低)的建議器被新增到棧頂。

  • 在棧展開時,它將第一個處理請求。

  • 在棧回捲時,它將最後一個處理響應。

提醒一下,以下是 Spring Ordered 介面的語義

public interface Ordered {

    /**
     * Constant for the highest precedence value.
     * @see java.lang.Integer#MIN_VALUE
     */
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    /**
     * Constant for the lowest precedence value.
     * @see java.lang.Integer#MAX_VALUE
     */
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    /**
     * Get the order value of this object.
     * <p>Higher values are interpreted as lower priority. As a consequence,
     * the object with the lowest value has the highest priority (somewhat
     * analogous to Servlet {@code load-on-startup} values).
     * <p>Same order values will result in arbitrary sort positions for the
     * affected objects.
     * @return the order value
     * @see #HIGHEST_PRECEDENCE
     * @see #LOWEST_PRECEDENCE
     */
    int getOrder();
}

對於需要在鏈的輸入側和輸出側都優先執行的用例

  1. 為每一側使用單獨的建議器。

  2. 為它們配置不同的 order 值。

  3. 使用建議器上下文在它們之間共享狀態。

API 概覽

主要的建議器介面位於 org.springframework.ai.chat.client.advisor.api 包中。以下是您建立自己的建議器時會遇到的關鍵介面

public interface Advisor extends Ordered {

	String getName();

}

同步和響應式建議器的兩個子介面是

public interface CallAroundAdvisor extends Advisor {

	/**
	 * Around advice that wraps the ChatModel#call(Prompt) method.
	 * @param advisedRequest the advised request
	 * @param chain the advisor chain
	 * @return the response
	 */
	AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);

}

public interface StreamAroundAdvisor extends Advisor {

	/**
	 * Around advice that wraps the invocation of the advised request.
	 * @param advisedRequest the advised request
	 * @param chain the chain of advisors to execute
	 * @return the result of the advised request
	 */
	Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);

}

要在您的 Advice 實現中繼續 Advice 鏈,請使用 CallAroundAdvisorChainStreamAroundAdvisorChain

介面是

public interface CallAroundAdvisorChain {

	AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);

}

public interface StreamAroundAdvisorChain {

	Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);

}

實現一個建議器

要建立一個建議器,請實現 CallAroundAdvisorStreamAroundAdvisor(或兩者)。需要實現的關鍵方法是用於非流式的 nextAroundCall() 或用於流式的 nextAroundStream()

示例

我們將提供一些動手示例,以說明如何實現用於觀察和增強用例的建議器。

日誌建議器

我們可以實現一個簡單的日誌建議器,它在呼叫鏈中的下一個建議器之前記錄 AdvisedRequest,並在之後記錄 AdvisedResponse。請注意,該建議器只觀察請求和響應,不會修改它們。此實現支援非流式和流式場景。

public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

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

	@Override
	public String getName() { (1)
		return this.getClass().getSimpleName();
	}

	@Override
	public int getOrder() { (2)
		return 0;
	}

	@Override
	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {

		logger.debug("BEFORE: {}", advisedRequest);

		AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);

		logger.debug("AFTER: {}", advisedResponse);

		return advisedResponse;
	}

	@Override
	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {

		logger.debug("BEFORE: {}", advisedRequest);

		Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);

        return new MessageAggregator().aggregateAdvisedResponse(advisedResponses,
                    advisedResponse -> logger.debug("AFTER: {}", advisedResponse)); (3)
	}
}
1 提供建議器的唯一名稱。
2 您可以透過設定 order 值來控制執行順序。值較低的先執行。
3 MessageAggregator 是一個工具類,它將 Flux 響應聚合到一個 AdvisedResponse 中。這對於記錄日誌或觀察整個響應而非流中單個項的其他處理非常有用。請注意,您不能在 MessageAggregator 中更改響應,因為它是一個只讀操作。

重複閱讀 (Re2) 建議器

重複閱讀提高了大型語言模型的推理能力》這篇文章介紹了一種名為重複閱讀 (Re2) 的技術,可以提高大型語言模型的推理能力。Re2 技術需要像這樣增強輸入提示詞:

{Input_Query}
Read the question again: {Input_Query}

實現一個將 Re2 技術應用於使用者輸入查詢的建議器可以像這樣完成:

public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {


	private AdvisedRequest before(AdvisedRequest advisedRequest) { (1)

		Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
		advisedUserParams.put("re2_input_query", advisedRequest.userText());

		return AdvisedRequest.from(advisedRequest)
			.userText("""
			    {re2_input_query}
			    Read the question again: {re2_input_query}
			    """)
			.userParams(advisedUserParams)
			.build();
	}

	@Override
	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { (2)
		return chain.nextAroundCall(this.before(advisedRequest));
	}

	@Override
	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { (3)
		return chain.nextAroundStream(this.before(advisedRequest));
	}

	@Override
	public int getOrder() { (4)
		return 0;
	}

    @Override
    public String getName() { (5)
		return this.getClass().getSimpleName();
	}
}
1 before 方法應用重複閱讀技術來增強使用者的輸入查詢。
2 aroundCall 方法攔截非流式請求並應用重複閱讀技術。
3 aroundStream 方法攔截流式請求並應用重複閱讀技術。
4 您可以透過設定 order 值來控制執行順序。值較低的先執行。
5 提供建議器的唯一名稱。

Spring AI 內建建議器

Spring AI 框架提供了幾個內建的建議器,以增強您的 AI 互動。以下是可用建議器的概覽:

聊天記憶建議器

這些建議器在聊天記憶儲存中管理對話歷史記錄

  • MessageChatMemoryAdvisor

    檢索記憶並將其作為訊息集合新增到提示詞中。這種方法保持了對話歷史的結構。注意,並非所有 AI 模型都支援這種方法。

  • PromptChatMemoryAdvisor

    檢索記憶並將其整合到提示詞的系統文字中。

  • VectorStoreChatMemoryAdvisor

    從 VectorStore 中檢索記憶並新增到提示詞的系統文字中。此建議器對於高效搜尋和檢索大型資料集中的相關資訊非常有用。

問答建議器
  • QuestionAnswerAdvisor

    此建議器使用向量儲存來提供問答功能,實現了 RAG(檢索增強生成)模式。

內容安全建議器
  • SafeGuardAdvisor

    一個簡單的建議器,旨在防止模型生成有害或不適當的內容。

流式 vs 非流式

Advisors Streaming vs Non-Streaming Flow
  • 非流式建議器處理完整的請求和響應。

  • 流式建議器使用響應式程式設計概念(例如,用於響應的 Flux)將請求和響應作為連續流進行處理。

@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {

    return  Mono.just(advisedRequest)
            .publishOn(Schedulers.boundedElastic())
            .map(request -> {
                // This can be executed by blocking and non-blocking Threads.
                // Advisor before next section
            })
            .flatMapMany(request -> chain.nextAroundStream(request))
            .map(response -> {
                // Advisor after next section
            });
}

最佳實踐

  1. 建議器應專注於特定任務,以提高模組化程度。

  2. 必要時使用 adviseContext 在建議器之間共享狀態。

  3. 實現建議器的流式和非流式版本,以獲得最大的靈活性。

  4. 仔細考慮鏈中建議器的順序,以確保正確的資料流。

向後相容性

AdvisedRequest 類已移至新包。

API 破壞性變更

Spring AI 建議器鏈從版本 1.0 M2 到 1.0 M3 發生了重大變化。以下是主要修改內容:

建議器介面

  • 在 1.0 M2 中,存在獨立的 RequestAdvisorResponseAdvisor 介面。

    • RequestAdvisorChatModel.callChatModel.stream 方法之前被呼叫。

    • ResponseAdvisor 在這些方法之後被呼叫。

  • 在 1.0 M3 中,這些介面已被以下介面取代:

    • CallAroundAdvisor

    • StreamAroundAdvisor

  • StreamResponseMode,之前是 ResponseAdvisor 的一部分,已被移除。

上下文 Map 處理

  • 在 1.0 M2 中

    • 上下文 map 是一個單獨的方法引數。

    • 該 map 是可變的,並在鏈中傳遞。

  • 在 1.0 M3 中

    • 上下文 map 現在是 AdvisedRequestAdvisedResponse 記錄的一部分。

    • 該 map 是不可變的。

    • 要更新上下文,請使用 updateContext 方法,該方法將建立一個包含更新內容的新不可修改 map。

在 1.0 M3 中更新上下文的示例

@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {

    this.advisedRequest = advisedRequest.updateContext(context -> {
        context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName());  // Add multiple key-value pairs
        context.put("lastBefore", getName());  // Add a single key-value pair
        return context;
    });

    // Method implementation continues...
}