Vertex AI ve LangChain4j ile Java'da Gemini

1. Giriş

Bu codelab'de, Google Cloud'da Vertex AI'da barındırılan Gemini Büyük Dil Modeli (LLM) üzerinde odaklanılmaktadır. Vertex AI, Google Cloud'daki tüm makine öğrenimi ürünlerini, hizmetlerini ve modellerini kapsayan bir platformdur.

LangChain4j çerçevesini kullanarak Gemini API ile etkileşimde bulunmak için Java'yı kullanacaksınız. Soru yanıtlama, fikir oluşturma, varlık ve yapılandırılmış içerik ayıklama, artırılmış oluşturma getirme ve işlev çağrısı için LLM'den yararlanmak üzere somut örnekler üzerinden geçeceksiniz.

Üretken yapay zeka nedir?

Üretken yapay zeka; metin, resim, müzik, ses ve video gibi yeni içerikler oluşturmak için yapay zekanın kullanılmasını ifade eder.

Üretken yapay zeka, çoklu görev ve özetleme, soru-cevap, sınıflandırma gibi kullanıma hazır görevleri gerçekleştirebilen büyük dil modelleri (LLM) tarafından desteklenir. Minimum eğitimle, temel modeller çok az örnek veriyle hedeflenmiş kullanım alanları için uyarlanabilir.

Üretken yapay zeka nasıl çalışır?

Üretken yapay zeka, insan tarafından oluşturulan içeriklerden oluşan bir veri kümesindeki kalıpları ve ilişkileri öğrenmek için makine öğrenimi (ML) modelinden yararlanır. Ardından öğrenilen kalıpları kullanarak yeni içerikler üretir.

Üretken yapay zeka modelini eğitmenin en yaygın yolu, gözetimli öğrenmeden yararlanmaktır. Modele, insan tarafından oluşturulan bir dizi içerik ve ilgili etiketler verilir. Daha sonra, gerçek kişiler tarafından oluşturulan içeriklere benzer içerikler üretmeyi öğrenir.

Yaygın olarak kullanılan üretken yapay zeka uygulamaları nelerdir?

Üretken yapay zeka şu amaçlarla kullanılabilir:

  • Gelişmiş sohbet ve arama deneyimleriyle müşteri etkileşimlerini iyileştirin.
  • Sohbet arayüzleri ve özetler sayesinde çok sayıda yapılandırılmamış veriyi keşfedin.
  • Teklif taleplerini yanıtlama, pazarlama içeriğini farklı dillerde yerelleştirme, müşteri sözleşmelerini uygunluk açısından kontrol etme gibi tekrar eden görevlerde destek olun.

Google Cloud hangi üretken yapay zeka tekliflerini sunuyor?

Vertex AI sayesinde makine öğrenimi konusunda çok az uzmanlaşmış veya hiç bilgi sahibi olmadan temel modellerle etkileşimde bulunabilir, modelleri özelleştirebilir ve uygulamanıza yerleştirebilirsiniz. Model Garden'da temel modellerine erişebilir, Vertex AI Studio'daki basit bir kullanıcı arayüzüyle model ayarlarını yapabilir veya modelleri bir veri bilimi not defterinde kullanabilirsiniz.

Vertex AI Arama ve Sohbet, geliştiricilere üretken yapay zeka destekli arama motorları ve chatbot'lar geliştirmenin en hızlı yolunu sunar.

Gemini tarafından desteklenen Google Cloud için Gemini, Google Cloud ve IDE'lerde daha fazla işi daha kısa sürede yapmanıza yardımcı olan yapay zeka destekli bir ortak çalışandır. Gemini Code Assist; kod tamamlama, kod oluşturma ve kod açıklamaları sunar. Ayrıca sohbet ederek teknik sorular sormanıza da olanak tanır.

Gemini nedir?

Gemini, Google DeepMind tarafından geliştirilen ve çok modlu kullanım alanları için tasarlanmış bir üretken yapay zeka modeli ailesidir. Çok modlu; metin, kod, resim ve ses gibi farklı içerik türlerini işleyip oluşturabileceği anlamına gelir.

b9913d011999e7c7.png

Gemini'ın farklı varyantları ve boyutları vardır:

  • Gemini Ultra: Karmaşık görevler için en büyük ve en yetenekli sürüm.
  • Gemini Flash: Yüksek hacimli görevler için optimize edilmiş, en hızlı ve en uygun maliyetli.
  • Gemini Pro: Orta boyutlu, çeşitli görevlerde ölçeklendirme için optimize edilmiş.
  • Gemini Nano: Cihazdaki görevler için tasarlanmış en verimli model.

Temel Özellikler:

  • Çok modluluk: Gemini'ın birden fazla bilgi biçimini anlayıp işleme becerisi, geleneksel yalnızca metin dili modellerinin ötesinde önemli bir adımdır.
  • Performans: Gemini Ultra, birçok karşılaştırmada mevcut en gelişmiş teknolojilerden daha iyi performans gösterir ve zorlu MMLU (Massive Multitask Language Understanding) karşılaştırmasında gerçek kişiler olan uzmanları geride bırakan ilk model olmuştur.
  • Esneklik: Farklı Gemini boyutları, büyük ölçekli araştırmalardan mobil cihazlarda dağıtıma kadar çeşitli kullanım alanlarına uyarlanabilir.

Java'dan Vertex AI'da Gemini ile nasıl etkileşimde bulunursunuz?

Bunun için iki seçeneğiniz bulunmaktadır:

  1. Resmi Vertex AI Java API for Gemini kitaplığı.
  2. LangChain4j çerçevesidir.

Bu codelab'de LangChain4j çerçevesini kullanacaksınız.

LangChain4j çerçevesi nedir?

LangChain4j çerçevesi, LLM'nin kendisi gibi çeşitli bileşenlerin yanı sıra vektör veritabanları (anlamsal aramalar için), belge yükleyiciler ve ayırıcılar (belgeleri analiz etmek ve bunlardan bilgi edinmek için), çıkış ayrıştırıcıları ve daha fazlasını düzenleyerek Java uygulamalarınıza LLM'leri entegre etmek için kullanılan açık kaynak bir kitaplıktır.

Proje, LangChain Python projesinden esinlenerek Java geliştiricilerine hizmet sunmayı amaçladı.

bb908ea1e6c96ac2.png

Neler öğreneceksiniz?

  • Gemini ve LangChain4j'i kullanmak için bir Java projesi oluşturma
  • İlk isteminizi Gemini'a programatik olarak gönderme
  • Gemini'daki yanıtları akış şeklinde gösterme
  • Kullanıcı ile Gemini arasında sohbet oluşturma
  • Gemini'ın hem metin hem de resim göndererek çok modlu bir bağlamda nasıl kullanılacağı
  • Yapılandırılmamış içerikten kullanışlı yapılandırılmış bilgileri ayıklama
  • İstem şablonlarını değiştirme
  • Yaklaşım analizi gibi metin sınıflandırmaları yapma
  • Kendi dokümanlarınızla sohbet etme (Alma Artırılmış Oluşturma)
  • İşlev çağrısı ile chatbot'larınızı genişletme
  • Gemma'yı Ollama ve TestContainers ile yerel olarak kullanma

Gerekenler

  • Java programlama dili bilgisi
  • Bir Google Cloud projesi
  • Chrome veya Firefox gibi bir tarayıcı

2. Kurulum ve şartlar

Kendi hızınızda ortam kurulumu

  1. Google Cloud Console'da oturum açıp yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. Gmail veya Google Workspace hesabınız yoksa hesap oluşturmanız gerekir.

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png

  • Proje adı, bu projenin katılımcıları için görünen addır. Google API'leri tarafından kullanılmayan bir karakter dizesidir. İstediğiniz zaman güncelleyebilirsiniz.
  • Proje Kimliği, tüm Google Cloud projelerinde benzersizdir ve değiştirilemez (belirlendikten sonra değiştirilemez). Cloud Console otomatik olarak benzersiz bir dize oluşturur. Genellikle bu dizenin ne olduğu sizin sorumluluğunuzdadır. Çoğu codelab'de proje kimliğinize (genellikle PROJECT_ID olarak tanımlanır) bakmanız gerekir. Oluşturulan kimliği beğenmezseniz başka bir rastgele dize oluşturabilirsiniz. Alternatif olarak, kendi ölçümünüzü deneyip mevcut olup olmadığına bakabilirsiniz. Bu adımdan sonra değiştirilemez ve proje süresince kalır.
  • Bilginiz olması açısından, bazı API'lerin kullandığı üçüncü bir değer, yani Proje Numarası daha vardır. Bu değerlerin üçü hakkında daha fazla bilgiyi belgelerde bulabilirsiniz.
  1. Sonraki adımda, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i çalıştırmanın maliyeti, yüksek değildir. Bu eğitim dışında faturalandırmanın tekrarlanmasını önlemek amacıyla kaynakları kapatmak için oluşturduğunuz kaynakları silebilir veya projeyi silebilirsiniz. Yeni Google Cloud kullanıcıları 300 ABD doları değerindeki ücretsiz denemeden yararlanabilir.

Cloud Shell'i başlatma

Google Cloud dizüstü bilgisayarınızdan uzaktan çalıştırılabilse de bu codelab'de Cloud'da çalışan bir komut satırı ortamı olan Cloud Shell'i kullanacaksınız.

