Advisors API

Spring AI Advisors API 提供了一種彈性且強大的方式,可在您的 Spring 應用程式中攔截、修改和增強 AI 驅動的互動。透過利用 Advisors API,開發人員可以建立更複雜、可重複使用且易於維護的 AI 組件。

主要優點包括封裝重複出現的生成式 AI 模式、轉換傳送至語言模型 (LLM) 和從語言模型 (LLM) 傳送的資料,以及在各種模型和使用案例之間提供可移植性。

您可以使用 ChatClient API 配置現有的 advisors,如下列範例所示

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
        new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()) // 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() 方法註冊 advisors。

Advisors 也參與可觀測性堆疊,因此您可以檢視與其執行相關的指標和追蹤。

核心組件

API 由非串流情境的 CallAroundAdvisorCallAroundAdvisorChain,以及串流情境的 StreamAroundAdvisorStreamAroundAdvisorChain 組成。它還包括 AdvisedRequest 以表示未封裝的 Prompt 請求,以及 AdvisedResponse 以表示聊天完成回應。兩者都包含一個 advise-context,以便在 advisor 鏈中共享狀態。

Advisors API Classes

nextAroundCall()nextAroundStream() 是主要的 advisor 方法,通常執行諸如檢查未封裝的 Prompt 資料、自訂和擴增 Prompt 資料、調用 advisor 鏈中的下一個實體、選擇性地阻止請求、檢查聊天完成回應以及擲回例外以指示處理錯誤等動作。

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

由 Spring AI 框架建立的 Advisor 鏈允許依其 getOrder() 值排序,依序調用多個 advisors。數值較低的 advisor 會先執行。最後一個自動加入的 advisor 會將請求傳送至 LLM。

以下流程圖說明了 advisor 鏈和聊天模型之間的互動

Advisors API Flow
  1. Spring AI 框架會從使用者的 Prompt 以及空的 AdvisorContext 物件建立 AdvisedRequest

  2. 鏈中的每個 advisor 都會處理請求,並可能修改它。或者,它可以選擇不調用下一個實體,從而阻止請求。在後一種情況下,advisor 負責填寫回應。

  3. 由框架提供的最終 advisor 會將請求傳送至 Chat Model

  4. 然後,聊天模型的回應會傳回 advisor 鏈,並轉換為 AdvisedResponse。後者包含共享的 AdvisorContext 實例。

  5. 每個 advisor 都可以處理或修改回應。

  6. 最終的 AdvisedResponse 會透過擷取 ChatCompletion 回傳給用戶端。

Advisor 順序

advisor 在鏈中的執行順序由 getOrder() 方法決定。需要理解的重點:

  • 順序值較低的 advisors 會先執行。

  • advisor 鏈作為堆疊運作

    • 鏈中的第一個 advisor 是第一個處理請求的 advisor。

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

  • 若要控制執行順序:

    • 將順序設定為接近 Ordered.HIGHEST_PRECEDENCE,以確保 advisor 在鏈中第一個執行(請求處理時為第一個,回應處理時為最後一個)。

    • 將順序設定為接近 Ordered.LOWEST_PRECEDENCE,以確保 advisor 在鏈中最後一個執行(請求處理時為最後一個,回應處理時為第一個)。

  • 較高的值會被解釋為較低的優先順序。

  • 如果多個 advisors 具有相同的順序值,則其執行順序不保證。

順序和執行順序之間看似矛盾的原因,是因為 advisor 鏈的堆疊式性質:* 具有最高優先順序(最低順序值)的 advisor 會新增至堆疊頂端。 * 當堆疊展開時,它將是第一個處理請求的 advisor。 * 當堆疊回溯時,它將是最後一個處理回應的 advisor。

提醒您,以下是 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. 針對每一端使用不同的 advisors。

  2. 使用不同的順序值配置它們。

  3. 使用 advisor context 在它們之間共享狀態。

API 總覽

主要的 Advisor 介面位於套件 org.springframework.ai.chat.client.advisor.api 中。以下是您在建立自己的 advisor 時會遇到的主要介面:

public interface Advisor extends Ordered {

	String getName();

}

同步和反應式 Advisors 的兩個子介面是:

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);

}

實作 Advisor

若要建立 advisor,請實作 CallAroundAdvisorStreamAroundAdvisor(或兩者)。要實作的主要方法是非串流 advisor 的 nextAroundCall() 或串流 advisor 的 nextAroundStream()

