I have a application, which calls OpenAI chat. My users can enter text, which is used to create the prompt. The result should be a Java object, see Structured Output Converter.
Unfortunatly, Spring AI is resolving the variables in the template twice. This causes an error, if an user uses some special characters.
Code
@SpringBootApplication
public class MyTestApp {
public static void main(String[] args) {
SpringApplication.run(MyTestApp.class, args);
}
@Data
class Result {
private String text;
}
@Controller
@AllArgsConstructor
class MyTestService {
private final ChatClient chatClient;
@GetMapping("/test")
public void test() {
final PromptTemplate promptTemplate = new PromptTemplate("{userInput}");
final Prompt prompt = promptTemplate.create(Map.of("userInput", "{test}"));
final Result result =
chatClient.prompt(prompt).advisors(new SimpleLoggerAdvisor()).call().entity(Result.class);
}
}
}
Log
java.lang.IllegalStateException: Not all template variables were replaced. Missing variable names are [test]
at .springframework.ai.chat.prompt.PromptTemplate.validate(PromptTemplate.java:232) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.prompt.PromptTemplate.render(PromptTemplate.java:125) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.api.AdvisedRequest.toPrompt(AdvisedRequest.java:171) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1.aroundCall(DefaultChatClient.java:675) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextAroundCall$1(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.3.jar:1.14.3]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.SimpleLoggerAdvisor.aroundCall(SimpleLoggerAdvisor.java:99) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextAroundCall$1(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.3.jar:1.14.3]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetChatResponse(DefaultChatClient.java:488) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.lambda$doGetObservableChatResponse$1(DefaultChatClient.java:477) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.3.jar:1.14.3]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatResponse(DefaultChatClient.java:477) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doSingleWithBeanOutputConverter(DefaultChatClient.java:451) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.entity(DefaultChatClient.java:446) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
Question
Why is Spring AI resolving variables twice? Is there any predefined method to escape the user input?
I have a application, which calls OpenAI chat. My users can enter text, which is used to create the prompt. The result should be a Java object, see Structured Output Converter.
Unfortunatly, Spring AI is resolving the variables in the template twice. This causes an error, if an user uses some special characters.
Code
@SpringBootApplication
public class MyTestApp {
public static void main(String[] args) {
SpringApplication.run(MyTestApp.class, args);
}
@Data
class Result {
private String text;
}
@Controller
@AllArgsConstructor
class MyTestService {
private final ChatClient chatClient;
@GetMapping("/test")
public void test() {
final PromptTemplate promptTemplate = new PromptTemplate("{userInput}");
final Prompt prompt = promptTemplate.create(Map.of("userInput", "{test}"));
final Result result =
chatClient.prompt(prompt).advisors(new SimpleLoggerAdvisor()).call().entity(Result.class);
}
}
}
Log
java.lang.IllegalStateException: Not all template variables were replaced. Missing variable names are [test]
at .springframework.ai.chat.prompt.PromptTemplate.validate(PromptTemplate.java:232) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.prompt.PromptTemplate.render(PromptTemplate.java:125) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.api.AdvisedRequest.toPrompt(AdvisedRequest.java:171) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1.aroundCall(DefaultChatClient.java:675) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextAroundCall$1(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.3.jar:1.14.3]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.SimpleLoggerAdvisor.aroundCall(SimpleLoggerAdvisor.java:99) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextAroundCall$1(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.3.jar:1.14.3]
at .springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetChatResponse(DefaultChatClient.java:488) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.lambda$doGetObservableChatResponse$1(DefaultChatClient.java:477) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.3.jar:1.14.3]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatResponse(DefaultChatClient.java:477) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doSingleWithBeanOutputConverter(DefaultChatClient.java:451) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
at .springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.entity(DefaultChatClient.java:446) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
Question
Why is Spring AI resolving variables twice? Is there any predefined method to escape the user input?
Share Improve this question asked Feb 17 at 12:23 durdur 17k26 gold badges88 silver badges143 bronze badges1 Answer
Reset to default 2Spring AI adds additional processing to handle structured output.
The toPrompt()
method of AdvisedRequest
includes the following code:
var processedUserText = StringUtils.hasText(formatParam)
? this.userText() + System.lineSeparator() + "{spring_ai_soc_format}" : this.userText();
if (StringUtils.hasText(processedUserText)) {
Map<String, Object> userParams = new HashMap<>(this.userParams());
if (StringUtils.hasText(formatParam)) {
userParams.put("spring_ai_soc_format", formatParam);
}
if (!CollectionUtils.isEmpty(userParams)) {
processedUserText = new PromptTemplate(processedUserText, userParams).render();
}
messages.add(new UserMessage(processedUserText, this.media()));
}
In this code, an additional rendering step is applied to process the {spring_ai_soc_format}
placeholder. This causes one more render()
call.