Cloud Shell'i etkinleştirme

  1. Cloud Console'da, Cloud Shell'i etkinleştir 853e55310c205094.png simgesini tıklayın.

3c1dabeca90e44e5.png

Cloud Shell'i ilk kez başlatıyorsanız ne olduğunu açıklayan bir ara ekran gösterilir. Ara bir ekran görüntülendiyse Devam'ı tıklayın.

9c92662c6a846a5c.png

Temel hazırlık ve Cloud Shell'e bağlanmak yalnızca birkaç dakika sürer.

9f0e51b578fecce5.png

Gereken tüm geliştirme araçları bu sanal makinede yüklüdür. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud'da çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Bu codelab'deki çalışmalarınızın tamamı olmasa bile büyük bir kısmı tarayıcıyla yapılabilir.

Cloud Shell'e bağlandıktan sonra kimliğinizin doğrulandığını ve projenin proje kimliğinize ayarlandığını göreceksiniz.

  1. Kimlik doğrulamanızın tamamlandığını onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın:
gcloud auth list

Komut çıkışı

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. gcloud komutunun projenizi bildiğini onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın:
gcloud config list project

Komut çıkışı

[core]
project = <PROJECT_ID>

Doğru değilse aşağıdaki komutla ayarlayabilirsiniz:

gcloud config set project <PROJECT_ID>

Komut çıkışı

Updated property [core/project].

3. Geliştirme ortamınızı hazırlama

Bu codelab'de, Java programlarınızı geliştirmek için Cloud Shell terminalini ve Cloud Shell düzenleyicisini kullanacaksınız.

Vertex AI API'lerini etkinleştirme

Google Cloud konsolunda projenizin adının, Google Cloud konsolunuzun en üst kısmında gösterildiğinden emin olun. Açık değilse Proje Seç'i tıklayarak Proje Seçici'yi açın ve istediğiniz projeyi seçin.

Vertex AI API'lerini Google Cloud Console'un Vertex AI bölümünden veya Cloud Shell terminalinden etkinleştirebilirsiniz.

Google Cloud konsolundan etkinleştirmek için önce Google Cloud Console menüsünün Vertex AI bölümüne gidin:

451976f1c8652341.png

Vertex AI kontrol panelinde Enable All Recommended APIs'ı (Önerilen Tüm API'leri Etkinleştir) tıklayın.

Bu işlem çeşitli API'leri etkinleştirir ancak codelab için en önemli olan aiplatform.googleapis.com API'dir.

Alternatif olarak, bu API'yi aşağıdaki komutla Cloud Shell terminalinden de etkinleştirebilirsiniz:

gcloud services enable aiplatform.googleapis.com

GitHub deposunu klonlama

Cloud Shell terminalinde bu codelab'e ait depoyu klonlayın:

git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git

Projenin çalıştırılmaya hazır olup olmadığını kontrol etmek için "Hello World" programını çalıştırmayı deneyebilirsiniz.

En üst düzey klasörde olduğunuzdan emin olun:

cd gemini-workshop-for-java-developers/ 

Gradle sarmalayıcıyı oluşturun:

gradle wrapper

gradlew ile çalıştır:

./gradlew run

Aşağıdaki çıkışı göreceksiniz:

..
> Task :app:run
Hello World!

Cloud Editor'ı açma ve kurma

Kodu Cloud Shell'deki Cloud Code Editor ile açın:

42908e11b28f4383.png

Cloud Code Editor'da File -> Open Folder öğesini seçerek codelab kaynak klasörünü açın ve codelab kaynak klasörünün (ör. /home/username/gemini-workshop-for-java-developers/).

Java için Gradle'ı yükleme

Bulut kod düzenleyicinin Gradle ile düzgün çalışmasını sağlamak amacıyla Gradle for Java uzantısını yükleyin.

İlk olarak Java Projeleri bölümüne gidin ve artı işaretine basın:

84d15639ac61c197.png

Gradle for Java öğesini seçin:

34d6c4136a3cc9ff.png

Install Pre-Release sürümünü seçin:

3b044fb450cccb7.png

Uzantıyı yükledikten sonra Disable ve Uninstall düğmelerini göreceksiniz:

46410fe86d777f9c.png

Son olarak, yeni ayarların uygulanması için çalışma alanını temizleyin:

31e27e9bb61d975d.png

Bu işlem sırasında atölyeyi yeniden yükleyip silmeniz istenir. Devam edin ve Reload and delete öğesini seçin:

d6303bc49e391dc.png

App.Java gibi dosyalardan birini açarsanız artık düzenleyicinin söz dizimi vurgulamayla düzgün bir şekilde çalıştığını görmeniz gerekir:

fed1b1b5de0dff58.png

Artık Gemini ile bazı örnekleri çalıştırmaya hazırsınız.

Ortam değişkenlerini ayarlama

Terminal -> New Terminal seçeneklerini belirleyerek Cloud Code Editor'da yeni bir terminal açın. Kod örneklerini çalıştırmak için gerekli iki ortam değişkenini ayarlayın:

  • PROJECT_ID: Google Cloud projenizin kimliği
  • LOCATION: Gemini modelinin dağıtıldığı bölge

Değişkenleri aşağıdaki gibi dışa aktarın:

export PROJECT_ID=$(gcloud config get-value project)
export LOCATION=us-central1

4. Gemini modeline ilk çağrı

Proje düzgün şekilde ayarlandığına göre artık Gemini API'yi çağırmanın zamanı geldi.

app/src/main/java/gemini/workshop dizinindeki QA.java kitaplığına göz atın:

package gemini.workshop;

import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;

public class QA {
    public static void main(String[] args) {
        ChatLanguageModel model = VertexAiGeminiChatModel.builder()
            .project(System.getenv("PROJECT_ID"))
            .location(System.getenv("LOCATION"))
            .modelName("gemini-1.5-flash-001")
            .build();

        System.out.println(model.generate("Why is the sky blue?"));
    }
}

Bu ilk örnekte, ChatModel arayüzünü uygulayan VertexAiGeminiChatModel sınıfını içe aktarmanız gerekir.

main yönteminde, VertexAiGeminiChatModel için oluşturucuyu kullanarak sohbet dili modelini yapılandırır ve şunları belirtirsiniz:

  • Proje
  • Konum
  • Model adı (gemini-1.5-flash-001).

Dil modeli hazır olduğuna göre artık generate() yöntemini çağırabilir ve isteminizi, sorunuzu veya LLM'ye gönderilecek talimatları iletebilirsiniz. Burada, gökyüzünü mavi yapan şeylerle ilgili basit bir soru soruyorsunuz.

Farklı soruları veya görevleri denemek için bu istemi değiştirebilirsiniz.

Kaynak kodu kök klasöründe örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.QA

Şuna benzer bir çıkış görürsünüz:

The sky appears blue because of a phenomenon called Rayleigh scattering.
When sunlight enters the atmosphere, it is made up of a mixture of
different wavelengths of light, each with a different color. The
different wavelengths of light interact with the molecules and particles
in the atmosphere in different ways.

The shorter wavelengths of light, such as those corresponding to blue
and violet light, are more likely to be scattered in all directions by
these particles than the longer wavelengths of light, such as those
corresponding to red and orange light. This is because the shorter
wavelengths of light have a smaller wavelength and are able to bend
around the particles more easily.

As a result of Rayleigh scattering, the blue light from the sun is
scattered in all directions, and it is this scattered blue light that we
see when we look up at the sky. The blue light from the sun is not
actually scattered in a single direction, so the color of the sky can
vary depending on the position of the sun in the sky and the amount of
dust and water droplets in the atmosphere.

Tebrikler, Gemini'ı ilk kez kullandınız.

Yanıt akışı

Yanıtın bir seferde, birkaç saniye sonra verildiğini fark ettiniz mi? Akış yanıtı varyantı sayesinde kademeli olarak da yanıt alabilirsiniz. Akış yanıtı olan model, kullanılabilir hale geldikçe yanıtı parça parça döndürür.

Bu codelab'de canlı olmayan yanıtı uygulayacağız ancak nasıl yapılacağını görmek için akış yanıtına göz atalım.

app/src/main/java/gemini/workshop dizinindeki StreamQA.java bölümünde akış yanıtını iş başında görebilirsiniz:

package gemini.workshop;

import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiStreamingChatModel;
import dev.langchain4j.model.StreamingResponseHandler;

public class StreamQA {
    public static void main(String[] args) {
        StreamingChatLanguageModel model = VertexAiGeminiStreamingChatModel.builder()
            .project(System.getenv("PROJECT_ID"))
            .location(System.getenv("LOCATION"))
            .modelName("gemini-1.5-flash-001")
            .build();
        
        model.generate("Why is the sky blue?", new StreamingResponseHandler<>() {
            @Override
            public void onNext(String text) {
                System.out.println(text);
            }

            @Override
            public void onError(Throwable error) {
                error.printStackTrace();
            }
        });
    }
}

Bu kez, StreamingChatLanguageModel arayüzünü uygulayan VertexAiGeminiStreamingChatModel akış sınıfı varyantlarını içe aktarıyoruz. Ayrıca, bir StreamingResponseHandler numarası da gereklidir.