範例

我們將提供一些實作範例,以說明如何實作 advisors 以觀察和擴增使用案例。

日誌記錄 Advisor

我們可以實作一個簡單的日誌記錄 advisor,在呼叫鏈中的下一個 advisor 之前記錄 AdvisedRequest,並在其之後記錄 AdvisedResponse。請注意,advisor 僅觀察請求和回應,而不修改它們。此實作支援非串流和串流情境。

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 為 advisor 提供唯一的名稱。
2 您可以透過設定順序值來控制執行順序。數值較低的 advisor 會先執行。
3 MessageAggregator 是一個工具類別,可將 Flux 回應聚合為單個 AdvisedResponse。這對於記錄或其他觀察整個回應而不是串流中個別項目的處理非常有用。請注意,您無法在 MessageAggregator 中變更回應,因為它是唯讀操作。

重新閱讀 (Re2) Advisor

重新閱讀提升大型語言模型的推理能力」文章介紹了一種稱為重新閱讀 (Re2) 的技術,該技術提高了大型語言模型的推理能力。Re2 技術需要像這樣擴增輸入提示:

{Input_Query}
Read the question again: {Input_Query}

可以像這樣實作一個 advisor,將 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)
			.withUserText("""
			    {re2_input_query}
			    Read the question again: {re2_input_query}
			    """)
			.withUserParams(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 您可以透過設定順序值來控制執行順序。數值較低的 advisor 會先執行。
5 為 advisor 提供唯一的名稱。

Spring AI 內建 Advisors

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

聊天記憶體 Advisors

這些 advisors 管理聊天記憶體儲存中的對話歷史記錄。

  • MessageChatMemoryAdvisor

    檢索記憶體並將其作為訊息集合新增至提示。此方法維護了對話歷史記錄的結構。請注意,並非所有 AI 模型都支援此方法。

  • PromptChatMemoryAdvisor

    檢索記憶體並將其併入提示的系統文字中。

  • VectorStoreChatMemoryAdvisor

    從 VectorStore 檢索記憶體並將其新增至提示的系統文字中。此 advisor 對於從大型資料集中有效搜尋和檢索相關資訊非常有用。

問題回答 Advisor
  • QuestionAnswerAdvisor

    此 advisor 使用向量儲存來提供問題回答功能,實作 RAG(檢索增強生成)模式。

內容安全 Advisor
  • SafeGuardAdvisor

    一個簡單的 advisor,旨在防止模型產生有害或不適當的內容。

串流與非串流

Advisors Streaming vs Non-Streaming Flow
  • 非串流 advisors 處理完整的請求和回應。

  • 串流 advisors 將請求和回應作為連續串流處理,使用反應式程式設計概念(例如,回應的 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. 讓 advisors 專注於特定任務,以獲得更好的模組化。

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

  3. 實作 advisor 的串流和非串流版本,以獲得最大的彈性。

  4. 仔細考慮 advisors 在鏈中的順序,以確保正確的資料流程。

回溯相容性

AdvisedRequest 類別已移至新的套件。雖然 RequestResponseAdvisor 介面仍然可用,但它已標記為已棄用,並將在 M3 版本左右移除。建議新的實作使用新的 CallAroundAdvisorStreamAroundAdvisor 介面。

重大 API 變更

Spring AI Advisor 鏈從 1.0 M2 版本到 1.0 M3 版本經歷了重大變更。以下是主要修改:

Advisor 介面

  • 在 1.0 M2 中,有單獨的 RequestAdvisorResponseAdvisor 介面。

    • RequestAdvisorChatModel.callChatModel.stream 方法之前調用。

    • ResponseAdvisor 在這些方法之後調用。

  • 在 1.0 M3 中,這些介面已替換為:

    • CallAroundAdvisor

    • StreamAroundAdvisor

  • 先前作為 ResponseAdvisor 一部分的 StreamResponseMode 已移除。

Context Map 處理

  • 在 1.0 M2 中:

    • context map 是一個單獨的方法引數。

    • map 是可變的,並沿著鏈傳遞。

  • 在 1.0 M3 中:

    • context map 現在是 AdvisedRequestAdvisedResponse 記錄的一部分。

    • map 是不可變的。

    • 若要更新 context,請使用 updateContext 方法,該方法會建立一個包含更新內容的新不可修改 map。

在 1.0 M3 中更新 context 的範例:

@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...
}