序
本文主要研究一下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去构建。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。