dur
dur

Reputation: 17009

Why is template variable resolution performed twice?

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 org.springframework.ai.chat.prompt.PromptTemplate.validate(PromptTemplate.java:232) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.springframework.ai.chat.prompt.PromptTemplate.render(PromptTemplate.java:125) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.springframework.ai.chat.client.advisor.api.AdvisedRequest.toPrompt(AdvisedRequest.java:171) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1.aroundCall(DefaultChatClient.java:675) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.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 org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor.aroundCall(SimpleLoggerAdvisor.java:99) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.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 org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetChatResponse(DefaultChatClient.java:488) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.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 org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatResponse(DefaultChatClient.java:477) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doSingleWithBeanOutputConverter(DefaultChatClient.java:451) ~[spring-ai-core-1.0.0-M5.jar:1.0.0-M5]
    at org.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?

Upvotes: 0

Views: 36

Answers (1)

jonghoonpark
jonghoonpark

Reputation: 31

Spring 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.

Upvotes: 2

Related Questions