Gemini trong Java với Vertex AI và LangChain4j

1. Giới thiệu

Lớp học lập trình này tập trung vào Mô hình ngôn ngữ lớn (LLM) Gemini, được lưu trữ trên Vertex AI trên Google Cloud. Vertex AI là một nền tảng bao gồm tất cả sản phẩm, dịch vụ và mô hình học máy trên Google Cloud.

Bạn sẽ dùng Java để tương tác với Gemini API bằng khung LangChain4j. Bạn sẽ tìm hiểu các ví dụ cụ thể để tận dụng LLM (mô hình ngôn ngữ lớn) để trả lời câu hỏi, tạo ý tưởng, trích xuất thực thể và nội dung có cấu trúc, tính năng tạo tăng cường truy xuất và gọi hàm.

AI tạo sinh là gì?

AI tạo sinh là việc sử dụng trí tuệ nhân tạo để tạo ra nội dung mới, chẳng hạn như văn bản, hình ảnh, nhạc, âm thanh và video.

AI tạo sinh hoạt động dựa trên các mô hình ngôn ngữ lớn (LLM) có khả năng làm nhiều việc cùng lúc và thực hiện ngay những việc như tóm tắt, hỏi và đáp, phân loại, v.v. Chỉ cần đào tạo rất ít, các mô hình cơ bản có thể được điều chỉnh cho phù hợp với các trường hợp sử dụng được nhắm mục tiêu mà có rất ít dữ liệu ví dụ.

AI tạo sinh hoạt động như thế nào?

AI tạo sinh hoạt động bằng cách sử dụng mô hình học máy (ML) để tìm hiểu các quy luật và mối quan hệ trong một tập dữ liệu gồm nội dung do con người tạo ra. Sau đó, công cụ này sử dụng các mẫu đã học để tạo nội dung mới.

Cách phổ biến nhất để huấn luyện một mô hình AI tạo sinh là sử dụng công nghệ học có giám sát. Mô hình này được cung cấp một tập hợp nội dung do con người tạo và các nhãn tương ứng. Sau đó, công nghệ này sẽ học cách tạo ra nội dung tương tự như nội dung do con người tạo.

Các ứng dụng AI tạo sinh phổ biến là gì?

AI tạo sinh có thể được dùng để:

  • Cải thiện mức độ tương tác của khách hàng thông qua trải nghiệm tìm kiếm và trò chuyện nâng cao.
  • Khám phá một lượng lớn dữ liệu phi cấu trúc thông qua giao diện trò chuyện và nội dung tóm tắt.
  • Hỗ trợ những công việc lặp đi lặp lại như phản hồi yêu cầu đề xuất, bản địa hoá nội dung tiếp thị bằng nhiều ngôn ngữ và kiểm tra hợp đồng với khách hàng để đảm bảo tuân thủ, v.v.

Google Cloud cung cấp sản phẩm/dịch vụ AI tạo sinh nào?

Nhờ Vertex AI, bạn có thể tương tác, tuỳ chỉnh và nhúng các mô hình nền tảng vào ứng dụng của mình mà không cần hoặc có rất ít kiến thức chuyên môn về công nghệ học máy. Bạn có thể truy cập vào các mô hình nền tảng trong Khu vườn mô hình, điều chỉnh các mô hình thông qua một giao diện người dùng đơn giản trên Vertex AI Studio hoặc sử dụng các mô hình trong sổ tay khoa học dữ liệu.

Công cụ tìm kiếm và trò chuyện bằng AI tạo sinh mang đến cho nhà phát triển cách nhanh nhất để xây dựng công cụ tìm kiếm và bot trò chuyện dựa trên AI tạo sinh.

Sử dụng Gemini, Gemini cho Google Cloud là một cộng tác viên dựa trên AI, có mặt trên Google Cloud và các môi trường phát triển tích hợp (IDE) để giúp bạn hoàn thành nhiều việc hơn, nhanh chóng hơn. Gemini Code Assist cung cấp các tính năng như hoàn thành mã, tạo mã, giải thích mã, đồng thời cho phép bạn trò chuyện với Gemini để đặt câu hỏi kỹ thuật.

Gemini là gì?

Gemini là một bộ mô hình AI tạo sinh do Google DeepMind phát triển, được thiết kế cho các trường hợp sử dụng đa phương thức. Đa phương thức có nghĩa là mô hình này có thể xử lý và tạo nhiều loại nội dung như văn bản, mã nguồn, hình ảnh và âm thanh.

b9913d011999e7c7.png

Gemini có nhiều biến thể và kích thước:

  • Gemini Ultra: Phiên bản lớn nhất và mạnh mẽ nhất, dành cho những công việc phức tạp.
  • Gemini Flash: Nhanh nhất và tiết kiệm chi phí nhất, được tối ưu hoá cho các công việc khối lượng lớn.
  • Gemini Pro: Kích thước vừa, được tối ưu hoá để mở rộng quy mô trong nhiều nhiệm vụ.
  • Gemini Nano: Giải pháp hiệu quả nhất, được thiết kế để thực hiện các công việc trên thiết bị.

Các tính năng chính:

  • Đa phương thức: Khả năng hiểu và xử lý nhiều định dạng thông tin của Gemini là một bước tiến đáng kể so với các mô hình ngôn ngữ truyền thống chỉ dùng văn bản.
  • Hiệu suất: Gemini Ultra vượt trội hơn công nghệ tiên tiến hiện tại ở nhiều điểm chuẩn và là mô hình đầu tiên vượt qua các chuyên gia của con người trong điểm chuẩn đầy thách thức MMLU (Hiểu ngôn ngữ đa nhiệm đa nhiệm) đầy thách thức.
  • Tính linh hoạt: Nhiều kích thước của Gemini giúp Gemini có thể thích ứng cho nhiều trường hợp sử dụng, từ nghiên cứu trên quy mô lớn đến việc triển khai trên thiết bị di động.

Làm cách nào để bạn tương tác với Gemini trên Vertex AI trong Java?

Bạn có hai lựa chọn:

  1. Thư viện chính thức Vertex AI Java API cho Gemini.
  2. LangChain4j.

Trong lớp học lập trình này, bạn sẽ sử dụng khung LangChain4j.

Khung LangChain4j là gì?

Khung LangChain4j là một thư viện nguồn mở để tích hợp các LLM vào các ứng dụng Java của bạn, bằng cách sắp xếp nhiều thành phần, chẳng hạn như chính LLM, và các công cụ khác như cơ sở dữ liệu vectơ (để tìm kiếm ngữ nghĩa), trình tải và bộ chia tài liệu (để phân tích và học hỏi từ các tài liệu đó), trình phân tích cú pháp đầu ra, v.v.

Dự án này được lấy cảm hứng từ dự án Python LangChain nhưng hướng đến mục tiêu phục vụ các nhà phát triển Java.

bb908ea1e6c96ac2.png