Bu kez, generate() yönteminin imzası biraz farklıdır. Dönüş türü, dize döndürmek yerine geçersizdir. İsteme ek olarak, bir akış yanıtı işleyicisi iletmeniz gerekir. Burada, arayüzü, onNext(String text) ve onError(Throwable error) olmak üzere iki yöntemle anonim bir iç sınıf oluşturarak uygularsınız. İlk yanıt, yanıtın yeni bir parçası her kullanılabilir olduğunda çağrılır; ikinci yanıt ise yalnızca bir hata oluşursa çağrılır.

Çalıştırma:

./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA

Önceki sınıfa benzer bir yanıt alacaksınız ancak bu kez tam cevabın gösterilmesini beklemek yerine yanıtın kabuğunuzda kademeli olarak göründüğünü fark edeceksiniz.

Ek yapılandırma

Yapılandırma için yalnızca projeyi, konumu ve model adını tanımladık. Ancak model için belirtebileceğiniz başka parametreler de var:

  • temperature(Float temp): Yanıtın nasıl reklam öğesi olmasını istediğinizi tanımlamak için (0 az reklam öğesi ve genellikle daha gerçekçi, 1 ise daha fazla reklam öğesi içindir)
  • topP(Float topP) — Toplam olasılığı bu kayan nokta sayısına denk gelen olası kelimeleri seçmek için (0 ile 1 arasında)
  • topK(Integer topK) — metin tamamlama için olası maksimum kelime sayısı arasından bir kelimeyi rastgele seçmek için (1 ile 40 arasında)
  • maxOutputTokens(Integer max): Modelin verdiği yanıtın maksimum uzunluğunu belirtmek için (genellikle 4 jeton yaklaşık 3 kelimeyi temsil eder)
  • maxRetries(Integer retries): İstek başına zaman kotasını aşıyorsanız veya platformla ilgili teknik sorunlar yaşanıyorsa modelin aramayı 3 kez yeniden denemesini sağlayabilirsiniz

Şimdiye kadar Gemini'a tek bir soru sordunuz ancak çok dönüşlü bir sohbet de yapabilirsiniz. Bu konuyu bir sonraki bölümde keşfedeceksiniz.

5. Gemini ile sohbet et

Önceki adımda tek bir soru sormuştunuz. Artık kullanıcı ile LLM arasında gerçek bir konuşma yapmanın zamanı geldi. Her soru ve yanıt öncekilerin üzerine koyarak gerçek bir tartışma oluşturabilir.

app/src/main/java/gemini/workshop klasöründeki Conversation.java klasörüne göz atın:

package gemini.workshop;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;

import java.util.List;

public class Conversation {
    public static void main(String[] args) {
        ChatLanguageModel model = VertexAiGeminiChatModel.builder()
            .project(System.getenv("PROJECT_ID"))
            .location(System.getenv("LOCATION"))
            .modelName("gemini-1.5-flash-001")
            .build();

        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
            .maxMessages(20)
            .build();

        interface ConversationService {
            String chat(String message);
        }

        ConversationService conversation =
            AiServices.builder(ConversationService.class)
                .chatLanguageModel(model)
                .chatMemory(chatMemory)
                .build();

        List.of(
            "Hello!",
            "What is the country where the Eiffel tower is situated?",
            "How many inhabitants are there in that country?"
        ).forEach( message -> {
            System.out.println("\nUser: " + message);
            System.out.println("Gemini: " + conversation.chat(message));
        });
    }
}

Bu sınıfta dikkat çekici birkaç yeni içe aktarma:

  • MessageWindowChatMemory - Konuşmanın çok dönüşlü yönünü ele almanıza ve önceki soru ve yanıtların yerel hafızada kalmasına yardımcı olacak bir sınıf
  • AiServices: Sohbet modeliyle sohbet belleğini birbirine bağlayacak sınıf

Ana yöntemde modeli, sohbet belleğini ve yapay zeka hizmetini kuracaksınız. Model; proje, konum ve model adı bilgileriyle her zamanki gibi yapılandırılır.

Sohbet belleği olarak, son 20 mesajın aktarıldığı bir anı oluşturmak için MessageWindowChatMemory uygulamasının oluşturucusunu kullanırız. Bağlamı Java sınıf istemcimizde yerel olarak saklanan ileti dizilerinin üzerinde kayan bir penceredir.

Ardından, sohbet modelini sohbet belleğine bağlayan AI service öğesini oluşturursunuz.

Yapay zeka hizmetinin, tanımladığımız, LangChain4j tarafından uygulanan ve String sorgusu alıp String yanıtı döndürdüğü özel bir ConversationService arayüzünden nasıl yararlandığına dikkat edin.

Şimdi Gemini ile sohbet etme zamanı. Önce basit bir selamlama gönderilir, ardından Eyfel Kulesi'nin hangi ülkede bulunabileceğini öğrenmek için ilk soru gönderilir. Son cümlenin ilk sorunun yanıtıyla ilgili olduğuna dikkat edin. Eyfel Kulesi'nin bulunduğu ülkede kaç kişinin yaşadığını merak edersiniz. Bir önceki yanıtta belirtilen ülkeden açıkça bahsetmeksizin. Bu grafik, eski soruların ve yanıtların her istemle birlikte gönderildiğini gösterir.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation

Aşağıdakilere benzer üç yanıt göreceksiniz:

User: Hello!
Gemini: Hi there! How can I assist you today?

User: What is the country where the Eiffel tower is situated?
Gemini: France

User: How many inhabitants are there in that country?
Gemini: As of 2023, the population of France is estimated to be around 67.8 million.

Gemini ile tek yanıtlı sorular sorabilir veya birden fazla dönüşlü sohbet edebilirsiniz ancak şimdiye kadar yalnızca metinden yararlanıldı. Peki ya resimler? Bir sonraki adımda resimleri keşfedelim.

6. Gemini ile çok modluluk

Gemini çok modlu bir modeldir. Giriş olarak yalnızca metin kabul etmekle kalmaz, aynı zamanda resim ve hatta videoları da giriş olarak kabul eder. Bu bölümde, metin ve resimlerin bir arada kullanıldığı bir kullanım alanı bulacaksınız.

Sence Gemini bu kediyi tanıyabilir mi?

af00516493ec9ade.png

Vikipedi'den alınmış, karda duran bir kedinin resmihttps://upload.wikimedia.org/wikipedia/commons/b/b6/Felis_catus-cat_on_snow.jpg

app/src/main/java/gemini/workshop dizininde Multimodal.java kitaplığına göz atın:

package gemini.workshop;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;

public class Multimodal {

    static final String CAT_IMAGE_URL =
        "https://upload.wikimedia.org/wikipedia/" +
        "commons/b/b6/Felis_catus-cat_on_snow.jpg";


    public static void main(String[] args) {
        ChatLanguageModel model = VertexAiGeminiChatModel.builder()
            .project(System.getenv("PROJECT_ID"))
            .location(System.getenv("LOCATION"))
            .modelName("gemini-1.5-flash-001")
            .build();

        UserMessage userMessage = UserMessage.from(
            ImageContent.from(CAT_IMAGE_URL),
            TextContent.from("Describe the picture")
        );

        Response<AiMessage> response = model.generate(userMessage);

        System.out.println(response.content().text());
    }
}

İçe aktarma işlemlerinde, farklı ileti ve içerik türlerini birbirinden ayırdığımıza dikkat edin. UserMessage öğesi hem TextContent hem de ImageContent nesnesi içerebilir. Burada çok modluluk, yani metin ve resimleri bir arada kullanma becerisi öne çıkıyor. Model, AiMessage içeren bir Response geri gönderir.

Daha sonra content() aracılığıyla yanıttan AiMessage metnini ve ardından text() sayesinde de ileti metnini alıyorsunuz.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal

Resmin adı kesinlikle resmin içeriğine dair ipucu verdi ama Gemini çıktısı şuna benziyor:

A cat with brown fur is walking in the snow. The cat has a white patch of fur on its chest and white paws. The cat is looking at the camera.

Resimleri ve metin istemlerini bir arada kullanmak, ilginç kullanım alanlarının önünü açar. Aşağıdaki işlemleri yapabilecek uygulamalar oluşturabilirsiniz:

  • Resimlerdeki metinleri tanır.
  • Bir resmin görüntülenmesinin güvenli olup olmadığını kontrol edin.
  • Görsel başlıkları oluşturun.
  • Düz metin açıklamaları içeren bir görüntü veritabanında arama yapın.

Resimlerden bilgi ayıklamanın yanı sıra yapılandırılmamış metinlerden de bilgi ayıklayabilirsiniz. Bir sonraki bölümde bunları öğreneceksiniz.

7. Yapılandırılmamış metinden yapılandırılmış bilgileri ayıklama

Önemli bilgilerin rapor dokümanlarında, e-postalarda veya diğer uzun metinlerde yapılandırılmamış bir şekilde verildiği birçok durum vardır. İdeal olarak, yapılandırılmamış metindeki önemli ayrıntıları yapılandırılmış nesneler biçiminde ayıklamanız gerekir. Bunu nasıl yapabileceğinize bakalım.

Bir kişinin adını ve yaşını, biyografisi veya açıklamasına göre çıkarmak istediğinizi varsayalım. LLM'ye, zekice düzenlenmiş bir istemle (genellikle "istem mühendisliği" olarak adlandırılır) yapılandırılmamış metinden JSON çıkarma talimatı verebilirsiniz.

app/src/main/java/gemini/workshop bölgesindeki ExtractData.java ürününe göz atın:

package gemini.workshop;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;

public class ExtractData {

    static record Person(String name, int age) {}

    interface PersonExtractor {
        @UserMessage("""
            Extract the name and age of the person described below.
            Return a JSON document with a "name" and an "age" property, \
            following this structure: {"name": "John Doe", "age": 34}
            Return only JSON, without any markdown markup surrounding it.
            Here is the document describing the person:
            ---
            {{it}}
            ---
            JSON:
            """)
        Person extractPerson(String text);
    }

    public static void main(String[] args) {
        ChatLanguageModel model = VertexAiGeminiChatModel.builder()
            .project(System.getenv("PROJECT_ID"))
            .location(System.getenv("LOCATION"))
            .modelName("gemini-1.5-flash-001")
            .temperature(0f)
            .topK(1)
            .build();

        PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);

        Person person = extractor.extractPerson("""
            Anna is a 23 year old artist based in Brooklyn, New York. She was born and 
            raised in the suburbs of Chicago, where she developed a love for art at a 
            young age. She attended the School of the Art Institute of Chicago, where 
            she studied painting and drawing. After graduating, she moved to New York 
            City to pursue her art career. Anna's work is inspired by her personal 
            experiences and observations of the world around her. She often uses bright 
            colors and bold lines to create vibrant and energetic paintings. Her work 
            has been exhibited in galleries and museums in New York City and Chicago.    
            """
        );

        System.out.println(person.name());  // Anna
        System.out.println(person.age());   // 23
    }
}

Bu dosyadaki çeşitli adımlara bakalım:

  • Person kaydı, bir kişiyi tanımlayan ayrıntıları ( ad ve yaş) temsil edecek şekilde tanımlanır.
  • PersonExtractor arayüzü, yapılandırılmamış metin dizesi sağlayan bir yöntemle tanımlanır ve Person örneği döndürür.
  • extractPerson(), bir istemi kendisiyle ilişkilendiren @UserMessage ek açıklamasına sahiptir. Bu, modelin bilgileri ayıklamak ve ayrıntıları sizin için ayrıştırılacak ve Person örneğine aktarılmadan JSON belgesi biçiminde döndürmek için kullanacağı istemdir.

Şimdi main() yönteminin içeriğine göz atalım:

  • Sohbet modeli örneklendirilir. Son derece deterministik bir yanıt elde etmek için çok düşük bir temperature (sıfır) ve yalnızca bir (topK) değerini kullandığımıza dikkat edin. Bu işlem, modelin talimatları daha iyi takip etmesine de yardımcı olur. Özellikle, Gemini'ın JSON yanıtını fazladan Markdown işaretlemesiyle sarmalamasını istemeyiz.
  • LangChain4j'in AiServices sınıfı sayesinde bir PersonExtractor nesnesi oluşturuldu.
  • Ardından, yapılandırılmamış metindeki kişinin ayrıntılarını ayıklamak için Person person = extractor.extractPerson(...) öğesini çağırmanız yeterlidir. Ardından, ad ve yaş bilgilerini içeren bir Person örneği geri alabilirsiniz.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData

Aşağıdaki çıkışı göreceksiniz:

Anna
23

Evet, bu Ayşe ve 23 yaşındalar.

Bu AiServices yaklaşımıyla, güçlü bir şekilde yazılmış nesnelerle çalışırsınız. Doğrudan LLM ile etkileşimde bulunmuyorsunuz. Bunun yerine, ayıklanan kişisel bilgileri temsil eden Person kaydı gibi somut sınıflarla çalışıyorsunuz ve Person örneği döndüren bir extractPerson() yöntemine sahip PersonExtractor nesneniz var. LLM kavramı soyutlanmış ve bir Java geliştiricisi olarak yalnızca normal sınıfları ve nesneleri değiştiriyorsunuz.

8. İstem şablonlarıyla istemleri yapılandırma

Yaygın olarak kullanılan bir dizi talimat veya soru kullanarak bir LLM ile etkileşimde bulunduğunuzda, bu istemin hiç değişmeyen bir bölümü bulunurken diğer kısımlarında veriler yer alır. Örneğin, yemek tarifi hazırlamak istiyorsanız "Yetenekli bir şefsiniz, lütfen şu malzemelerle bir tarif oluştur: ..." gibi bir istem kullanabilir ve ardından malzemeleri bu metnin sonuna eklersiniz. Programlama dillerindeki interpolasyon dizeleri gibi istem şablonları da bu amaca yöneliktir. İstem şablonu, LLM'ye yapılan belirli bir çağrı için doğru verilerle değiştirebileceğiniz yer tutucular içerir.

Daha somut bir şekilde anlatmak gerekirse app/src/main/java/gemini/workshop dizininde TemplatePrompt.java alanını inceleyelim:

package gemini.workshop;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;

import java.util.HashMap;
import java.util.Map;

public class TemplatePrompt {
    public static void main(String[] args) {
        ChatLanguageModel model = VertexAiGeminiChatModel.builder()
            .project(System.getenv("PROJECT_ID"))
            .location(System.getenv("LOCATION"))
            .modelName("gemini-1.5-flash-001")
            .maxOutputTokens(500)
            .temperature(0.8f)
            .topK(40)
            .topP(0.95f)
            .maxRetries(3)
            .build();

        PromptTemplate promptTemplate = PromptTemplate.from("""
            You're a friendly chef with a lot of cooking experience.
            Create a recipe for a {{dish}} with the following ingredients: \
            {{ingredients}}, and give it a name.
            """
        );

        Map<String, Object> variables = new HashMap<>();
        variables.put("dish", "dessert");
        variables.put("ingredients", "strawberries, chocolate, and whipped cream");

        Prompt prompt = promptTemplate.apply(variables);

        Response<AiMessage> response = model.generate(prompt.toUserMessage());

        System.out.println(response.content().text());
    }
}

Her zaman olduğu gibi, VertexAiGeminiChatModel modelini yapılandırırken yaratıcılık düzeyiniz yüksek, topP ve topK değerleri yüksek. Ardından, istemimizin dizesini ileterek from() statik yöntemiyle bir PromptTemplate oluşturursunuz ve çift küme parantezi yer tutucu değişkenlerini kullanırsınız: {{dish}} ve {{ingredients}}.

Yer tutucunun adını ve yerine kullanılacak dize değerini temsil eden anahtar/değer çiftlerinin bir eşlemesini alan apply() öğesini çağırarak son istemi oluşturursunuz.

Son olarak, bu istemden prompt.toUserMessage() talimatıyla bir kullanıcı mesajı oluşturarak Gemini modelinin generate() yöntemini çağırırsınız.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt

Şuna benzer şekilde oluşturulmuş bir çıkış görürsünüz:

**Strawberry Shortcake**

Ingredients:

* 1 pint strawberries, hulled and sliced
* 1/2 cup sugar
* 1/4 cup cornstarch
* 1/4 cup water
* 1 tablespoon lemon juice
* 1/2 cup heavy cream, whipped
* 1/4 cup confectioners' sugar
* 1/4 teaspoon vanilla extract
* 6 graham cracker squares, crushed

Instructions:

1. In a medium saucepan, combine the strawberries, sugar, cornstarch, 
water, and lemon juice. Bring to a boil over medium heat, stirring 
constantly. Reduce heat and simmer for 5 minutes, or until the sauce has 
thickened.
2. Remove from heat and let cool slightly.
3. In a large bowl, combine the whipped cream, confectioners' sugar, and 
vanilla extract. Beat until soft peaks form.
4. To assemble the shortcakes, place a graham cracker square on each of 
6 dessert plates. Top with a scoop of whipped cream, then a spoonful of 
strawberry sauce. Repeat layers, ending with a graham cracker square.
5. Serve immediately.

**Tips:**

* For a more elegant presentation, you can use fresh strawberries 
instead of sliced strawberries.
* If you don't have time to make your own whipped cream, you can use 
store-bought whipped cream.

Haritada dish ve ingredients değerlerini değiştirebilir, sıcaklık, topK ve tokP ayarlarını yapıp kodu yeniden çalıştırabilirsiniz. Böylece, bu parametreleri değiştirmenin LLM'ye etkisini gözlemleyebilirsiniz.

İstem şablonları, LLM çağrıları için yeniden kullanılabilir ve parametrelenebilir talimatlara sahip olmanın iyi bir yoludur. Kullanıcılarınızın sağladığı farklı değerler için verileri iletebilir ve istemleri özelleştirebilirsiniz.

9. Birkaç çekim istemi içeren metin sınıflandırma

LLM'ler, metinleri farklı kategorilerde sınıflandırma konusunda oldukça iyidir. Metin örnekleri ve ilişkili kategorileriyle bu görevde bir LLM'ye yardımcı olabilirsiniz. Bu yaklaşıma genellikle az sayıda çekim isteme denir.

Belirli bir metin sınıflandırma türü: yaklaşım analizi yapmak için app/src/main/java/gemini/workshop dizinindeki TextClassification.java etiketine göz atın.

package gemini.workshop;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;

package gemini.workshop;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;

import java.util.Map;

