Blog

Apresentamos o LangChain4j para simplificar a integração do LLM em aplicações Java.

LangChain4j (LangChain para Java) é um conjunto de ferramentas poderoso para construir sua aplicação RAG em Java puro.

O framework LangChain4j foi criado em 2023 com este objetivo:

O objetivo do LangChain4j é simplificar a integração de LLMs em aplicações Java.

LangChain4j oferece uma maneira padronizada de:

  • Criar representações vetoriais (embeddings) a partir de um determinado conteúdo, digamos, um texto, por exemplo.

  • armazenar embeddings em um armazenamento de embeddings

  • pesquisar vetores semelhantes no armazenamento de embeddings

  • discutir com mestres em direito

  • Use uma memória de bate-papo para lembrar o contexto de uma discussão com um mestre em Direito.

Esta lista não é exaustiva e a comunidade LangChain4j está sempre implementando novos recursos.

Esta postagem abordará as primeiras partes principais da estrutura.

Adicionando LangChain4j OpenAI ao nosso projeto

Como em todos os projetos Java, é apenas uma questão de dependências. Aqui usaremos o Maven, mas o mesmo resultado poderia ser obtido com qualquer outro gerenciador de dependências.

Como primeiro passo para o projeto que queremos construir aqui, usaremos o OpenAI, então precisamos apenas adicionar o artefato langchain4j-open-ai :

<properties>
  <langchain4j.version>0.34.0</langchain4j.version>
</properties>

<dependencies>
  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
    <version>${langchain4j.version}</version>
  </dependency>
</dependencies>

Para o restante do código, usaremos nossa própria chave de API, que você pode obter criando uma conta na OpenAI, ou a fornecida pelo projeto LangChain4j apenas para fins de demonstração:

static String getOpenAiApiKey() {
  String apiKey = System.getenv(API_KEY_ENV_NAME);
  if (apiKey == null || apiKey.isEmpty()) {
    Logger.warn("Please provide your own key instead using [{}] env variable", API_KEY_ENV_NAME);
    return "demo";
  }
  return apiKey;
}

Agora podemos criar uma instância do nosso ChatLanguageModel:

ChatLanguageModel model = OpenAiChatModel.withApiKey(getOpenAiApiKey());

E, finalmente, podemos fazer uma pergunta simples e obter a resposta:

String answer = model.generate("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);

A resposta fornecida poderia ser algo como:

Thomas Pesquet is a French aerospace engineer, pilot, and European Space Agency astronaut.
He was selected as a member of the European Astronaut Corps in 2009 and has since completed 
two space missions to the International Space Station, including serving as a flight engineer 
for Expedition 50/51 in 2016-2017. Pesquet is known for his contributions to scientific 
research and outreach activities during his time in space.

Se você quiser executar este código, consulte a classe Step1AiChatTest.java .

Fornecendo mais contexto com langchain4j

Vamos adicionar o artefato langchain4j :

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j</artifactId>
  <version>${langchain4j.version}</version>
</dependency>

Esta ferramenta oferece um conjunto de recursos que pode nos ajudar a construir uma integração LLM mais avançada para criar nosso assistente. Aqui, criaremos uma interface Assistant que fornece o método chat , o qual chamará automaticamente o ChatLanguageModel que definimos anteriormente:

interface Assistant {
  String chat(String userMessage);
}

Basta pedirmos à classe LangChain4j AiServices para construir uma instância para nós:

Assistant assistant = AiServices.create(Assistant.class, model);

Em seguida, chame o método chat(String) :

String answer = assistant.chat("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);

Está apresentando o mesmo comportamento de antes. Então, por que alteramos o código? Em primeiro lugar, é mais elegante, mas além disso, agora você pode dar instruções ao LLM usando anotações simples:

interface Assistant {
  @SystemMessage("Please answer in a funny way.")
  String chat(String userMessage);
}

Isso está resultando em:

Ah, Thomas Pesquet is actually a super secret spy disguised as an astronaut! 
He's out there in space fighting aliens and saving the world one spacewalk at a time. 
Or maybe he's just a really cool French astronaut who has been to the International 
Space Station. But my spy theory is much more exciting, don't you think?

Se você quiser executar este código, consulte a classe Step2AssistantTest.java .

Mudando para outro LLM: langchain4j-ollama

Podemos usar o excelente projeto Ollama. Executar um LLM localmente em sua máquina pode ser útil.

Vamos adicionar o artefato langchain4j-ollama :

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-ollama</artifactId>
  <version>${langchain4j.version}</version>
</dependency>

Como estamos executando o código de exemplo usando testes, vamos adicionar Testcontainers ao nosso projeto:

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>ollama</artifactId>
  <version>1.20.1</version>
  <scope>test</scope>
</dependency>

Agora podemos iniciar/parar contêineres Docker:

static String MODEL_NAME = "mistral";
static String DOCKER_IMAGE_NAME = "langchain4j/ollama-" + MODEL_NAME + ":latest";

static OllamaContainer ollama = new OllamaContainer(
  DockerImageName.parse(DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("ollama/ollama"));

@BeforeAll
public static void setup() {
  ollama.start();
}

@AfterAll
public static void teardown() {
  ollama.stop();
}

Nós "apenas" precisamos mudar o objeto model para se tornar um OllamaChatModel em vez do OpenAiChatModel que usamos anteriormente:

OllamaChatModel model = OllamaChatModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();

Observe que pode levar algum tempo para obter a imagem com seu modelo, mas depois de um tempo, você poderá obter a resposta:

Oh, Thomas Pesquet, the man who single-handedly keeps the French space program running 
while sipping on his crisp rosé and munching on a baguette! He's our beloved astronaut 
with an irresistible accent that makes us all want to learn French just so we can 
understand him better. When he's not floating in space, he's probably practicing his 
best "je ne sais quoi" face for the next family photo. Vive le Thomas Pesquet! 
🚀🌍🇫🇷 #FrenchSpaceHero

Melhor com memória

Se fizermos várias perguntas, por padrão o sistema não se lembrará das perguntas e respostas anteriores. Então, se perguntarmos depois da primeira pergunta "Quando ele nasceu?", Nosso aplicativo responderá:

Oh, you're asking about this legendary figure from history, huh? Well, let me tell 
you a hilarious tale! He was actually born on Leap Year's Day, but only every 400 
years! So, do the math... if we count backwards from 2020 (which is also a leap year), 
then he was born in... *drumroll please* ...1600! Isn't that a hoot? But remember 
folks, this is just a joke, and historical records may vary.

O que é um absurdo. Em vez disso, devemos usar a Memória de Chat:

ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
  .chatLanguageModel(model)
  .chatMemory(chatMemory)
  .build();

Executar as mesmas perguntas agora fornece uma resposta significativa:

Oh, Thomas Pesquet, the man who was probably born before sliced bread but after dinosaurs! 
You know, around the time when people started putting wheels on suitcases and calling it 
a revolution. So, roughly speaking, he came into this world somewhere in the late 70s or 
early 80s, give or take a year or two - just enough time for him to grow up, become an 
astronaut, and make us all laugh with his space-aged antics! Isn't that a hoot? 
*laughs maniacally*

Conclusão

Na próxima publicação, descobriremos como podemos fazer perguntas ao nosso conjunto de dados privado usando o Elasticsearch como repositório de embeddings. Isso nos dará uma maneira de elevar nossa busca de aplicativos a um novo patamar.

Conteúdo relacionado

Testando seu código Java com mocks e Elasticsearch real.

Piotr Przybyl

LangChain4j com Elasticsearch como armazenamento de incorporação

David Pilato

Pronto para criar buscas de última geração?

Uma pesquisa suficientemente avançada não se consegue apenas com o esforço de uma só pessoa. O Elasticsearch é impulsionado por cientistas de dados, especialistas em operações de aprendizado de máquina, engenheiros e muitos outros que são tão apaixonados por buscas quanto você. Vamos nos conectar e trabalhar juntos para construir a experiência de busca mágica que lhe trará os resultados desejados.

Experimente você mesmo(a)