Kiến thức bạn sẽ học được

  • Cách thiết lập dự án Java để sử dụng Gemini và LangChain4j
  • Cách gửi câu lệnh đầu tiên tới Gemini bằng phương thức lập trình
  • Cách hiện câu trả lời của Gemini
  • Cách tạo cuộc trò chuyện giữa người dùng và Gemini
  • Cách sử dụng Gemini trong bối cảnh đa phương thức bằng cách gửi cả văn bản và hình ảnh
  • Cách trích xuất thông tin có cấu trúc hữu ích từ nội dung không có cấu trúc
  • Cách thao tác với các mẫu câu lệnh
  • Cách phân loại văn bản, chẳng hạn như phân tích cảm xúc
  • Cách trò chuyện bằng tài liệu của riêng bạn (Truy xuất thế hệ tăng cường)
  • Cách mở rộng bot trò chuyện bằng tính năng gọi hàm
  • Cách sử dụng Gemma cục bộ với Ollama và TestContainers

Bạn cần

  • Kiến thức về ngôn ngữ lập trình Java
  • Một dự án trên Google Cloud
  • Một trình duyệt, chẳng hạn như Chrome hoặc Firefox

2. Thiết lập và yêu cầu

Thiết lập môi trường theo tiến độ riêng

  1. Đăng nhập vào Google Cloud Console rồi tạo dự án mới hoặc sử dụng lại dự án hiện có. Nếu chưa có tài khoản Gmail hoặc Google Workspace, bạn phải tạo một tài khoản.

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png.

  • Tên dự án là tên hiển thị của những người tham gia dự án này. Đây là một chuỗi ký tự không được API của Google sử dụng. Bạn luôn có thể cập nhật ứng dụng.
  • Mã dự án là duy nhất trong tất cả các dự án Google Cloud và không thể thay đổi (không thể thay đổi sau khi đã đặt). Cloud Console tự động tạo một chuỗi duy nhất. Thường thì bạn sẽ không quan tâm đến chuỗi này. Trong hầu hết các lớp học lập trình, bạn cần tham khảo Mã dự án của mình (thường được xác định là PROJECT_ID). Nếu không thích mã đã tạo, bạn có thể tạo một chuỗi ngẫu nhiên khác. Ngoài ra, bạn có thể thử cách riêng của mình để xem có thể sử dụng hay không. Bạn không thể thay đổi mã này sau bước này và mã vẫn giữ nguyên trong thời gian của dự án.
  • Đối với thông tin của bạn, có giá trị thứ ba, Project Number (Số dự án), mà một số API sử dụng. Tìm hiểu thêm về cả ba giá trị này trong tài liệu này.
  1. Tiếp theo, bạn sẽ phải bật tính năng thanh toán trong Cloud Console để sử dụng API/tài nguyên trên đám mây. Việc chạy qua lớp học lập trình này sẽ không tốn nhiều chi phí. Để tắt các tài nguyên nhằm tránh phát sinh việc thanh toán ngoài hướng dẫn này, bạn có thể xoá các tài nguyên bạn đã tạo hoặc xoá dự án. Người dùng mới của Google Cloud đủ điều kiện tham gia chương trình Dùng thử miễn phí 300 USD.

Khởi động Cloud Shell

Mặc dù bạn có thể vận hành Google Cloud từ xa trên máy tính xách tay, nhưng trong lớp học lập trình này, bạn sẽ sử dụng Cloud Shell, một môi trường dòng lệnh chạy trong Đám mây.

Kích hoạt Cloud Shell

  1. Trong Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell 853e55310c205094.pngs.

3c1dabeca90e44e5.png.

Nếu đây là lần đầu tiên bạn khởi động Cloud Shell, bạn sẽ thấy một màn hình trung gian mô tả phần này. Nếu bạn thấy màn hình trung gian, hãy nhấp vào Tiếp tục.

9c92662c6a846a5c.pngS

Quá trình cấp phép và kết nối với Cloud Shell chỉ mất vài phút.

9f0e51b578fecce5.pngs

Máy ảo này được tải tất cả các công cụ phát triển cần thiết. Dịch vụ này cung cấp thư mục gốc có dung lượng ổn định 5 GB và chạy trong Google Cloud, giúp nâng cao đáng kể hiệu suất và khả năng xác thực của mạng. Nhiều (nếu không nói là) tất cả công việc của bạn trong lớp học lập trình này đều có thể thực hiện bằng trình duyệt.

Sau khi kết nối với Cloud Shell, bạn sẽ thấy mình đã được xác thực và dự án được đặt thành mã dự án.

  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng bạn đã được xác thực:
gcloud auth list

Kết quả lệnh

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

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng lệnh gcloud biết về dự án của bạn:
gcloud config list project

Kết quả lệnh

[core]
project = <PROJECT_ID>

Nếu chưa, bạn có thể thiết lập chế độ này bằng lệnh sau:

gcloud config set project <PROJECT_ID>

Kết quả lệnh

Updated property [core/project].

3. Chuẩn bị môi trường phát triển

Trong lớp học lập trình này, bạn sẽ sử dụng thiết bị đầu cuối Cloud Shell và trình chỉnh sửa Cloud Shell để phát triển các chương trình Java.

Bật các API Vertex AI

Trong bảng điều khiển Google Cloud, hãy đảm bảo tên dự án của bạn xuất hiện ở đầu bảng điều khiển Google Cloud. Nếu chưa, hãy nhấp vào Chọn dự án để mở Bộ chọn dự án và chọn dự án bạn muốn.

Bạn có thể bật các API Vertex AI trong phần Vertex AI trên bảng điều khiển của Google Cloud hoặc trong thiết bị đầu cuối Cloud Shell.

Để bật tính năng này trong bảng điều khiển Google Cloud, trước tiên, hãy chuyển đến phần Vertex AI trong trình đơn của bảng điều khiển Google Cloud:

451976f1c8652341.pngS

Nhấp vào Enable All Recommended API (Bật tất cả API được đề xuất) trong trang tổng quan của Vertex AI.

Thao tác này sẽ bật nhiều API, nhưng API quan trọng nhất đối với lớp học lập trình này là aiplatform.googleapis.com.

Ngoài ra, bạn cũng có thể bật API này trên thiết bị đầu cuối Cloud Shell bằng lệnh sau:

gcloud services enable aiplatform.googleapis.com

Sao chép kho lưu trữ GitHub

Trong cửa sổ dòng lệnh của Cloud Shell, hãy sao chép kho lưu trữ cho lớp học lập trình này:

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

Để kiểm tra xem dự án đã sẵn sàng chạy hay chưa, bạn có thể thử chạy chương trình "Hello World" ("Xin chào thế giới").

Đảm bảo bạn đang ở thư mục cấp cao nhất:

cd gemini-workshop-for-java-developers/ 

Tạo trình bao bọc Gradle:

gradle wrapper

Chạy bằng gradlew:

./gradlew run

Bạn sẽ thấy kết quả sau đây:

..
> Task :app:run
Hello World!

Mở và thiết lập Cloud Editor

Mở mã bằng Cloud Code Editor (Trình soạn thảo mã đám mây) của Cloud Shell:

42908e11b28f4383.pngS

Trong Cloud Code Editor, hãy mở thư mục nguồn của lớp học lập trình bằng cách chọn File -> Open Folder rồi trỏ đến thư mục nguồn của lớp học lập trình (ví dụ: /home/username/gemini-workshop-for-java-developers/).

Cài đặt Gradle cho Java

Để trình soạn thảo mã trên đám mây hoạt động đúng cách với Gradle, hãy cài đặt tiện ích Gradle cho Java.

Trước tiên, hãy chuyển đến mục Dự án Java rồi nhấn vào dấu cộng:

84d15639ac61c197.pngS

Chọn Gradle for Java:

34d6c4136a3cc9ff.png.

Chọn phiên bản Install Pre-Release:

3b044fb450ccb7.pngs

Sau khi cài đặt, bạn sẽ thấy các nút DisableUninstall:

46410fe86d777f9c.pngS

Cuối cùng, hãy dọn dẹp không gian làm việc để áp dụng các chế độ cài đặt mới:

31e27e9bb61d975d.png.

Bạn sẽ phải tải lại và xoá hội thảo theo thao tác này. Hãy tiếp tục và chọn Reload and delete:

d6303bc49e391dc.png

Nếu mở một trong các tệp, chẳng hạn như App.java, thì giờ đây, bạn sẽ thấy trình chỉnh sửa hoạt động chính xác với tính năng đánh dấu cú pháp:

fed1b1b5de0dff58.png

Giờ đây, bạn đã sẵn sàng chạy một số mẫu trên Gemini!

Thiết lập biến môi trường

Mở một thiết bị đầu cuối mới trong Cloud Code Editor bằng cách chọn Terminal -> New Terminal. Thiết lập 2 biến môi trường cần thiết để chạy các mã ví dụ:

  • PROJECT_ID — Mã dự án trên Google Cloud của bạn
  • LOCATION – Khu vực triển khai mô hình Gemini

Xuất các biến như sau:

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

4. Cuộc gọi đầu tiên đến mô hình Gemini

Hiện tại, dự án đã được thiết lập đúng cách, đã đến lúc gọi Gemini API.

Hãy xem QA.java trong thư mục app/src/main/java/gemini/workshop:

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?"));
    }
}

Trong ví dụ đầu tiên này, bạn cần nhập lớp VertexAiGeminiChatModel. Lớp này sẽ triển khai giao diện ChatModel.

Trong phương thức main, bạn định cấu hình mô hình ngôn ngữ trò chuyện bằng cách sử dụng trình tạo cho VertexAiGeminiChatModel và chỉ định:

  • Dự án
  • Vị trí
  • Tên mô hình (gemini-1.5-flash-001).

Giờ đây, khi mô hình ngôn ngữ đã sẵn sàng, bạn có thể gọi phương thức generate() và chuyển câu lệnh, câu hỏi hoặc hướng dẫn của bạn để gửi đến LLM. Ở đây, bạn đặt một câu hỏi đơn giản về yếu tố làm cho bầu trời có màu xanh lam.

Bạn có thể thay đổi câu lệnh này để thử các câu hỏi hoặc nhiệm vụ khác.

Chạy mẫu tại thư mục gốc của mã nguồn:

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

Bạn sẽ thấy kết quả tương tự như kết quả sau:

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.

Xin chúc mừng! Bạn đã thực hiện cuộc gọi đầu tiên đến Gemini!

Truyền trực tuyến câu trả lời

Bạn có thấy câu trả lời được đưa ra chỉ trong một lượt, sau vài giây không? Bạn cũng có thể nhận được phản hồi dần dần, nhờ biến thể phản hồi truyền trực tuyến. Khi có phản hồi truyền trực tuyến, mô hình sẽ trả về từng phần phản hồi khi có.

Trong lớp học lập trình này, chúng ta sẽ tiếp tục với phản hồi không truyền trực tuyến. Tuy nhiên, hãy xem phản hồi truyền trực tuyến để tìm hiểu cách thực hiện.

Trong StreamQA.java của thư mục app/src/main/java/gemini/workshop, bạn có thể xem phản hồi truyền trực tuyến trong thực tế:

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();
            }
        });
    }
}

Lần này, chúng ta sẽ nhập các biến thể của lớp truyền trực tuyến VertexAiGeminiStreamingChatModel. Các biến thể này sẽ triển khai giao diện StreamingChatLanguageModel. Bạn cũng cần có một StreamingResponseHandler.

Lần này, chữ ký của phương thức generate() có đôi chút khác biệt. Thay vì trả về một chuỗi, loại dữ liệu trả về sẽ trống. Ngoài lời nhắc, bạn phải truyền một trình xử lý phản hồi truyền trực tuyến. Ở đây, bạn triển khai giao diện bằng cách tạo một lớp ẩn danh bên trong, với 2 phương thức onNext(String text)onError(Throwable error). Phương thức gọi trước được gọi mỗi khi có một phần mới của phản hồi, còn phương thức sau chỉ được gọi khi có lỗi.

Chạy:

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

Bạn sẽ nhận được câu trả lời tương tự cho lớp trước, nhưng lần này, bạn sẽ nhận thấy rằng câu trả lời sẽ xuất hiện dần dần trong shell, thay vì đợi hiển thị câu trả lời đầy đủ.

Cấu hình bổ sung

Đối với cấu hình, chúng tôi chỉ xác định dự án, vị trí và tên mô hình. Tuy nhiên, bạn có thể chỉ định các tham số khác cho mô hình:

  • temperature(Float temp) – để xác định mức độ phù hợp của mẫu quảng cáo bạn muốn nhận được (0 là mẫu quảng cáo thấp và thường là thực tế hơn, trong khi 1 là dành cho nhiều mẫu quảng cáo hơn)
  • topP(Float topP) – để chọn những từ có thể có tổng xác suất cộng lại bằng số thực đó (từ 0 đến 1)
  • topK(Integer topK) – để chọn ngẫu nhiên một từ trong số từ tối đa có thể xuất hiện để hoàn thành văn bản (từ 1 đến 40)
  • maxOutputTokens(Integer max) – để chỉ định độ dài tối đa của câu trả lời do mô hình đưa ra (thường, 4 mã thông báo tương ứng với khoảng 3 từ)
  • maxRetries(Integer retries) – trong trường hợp bạn chạy vượt quá hạn mức yêu cầu cho mỗi thời gian hoặc nền tảng đang gặp vấn đề kỹ thuật, bạn có thể yêu cầu mô hình thử gọi lại lệnh gọi 3 lần

Cho đến nay, bạn đã đặt một câu hỏi cho Gemini, nhưng bạn cũng có thể trò chuyện nhiều lượt. Đó là những gì bạn sẽ tìm hiểu trong phần tiếp theo.

5. Trò chuyện với Gemini

Trong bước trước, bạn đã đặt một câu hỏi duy nhất. Đã đến lúc trò chuyện thực sự giữa người dùng và LLM. Mỗi câu hỏi và câu trả lời có thể dựa trên các câu hỏi và câu trả lời trước đó để tạo thành một cuộc thảo luận thực sự.

Hãy xem Conversation.java trong thư mục app/src/main/java/gemini/workshop:

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));
        });
    }
}

Một vài lệnh nhập mới thú vị trong lớp này:

  • MessageWindowChatMemory – một lớp giúp xử lý khía cạnh nhiều lượt của cuộc trò chuyện và lưu giữ các câu hỏi và câu trả lời trước đó trong bộ nhớ cục bộ
  • AiServices — một lớp học liên kết mô hình trò chuyện và bộ nhớ trò chuyện

Trong phương thức chính, bạn sẽ thiết lập mô hình, bộ nhớ trò chuyện và dịch vụ AI. Mô hình được định cấu hình như bình thường với thông tin về dự án, vị trí và tên mô hình.