public class TextClassification {
    public static void main(String[] args) {
        ChatLanguageModel model = VertexAiGeminiChatModel.builder()
            .project(System.getenv("PROJECT_ID"))
            .location(System.getenv("LOCATION"))
            .modelName("gemini-1.5-flash-001")
            .maxOutputTokens(10)
            .maxRetries(3)
            .build();

        PromptTemplate promptTemplate = PromptTemplate.from("""
            Analyze the sentiment of the text below. Respond only with one word to describe the sentiment.

            INPUT: This is fantastic news!
            OUTPUT: POSITIVE

            INPUT: Pi is roughly equal to 3.14
            OUTPUT: NEUTRAL

            INPUT: I really disliked the pizza. Who would use pineapples as a pizza topping?
            OUTPUT: NEGATIVE

            INPUT: {{text}}
            OUTPUT: 
            """);

        Prompt prompt = promptTemplate.apply(
            Map.of("text", "I love strawberries!"));

        Response<AiMessage> response = model.generate(prompt.toUserMessage());

        System.out.println(response.content().text());
    }
}

main() yönteminde, Gemini sohbet modelini her zamanki gibi oluşturursunuz ancak yalnızca kısa bir yanıt istediğiniz için küçük bir maksimum çıkış jetonu numarasıyla oluşturursunuz: Metin POSITIVE, NEGATIVE veya NEUTRAL biçimindedir.

Ardından, modele birkaç giriş ve çıkış örneği hakkında talimat vererek birkaç atışlık istem tekniğiyle yeniden kullanılabilir bir istem şablonu oluşturuyorsunuz. Bu, modelin de gerçek çıktıyı takip etmesine yardımcı olur. Gemini size tek cümleyle yanıt vermez. Bunun yerine tek kelimeyle yanıt vermesi istenir.

{{text}} yer tutucusunu gerçek parametreyle ("I love strawberries") değiştirmek ve bu şablonu toUserMessage() içeren bir kullanıcı mesajına dönüştürmek için değişkenleri apply() yöntemiyle uygularsınız.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification

Tek bir kelime göreceksiniz:

POSITIVE

Çilek sevgisi olumlu bir duygu gibi görünüyor.

10. Alma için Artırılmış Nesil

LLM'ler çok sayıda metinle eğitilir. Ancak bilgileri yalnızca eğitim sırasında gördüğü bilgileri kapsıyor. Model eğitimi bitiş tarihinden sonra yayınlanan yeni bilgiler varsa bu ayrıntılar model tarafından kullanılamaz. Dolayısıyla, model daha önce görmediği bilgilerle ilgili soruları yanıtlayamaz.

Bu nedenle Almayla Artırılmış Oluşturma (RAG) gibi yaklaşımlar, LLM'nin kullanıcılarının isteklerini yerine getirmek, daha güncel olabilecek bilgilerle yanıt vermek veya eğitim sırasında erişilemeyen özel bilgilerle yanıt vermek için bilmesi gereken ek bilgilerin sağlanmasına yardımcı olur.

Şimdi sohbetlere dönelim. Bu kez, dokümanlarınız hakkında soru sorabileceksiniz. Daha küçük parçalara ("parçalar") ayrılmış dokümanlarınızı içeren bir veritabanından alakalı bilgileri alabilecek bir chatbot oluşturacaksınız ve model, sadece eğitimde yer alan bilgilere güvenmek yerine, bu bilgileri kullanarak cevaplarını sağlamlaştıracak.

RAG'de iki aşama vardır:

  1. Besleme aşaması: Belgeler belleğe yüklenir, daha küçük parçalara bölünür ve vektör yerleştirmeler (parçaların yüksek çok boyutlu vektör temsili) hesaplanır ve anlamsal aramalar yapabilen bir vektör veritabanında depolanır. Bu alıp kullanma aşaması, normalde bir kez, belge kitaplığına yeni dokümanların eklenmesi gerektiğinde yapılır.

cd07d33d20ffa1c8.png

  1. Sorgu aşaması: Kullanıcılar artık dokümanlar hakkında sorular sorabilir. Soru da bir vektöre dönüştürülür ve veritabanındaki diğer tüm vektörlerle karşılaştırılır. En benzer vektörler genellikle anlam açısından alakalıdır ve vektör veritabanı tarafından döndürülür. Ardından LLM'ye konuşmanın bağlamı, veritabanı tarafından döndürülen vektörlere karşılık gelen metin parçaları verilir ve kullanıcıdan bu parçalara bakarak yanıtını temel alması istenir.

a1d2e2deb83c6d27.png

Dokümanlarınızı hazırlama

Bu yeni demo için "Tüm ihtiyacınız olan ilgiyi çekmek" araştırma makalesi hakkında sorular soracaksınız. Google'ın öncülük ettiği ve tüm modern büyük dil modellerinin günümüzde uygulanma şekli olan dönüştürücü nöral ağ mimarisini açıklar.

Gazete, depodaki attention-is-all-you-need.pdf dosyasına zaten indirilmiştir.

Chatbot'u uygulama

2 aşamalı yaklaşımın nasıl geliştirileceğini inceleyelim: İlk olarak belge besleme ve ardından kullanıcıların doküman hakkında sorular sorduğu sorgu zamanı.

Bu örnekte, her iki aşama aynı sınıfta uygulanmıştır. Normalde, aktarımı gerçekleştiren bir uygulamanız ve kullanıcılarınıza chatbot arayüzünü sunan başka bir uygulamanız olur.

Ayrıca, bu örnekte bellek içi vektör veritabanını kullanacağız. Gerçek bir üretim senaryosunda, besleme ve sorgulama aşamaları iki farklı uygulamada ayrılır ve vektörler, bağımsız bir veritabanında saklanır.

Belge besleme

Belge besleme aşamasının ilk adımı, daha önce indirdiğimiz PDF dosyasını bulmak ve bu dosyayı okumak için bir PdfParser hazırlamaktır:

URL url = new URI("https://github.com/glaforge/gemini-workshop-for-java-developers/raw/main/attention-is-all-you-need.pdf").toURL();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document document = pdfParser.parse(url.openStream());

Normal sohbet dili modelini oluşturmak yerine bir yerleştirme modelinin örneği oluşturursunuz. Bu model, rolü metin parçalarının (kelimeler, cümleler, hatta paragraflar) vektör gösterimlerini oluşturmaktır. Metin yanıtlarını döndürmek yerine, kayan nokta sayılarının vektörlerini döndürür.

VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
    .endpoint(System.getenv("LOCATION") + "-aiplatform.googleapis.com:443")
    .project(System.getenv("PROJECT_ID"))
    .location(System.getenv("LOCATION"))
    .publisher("google")
    .modelName("textembedding-gecko@003")
    .maxRetries(3)
    .build();

Daha sonra, birlikte çalışmak için birkaç sınıfa ihtiyacınız olacak:

  • PDF dokümanını parçalar halinde yükleyin ve bölün.
  • Bu parçaların tümü için vektör yerleştirmeleri oluşturun.
InMemoryEmbeddingStore<TextSegment> embeddingStore = 
    new InMemoryEmbeddingStore<>();

EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
    .documentSplitter(DocumentSplitters.recursive(500, 100))
    .embeddingModel(embeddingModel)
    .embeddingStore(embeddingStore)
    .build();
storeIngestor.ingest(document);

Vektör yerleştirmelerini depolamak için bellek içi vektör veritabanı olan InMemoryEmbeddingStore örneği oluşturuldu.

Doküman, DocumentSplitters sınıfı sayesinde parçalara ayrıldı. PDF dosyasındaki metin, 100 karakterlik örtüşmeyle 500 karakterlik snippet'lere bölünür (kelimeleri veya cümleleri parçalar halinde kesmemek için sonraki parçayla birlikte).

Mağaza aktarıcısı belge ayırıcıyı, vektörleri hesaplamak için yerleştirme modelini ve bellek içi vektör veritabanını bağlar. Ardından ingest() yöntemi, besleme işlemini gerçekleştirir.

İlk aşama bitti. Doküman, ilişkili vektör yerleştirmeleriyle birlikte metin parçalarına dönüştürüldü ve vektör veritabanında depolandı.

Soru sorma

Soru sormaya hazırlanma vakti geldi. Görüşmeyi başlatmak için bir sohbet modeli oluşturun:

ChatLanguageModel model = VertexAiGeminiChatModel.builder()
        .project(System.getenv("PROJECT_ID"))
        .location(System.getenv("LOCATION"))
        .modelName("gemini-1.5-flash-001")
        .maxOutputTokens(1000)
        .build();

Vektör veritabanını (embeddingStore değişkeninde) yerleştirme modeline bağlamak için de bir retriever sınıfına ihtiyacınız vardır. Görevi, kullanıcı sorgusu için vektör yerleştirmeyi hesaplayarak vektör veritabanını sorgulamak ve veritabanındaki benzer vektörleri bulmaktır.

EmbeddingStoreContentRetriever retriever =
    new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);

Ana yöntemin dışında, bir LLM uzman asistanını temsil eden bir arayüz oluşturun. Bu arayüz, modelle etkileşimde bulunmanız için AiServices sınıfının uygulayacağı bir arayüzdür:

interface LlmExpert {
    String ask(String question);
}

Bu noktada yeni bir AI hizmeti yapılandırabilirsiniz:

LlmExpert expert = AiServices.builder(LlmExpert.class)
    .chatLanguageModel(model)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .contentRetriever(retriever)
    .build();

Bu hizmet birbirine bağlanır:

  • Daha önce yapılandırdığınız sohbet dili modeli.
  • Görüşmeyi takip etmek için bir sohbet belleği.
  • retriever, bir vektör yerleştirme sorgusunu veritabanındaki vektörlerle karşılaştırır.
  • İstem şablonunda, sohbet modelinin verdiği bilgilere (ör. vektör yerleştirmesi kullanıcının sorusu vektörüne benzer olan dokümanlardan ilgili alıntılar) dayanarak yanıt vermesi gerektiğini açıkça belirten bir ifade vardır.
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
    .contentInjector(DefaultContentInjector.builder()
        .promptTemplate(PromptTemplate.from("""
            You are an expert in large language models,\s
            you excel at explaining simply and clearly questions about LLMs.

            Here is the question: {{userMessage}}

            Answer using the following information:
            {{contents}}
            """))
        .build())
    .contentRetriever(retriever)
    .build())

Nihayet sorularınızı sormaya hazırsınız!

List.of(
    "What neural network architecture can be used for language models?",
    "What are the different components of a transformer neural network?",
    "What is attention in large language models?",
    "What is the name of the process that transforms text into vectors?"
).forEach(query ->
    System.out.printf("%n=== %s === %n%n %s %n%n", query, expert.ask(query)));
);

Tam kaynak kodu, app/src/main/java/gemini/workshop dizinindeki RAG.java klasöründedir:

Örneği çalıştırın:

./gradlew -q run -DjavaMainClass=gemini.workshop.RAG

Çıktıda sorularınızın yanıtlarını göreceksiniz:

=== What neural network architecture can be used for language models? === 

 Transformer architecture 


=== What are the different components of a transformer neural network? === 

 The different components of a transformer neural network are:

1. Encoder: The encoder takes the input sequence and converts it into a 
sequence of hidden states. Each hidden state represents the context of 
the corresponding input token.
2. Decoder: The decoder takes the hidden states from the encoder and 
uses them to generate the output sequence. Each output token is 
generated by attending to the hidden states and then using a 
feed-forward network to predict the token's probability distribution.
3. Attention mechanism: The attention mechanism allows the decoder to 
attend to the hidden states from the encoder when generating each output 
token. This allows the decoder to take into account the context of the 
input sequence when generating the output sequence.
4. Positional encoding: Positional encoding is a technique used to 
inject positional information into the input sequence. This is important 
because the transformer neural network does not have any inherent sense 
of the order of the tokens in the input sequence.
5. Feed-forward network: The feed-forward network is a type of neural 
network that is used to predict the probability distribution of each 
output token. The feed-forward network takes the hidden state from the 
decoder as input and outputs a vector of probabilities. 


=== What is attention in large language models? === 

Attention in large language models is a mechanism that allows the model 
to focus on specific parts of the input sequence when generating the 
output sequence. This is important because it allows the model to take 
into account the context of the input sequence when generating each output token.

Attention is implemented using a function that takes two sequences as 
input: a query sequence and a key-value sequence. The query sequence is 
typically the hidden state from the previous decoder layer, and the 
key-value sequence is typically the sequence of hidden states from the 
encoder. The attention function computes a weighted sum of the values in 
the key-value sequence, where the weights are determined by the 
similarity between the query and the keys.

The output of the attention function is a vector of context vectors, 
which are then used as input to the feed-forward network in the decoder. 
The feed-forward network then predicts the probability distribution of 
the next output token.

Attention is a powerful mechanism that allows large language models to 
generate text that is both coherent and informative. It is one of the 
key factors that has contributed to the recent success of large language 
models in a wide range of natural language processing tasks. 


=== What is the name of the process that transforms text into vectors? === 

The process of transforming text into vectors is called **word embedding**.

Word embedding is a technique used in natural language processing (NLP) 
to represent words as vectors of real numbers. Each word is assigned a 
unique vector, which captures its meaning and semantic relationships 
with other words. Word embeddings are used in a variety of NLP tasks, 
such as machine translation, text classification, and question 
answering.

There are a number of different word embedding techniques, but one of 
the most common is the **skip-gram** model. The skip-gram model is a 
neural network that is trained to predict the surrounding words of a 
given word. By learning to predict the surrounding words, the skip-gram 
model learns to capture the meaning and semantic relationships of words.

Once a word embedding model has been trained, it can be used to 
transform text into vectors. To do this, each word in the text is 
converted to its corresponding vector. The vectors for all of the words 
in the text are then concatenated to form a single vector, which 
represents the entire text.

Text vectors can be used in a variety of NLP tasks. For example, text 
vectors can be used to train machine translation models, text 
classification models, and question answering models. Text vectors can 
also be used to perform tasks such as text summarization and text 
clustering. 

11. İşlev çağırma

Ayrıca, bir LLM'nin harici sistemlere erişmesini istediğiniz durumlar da vardır. Örneğin, bilgi alan veya işlem yapan bir uzak web API'si ya da bir tür hesaplama yapan hizmetler buna örnek olarak gösterilebilir. Örneğin:

Uzak web API'leri:

  • Müşteri siparişlerini takip etme ve güncelleme.
  • Sorun izleyicide bir destek kaydı bulun veya oluşturun.
  • Hisse senedi fiyatları veya IoT sensör ölçümleri gibi gerçek zamanlı verileri getirin.
  • E-posta gönderin.

Hesaplama araçları:

  • Daha ileri düzey matematik problemleri için hesap makinesi.
  • LLM'ler akıl yürütme mantığına ihtiyaç duyduğunda kod çalıştırmak için kod yorumlama.
  • LLM'nin bir veritabanını sorgulayabilmesi için doğal dil isteklerini SQL sorgularına dönüştürün.

İşlev çağrısı, modelin kendi adına bir veya daha fazla işlev çağrısının yapılmasını isteme olanağıdır. Böylece, kullanıcının istemine daha fazla yeni veri sunarak doğru şekilde yanıt verebilir.

Kullanıcıdan gelen belirli bir istem ve bu bağlamla alakalı olabilecek mevcut işlevler hakkında bilgi göz önünde bulundurulduğunda LLM, işlev çağrısı isteğiyle yanıt verebilir. Ardından LLM'yi entegre eden uygulama işlevi çağırabilir ve ardından LLM'ye yanıt vererek LLM'ye yanıt verebilir. Daha sonra LLM, yazılı bir yanıt vererek söz konusu yanıtı yorumlar.

İşlev çağrısının dört adımı

Şimdi bir işlev çağrısı örneğine bakalım: hava durumu tahmini hakkında bilgi alma.

Gemini'a veya başka bir LLM'ye Paris'teki hava durumunu sorduğunuzda, hava durumu tahminiyle ilgili bilgi olmadığını söyleyerek yanıt verirler. LLM'nin hava durumu verilerine gerçek zamanlı olarak erişebilmesini istiyorsanız kullanabileceği bazı işlevleri tanımlamanız gerekir.

Aşağıdaki şemaya göz atın:

31e0c2aba5e6f21c.png

1️⃣ İlk olarak, bir kullanıcı Paris'teki hava durumunu sorar. Chatbot uygulaması, LLM'nin sorguyu yerine getirmesine yardımcı olacak bir veya daha fazla işlev olduğunu bilir. Chatbot, hem ilk istemi hem de çağrılabilecek işlevlerin listesini gönderir. Burada, konum için bir dize parametresi alan getWeather() adlı bir işlev görünür.

8863be53a73c4a70.png

LLM hava durumu tahminlerinden haberdar olmadığından, metinle yanıt vermek yerine bir işlev yürütme isteği gönderir. Chatbot, konum parametresi olarak "Paris" ile getWeather() işlevini çağırmalıdır.

d1367cc69c07b14d.png

2️⃣ Chatbot, bu işlevi LLM adına çağırır, işlev yanıtını alır. Burada, yanıtın {"forecast": "sunny"} olduğunu varsayıyoruz.

73a5f2ed19f47d8.png

3️⃣ Chatbot uygulaması, JSON yanıtını LLM'ye geri gönderir.

20832cb1ee6fbfeb.png

4️⃣ LLM, JSON yanıtına bakar, bu bilgiyi yorumlar ve sonunda Paris'te havanın güneşli olduğunu belirten bir metinle yanıt verir.

Her adımı kod olarak

İlk olarak Gemini modelini her zamanki gibi yapılandıracaksınız:

ChatLanguageModel model = VertexAiGeminiChatModel.builder()
    .project(System.getenv("PROJECT_ID"))
    .location(System.getenv("LOCATION"))
    .modelName("gemini-1.5-flash-001")
    .maxOutputTokens(100)
    .build();

Çağrılabilecek işlevi açıklayan bir araç spesifikasyonu belirtirsiniz:

ToolSpecification weatherToolSpec = ToolSpecification.builder()
    .name("getWeatherForecast")
    .description("Get the weather forecast for a location")
    .addParameter("location", JsonSchemaProperty.STRING,
        JsonSchemaProperty.description("the location to get the weather forecast for"))
    .build();

Parametrenin adı ve türünün yanı sıra işlevin adı tanımlanır, ancak hem işleve hem de parametrelere açıklama verildiğine dikkat edin. Açıklamalar çok önemlidir ve LLM'nin bir işlevin neler yapabileceğini gerçekten anlamasına yardımcı olur, böylece bu işlevin görüşme bağlamında çağrılması gerekip gerekmediğine karar verir.

Paris'teki hava durumuyla ilgili ilk soruyu göndererek 1. adıma başlayalım:

List<ChatMessage> allMessages = new ArrayList<>();

// 1) Ask the question about the weather
UserMessage weatherQuestion = UserMessage.from("What is the weather in Paris?");
allMessages.add(weatherQuestion);

2. adımda, modelin kullanmasını istediğimiz aracı iletiriz ve model çok fazla yürütme isteğiyle yanıt verir:

// 2) The model replies with a function call request
Response<AiMessage> messageResponse = model.generate(allMessages, weatherToolSpec);
ToolExecutionRequest toolExecutionRequest = messageResponse.content().toolExecutionRequests().getFirst();
System.out.println("Tool execution request: " + toolExecutionRequest);
allMessages.add(messageResponse.content());

3. Adım: Bu noktada, LLM'nin hangi işlevi çağırmamızı istediğini anlıyoruz. Kodda, harici bir API'ye gerçek bir çağrıda bulunmak yerine, doğrudan varsayıma dayalı bir hava durumu tahmini döndürülür:

// 3) We send back the result of the function call
ToolExecutionResultMessage toolExecResMsg = ToolExecutionResultMessage.from(toolExecutionRequest,
    "{\"location\":\"Paris\",\"forecast\":\"sunny\", \"temperature\": 20}");
allMessages.add(toolExecResMsg);

4. adımda LLM, işlev yürütme sonucunu öğrenir ve metinsel bir yanıt sentezleyebilir:

// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());

Çıkış şu şekildedir:

Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer:  The weather in Paris is sunny with a temperature of 20 degrees Celsius.

Aracı yürütme isteğinin üzerindeki çıktıyı ve yanıtı görebilirsiniz.

Tam kaynak kodu, app/src/main/java/gemini/workshop dizinindeki FunctionCalling.java klasöründedir:

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling

Şuna benzer bir çıkış alırsınız:

Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer:  The weather in Paris is sunny with a temperature of 20 degrees Celsius.

12. LangChain4j, işlev çağrısını gerçekleştiriyor

Önceki adımda, normal metin sorusu/yanıtı ve işlev istek/yanıt etkileşimlerinin araya nasıl dahil edildiğini gördünüz. Bunların arasında, gerçek bir işlev çağırmadan istenen işlev yanıtını doğrudan sağladınız.

Ancak LangChain4j, ileti dizisini her zamanki gibi ele alırken işlev çağrılarını sizin için şeffaf bir şekilde işleyebilen daha üst düzey bir soyutlama sunar.

Tek işlev çağrısı

FunctionCallingAssistant.java hizmetini adım adım inceleyelim.

Öncelikle, işlevin yanıt veri yapısını temsil edecek bir kayıt oluşturursunuz:

record WeatherForecast(String location, String forecast, int temperature) {}

Yanıtta konum, hava durumu ve sıcaklıkla ilgili bilgiler yer alır.

Ardından, modelde kullanılabilir olmasını istediğiniz gerçek işlevi içeren bir sınıf oluşturursunuz:

static class WeatherForecastService {
    @Tool("Get the weather forecast for a location")
    WeatherForecast getForecast(@P("Location to get the forecast for") String location) {
        if (location.equals("Paris")) {
            return new WeatherForecast("Paris", "Sunny", 20);
        } else if (location.equals("London")) {
            return new WeatherForecast("London", "Rainy", 15);
        } else {
            return new WeatherForecast("Unknown", "Unknown", 0);
        }
    }
}

Bu sınıfın tek bir işlev içerdiğini, ancak modelin çağırmak isteyebileceği işlevin açıklamasına karşılık gelen @Tool ek açıklamasıyla birlikte not aldığını unutmayın.

İşlevin parametreleri de (burada tek bir tane) gösterilir, ancak bu kısa @P ek açıklaması da parametrenin bir açıklamasını sunar. Daha karmaşık senaryolarda modelin kullanılabilir olmasını sağlamak için istediğiniz kadar işlev ekleyebilirsiniz.

Bu sınıfta bazı hazır yanıtlar veriyorsunuz. Ancak gerçek bir harici hava durumu tahmin hizmeti çağırmak isterseniz bu, söz konusu yöntemin gövdesinde bulunan ve ilgili hizmete çağrı yapacaktınız.

Önceki yaklaşımda bir ToolSpecification oluşturduğunuzda gördüğümüz gibi, bir işlevin ne yaptığını belgelemek ve parametrelerin neye karşılık geldiğini açıklamak önemlidir. Bu, modelin bu işlevin nasıl ve ne zaman kullanılabileceğini anlamasına yardımcı olur.

Ardından, LangChain4j, modelle etkileşimde bulunmak için kullanmak istediğiniz sözleşmeye karşılık gelen bir arayüz sağlamanıza olanak tanır. Burada, kullanıcı mesajını temsil eden bir dizeyi alıp modelin yanıtına karşılık gelen bir dize döndüren basit bir arayüz gösterilmektedir:

interface WeatherAssistant {
    String chat(String userMessage);
}

Daha ileri düzey durumları ele almak isterseniz, LangChain4j'in UserMessage (bir kullanıcı mesajı için) veya AiMessage (model yanıtı için) veya AiMessage (hatta bir TokenStream) içeren daha karmaşık imzalar kullanmak da mümkündür. Çünkü daha karmaşık nesneler, tüketilen jeton sayısı gibi ek bilgiler de içerir. Ancak kolaylık olması açısından sadece dizeyi girdi ve çıktıdaki dizeyi dahil edeceğiz.

Tüm parçaları birbirine bağlayan main() yöntemiyle başlayalım:

public static void main(String[] args) {
    ChatLanguageModel model = VertexAiGeminiChatModel.builder()
        .project(System.getenv("PROJECT_ID"))
        .location(System.getenv("LOCATION"))
        .modelName("gemini-1.5-flash-001")
        .maxOutputTokens(100)
        .build();

    WeatherForecastService weatherForecastService = new WeatherForecastService();

    WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
        .chatLanguageModel(model)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
        .tools(weatherForecastService)
        .build();

    System.out.println(assistant.chat("What is the weather in Paris?"));
}

Gemini sohbet modelini her zaman olduğu gibi yapılandırırsınız. Ardından, modelin aramamızı isteyeceği "işlevi" içeren hava durumu tahmini hizmetinizi örneklendirirsiniz.

Şimdi AiServices sınıfını tekrar kullanarak sohbet modelini, sohbet belleğini ve aracı (ör. hava durumu tahmin hizmeti işleviyle) bağlayabilirsiniz. AiServices, tanımladığınız WeatherAssistant arayüzünüzü uygulayan bir nesne döndürür. Kalan tek şey söz konusu asistanın chat() yöntemini çağırmaktır. Çağrı yaparken yalnızca metin yanıtlarını görürsünüz, ancak işlev çağrısı istekleri ve işlev çağrısı yanıtları geliştirici tarafından görülemez, ayrıca bu istekler otomatik ve şeffaf bir şekilde işlenir. Gemini bir işlevin çağrılması gerektiğini düşünürse işlev çağrısı isteğiyle yanıt verir ve LangChain4j, sizin adınıza yerel işlevi çağırma işlemini gerçekleştirir.

Örneği çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant

Şuna benzer bir çıkış alırsınız:

OK. The weather in Paris is sunny with a temperature of 20 degrees.

Bu, tek bir işlevin örneğidir.

Birden çok işlev çağrısı

Birden çok işleviniz de olabilir ve LangChain4j'in sizin adınıza birden çok işlev çağrısını işlemesine izin verebilirsiniz. Birden çok işlev örneği için MultiFunctionCallingAssistant.java bölümüne bakın.

Para birimlerini dönüştürme işlevi vardır:

@Tool("Convert amounts between two currencies")
double convertCurrency(
    @P("Currency to convert from") String fromCurrency,
    @P("Currency to convert to") String toCurrency,
    @P("Amount to convert") double amount) {

    double result = amount;

    if (fromCurrency.equals("USD") && toCurrency.equals("EUR")) {
        result = amount * 0.93;
    } else if (fromCurrency.equals("USD") && toCurrency.equals("GBP")) {
        result = amount * 0.79;
    }

    System.out.println(
        "convertCurrency(fromCurrency = " + fromCurrency +
            ", toCurrency = " + toCurrency +
            ", amount = " + amount + ") == " + result);

    return result;
}

Hisse senedinin değerini öğrenmek için kullanılan diğer bir fonksiyon:

@Tool("Get the current value of a stock in US dollars")
double getStockPrice(@P("Stock symbol") String symbol) {
    double result = 170.0 + 10 * new Random().nextDouble();

    System.out.println("getStockPrice(symbol = " + symbol + ") == " + result);

    return result;
}

Belirli bir tutara yüzde uygulamak için kullanılan diğer bir fonksiyon:

@Tool("Apply a percentage to a given amount")
double applyPercentage(@P("Initial amount") double amount, @P("Percentage between 0-100 to apply") double percentage) {
    double result = amount * (percentage / 100);

    System.out.println("applyPercentage(amount = " + amount + ", percentage = " + percentage + ") == " + result);

    return result;
}

Ardından tüm bu fonksiyonları ve bir Çok Amaçlı Araçlar sınıfını birleştirebilirsiniz. Örneğin, "AAPL hisse senedi fiyatının% 10'u USD'den EUR'ye dönüştürülür?" gibi sorular sorabilirsiniz.

public static void main(String[] args) {
    ChatLanguageModel model = VertexAiGeminiChatModel.builder()
        .project(System.getenv("PROJECT_ID"))
        .location(System.getenv("LOCATION"))
        .modelName("gemini-1.5-flash-001")
        .maxOutputTokens(100)
        .build();

    MultiTools multiTools = new MultiTools();

    MultiToolsAssistant assistant = AiServices.builder(MultiToolsAssistant.class)
        .chatLanguageModel(model)
        .chatMemory(withMaxMessages(10))
        .tools(multiTools)
        .build();

    System.out.println(assistant.chat(
        "What is 10% of the AAPL stock price converted from USD to EUR?"));
}

Aşağıdaki gibi çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant

Şu ada sahip birden çok işlev göreceksiniz:

getStockPrice(symbol = AAPL) == 172.8022224055534
convertCurrency(fromCurrency = USD, toCurrency = EUR, amount = 172.8022224055534) == 160.70606683716468
applyPercentage(amount = 160.70606683716468, percentage = 10.0) == 16.07060668371647
10% of the AAPL stock price converted from USD to EUR is 16.07060668371647 EUR.

Temsilcilere Doğru

İşlev çağırma, Gemini gibi büyük dil modelleri için mükemmel bir uzantı mekanizmasıdır. Genellikle "aracı" veya "yapay zeka asistanı" olarak adlandırılan daha karmaşık sistemler oluşturmamızı sağlar. Bu aracılar, harici API'ler ve harici ortamda yan etkileri olabilecek hizmetlerle (örneğin, e-posta gönderme, destek kaydı oluşturma vb.) harici dünyayla etkileşim kurabilir.

Böyle güçlü aracılar oluştururken bunu sorumlu bir şekilde yapmalısınız. Otomatik işlemler yapmadan önce işin bir parçasını göz önünde bulundurmalısınız. Dış dünyayla etkileşim kuran LLM destekli temsilciler tasarlarken güvenliği göz önünde bulundurmak önemlidir.

13. Gemma'yı Ollama ve TestContainers ile çalıştırma

Şimdiye kadar Gemini'ı kullandık. Ancak bir de Gemma, yani küçük kardeş modeli var.

Gemma, Gemini modellerini oluşturmak için kullanılan araştırma ve teknolojiyle oluşturulmuş hafif ve son teknoloji ürünü açık modeller ailesidir. Gemma, Gemma1 ve Gemma2 olmak üzere her biri farklı boyutlara sahip iki varyant halinde sunulur. Gemma1'in iki boyutu vardır: 2B ve 7B. Gemma2'nin iki boyutu vardır: 9B ve 27B. Ağırlıkları serbest, küçük boyutları sayesinde dizüstü bilgisayarınızda veya Cloud Shell'de bile kendi başınıza çalıştırabilirsiniz.

Gemma nasıl çalıştırılır?

Gemma'yı bulutta, tek tıkla Vertex AI'ı veya bazı GPU'larla birlikte GKE'yi kullanarak çalıştırabilirsiniz. Gemma'yı yerel olarak da çalıştırabilirsiniz.

Gemma'yı yerel olarak çalıştırmak için iyi bir seçenek olan Ollama aracı, Lama 2 ve Mistral gibi birçok küçük modeli yerel makinenizde çalıştırmanıza olanak tanır. Docker'a benzer, ancak LLM'ler içindir.

İşletim Sisteminize ilişkin talimatları uygulayarak Ollama'yı yükleyin.

Linux ortamı kullanıyorsanız, Ollama'yı yükledikten sonra etkinleştirmeniz gerekir.

ollama serve > /dev/null 2>&1 & 

Yerel olarak yüklendikten sonra, model çekmek için komutlar çalıştırabilirsiniz:

ollama pull gemma:2b

Modelin çekilmesini bekleyin. Bu işlem biraz zaman alabilir.

Modeli çalıştırın:

ollama run gemma:2b

Artık modelle etkileşimde bulunabilirsiniz:

>>> Hello!
Hello! It's nice to hear from you. What can I do for you today?

İstemden çıkmak için Ctrl+D tuşlarına basın

TestContainers'da Ollama'da Gemma'yı çalıştırma

Ollama'yı yerel olarak yükleyip çalıştırmak zorunda kalmadan, TestContainers tarafından işlenen bir kapsayıcı içinde Ollama'yı kullanabilirsiniz.

TestContainers yalnızca test için faydalı değildir, container'ları yürütmek için de kullanılabilir. Yararlanabileceğiniz özel bir OllamaContainer bile var.

Resmin tamamını aşağıda görebilirsiniz:

2382c05a48708dfd.png

Uygulama

GemmaWithOllamaContainer.java hizmetini adım adım inceleyelim.

Öncelikle, Gemma modelini çeken, türetilmiş bir Ollama kapsayıcısı oluşturmanız gerekir. Bu görüntü, önceki bir çalıştırmada zaten mevcut ya da oluşturulacak. Resim zaten mevcutsa TestContainers'a, varsayılan Ollama resmini Gemma destekli varyantınızla değiştirmek istediğinizi söyleyin:

private static final String TC_OLLAMA_GEMMA_2_B = "tc-ollama-gemma-2b";

// Creating an Ollama container with Gemma 2B if it doesn't exist.
private static OllamaContainer createGemmaOllamaContainer() throws IOException, InterruptedException {

    // Check if the custom Gemma Ollama image exists already
    List<Image> listImagesCmd = DockerClientFactory.lazyClient()
        .listImagesCmd()
        .withImageNameFilter(TC_OLLAMA_GEMMA_2_B)
        .exec();

    if (listImagesCmd.isEmpty()) {
        System.out.println("Creating a new Ollama container with Gemma 2B image...");
        OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.1.26");
        ollama.start();
        ollama.execInContainer("ollama", "pull", "gemma:2b");
        ollama.commitToImage(TC_OLLAMA_GEMMA_2_B);
        return ollama;
    } else {
        System.out.println("Using existing Ollama container with Gemma 2B image...");
        // Substitute the default Ollama image with our Gemma variant
        return new OllamaContainer(
            DockerImageName.parse(TC_OLLAMA_GEMMA_2_B)
                .asCompatibleSubstituteFor("ollama/ollama"));
    }
}

Ardından Ollama test container'ı oluşturup başlatın. Ardından, kullanmak istediğiniz modelle birlikte container'ın adresine ve bağlantı noktasına işaret ederek bir Ollama sohbet modeli oluşturun. Son olarak, model.generate(yourPrompt) öğesini her zamanki gibi çağırırsınız:

public static void main(String[] args) throws IOException, InterruptedException {
    OllamaContainer ollama = createGemmaOllamaContainer();
    ollama.start();

    ChatLanguageModel model = OllamaChatModel.builder()
        .baseUrl(String.format("http://%s:%d", ollama.getHost(), ollama.getFirstMappedPort()))
        .modelName("gemma:2b")
        .build();

    String response = model.generate("Why is the sky blue?");

    System.out.println(response);
}

Aşağıdaki gibi çalıştırın:

./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer

İlk çalıştırmanın kapsayıcı oluşturulması ve çalıştırılması biraz zaman alabilir, ancak işlem tamamlandığında Gemma'nın şu şekilde yanıt verdiğini göreceksiniz:

INFO: Container ollama/ollama:0.1.26 started in PT2.827064047S
The sky appears blue due to Rayleigh scattering. Rayleigh scattering is a phenomenon that occurs when sunlight interacts with molecules in the Earth's atmosphere.

* **Scattering particles:** The main scattering particles in the atmosphere are molecules of nitrogen (N2) and oxygen (O2).
* **Wavelength of light:** Blue light has a shorter wavelength than other colors of light, such as red and yellow.
* **Scattering process:** When blue light interacts with these molecules, it is scattered in all directions.
* **Human eyes:** Our eyes are more sensitive to blue light than other colors, so we perceive the sky as blue.

This scattering process results in a blue appearance for the sky, even though the sun is actually emitting light of all colors.

In addition to Rayleigh scattering, other atmospheric factors can also influence the color of the sky, such as dust particles, aerosols, and clouds.

Cloud Shell'de Gemma çalışıyor.

14. Tebrikler

Tebrikler, LangChain4j ve Gemini API'yi kullanarak Java'da ilk üretken yapay zeka sohbet uygulamanızı başarıyla derlediniz. Bu süreçte çok modlu büyük dil modellerinin oldukça güçlü olduğunu ve kendi belgeleriniz, veri ayıklama, harici API'lerle etkileşime geçme ve hatta soru/cevap gibi çeşitli görevleri yerine getirebildiğini keşfettiniz.

Sırada ne var?

Güçlü LLM entegrasyonlarıyla uygulamalarınızı geliştirme sırası sizde.

Daha fazla bilgi

Referans belgeler