本文主要研究一下langchain4j的Agent

示例

CustomerSupportAgent

customer-support-agent-example/src/main/java/dev/langchain4j/example/CustomerSupportAgent.java

@AiService
public interface CustomerSupportAgent {

    @SystemMessage("""
            Your name is Roger, you are a customer support agent of a car rental company named 'Miles of Smiles'.
            You are friendly, polite and concise.
            
            Rules that you must obey:
            
            1. Before getting the booking details or canceling the booking,
            you must make sure you know the customer's first name, last name, and booking number.
            
            2. When asked to cancel the booking, first make sure it exists, then ask for an explicit confirmation.
            After cancelling the booking, always say "We hope to welcome you back again soon".
            
            3. You should answer only questions related to the business of Miles of Smiles.
            When asked about something not relevant to the company business,
            apologize and say that you cannot help with that.
            
            Today is {{current_date}}.
            """)
    Result<String> answer(@MemoryId String memoryId, @UserMessage String userMessage);
}
CustomerSupportAgent定义了SystemMessage、memoryId、userMessage

CustomerSupportAgentIT

customer-support-agent-example/src/test/java/dev/langchain4j/example/CustomerSupportAgentIT.java

    @Test
    void should_provide_booking_details_for_existing_booking() {

        // given
        String userMessage = "Hi, I am %s %s. When does my booking %s start?"
                .formatted(CUSTOMER_NAME, CUSTOMER_SURNAME, BOOKING_NUMBER);

        // when
        Result<String> result = agent.answer(memoryId, userMessage);
        String answer = result.content();

        // then
        assertThat(answer)
                .containsIgnoringCase(getDayFrom(BOOKING_BEGIN_DATE))
                .containsIgnoringCase(getMonthFrom(BOOKING_BEGIN_DATE))
                .containsIgnoringCase(getYearFrom(BOOKING_BEGIN_DATE));

        assertThat(result).onlyToolWasExecuted("getBookingDetails");
        verify(bookingService).getBookingDetails(BOOKING_NUMBER, CUSTOMER_NAME, CUSTOMER_SURNAME);
        verifyNoMoreInteractions(bookingService);

        TokenUsage tokenUsage = result.tokenUsage();
        assertThat(tokenUsage.inputTokenCount()).isLessThan(1000);
        assertThat(tokenUsage.outputTokenCount()).isLessThan(200);

        with(judgeModel).assertThat(answer)
                .satisfies("mentions that booking starts on %s".formatted(BOOKING_BEGIN_DATE));
    }

    @Test
    void should_not_provide_booking_details_when_booking_does_not_exist() {

        // given
        String invalidBookingNumber = "54321";
        String userMessage = "Hi, I am %s %s. When does my booking %s start?"
                .formatted(CUSTOMER_NAME, CUSTOMER_SURNAME, invalidBookingNumber);

        // when
        Result<String> result = agent.answer(memoryId, userMessage);
        String answer = result.content();

        // then
        assertThat(answer)
                .doesNotContainIgnoringCase(getDayFrom(BOOKING_BEGIN_DATE))
                .doesNotContainIgnoringCase(getMonthFrom(BOOKING_BEGIN_DATE))
                .doesNotContainIgnoringCase(getYearFrom(BOOKING_BEGIN_DATE));

        assertThat(result).onlyToolWasExecuted("getBookingDetails");
        verify(bookingService).getBookingDetails(invalidBookingNumber, CUSTOMER_NAME, CUSTOMER_SURNAME);
        verifyNoMoreInteractions(bookingService);

        with(judgeModel).assertThat(answer).satisfies(
                "mentions that booking cannot be found",
                "does not mention any dates"
        );
    }

    @Test
    void should_not_provide_booking_details_when_not_enough_data_is_provided() {

        // given
        String userMessage = "When does my booking %s start?".formatted(BOOKING_NUMBER); // name and surname are not provided

        // when
        Result<String> result = agent.answer(memoryId, userMessage);
        String answer = result.content();

        // then
        assertThat(answer)
                .doesNotContainIgnoringCase(getDayFrom(BOOKING_BEGIN_DATE))
                .doesNotContainIgnoringCase(getMonthFrom(BOOKING_BEGIN_DATE))
                .doesNotContainIgnoringCase(getYearFrom(BOOKING_BEGIN_DATE));

        assertThat(result).noToolsWereExecuted();

        with(judgeModel).assertThat(answer).satisfies(
                "asks user to provide their name and surname",
                "does not mention any dates"
        );
    }


    // cancelling booking

    @Test
    void should_cancel_booking() {

        // given
        String userMessage = "Cancel my booking %s. My name is %s %s."
                .formatted(BOOKING_NUMBER, CUSTOMER_NAME, CUSTOMER_SURNAME);

        // when
        Result<String> result = agent.answer(memoryId, userMessage);

        // then
        assertThat(result).onlyToolWasExecuted("getBookingDetails");
        verify(bookingService).getBookingDetails(BOOKING_NUMBER, CUSTOMER_NAME, CUSTOMER_SURNAME);
        verifyNoMoreInteractions(bookingService);

        with(judgeModel).assertThat(result.content())
                .satisfies("is asking for the confirmation to cancel the booking");

        // when
        Result<String> result2 = agent.answer(memoryId, "yes, cancel it");

        // then
        assertThat(result2.content()).containsIgnoringCase("We hope to welcome you back again soon");

        assertThat(result2).onlyToolWasExecuted("cancelBooking");
        verify(bookingService).cancelBooking(BOOKING_NUMBER, CUSTOMER_NAME, CUSTOMER_SURNAME);
        verifyNoMoreInteractions(bookingService);
    }

小结

Agent这个词比较宽泛,而且有很多不同的定义,通常基本的agentic的功能可以基于high-level的AI Service和Tool来构建,如果还需要更灵活的设置,则可以基于low-level的ChatLanguageModel、ToolSpecification以及ChatMemory APIs来构建。langchain4j目前暂不支持类似AutoGen或CrewAI中用于构建多智能体系统的"Agent"高级抽象功能,如果需要则可以基于low-level的API去构建。

doc


codecraft
11.9k 声望2k 粉丝

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下...