Đối với bộ nhớ trò chuyện, chúng ta sử dụng trình tạo của MessageWindowChatMemory để tạo bộ nhớ lưu giữ 20 tin nhắn được trao đổi gần đây nhất. Đây là một cửa sổ trượt trên cuộc trò chuyện có ngữ cảnh được lưu giữ cục bộ trong ứng dụng lớp Java.

Sau đó, bạn tạo AI service để liên kết mô hình trò chuyện với bộ nhớ trò chuyện.

Hãy lưu ý cách dịch vụ AI sử dụng giao diện ConversationService tuỳ chỉnh mà chúng ta đã xác định, do LangChain4j triển khai, đồng thời nhận truy vấn String và trả về phản hồi String.

Bây giờ, đã đến lúc bạn trò chuyện với Gemini. Đầu tiên là một lời chào đơn giản được gửi đi, sau đó là câu hỏi đầu tiên về tháp Eiffel để biết có thể tìm thấy tháp ở quốc gia nào. Xin lưu ý rằng câu cuối cùng có liên quan đến câu trả lời của câu hỏi đầu tiên, vì bạn muốn biết có bao nhiêu người ở quốc gia có tháp Eiffel mà không đề cập rõ ràng quốc gia được nêu trong câu trả lời trước. Tính năng này cho thấy các câu hỏi và câu trả lời trước đây được gửi đi sau mỗi câu lệnh.

Chạy mẫu:

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

Bạn sẽ thấy 3 câu trả lời tương tự như sau:

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.

Bạn có thể đặt câu hỏi một lượt hoặc trò chuyện nhiều lượt với Gemini, nhưng cho đến nay, nội dung trả lời chỉ là văn bản. Còn hình ảnh thì sao? Hãy cùng tìm hiểu về hình ảnh trong bước tiếp theo.

6. Đa phương thức với Gemini

Gemini là một mô hình đa phương thức. API này không chỉ chấp nhận văn bản làm dữ liệu đầu vào, mà còn chấp nhận hình ảnh hoặc thậm chí là video làm dữ liệu đầu vào. Trong phần này, bạn sẽ thấy một trường hợp sử dụng để kết hợp văn bản và hình ảnh.

Bạn có nghĩ Gemini sẽ nhận ra chú mèo này không?

af00516493ec9ade.png

Hình ảnh một con mèo trong tuyết được lấy từ Wikipediahttps://upload.wikimedia.org/wikipedia/commons/b/b6/Felis_catus-cat_on_snow.jpg

Hãy xem Multimodal.java trong thư mục app/src/main/java/gemini/workshop:

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());
    }
}

Trong tệp nhập, hãy lưu ý rằng chúng ta có thể phân biệt các loại thông báo và nội dung khác nhau. UserMessage có thể chứa cả đối tượng TextContentImageContent. Đây là cách chơi đa phương thức: kết hợp văn bản và hình ảnh. Mô hình này sẽ gửi lại một Response chứa AiMessage.

Sau đó, bạn truy xuất AiMessage trong phản hồi qua content(), rồi truy xuất nội dung tin nhắn nhờ text().

Chạy mẫu:

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

Tên của hình ảnh chắc chắn đã cho bạn biết gợi ý về nội dung trong ảnh nhưng Gemini sẽ đưa ra kết quả tương tự như sau:

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.

Việc kết hợp hình ảnh và câu lệnh bằng văn bản sẽ mở ra các trường hợp sử dụng thú vị. Bạn có thể tạo các ứng dụng có thể:

  • Nhận dạng văn bản trong hình ảnh.
  • Kiểm tra xem hình ảnh có an toàn để hiển thị hay không.
  • Tạo chú thích cho hình ảnh.
  • Tìm kiếm trong cơ sở dữ liệu gồm các hình ảnh bằng nội dung mô tả dạng văn bản thuần tuý.

Ngoài việc trích xuất thông tin từ hình ảnh, bạn cũng có thể trích xuất thông tin từ văn bản không có cấu trúc. Đó là những gì bạn sẽ tìm hiểu trong phần tiếp theo.

7. Trích xuất thông tin có cấu trúc từ văn bản không có cấu trúc

Có nhiều trường hợp mà thông tin quan trọng được cung cấp trong tài liệu báo cáo, email hoặc văn bản dài khác theo cách không có cấu trúc. Lý tưởng nhất là bạn muốn có khả năng trích xuất chi tiết chính có trong văn bản không có cấu trúc, dưới dạng các đối tượng có cấu trúc. Hãy xem cách thực hiện.

Giả sử bạn muốn trích xuất tên và tuổi của một người dựa trên tiểu sử hoặc thông tin mô tả về người đó. Bạn có thể hướng dẫn LLM trích xuất JSON từ văn bản không có cấu trúc bằng một câu lệnh được tinh chỉnh thông minh (điều này thường được gọi là "kỹ thuật tạo câu lệnh").

Hãy xem ExtractData.java trong app/src/main/java/gemini/workshop:

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
    }
}

Hãy cùng tìm hiểu các bước trong tệp này:

  • Bản ghi Person được định nghĩa để đại diện cho các thông tin chi tiết mô tả một người ( tên và tuổi).
  • Giao diện PersonExtractor được xác định bằng một phương thức đã cho là một chuỗi văn bản không có cấu trúc, trả về một thực thể Person.
  • extractPerson() được chú thích bằng chú thích @UserMessage liên kết một lời nhắc với câu lệnh đó. Đó là lời nhắc mà mô hình sẽ sử dụng để trích xuất thông tin và trả về thông tin chi tiết dưới dạng tài liệu JSON. Tài liệu này sẽ được phân tích cú pháp cho bạn và được phân tích cú pháp thành một thực thể Person.

Bây giờ, hãy xem nội dung của phương thức main():

  • Mô hình trò chuyện đã được tạo thực thể. Hãy lưu ý chúng ta sử dụng temperature rất thấp là 0 và topK chỉ là 1, để đảm bảo đưa ra câu trả lời có tính tất định. Điều này cũng giúp mô hình làm theo hướng dẫn tốt hơn. Cụ thể, chúng ta không muốn Gemini gói phản hồi JSON bằng mã đánh dấu Markdown bổ sung.
  • Đối tượng PersonExtractor được tạo nhờ lớp AiServices của LangChain4j.
  • Sau đó, bạn chỉ cần gọi Person person = extractor.extractPerson(...) để trích xuất thông tin chi tiết về người đó từ văn bản không có cấu trúc và lấy lại một thực thể Person có tên và tuổi.

Chạy mẫu:

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

Bạn sẽ thấy kết quả sau đây:

Anna
23

Đúng, tôi là Anna và họ 23 tuổi!

Với phương pháp AiServices này, bạn hoạt động với các đối tượng được nhập mạnh. Bạn hiện không tương tác trực tiếp với mô hình ngôn ngữ lớn (LLM). Thay vào đó, bạn sẽ làm việc với các lớp cụ thể, chẳng hạn như bản ghi Person để biểu thị thông tin cá nhân đã trích xuất. Đồng thời, bạn có đối tượng PersonExtractor có phương thức extractPerson() trả về một thực thể Person. Khái niệm LLM bị loại bỏ và là nhà phát triển Java, bạn chỉ thao tác với các lớp và đối tượng thông thường.

8. Tạo cấu trúc câu lệnh bằng mẫu câu lệnh

Khi bạn tương tác với một LLM bằng một bộ hướng dẫn hoặc câu hỏi thông thường, có một phần trong câu lệnh đó không bao giờ thay đổi, trong khi các phần khác chứa dữ liệu. Ví dụ: nếu muốn tạo công thức nấu ăn, bạn có thể sử dụng câu lệnh như "Bạn là một đầu bếp tài năng, hãy tạo một công thức với những nguyên liệu sau đây: ...", sau đó bạn sẽ thêm các nguyên liệu đó vào cuối văn bản đó. Đây cũng chính là công cụ của mẫu lời nhắc, tương tự như chuỗi nội suy trong ngôn ngữ lập trình. Mẫu câu lệnh chứa phần giữ chỗ mà bạn có thể thay thế bằng dữ liệu phù hợp cho một lệnh gọi cụ thể đến LLM.

Cụ thể hơn, hãy tìm hiểu về TemplatePrompt.java trong thư mục app/src/main/java/gemini/workshop:

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());
    }
}

Như thường lệ, bạn định cấu hình mô hình VertexAiGeminiChatModel với mức độ sáng tạo cao với nhiệt độ cao cũng như giá trị topP và topK cao. Sau đó, bạn tạo một PromptTemplate với phương thức tĩnh from(), bằng cách truyền chuỗi lời nhắc của chúng ta và sử dụng các biến giữ chỗ dấu ngoặc nhọn: {{dish}}{{ingredients}}.

Bạn tạo câu lệnh cuối cùng bằng cách gọi apply(). Lệnh này sẽ lấy bản đồ các cặp khoá/giá trị đại diện cho tên của phần giữ chỗ và giá trị chuỗi để thay thế.

Cuối cùng, bạn gọi phương thức generate() của mô hình Gemini bằng cách tạo một thông báo cho người dùng từ câu lệnh đó, theo hướng dẫn prompt.toUserMessage().

Chạy mẫu:

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

Bạn sẽ thấy kết quả được tạo giống như sau:

**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.

Bạn có thể thay đổi giá trị của dishingredients trong bản đồ cũng như điều chỉnh nhiệt độ, topKtokP, rồi chạy lại mã. Việc này sẽ cho phép bạn quan sát tác động của việc thay đổi các tham số này đối với LLM.

Mẫu câu lệnh là một cách hay để có các hướng dẫn có thể sử dụng lại và có thể tham số cho các lệnh gọi LLM. Bạn có thể truyền dữ liệu và tuỳ chỉnh lời nhắc cho nhiều giá trị do người dùng cung cấp.

9. Phân loại văn bản bằng lời nhắc chỉ dùng một vài lần

Các LLM khá hiệu quả trong việc phân loại văn bản thành nhiều danh mục. Bạn có thể giúp một mô hình ngôn ngữ lớn (LLM) thực hiện nhiệm vụ đó bằng cách cung cấp một số ví dụ về văn bản và các danh mục liên quan. Phương pháp này thường được gọi là nhắc nhở vài cảnh.

Hãy xem TextClassification.java trong thư mục app/src/main/java/gemini/workshop để thực hiện một loại phân loại văn bản cụ thể: phân tích cảm xúc.

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());
    }
}

Trong phương thức main(), bạn sẽ tạo mô hình cuộc trò chuyện trên Gemini như bình thường, nhưng chỉ cung cấp một số ít mã thông báo đầu ra tối đa, vì bạn chỉ muốn nhận câu trả lời ngắn: văn bản là POSITIVE, NEGATIVE hoặc NEUTRAL.

Sau đó, bạn sẽ tạo một mẫu lời nhắc có thể sử dụng lại với kỹ thuật nhắc vài lần, bằng cách hướng dẫn cho mô hình này một vài ví dụ về dữ liệu đầu vào và đầu ra. Điều này cũng giúp mô hình theo dõi kết quả thực tế. Gemini sẽ không trả lời bằng một câu thổi phồng, thay vào đó, Gemini chỉ trả lời bằng một từ.

Bạn sẽ áp dụng các biến bằng phương thức apply() để thay thế phần giữ chỗ {{text}} bằng tham số thực ("I love strawberries"), rồi biến mẫu đó thành một thông báo cho người dùng bằng toUserMessage().

Chạy mẫu:

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

Bạn sẽ thấy một từ duy nhất:

POSITIVE

Có vẻ như tình cảm yêu thích dâu tây là một tình cảm tích cực!

10. Thế hệ tăng cường truy xuất

Các LLM được huấn luyện dựa trên một số lượng lớn văn bản. Tuy nhiên, kiến thức của họ chỉ bao gồm những thông tin mà AI nhìn thấy trong quá trình huấn luyện. Nếu có thông tin mới được công bố sau hạn chót huấn luyện mô hình, thì mô hình sẽ không nhận được những thông tin đó. Do đó, mô hình đó sẽ không thể trả lời các câu hỏi về thông tin mà nó chưa nhìn thấy.

Đó là lý do các phương pháp như Thế hệ tăng cường truy xuất (RAG) giúp cung cấp thêm thông tin mà một LLM có thể cần biết để đáp ứng yêu cầu của người dùng, nhằm trả lời bằng thông tin có thể mới hơn hoặc dựa trên thông tin riêng tư không thể truy cập được tại thời điểm đào tạo.

Hãy quay lại các cuộc trò chuyện. Lần này, bạn sẽ có thể đặt câu hỏi về giấy tờ của mình. Bạn sẽ xây dựng một bot trò chuyện có khả năng truy xuất thông tin liên quan từ cơ sở dữ liệu chứa tài liệu được chia thành nhiều phần nhỏ ("các khúc dữ liệu") và mô hình này sẽ sử dụng thông tin đó để làm căn cứ cho câu trả lời của mình, thay vì chỉ dựa vào kiến thức có trong quá trình huấn luyện.

Trong RAG, có 2 giai đoạn:

  1. Giai đoạn nhập – Tài liệu được tải trong bộ nhớ, chia thành các phần nhỏ hơn và các mục nhúng vectơ (đại diện vectơ đa chiều cao của các phân đoạn) được tính toán và lưu trữ trong một cơ sở dữ liệu vectơ có khả năng thực hiện tìm kiếm theo ngữ nghĩa. Giai đoạn nhập này thường được thực hiện một lần khi bạn cần thêm tài liệu mới vào tập sao lục tài liệu.

cd07d33d20ffa1c8.png

  1. Giai đoạn truy vấn – Giờ đây, người dùng có thể đặt câu hỏi về tài liệu. Câu hỏi cũng sẽ được chuyển đổi thành một vectơ và được so sánh với tất cả các vectơ khác trong cơ sở dữ liệu. Các vectơ giống nhau nhất thường có liên quan về mặt ngữ nghĩa và được cơ sở dữ liệu vectơ trả về. Sau đó, LLM được cung cấp ngữ cảnh của cuộc trò chuyện, các đoạn văn bản tương ứng với các vectơ mà cơ sở dữ liệu trả về và được yêu cầu đặt nền móng cho câu trả lời bằng cách xem xét các đoạn văn bản đó.

a1d2e2deb83c6d27.png

Chuẩn bị tài liệu

Đối với bản minh hoạ mới này, bạn sẽ đặt câu hỏi về bài viết nghiên cứu "Cần chú ý là tất cả những gì bạn cần". Tài liệu này mô tả kiến trúc mạng nơron biến áp do Google tiên phong triển khai. Đây là cách triển khai tất cả các mô hình ngôn ngữ lớn hiện đại ngày nay.

Bài viết này đã được tải xuống attention-is-all-you-need.pdf trong kho lưu trữ.

Triển khai chatbot

Hãy khám phá cách xây dựng phương pháp 2 giai đoạn: đầu tiên là với việc nhập tài liệu, sau đó là thời gian truy vấn khi người dùng đặt câu hỏi về tài liệu.

Trong ví dụ này, cả hai giai đoạn đều được triển khai trong cùng một lớp. Thông thường, bạn có một ứng dụng đảm nhận việc nhập và một ứng dụng khác cung cấp giao diện chatbot cho người dùng.

Ngoài ra, trong ví dụ này, chúng ta sẽ sử dụng một cơ sở dữ liệu vectơ trong bộ nhớ. Trong tình huống sản xuất thực tế, giai đoạn truyền dẫn và giai đoạn truy vấn sẽ được tách riêng trong 2 ứng dụng riêng biệt và các vectơ được lưu giữ trong một cơ sở dữ liệu độc lập.

Nhập tài liệu

Bước đầu tiên của giai đoạn nhập tài liệu là tìm tệp PDF mà chúng ta đã tải xuống rồi chuẩn bị một PdfParser để đọc tệp đó:

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());

Thay vì tạo mô hình ngôn ngữ trò chuyện thông thường, bạn tạo một phiên bản của mô hình nhúng. Đây là một mô hình cụ thể có vai trò tạo các đại diện vectơ của các đoạn văn bản (từ, câu hoặc thậm chí đoạn). Phương thức này trả về các vectơ của các số có dấu phẩy động, thay vì trả về phản hồi bằng văn bản.

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();

Tiếp theo, bạn cần cộng tác với một số lớp để:

  • Tải và chia tài liệu PDF theo nhiều phần.
  • Tạo các mục nhúng vectơ cho tất cả các phần này.
InMemoryEmbeddingStore<TextSegment> embeddingStore = 
    new InMemoryEmbeddingStore<>();

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

Một thực thể của InMemoryEmbeddingStore (cơ sở dữ liệu vectơ trong bộ nhớ) được tạo để lưu trữ các vectơ nhúng.

Tài liệu được chia thành nhiều phần nhờ lớp DocumentSplitters. Nó sẽ chia văn bản của tệp PDF thành các đoạn mã gồm 500 ký tự, với sự chồng chéo 100 ký tự (với đoạn sau, để tránh cắt các từ hoặc câu, thành từng đoạn).

Trình nhập cửa hàng liên kết bộ chia tài liệu, mô hình nhúng để tính toán vectơ và cơ sở dữ liệu vectơ trong bộ nhớ. Sau đó, phương thức ingest() sẽ xử lý quá trình nhập.

Giờ đây, giai đoạn đầu tiên đã kết thúc, tài liệu đã được chuyển đổi thành các phần văn bản với các mục nhúng vectơ liên quan và được lưu trữ trong cơ sở dữ liệu vectơ.

Đặt câu hỏi

Đã đến lúc chuẩn bị đặt câu hỏi! Tạo mẫu trò chuyện để bắt đầu cuộc trò chuyện:

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

Bạn cũng cần một lớp truy xuất để liên kết cơ sở dữ liệu vectơ (trong biến embeddingStore) với mô hình nhúng. Nhiệm vụ của nó là truy vấn cơ sở dữ liệu vectơ bằng cách tính toán nhúng vectơ cho truy vấn của người dùng, để tìm các vectơ tương tự trong cơ sở dữ liệu:

EmbeddingStoreContentRetriever retriever =
    new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);

Ngoài phương thức chính, hãy tạo một giao diện đại diện cho trợ lý chuyên gia LLM. Đây là giao diện mà lớp AiServices sẽ triển khai để bạn tương tác với mô hình:

interface LlmExpert {
    String ask(String question);
}

Tại thời điểm này, bạn có thể định cấu hình một dịch vụ AI mới:

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

Dịch vụ này liên kết với nhau:

  • Mô hình ngôn ngữ trò chuyện mà bạn đã thiết lập trước đó.
  • Bộ nhớ trò chuyện để theo dõi cuộc trò chuyện.
  • Trình truy xuất so sánh một truy vấn nhúng vectơ với các vectơ trong cơ sở dữ liệu.
  • Mẫu lời nhắc cho biết rõ rằng mô hình trò chuyện phải trả lời bằng cách đưa ra câu trả lời dựa trên thông tin được cung cấp (tức là các phần trích dẫn có liên quan của tài liệu có nhúng vectơ tương tự như vectơ câu hỏi của người dùng).
.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())

Cuối cùng, bạn đã sẵn sàng đặt câu hỏi!

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)));
);

Mã nguồn đầy đủ nằm trong RAG.java trong thư mục app/src/main/java/gemini/workshop:

Chạy mẫu:

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

Trong kết quả, bạn sẽ thấy câu trả lời cho các câu hỏi của mình:

=== 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. Gọi hàm

Cũng có trường hợp bạn muốn một LLM có quyền truy cập vào các hệ thống bên ngoài, chẳng hạn như một API web từ xa để truy xuất thông tin hoặc có một hành động, hoặc các dịch vụ thực hiện một số loại phép tính. Ví dụ:

API web từ xa:

  • Theo dõi và cập nhật đơn đặt hàng của khách hàng.
  • Tìm hoặc tạo phiếu yêu cầu hỗ trợ trong công cụ theo dõi lỗi.
  • Tìm nạp dữ liệu theo thời gian thực như giá cổ phiếu hoặc phép đo cảm biến IoT.
  • Gửi email.

Công cụ tính toán:

  • Một máy tính cho những bài toán nâng cao hơn.
  • Diễn giải mã để chạy mã khi các LLM cần logic suy luận.
  • Chuyển đổi các yêu cầu bằng ngôn ngữ tự nhiên thành truy vấn SQL để một LLM có thể truy vấn một cơ sở dữ liệu.

Lệnh gọi hàm là khả năng để mô hình yêu cầu thực hiện một hoặc nhiều lệnh gọi hàm thay mặt mình. Nhờ đó, mô hình có thể trả lời đúng câu lệnh của người dùng bằng dữ liệu mới hơn.

Khi nhận được một câu lệnh cụ thể của người dùng và khi biết các hàm hiện có có liên quan đến bối cảnh đó, một LLM có thể trả lời bằng một yêu cầu gọi hàm. Sau đó, ứng dụng tích hợp LLM có thể gọi hàm rồi trả lời lại cho LLM bằng câu trả lời. Sau đó, LLM này sẽ diễn giải lại bằng cách trả lời bằng câu trả lời dạng văn bản.

Bốn bước gọi hàm

Hãy xem ví dụ về lệnh gọi hàm: lấy thông tin dự báo thời tiết.

Nếu bạn hỏi Gemini hoặc bất kỳ mô hình ngôn ngữ lớn nào khác về thời tiết ở Paris, thì họ sẽ trả lời bằng cách nói rằng Gemini không có thông tin về dự báo thời tiết. Nếu muốn LLM truy cập dữ liệu thời tiết theo thời gian thực, bạn cần xác định một số chức năng mà LLM này có thể sử dụng.

Hãy xem sơ đồ dưới đây:

31e0c2aba5e6f21c.png.

1️️ Đầu tiên, một người dùng hỏi về thời tiết ở Paris. Ứng dụng bot trò chuyện biết rằng có một hoặc nhiều chức năng có thể dùng để giúp LLM thực hiện truy vấn. Bot trò chuyện sẽ gửi cả lời nhắc ban đầu và danh sách hàm có thể gọi. Ở đây, một hàm có tên là getWeather(). Hàm này sẽ lấy tham số chuỗi cho vị trí đó.

8863be53a73c4a70.pngS

Vì LLM không biết về thông tin dự báo thời tiết nên thay vì trả lời qua văn bản, LLM này sẽ gửi lại một yêu cầu thực thi hàm. Bot trò chuyện phải gọi hàm getWeather() với "Paris" là tham số vị trí.

d1367cc69c07b14d.png

2️️ Bot trò chuyện gọi chức năng đó thay mặt cho LLM và truy xuất phản hồi của hàm. Ở đây, chúng ta giả định rằng phản hồi là {"forecast": "sunny"}.

73a5f2ed19f47d8.pngS.

3️️ Ứng dụng chatbot sẽ gửi phản hồi JSON lại cho mô hình ngôn ngữ lớn (LLM).

20832cb1ee6fbfc.pngS

4️️ LLM xem xét phản hồi JSON, diễn giải thông tin đó và cuối cùng trả lời bằng văn bản cho biết thời tiết ở Paris đang nắng.

Từng bước dưới dạng mã

Trước tiên, bạn sẽ định cấu hình mô hình Gemini như bình thường:

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

Bạn chỉ định thông số của công cụ để mô tả hàm có thể được gọi:

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();

Tên của hàm cũng như tên và loại của tham số đã được định nghĩa, nhưng xin lưu ý rằng cả hàm và tham số đều được cung cấp nội dung mô tả. Nội dung mô tả rất quan trọng và giúp LLM thực sự hiểu được chức năng của một hàm, từ đó đánh giá xem chức năng này có cần được gọi trong bối cảnh trò chuyện hay không.

Hãy bắt đầu bước 1 bằng cách gửi câu hỏi ban đầu về thời tiết ở Paris:

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);

Ở bước 2, chúng ta chuyển công cụ mà chúng ta muốn mô hình sử dụng và mô hình phản hồi bằng một yêu cầu thực thi:

// 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());

Bước 3. Đến đây, chúng tôi biết LLM muốn chúng tôi gọi đến chức năng nào. Trong mã, chúng ta không thực hiện lệnh gọi đến một API bên ngoài, mà chỉ trả về trực tiếp một thông tin dự báo thời tiết giả định:

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

Ở bước 4, LLM tìm hiểu về kết quả thực thi hàm, sau đó có thể tổng hợp một câu trả lời dạng văn bản:

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

Kết quả là:

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.

Bạn có thể xem kết quả phía trên yêu cầu thực thi công cụ, cũng như câu trả lời.

Mã nguồn đầy đủ nằm trong FunctionCalling.java trong thư mục app/src/main/java/gemini/workshop:

Chạy mẫu:

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

Bạn sẽ thấy kết quả tương tự như sau:

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 xử lý lệnh gọi hàm

Ở bước trước, bạn đã thấy cách các hoạt động tương tác giữa câu hỏi/câu trả lời thông thường và yêu cầu/phản hồi của hàm, và ở giữa, bạn đã trực tiếp cung cấp phản hồi hàm được yêu cầu mà không gọi một hàm thực.

Tuy nhiên, LangChain4j cũng cung cấp một mô hình trừu tượng ở cấp cao hơn để có thể xử lý các lệnh gọi hàm một cách minh bạch cho bạn mà vẫn xử lý cuộc trò chuyện như bình thường.

Lệnh gọi một hàm

Hãy cùng xem FunctionCallingAssistant.java theo từng phần.

Trước tiên, bạn tạo một bản ghi đại diện cho cấu trúc dữ liệu phản hồi của hàm:

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

Câu trả lời chứa thông tin về vị trí, thông tin dự báo và nhiệt độ.

Sau đó, bạn tạo một lớp chứa hàm thực tế mà bạn muốn cung cấp cho mô hình:

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);
        }
    }
}

Xin lưu ý rằng lớp này chứa một hàm duy nhất, nhưng được chú thích bằng chú thích @Tool tương ứng với nội dung mô tả về hàm mà mô hình có thể yêu cầu gọi.

Các tham số của hàm (chỉ có một tham số ở đây) cũng được chú thích, nhưng với chú thích @P ngắn này, cũng cung cấp nội dung mô tả về tham số. Bạn có thể thêm bao nhiêu hàm tuỳ thích để mô hình có thể sử dụng trong các trường hợp phức tạp hơn.

Trong lớp này, bạn sẽ trả về một số câu trả lời soạn trước, nhưng nếu bạn muốn gọi một dịch vụ dự báo thời tiết bên ngoài thực, thì đây nằm trong phần nội dung của phương thức mà bạn sẽ thực hiện lệnh gọi đến dịch vụ đó.

Như chúng ta đã thấy khi tạo ToolSpecification ở phương pháp trước, bạn cần ghi lại chức năng của hàm và mô tả các tham số tương ứng với nhau. Điều này giúp mô hình hiểu cách thức và thời điểm hàm này có thể được sử dụng.

Tiếp theo, LangChain4j cho phép bạn cung cấp một giao diện tương ứng với hợp đồng mà bạn muốn sử dụng để tương tác với mô hình. Ở đây là một giao diện đơn giản, lấy một chuỗi đại diện cho thông báo của người dùng và trả về một chuỗi tương ứng với phản hồi của mô hình:

interface WeatherAssistant {
    String chat(String userMessage);
}

Bạn cũng có thể sử dụng các chữ ký phức tạp hơn liên quan đến UserMessage của LangChain4j (cho thông báo của người dùng) hoặc AiMessage (cho phản hồi mô hình) hoặc thậm chí là TokenStream, nếu muốn xử lý các tình huống nâng cao hơn, vì những đối tượng phức tạp hơn đó cũng chứa thêm thông tin như số lượng mã thông báo đã sử dụng, v.v. Nhưng để đơn giản, chúng ta sẽ chỉ lấy chuỗi trong dữ liệu đầu vào và chuỗi trong đầu ra.

Hãy hoàn tất bằng phương thức main() liên kết tất cả các phần với nhau:

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?"));
}

Như thường lệ, bạn sẽ thiết lập mô hình trò chuyện trên Gemini. Sau đó, bạn sẽ tạo thực thể cho dịch vụ dự báo thời tiết có chứa "hàm" mà mô hình sẽ yêu cầu chúng tôi gọi.

Bây giờ, bạn sẽ sử dụng lại lớp AiServices để liên kết mô hình trò chuyện, bộ nhớ trò chuyện và công cụ (cụ thể là dịch vụ dự báo thời tiết với hàm tương ứng). AiServices trả về một đối tượng sẽ triển khai giao diện WeatherAssistant mà bạn đã xác định. Việc duy nhất còn lại là gọi phương thức chat() của trợ lý đó. Khi gọi hàm này, bạn sẽ chỉ thấy phản hồi bằng văn bản, nhưng nhà phát triển sẽ không nhìn thấy các yêu cầu gọi hàm và phản hồi lệnh gọi hàm, đồng thời các yêu cầu đó sẽ được xử lý tự động và minh bạch. Nếu cho rằng nên gọi một hàm, Gemini sẽ phản hồi bằng yêu cầu gọi hàm và LangChain4j sẽ thay mặt bạn đảm nhận việc gọi hàm cục bộ.

Chạy mẫu:

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

Bạn sẽ thấy kết quả tương tự như sau:

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

Đây là ví dụ về một hàm đơn lẻ.

Nhiều lệnh gọi hàm

Bạn cũng có thể sử dụng nhiều hàm và để LangChain4j thay mặt bạn xử lý nhiều lệnh gọi hàm. Hãy xem MultiFunctionCallingAssistant.java để tham khảo ví dụ về nhiều hàm.

Thẻ này có chức năng chuyển đổi đơn vị tiền tệ:

@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;
}

Một hàm khác để nhận giá trị của một cổ phiếu:

@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;
}

Một hàm khác để áp dụng tỷ lệ phần trăm cho một số tiền nhất định:

@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;
}

Sau đó, bạn có thể kết hợp tất cả các chức năng này và một lớp MultiTools rồi đặt các câu hỏi như "10% giá cổ phiếu AAPL chuyển đổi từ USD sang EUR là bao nhiêu?"

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?"));
}

Hãy chạy như sau:

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

Bạn sẽ thấy nhiều hàm được gọi:

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.

Hướng tới nhân viên hỗ trợ

Gọi hàm là một cơ chế mở rộng hữu ích dành cho các mô hình ngôn ngữ lớn như Gemini. AI của Google cho phép chúng tôi xây dựng những hệ thống phức tạp hơn, thường được gọi là "nhân viên hỗ trợ" hoặc "trợ lý AI". Những nhân viên hỗ trợ này có thể tương tác với thế giới bên ngoài thông qua API bên ngoài và với các dịch vụ có thể có ảnh hưởng phụ đến môi trường bên ngoài (như gửi email, tạo phiếu yêu cầu hỗ trợ, v.v.)

Bạn nên tạo ra những nhân viên hỗ trợ mạnh mẽ như vậy một cách có trách nhiệm. Bạn nên cân nhắc cơ chế hoạt động bình thường trước khi thực hiện thao tác tự động. Điều quan trọng là phải đảm bảo an toàn khi thiết kế những tác nhân sử dụng LLM (mô hình ngôn ngữ lớn) có khả năng tương tác với thế giới bên ngoài.

13. Chạy Gemma bằng Ollama và TestContainers

Đến nay, chúng tôi vẫn dùng Gemini, nhưng ngoài ra còn có Gemma, một mô hình chị em nhỏ của Gemini.

Gemma là một dòng mô hình mở, gọn nhẹ, tiên tiến được xây dựng từ chính nghiên cứu và công nghệ dùng để tạo các mô hình Gemini. Gemma có hai biến thể Gemma1 và Gemma2, mỗi biến thể có nhiều kích thước khác nhau. Gemma1 có hai kích thước: 2B và 7B. Gemma2 có hai kích thước: 9B và 27B. Trọng lượng của các mô-đun này được cung cấp thoải mái và bạn có thể tự chạy thiết bị, kể cả trên máy tính xách tay hoặc trong Cloud Shell.

Bạn chạy Gemma như thế nào?

Có nhiều cách để chạy Gemma: trên đám mây, dùng Vertex AI chỉ với một cú nhấp chuột hoặc dùng GKE với một số GPU, nhưng bạn cũng có thể chạy trên máy tính.

Một lựa chọn phù hợp để chạy Gemma trên thiết bị là Ollama, một công cụ hỗ trợ bạn chạy các mô hình nhỏ, như Llama 2, Mistral và nhiều mô hình khác trên máy cục bộ. Tương tự như Docker nhưng dành cho các mô hình ngôn ngữ lớn (LLM).

Cài đặt Ollama theo hướng dẫn dành cho Hệ điều hành của bạn.

Nếu đang sử dụng môi trường Linux, bạn cần bật Ollama trước sau khi cài đặt.

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

Sau khi cài đặt trên máy, bạn có thể chạy các lệnh để kéo một mô hình:

ollama pull gemma:2b

Chờ mô hình được kéo. Quá trình này có thể mất chút thời gian.

Chạy mô hình:

ollama run gemma:2b

Giờ đây, bạn có thể tương tác với mô hình:

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

Để thoát khỏi lời nhắc, hãy nhấn Ctrl+D

Chạy Gemma ở Ollama trên TestContainers

Thay vì phải cài đặt và chạy Ollama cục bộ, bạn có thể sử dụng Ollama trong một vùng chứa do TestContainers xử lý.

TestContainers không chỉ hữu ích cho việc kiểm thử mà bạn còn có thể sử dụng để thực thi các vùng chứa. Thậm chí còn có một OllamaContainer cụ thể mà bạn có thể tận dụng!

Sau đây là toàn bộ bức tranh:

2382c05a48708dfd.png.

Triển khai

Hãy cùng xem GemmaWithOllamaContainer.java theo từng phần.

Trước tiên, bạn cần tạo một vùng chứa Ollama phái sinh để lấy mô hình Gemma. Hình ảnh này đã tồn tại từ lần chạy trước hoặc sẽ được tạo. Nếu hình ảnh đã tồn tại, bạn chỉ cần cho TestContainers biết rằng bạn muốn thay thế hình ảnh Ollama mặc định bằng biến thể được hỗ trợ bởi Gemma:

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"));
    }
}

Tiếp theo, bạn tạo và bắt đầu một vùng chứa thử nghiệm Ollama, rồi tạo một mô hình trò chuyện tại Ollama bằng cách trỏ vào địa chỉ và cổng của vùng chứa có mô hình mà bạn muốn sử dụng. Cuối cùng, bạn chỉ cần gọi model.generate(yourPrompt) như thường lệ:

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);
}

Hãy chạy như sau:

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

Lần chạy đầu tiên sẽ mất chút thời gian để tạo và chạy vùng chứa nhưng sau khi hoàn tất, bạn sẽ thấy Gemma phản hồi:

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.

Bạn có Gemma đang chạy trong Cloud Shell!

14. Xin chúc mừng

Xin chúc mừng! Bạn đã tạo thành công ứng dụng trò chuyện dựa trên AI tạo sinh đầu tiên trong Java bằng LangChain4j và Gemini API! Trong quá trình này, bạn đã phát hiện ra rằng các mô hình ngôn ngữ lớn đa phương thức khá mạnh mẽ và có thể xử lý nhiều công việc như đặt câu hỏi/trả lời, ngay cả trong tài liệu của riêng bạn, trích xuất dữ liệu, tương tác với API bên ngoài, v.v.

Tiếp theo là gì?

Đã đến lượt bạn cải thiện ứng dụng bằng các tiện ích tích hợp LLM (mô hình ngôn ngữ lớn) mạnh mẽ!

Tài liệu đọc thêm

Tài liệu tham khảo