Bug description
ChatClientRequest only forbids null keys, not null values. The advisors (ChatModelStreamAdvisor, ChatModelCallAdvisor, SafeGuardAdvisor) call Map.copyOf(request.context()), and that blows up with NullPointerException if any value is null. The call/stream fails before the model is invoked.
Environment
- Spring AI: main (latest)
- Java: 17 (also seen on 21)
- Vector store: not involved
Steps to reproduce
1) Build a ChatClientRequest with a null context value, e.g. context("tenantId", null).
2) Use a ChatClient with default advisors (or directly use ChatModelStreamAdvisor/ChatModelCallAdvisor).
3) Call .stream(...) or .call(...).
4) Map.copyOf throws NPE inside the advisor.
Expected behavior
Either reject the request up front with a clear validation error, or avoid throwing NPE in the advisors when context contains null values.
Minimal complete reproducible example
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.prompt.Prompt;
class ChatClientRequestNullContextTests {
@Test
void nullContextValueTriggersNpeInAdvisor() {
ChatClientRequest request = ChatClientRequest.builder()
.prompt(new Prompt("hi"))
.context("tenantId", null) // null value
.build();
// Same failure point as in the advisors:
assertThatThrownBy(() -> java.util.Map.copyOf(request.context()))
.isInstanceOf(NullPointerException.class);
}
}
Affected code
spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ChatModelStreamAdvisor.javaspring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ChatModelCallAdvisor.javaspring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/SafeGuardAdvisor.javaspring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClientRequest.java
Proposed resolution
Tighten ChatClientRequest validation to disallow null values in context, so invalid requests fail fast with a clear message, and the advisors can keep their immutable Map.copyOf calls.