Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat messages with missing variables as text #1137

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

anunnakian
Copy link
Contributor

@anunnakian anunnakian commented May 21, 2024

Issue

#1125

Change

When we declare an assistant as following example :

interface AiService {

    @UserMessage("What is the capital of {{it}}?")
    String answer();
}

We don't throw an illegal exception like before, we treat the user message like text without resolving the {{it}} variable.

Here is a test that explain the treatment clearly :

@Test
void test_user_message_configuration_8() {

    // given
    AiService aiService = AiServices.builder(AiService.class)
            .chatLanguageModel(chatLanguageModel)
            .build();

    // when-then
    assertThat(aiService.answer()).containsIgnoringCase("");
    verify(chatLanguageModel).generate(singletonList(userMessage("What is the capital of {{it}}?")));
}

General checklist

  • There are no breaking changes
  • I have added unit and integration tests for my change
  • I have manually run all the unit and integration tests in the module I have added/changed, and they are all green
  • I have manually run all the unit and integration tests in the core and main modules, and they are all green

Checklist for adding new model integration

  • I have added my new module in the BOM

Checklist for adding new embedding store integration

  • I have added a {NameOfIntegration}EmbeddingStoreIT that extends from either EmbeddingStoreIT or EmbeddingStoreWithFilteringIT
  • I have added my new module in the BOM

Checklist for changing existing embedding store integration

  • I have manually verified that the {NameOfIntegration}EmbeddingStore works correctly with the data persisted using the latest released version of LangChain4j

@langchain4j
Copy link
Owner

Hi @anunnakian thanks a lot!

This seems to be a bit different from what was described in #1125

This is definitely a wrong configuration:

interface AiService {

    @UserMessage("What is the capital of {{it}}?")
    String answer();
}

The case described in the issue is different:

@AiService
public interface AiAssistant {

    TokenStream chat(@MemoryId String chatId, @UserMessage String userMessage);
}

assistant.chat("12345", "Text containing {{it}}");

@anunnakian
Copy link
Contributor Author

anunnakian commented May 22, 2024

@langchain4j if we consider that the issue was fixed, the following tests must be green right ?

@Test
void test_user_message_configuration_8() {

    // given
    AiService aiService = AiServices.builder(AiService.class)
            .chatLanguageModel(chatLanguageModel)
            .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
            .build();

    // when
    aiService.chat8("12345", "What is the capital of {{it}}?");

    // then
    verify(chatLanguageModel).generate(singletonList(userMessage("What is the capital of {{it}}?")));
}

@Test
void test_user_message_configuration_9() {

        // given
        AiService aiService = AiServices.builder(AiService.class)
                .chatLanguageModel(chatLanguageModel)
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
                .build();

        // when
        aiService.chat8("12345", "What is the capital of {{variable}}?");

        // then
        verify(chatLanguageModel).generate(singletonList(userMessage("What is the capital of {{variable}}?")));
    }

@anunnakian
Copy link
Contributor Author

anunnakian commented May 22, 2024

We should remove this test case, if the missing variable {{name}} must be treated like text, right ?

@Test
void should_fail_when_value_is_missing() {

   // given
   PromptTemplate promptTemplate = PromptTemplate.from("My name is {{name}}.");

   Map<String, Object> variables = emptyMap();

   // when-then
   assertThatThrownBy(() -> promptTemplate.apply(variables))
           .isExactlyInstanceOf(IllegalArgumentException.class)
           .hasMessage("Value for the variable 'name' is missing");
}

@langchain4j
Copy link
Owner

@anunnakian to your first question: yes.
Second: not sure, is there a way to treat it as a text (instead of template) only for that use case?

@anunnakian
Copy link
Contributor Author

anunnakian commented May 22, 2024

@langchain4j If we treat the message as text instead of a template, we will break the case if we have two variable with one missing like the following test :

interface AiService {

    String chat9(@MemoryId String chatId, @UserMessage String userMessage, @V("country") String country);
}

@Test
void test_user_message_configuration_10() {

    // given
    AiService aiService = AiServices.builder(AiService.class)
            .chatLanguageModel(chatLanguageModel)
            .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
            .build();

    // when
    aiService.chat9("12345", "What is the {{it}} of {{country}}?", "Germany");

    // then
    verify(chatLanguageModel).generate(singletonList(userMessage("What is the {{it}} of Germany?")));
}

This test must be green too at the end, right ?

@anunnakian anunnakian changed the title Threat prompt with missing 'it' variable as text Treat prompt with missing 'it' variable as text May 23, 2024
@anunnakian
Copy link
Contributor Author

anunnakian commented May 24, 2024

@langchain4j I found a solution to make all tests above and the following one green!
my branch is up to date and waiting for your feedback

interface AiService {
    String chat(@MemoryId String chatId, @UserMessage String userMessage);
}

@Test
void test_user_message_configuration_9() {

    // given
    AiService aiService = AiServices.builder(AiService.class)
            .chatLanguageModel(chatLanguageModel)
            .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
            .build();

    // when
    aiService.chat("12345", "What is the {{it}} of {{var}}?");

    // then
    verify(chatLanguageModel).generate(singletonList(userMessage("What is the {{it}} of {{var}}?")));
}

* Get all variables extracted from the template.
* @return A set of variable names.
*/
default Set<String> getAllVariables() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A make this method default to avoid breaking changes

@langchain4j langchain4j added the P1 Highest priority label May 27, 2024
@anunnakian anunnakian changed the title Treat prompt with missing 'it' variable as text Treat messages with missing variables as text May 27, 2024
@ashimoon
Copy link

Should we just always treat missing variables, including {{it}} as an illegal state? I would imagine in the vast majority of cases, if the author created a @UserMessage with a prompt variable in it, then the method signature must match that user message, otherwise an exception should be thrown.

If we do indeed want to support What is the capital of {{it}}? where we want the string to be supported literally without any templating, then could we just add a property to @UserMessage to indicate that?

e.g.

@UserMessage(value = "What is the capital of {{it}}?", templated = false)

public @interface UserMessage {
    String value()
    boolean templated() default true;
}

If we need even further grained control, such that only some variables are excluded from templating, that could also be defined in the annotation?

@UserMessage(value = "What is the capital of {{it}} in {{country}}?", excludedVariables = { "it" })

public @interface UserMessage {
    String value()
    String[] excludedVariables() default {};
}

@langchain4j
Copy link
Owner

@ashimoon yes, I think you are right. We should be strict by default (as it is now).

I think I rushed a bit with #1125. It can be solved by escaping {{it}} instead of making changes to the default behaviour of prompt templates.

@anunnakian sorry about that, let's put this PR on hold for now

@anunnakian
Copy link
Contributor Author

Ok! Don't panic everything is under control (just kidding... 😁)

So, I'll create another PR to escape {{it}} variable tonight

Thanks for your feedback @ashimoon @langchain4j 😉

@langchain4j
Copy link
Owner

@anunnakian I meant that the current implementation (without this PR) indeed seems to be the most reasonable. I guess that specific corner case in #1125 is actually ok and the user can escape manually, if there is a need. Whether we should support auto-escaping, it is tricky. I would take a break and return to this problem a bit later.

@anunnakian
Copy link
Contributor Author

@langchain4j got it ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P1 Highest priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants