1. Введение
В этой лаборатории кода основное внимание уделяется модели большого языка Gemini (LLM), размещенной на Vertex AI в облаке Google. Vertex AI — это платформа, которая включает в себя все продукты, услуги и модели машинного обучения в Google Cloud.
Вы будете использовать Java для взаимодействия с API Gemini с помощью платформы LangChain4j . Вы рассмотрите конкретные примеры, чтобы воспользоваться преимуществами LLM для ответов на вопросы, генерации идей, извлечения сущностей и структурированного контента, генерации расширенного поиска и вызова функций.
Что такое генеративный ИИ?
Генеративный ИИ подразумевает использование искусственного интеллекта для создания нового контента, такого как текст, изображения, музыка, аудио и видео.
Генеративный ИИ основан на больших языковых моделях (LLM), которые могут выполнять несколько задач одновременно и выполнять нестандартные задачи, такие как обобщение, вопросы и ответы, классификация и многое другое. При минимальном обучении базовые модели можно адаптировать для целевых случаев использования с очень небольшим количеством примеров данных.
Как работает генеративный ИИ?
Генеративный ИИ работает с использованием модели машинного обучения (ML) для изучения закономерностей и взаимосвязей в наборе данных контента, созданного человеком. Затем он использует изученные шаблоны для создания нового контента.
Самый распространенный способ обучения генеративной модели ИИ — использование обучения с учителем. Модель получает набор созданного человеком контента и соответствующих меток. Затем он учится генерировать контент, похожий на контент, созданный человеком.
Каковы распространенные приложения генеративного ИИ?
Генеративный ИИ может использоваться для:
- Улучшите взаимодействие с клиентами с помощью расширенного чата и поиска.
- Исследуйте огромные объемы неструктурированных данных с помощью диалоговых интерфейсов и обобщений.
- Помогите выполнять повторяющиеся задачи, такие как ответы на запросы предложений, локализация маркетингового контента на разные языки, проверка договоров с клиентами на соответствие требованиям и многое другое.
Какие предложения генеративного искусственного интеллекта есть в Google Cloud?
С помощью Vertex AI вы можете взаимодействовать, настраивать и встраивать базовые модели в свои приложения, практически не обладая знаниями в области машинного обучения. Вы можете получить доступ к базовым моделям в Model Garden , настроить модели через простой пользовательский интерфейс в Vertex AI Studio или использовать модели в блокноте для анализа данных.
Vertex AI Search and Conversation предлагает разработчикам самый быстрый способ создания генеративных поисковых систем и чат-ботов на базе искусственного интеллекта.
Gemini for Google Cloud на базе Gemini — это средство совместной работы на базе искусственного интеллекта, доступное в Google Cloud и IDE, которое поможет вам делать больше и быстрее. Gemini Code Assist обеспечивает завершение кода, генерацию кода, объяснения кода и позволяет вам общаться с ним в чате, чтобы задавать технические вопросы.
Что такое Близнецы?
Gemini — это семейство генеративных моделей искусственного интеллекта, разработанное Google DeepMind и предназначенное для мультимодальных случаев использования. Мультимодальность означает, что он может обрабатывать и генерировать различные виды контента, такие как текст, код, изображения и аудио.
Близнецы бывают разных вариаций и размеров:
- Gemini Ultra : самая большая и мощная версия для сложных задач.
- Gemini Flash : самый быстрый и экономичный, оптимизированный для задач большого объема.
- Gemini Pro : среднего размера, оптимизирован для масштабирования для решения различных задач.
- Gemini Nano : самый эффективный, предназначен для задач на устройстве.
Ключевые особенности:
- Мультимодальность : способность Gemini понимать и обрабатывать несколько форматов информации является значительным шагом по сравнению с традиционными языковыми моделями, состоящими только из текста.
- Производительность : Gemini Ultra превосходит современные модели по многим тестам и является первой моделью, которая превзошла экспертов-людей в сложном тесте MMLU (массовое многозадачное понимание языка).
- Гибкость : различные размеры Gemini позволяют адаптировать его для различных случаев использования: от крупномасштабных исследований до развертывания на мобильных устройствах.
Как вы можете взаимодействовать с Gemini в Vertex AI из Java?
У вас есть два варианта:
- Официальный Java API Vertex AI для библиотеки Gemini .
- Фреймворк LangChain4j .
В этой лаборатории кода вы будете использовать платформу LangChain4j .
Что такое платформа LangChain4j?
Платформа LangChain4j — это библиотека с открытым исходным кодом для интеграции LLM в ваши приложения Java путем координации различных компонентов, таких как сам LLM, а также других инструментов, таких как векторные базы данных (для семантического поиска), загрузчики документов и разделители (для анализа документов и обучения). из них), парсеры вывода и многое другое.
Проект был вдохновлен проектом LangChain Python, но его целью было обслуживание разработчиков Java.
Что вы узнаете
- Как настроить проект Java для использования Gemini и LangChain4j
- Как отправить первое приглашение в Gemini программно
- Как транслировать ответы от Gemini
- Как создать разговор между пользователем и Близнецами
- Как использовать Gemini в мультимодальном контексте, отправляя как текст, так и изображения
- Как извлечь полезную структурированную информацию из неструктурированного контента
- Как манипулировать шаблонами подсказок
- Как выполнить классификацию текста, например анализ настроений
- Как общаться со своими собственными документами (Поисковая расширенная генерация)
- Как расширить возможности ваших чат-ботов вызовом функций
- Как использовать Gemma локально с Ollama и TestContainers
Что вам понадобится
- Знание языка программирования Java
- Проект Google Cloud
- Браузер, например Chrome или Firefox.
2. Настройка и требования
Самостоятельная настройка среды
- Войдите в Google Cloud Console и создайте новый проект или повторно используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .
- Имя проекта — это отображаемое имя для участников этого проекта. Это строка символов, не используемая API Google. Вы всегда можете обновить его.
- Идентификатор проекта уникален для всех проектов Google Cloud и является неизменяемым (невозможно изменить после его установки). Cloud Console автоматически генерирует уникальную строку; обычно тебя не волнует, что это такое. В большинстве лабораторий кода вам потребуется указать идентификатор проекта (обычно идентифицируемый как
PROJECT_ID
). Если вам не нравится сгенерированный идентификатор, вы можете создать другой случайный идентификатор. Альтернативно, вы можете попробовать свой собственный и посмотреть, доступен ли он. Его нельзя изменить после этого шага и он сохраняется на протяжении всего проекта. - К вашему сведению, есть третье значение — номер проекта , которое используют некоторые API. Подробнее обо всех трех этих значениях читайте в документации .
- Затем вам необходимо включить выставление счетов в Cloud Console, чтобы использовать облачные ресурсы/API. Прохождение этой лаборатории кода не будет стоить много, если вообще что-то стоить. Чтобы отключить ресурсы и избежать выставления счетов за пределами этого руководства, вы можете удалить созданные вами ресурсы или удалить проект. Новые пользователи Google Cloud имеют право на участие в программе бесплатной пробной версии стоимостью 300 долларов США .
Запустить Cloud Shell
Хотя Google Cloud можно управлять удаленно с вашего ноутбука, в этой лаборатории вы будете использовать Cloud Shell , среду командной строки, работающую в облаке.
Активировать Cloud Shell
- В Cloud Console нажмите «Активировать Cloud Shell».
.
Если вы запускаете Cloud Shell впервые, вы увидите промежуточный экран с описанием того, что это такое. Если вам был представлен промежуточный экран, нажмите «Продолжить» .
Подготовка и подключение к Cloud Shell займет всего несколько минут.
Эта виртуальная машина загружена всеми необходимыми инструментами разработки. Он предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Большую часть, если не всю, работу в этой лаборатории кода можно выполнить с помощью браузера.
После подключения к Cloud Shell вы увидите, что вы прошли аутентификацию и что для проекта установлен идентификатор вашего проекта.
- Выполните следующую команду в Cloud Shell, чтобы подтвердить, что вы прошли аутентификацию:
gcloud auth list
Вывод команды
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Выполните следующую команду в Cloud Shell, чтобы убедиться, что команда gcloud знает о вашем проекте:
gcloud config list project
Вывод команды
[core] project = <PROJECT_ID>
Если это не так, вы можете установить это с помощью этой команды:
gcloud config set project <PROJECT_ID>
Вывод команды
Updated property [core/project].
3. Подготовка среды разработки
В этой лаборатории кода вы будете использовать терминал Cloud Shell и редактор Cloud Shell для разработки программ на Java.
Включить API Vertex AI
В консоли Google Cloud убедитесь, что название вашего проекта отображается в верхней части консоли Google Cloud . Если это не так, нажмите «Выбрать проект» , чтобы открыть « Выбор проекта» , и выберите нужный проект.
Вы можете включить API Vertex AI либо из раздела Vertex AI консоли Google Cloud, либо из терминала Cloud Shell.
Чтобы включить консоль Google Cloud, сначала перейдите в раздел Vertex AI меню консоли Google Cloud:
Нажмите «Включить все рекомендуемые API» на панели управления Vertex AI.
Это активирует несколько API, но наиболее важным для лаборатории кода является aiplatform.googleapis.com
.
Альтернативно вы также можете включить этот API из терминала Cloud Shell с помощью следующей команды:
gcloud services enable aiplatform.googleapis.com
Клонировать репозиторий Github
В терминале Cloud Shell клонируйте репозиторий для этой лаборатории кода:
git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git
Чтобы проверить готовность проекта к запуску, вы можете попробовать запустить программу «Hello World».
Убедитесь, что вы находитесь в папке верхнего уровня:
cd gemini-workshop-for-java-developers/
Создайте оболочку Gradle:
gradle wrapper
Запускаем с помощью gradlew
:
./gradlew run
Вы должны увидеть следующий вывод:
.. > Task :app:run Hello World!
Откройте и настройте Cloud Editor
Откройте код с помощью редактора кода Cloud из Cloud Shell:
В редакторе кода Cloud откройте папку с исходным кодом, выбрав File
-> Open Folder
и укажите папку с исходным кодом (например, /home/username/gemini-workshop-for-java-developers/
).
Настройка переменных среды
Откройте новый терминал в Cloud Code Editor, выбрав Terminal
-> New Terminal
. Настройте две переменные среды, необходимые для запуска примеров кода:
- PROJECT_ID — идентификатор вашего проекта Google Cloud.
- РАСПОЛОЖЕНИЕ — регион, в котором развернута модель Gemini.
Экспортируйте переменные следующим образом:
export PROJECT_ID=$(gcloud config get-value project) export LOCATION=us-central1
4. Первый вызов модели Близнецов
Теперь, когда проект правильно настроен, пришло время вызвать Gemini API.
Взгляните на QA.java
в каталоге 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-002")
.build();
System.out.println(model.generate("Why is the sky blue?"));
}
}
В этом первом примере вам необходимо импортировать класс VertexAiGeminiChatModel
, который реализует интерфейс ChatModel
.
В main
методе вы настраиваете модель языка чата с помощью построителя VertexAiGeminiChatModel
и указываете:
- Проект
- Расположение
- Название модели (
gemini-1.5-flash-002
).
Теперь, когда языковая модель готова, вы можете вызвать generate()
и передать приглашение, вопрос или инструкции для отправки в LLM. Здесь вы задаете простой вопрос о том, что делает небо голубым.
Не стесняйтесь изменять это приглашение, чтобы попробовать разные вопросы или задания.
Запустите образец в корневой папке исходного кода:
./gradlew run -q -DjavaMainClass=gemini.workshop.QA
Вы должны увидеть вывод, похожий на этот:
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.
Поздравляем, вы сделали первый звонок в Близнецы!
Потоковое ответ
Вы заметили, что ответ был дан сразу, через несколько секунд? Также возможно получать ответ постепенно благодаря варианту потокового ответа. При потоковом ответе модель возвращает ответ по частям по мере его поступления.
В этой лаборатории кода мы будем придерживаться непотокового ответа, но давайте посмотрим на потоковый ответ, чтобы увидеть, как это можно сделать.
В StreamQA.java
в каталоге app/src/main/java/gemini/workshop
вы можете увидеть потоковый ответ в действии:
package gemini.workshop;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiStreamingChatModel;
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onNext;
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-002")
.maxOutputTokens(4000)
.build();
model.generate("Why is the sky blue?", onNext(System.out::println));
}
}
На этот раз мы импортируем варианты класса потоковой передачи VertexAiGeminiStreamingChatModel
, который реализует интерфейс StreamingChatLanguageModel
. Вам также потребуется статический импорт LambdaStreamingResponseHandler.onNext
, который представляет собой удобный метод, предоставляющий StreamingResponseHandler
для создания обработчика потоковой передачи с лямбда-выражениями Java.
На этот раз сигнатура generate()
немного другая. Вместо возврата строки тип возвращаемого значения — void. В дополнение к приглашению вам необходимо передать обработчик потокового ответа. Здесь, благодаря статическому импорту, о котором мы упоминали выше, мы можем определить лямбда-выражение, которое вы передаете методу onNext()
. Лямбда-выражение вызывается каждый раз, когда доступна новая часть ответа, а последнее вызывается только в случае возникновения ошибки.
Бегать:
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
Вы получите ответ, аналогичный предыдущему классу, но на этот раз вы заметите, что ответ постепенно появляется в вашей оболочке, а не ждет отображения полного ответа.
Дополнительная конфигурация
Для конфигурации мы определили только проект, местоположение и имя модели, но есть и другие параметры, которые вы можете указать для модели:
-
temperature(Float temp)
— чтобы определить, насколько креативным должен быть ответ (0 — низкий уровень креативности и часто более фактический, а 2 — для более творческих результатов) -
topP(Float topP)
— для выбора возможных слов, общая вероятность которых в сумме равна этому числу с плавающей запятой (от 0 до 1) -
topK(Integer topK)
— случайным образом выбрать слово из максимального количества вероятных слов для завершения текста (от 1 до 40) -
maxOutputTokens(Integer max)
— чтобы указать максимальную длину ответа, заданного моделью (обычно 4 токена представляют примерно 3 слова) -
maxRetries(Integer retries)
— если вы превышаете квоту запроса на время или на платформе возникла какая-то техническая проблема, вы можете попросить модель повторить вызов 3 раза.
Пока что вы задали Близнецам один вопрос, но можете вести и многоходовой разговор. Это то, что вы изучите в следующем разделе.
5. Общайтесь с Близнецами
На предыдущем шаге вы задали один вопрос. Настало время настоящего разговора между пользователем и LLM. Каждый вопрос и ответ могут основываться на предыдущих, образуя настоящую дискуссию.
Взгляните на Conversation.java
в папке 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-002")
.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));
});
}
}
Пара новых интересных импортов в этом классе:
-
MessageWindowChatMemory
— класс, который поможет обрабатывать многоходовой аспект разговора и сохранять в локальной памяти предыдущие вопросы и ответы. -
AiServices
— класс абстракции более высокого уровня, который свяжет воедино модель чата и память чата.
В основном методе вы собираетесь настроить модель, память чата и службу искусственного интеллекта. Модель настраивается как обычно с использованием информации о проекте, местоположении и названии модели.
Для памяти чата мы используем построитель MessageWindowChatMemory
для создания памяти, в которой хранятся последние 20 сообщений, которыми обменивались. Это скользящее окно разговора, контекст которого хранится локально в нашем клиенте класса Java.
Затем вы создаете AI service
, которая связывает модель чата с памятью чата.
Обратите внимание, как служба AI использует определенный нами пользовательский интерфейс ConversationService
, который реализует LangChain4j, который принимает запрос String
и возвращает ответ String
.
Теперь пришло время поговорить с Близнецами. Сначала отправляется простое приветствие, затем первый вопрос об Эйфелевой башне, чтобы узнать, в какой стране ее можно найти. Обратите внимание, что последнее предложение связано с ответом на первый вопрос, поскольку вам интересно, сколько жителей в стране, где расположена Эйфелева башня, без явного упоминания страны, указанной в предыдущем ответе. Это показывает, что прошлые вопросы и ответы отправляются с каждым приглашением.
Запустите образец:
./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation
Вы должны увидеть три ответа, похожих на эти:
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.
Вы можете задавать одноходовые вопросы или вести многоходовые беседы с Близнецами, но до сих пор вводимые данные представляли собой только текст. А как насчет изображений? Давайте рассмотрим изображения на следующем этапе.
6. Мультимодальность с Близнецами
Gemini — мультимодальная модель. Он не только принимает текст в качестве входных данных, но также принимает изображения или даже видео в качестве входных данных. В этом разделе вы увидите вариант использования для смешивания текста и изображений.
Как вы думаете, Близнецы узнают этого кота?
Фотография кота на снегу взята из Википедии https://upload.wikimedia.org/wikipedia/commons/b/b6/Felis_catus-cat_on_snow.jpg
Взгляните на Multimodal.java
в каталоге 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-002")
.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());
}
}
Обратите внимание, что при импорте мы различаем разные типы сообщений и содержимого. UserMessage
может содержать как объект TextContent
, так и объект ImageContent
. Это мультимодальность: смешивание текста и изображений. Мы не просто отправляем простое строковое приглашение, мы отправляем более структурированный объект, который представляет пользовательское сообщение, состоящее из части изображения и части текстового содержимого. Модель отправляет обратно Response
, содержащий AiMessage
.
Затем вы извлекаете AiMessage
из ответа через content()
, а затем текст сообщения благодаря text()
.
Запустите образец:
./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal
Название изображения, безусловно, дало вам представление о том, что оно содержит, но вывод Gemini аналогичен следующему:
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.
Смешение изображений и текстовых подсказок открывает интересные варианты использования. Вы можете создавать приложения, которые могут:
- Распознавание текста на картинках.
- Проверьте, безопасно ли изображение для отображения.
- Создайте подписи к изображениям.
- Поиск по базе данных изображений с текстовыми описаниями.
Помимо извлечения информации из изображений, вы также можете извлекать информацию из неструктурированного текста. Это то, что вы узнаете в следующем разделе.
7. Извлекайте структурированную информацию из неструктурированного текста
Во многих ситуациях важная информация предоставляется в отчетных документах, электронных письмах или других длинных текстах в неструктурированном виде. В идеале вы хотели бы иметь возможность извлекать ключевые детали, содержащиеся в неструктурированном тексте, в форме структурированных объектов. Давайте посмотрим, как вы можете это сделать.
Допустим, вы хотите извлечь имя и возраст человека, учитывая биографию, резюме или описание этого человека. Вы можете поручить LLM извлекать JSON из неструктурированного текста с помощью умело настроенной подсказки (это обычно называется «подсказкой» ).
Но в приведенном ниже примере вместо создания подсказки, описывающей выходные данные JSON, мы будем использовать мощную функцию Gemini, называемую структурированным выводом или иногда ограниченным декодированием, заставляющую модель выводить только допустимый контент JSON в соответствии с указанной схемой JSON .
Взгляните на ExtractData.java
в 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.SystemMessage;
import static dev.langchain4j.model.vertexai.SchemaHelper.fromClass;
public class ExtractData {
record Person(String name, int age) { }
interface PersonExtractor {
@SystemMessage("""
Your role is to extract the name and age
of the person described in the biography.
""")
Person extractPerson(String biography);
}
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-002")
.responseMimeType("application/json")
.responseSchema(fromClass(Person.class))
.build();
PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);
String bio = """
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.
""";
Person person = extractor.extractPerson(bio);
System.out.println(person.name()); // Anna
System.out.println(person.age()); // 23
}
}
Давайте посмотрим на различные шаги в этом файле:
- Запись
Person
предназначена для представления сведений, описывающих человека (имя и возраст). - Интерфейс
PersonExtractor
определен с помощью метода, который по неструктурированной текстовой строке возвращает экземплярPerson
. -
extractPerson()
помечен аннотацией@SystemMessage
, которая связывает с ним приглашение инструкции. Это приглашение, которое модель будет использовать для извлечения информации и возврата деталей в форме документа JSON, который будет проанализирован для вас и демаршаллирован в экземплярPerson
.
Теперь давайте посмотрим на содержимое метода main()
:
- Модель чата настроена и создана. Мы используем два новых метода класса построителя моделей:
responseMimeType()
иresponseSchema()
. Первый сообщает Gemini о необходимости генерировать действительный JSON на выходе. Второй метод определяет схему объекта JSON, который должен быть возвращен. Более того, последний делегирует удобный метод, который может преобразовать класс Java или запись в правильную схему JSON. - Объект
PersonExtractor
создается благодаря классуAiServices
LangChain4j. - Затем вы можете просто вызвать
Person person = extractor.extractPerson(...)
чтобы извлечь сведения о человеке из неструктурированного текста и получить обратно экземплярPerson
с именем и возрастом.
Запустите образец:
./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData
Вы должны увидеть следующий вывод:
Anna 23
Да, это Анна и им 23!
При таком подходе AiServices
вы работаете со строго типизированными объектами. Вы не взаимодействуете напрямую с LLM. Вместо этого вы работаете с конкретными классами, такими как запись Person
, для представления извлеченной личной информации, и у вас есть объект PersonExtractor
с методом extractPerson()
, который возвращает экземпляр Person
. Понятие LLM абстрагировано, и как разработчик Java вы просто манипулируете обычными классами и объектами, когда используете этот интерфейс PersonExtractor
.
8. Структурируйте подсказки с помощью шаблонов подсказок.
Когда вы взаимодействуете с LLM, используя общий набор инструкций или вопросов, часть этого приглашения никогда не меняется, в то время как другие части содержат данные. Например, если вы хотите создавать рецепты, вы можете использовать подсказку типа «Вы талантливый шеф-повар, создайте рецепт со следующими ингредиентами: ...», а затем добавить ингредиенты в конец этот текст. Для этого и нужны шаблоны подсказок — они аналогичны интерполированным строкам в языках программирования. Шаблон приглашения содержит заполнители, которые вы можете заменить нужными данными для конкретного вызова LLM.
Более конкретно, давайте изучим TemplatePrompt.java
в каталоге 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-002")
.maxOutputTokens(500)
.temperature(1.0f)
.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());
}
}
Как обычно, вы настраиваете модель VertexAiGeminiChatModel
с высоким уровнем креативности, высокой температурой, а также высокими значениями topP и topK. Затем вы создаете PromptTemplate
с его статическим методом from()
, передавая строку нашего приглашения и используете переменные-заполнители в двойных фигурных скобках: и
.
Вы создаете последнее приглашение, вызывая apply()
, который принимает карту пар ключ/значение, представляющих имя заполнителя и строковое значение, которым его можно заменить.
Наконец, вы вызываете generate()
модели Gemini, создавая пользовательское сообщение из этого приглашения с помощью инструкции prompt.toUserMessage()
.
Запустите образец:
./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt
Вы должны увидеть сгенерированный вывод, похожий на этот:
**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.
Не стесняйтесь менять значения dish
и ingredients
на карте, настраивать температуру, topK
и tokP
и повторно запускать код. Это позволит вам наблюдать эффект изменения этих параметров на LLM.
Шаблоны подсказок — это хороший способ иметь многократно используемые и параметризуемые инструкции для вызовов LLM. Вы можете передавать данные и настраивать подсказки для различных значений, предоставленных вашими пользователями.
9. Классификация текста с подсказками из нескольких кадров.
LLM довольно хорошо классифицируют текст по различным категориям. Вы можете помочь LLM в этой задаче, предоставив несколько примеров текстов и связанных с ними категорий. Этот подход часто называют подсказкой с несколькими выстрелами .
Давайте откроем TextClassification.java
в каталоге app/src/main/java/gemini/workshop
, чтобы выполнить определенный тип классификации текста: анализ настроений.
package gemini.workshop;
import com.google.cloud.vertexai.api.Schema;
import com.google.cloud.vertexai.api.Type;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import java.util.List;
public class TextClassification {
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-002")
.maxOutputTokens(10)
.maxRetries(3)
.responseSchema(Schema.newBuilder()
.setType(Type.STRING)
.addAllEnum(List.of("POSITIVE", "NEUTRAL", "NEGATIVE"))
.build())
.build();
interface SentimentAnalysis {
@SystemMessage("""
Analyze the sentiment of the text below.
Respond only with one word to describe the sentiment.
""")
Sentiment analyze(String text);
}
MessageWindowChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
memory.add(UserMessage.from("This is fantastic news!"));
memory.add(AiMessage.from(Sentiment.POSITIVE.name()));
memory.add(UserMessage.from("Pi is roughly equal to 3.14"));
memory.add(AiMessage.from(Sentiment.NEUTRAL.name()));
memory.add(UserMessage.from("I really disliked the pizza. Who would use pineapples as a pizza topping?"));
memory.add(AiMessage.from(Sentiment.NEGATIVE.name()));
SentimentAnalysis sentimentAnalysis =
AiServices.builder(SentimentAnalysis.class)
.chatLanguageModel(model)
.chatMemory(memory)
.build();
System.out.println(sentimentAnalysis.analyze("I love strawberries!"));
}
}
Перечисление Sentiment
перечисляет различные значения настроения: отрицательное, нейтральное или положительное.
В методе main()
вы создаете модель чата Gemini, как обычно, но с небольшим максимальным количеством выходных токенов, так как вам нужен только короткий ответ: текст POSITIVE
, NEGATIVE
или NEUTRAL
. А чтобы ограничить модель возвратом только этих значений, вы можете воспользоваться поддержкой структурированного вывода, которую вы обнаружили в разделе извлечения данных. Вот почему используется метод responseSchema()
. На этот раз вы не будете использовать удобный метод SchemaHelper
для вывода определения схемы, а вместо этого воспользуетесь построителем Schema
, чтобы понять, как выглядит определение схемы.
После настройки модели вы создаете интерфейс SentimentAnalysis
, который AiServices
LangChain4j реализует для вас с помощью LLM. Этот интерфейс содержит один метод: analyze()
. Он принимает текст для анализа на входе и возвращает значение перечисления Sentiment
. Таким образом, вы манипулируете только строго типизированным объектом, который представляет распознаваемый класс настроений.
Затем, чтобы дать «несколько примеров», которые подтолкнут модель к выполнению работы по классификации, вы создаете память чата для передачи пар пользовательских сообщений и ответов ИИ, которые представляют текст и связанные с ним настроения.
Давайте свяжем все вместе с помощью метода AiServices.builder()
, передав наш интерфейс SentimentAnalysis
, используемую модель и память чата с несколькими примерами. Наконец, вызовите метод analyze()
с текстом для анализа.
Запустите образец:
./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification
Вы должны увидеть одно слово:
POSITIVE
Похоже, любовь к клубнике – это позитивное чувство!
10. Поисковая дополненная генерация
LLM обучаются на большом количестве текста. Однако их знания охватывают только ту информацию, которую он видел во время обучения. Если после окончания обучения модели появится новая информация, эта информация не будет доступна модели. Таким образом, модель не сможет отвечать на вопросы по информации, которую она не видела.
Вот почему такие подходы, как поисковая дополненная генерация (RAG) , которые будут рассмотрены в этом разделе, помогают предоставить дополнительную информацию, которую LLM может потребоваться знать для выполнения запросов своих пользователей, для ответа на информацию, которая может быть более актуальной или конфиденциальной. который недоступен во время обучения.
Вернемся к разговорам. На этот раз вы сможете задавать вопросы о ваших документах. Вы создадите чат-бота, который сможет извлекать соответствующую информацию из базы данных, содержащей ваши документы, разделенные на более мелкие части («куски»), и эта информация будет использоваться моделью для обоснования ее ответов, вместо того, чтобы полагаться исключительно на знания, содержащиеся в его обучение.
В тряпке есть две фазы:
- Фаза проглатывания - документы загружаются в память, разделяются на более мелкие куски, а векторные встроения (высокомерное векторное представление векторных векторных Эта фаза приема обычно выполняется один раз, когда новые документы должны быть добавлены в корпус документа.
- Этап запроса - теперь пользователи могут задавать вопросы о документах. Вопрос также будет преобразован в вектор и сравнивается со всеми другими векторами в базе данных. Наиболее похожие векторы обычно семантически связаны и возвращаются векторной базой данных. Затем LLM дается контекст разговора, куски текста, которые соответствуют векторам, возвращенным базой данных, и просят заземлить его ответ, посмотрев на эти кусочки.
Подготовьте свои документы
Для этого нового примера вы зададите вопросы о вымышленной модели автомобиля из также вымышленного автомобиля: автомобиль Cymbal Starlight! Идея состоит в том, что документ о вымышленном автомобиле не должен быть частью знания модели. Поэтому, если Gemini может правильно ответить на вопросы об этом автомобиле, то это означает, что подход RAG работает: он способен искать в вашем документе.
Реализуйте чат -бот
Давайте рассмотрим, как построить 2-фазный подход: сначала с приглашением документа, а затем временем запроса (также называемого «фазой поиска»), когда пользователи задают вопросы о документе.
В этом примере обе фазы реализованы в одном классе. Обычно у вас будет одно приложение, которое позаботится о приеме, и другое приложение, которое предлагает интерфейс чатбота для ваших пользователей.
Кроме того, в этом примере мы будем использовать векторную базу данных в памяти. В реальном сценарии производства проглатывание и фазы запроса будут разделены в двух различных приложениях, а векторы сохраняются в автономной базе данных.
Проглатывание документов
Самым первым шагом фазы проглатывания документа является местонахождение файла PDF о нашем фиктивном автомобиле и подготовить PdfParser
для его прочтения:
URL url = new URI("https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/grounding/vertexai-search/cymbal-starlight-2024.pdf").toURL();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document document = pdfParser.parse(url.openStream());
Вместо того, чтобы сначала создавать обычную модель языка чата, вы создаете экземпляр модели встраивания . Это конкретная модель, роль которого состоит в том, чтобы создать векторные представления текстовых произведений (слова, предложения или даже абзацы). Он возвращает векторы чисел с плавающей запятой, а не возвращает текстовые ответы.
VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
.endpoint(System.getenv("LOCATION") + "-aiplatform.googleapis.com:443")
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.publisher("google")
.modelName("text-embedding-005")
.maxRetries(3)
.build();
Далее вам понадобится несколько занятий, чтобы сотрудничать вместе с:
- Загрузите и разделите документ PDF на куски.
- Создайте векторные встроения для всех этих кусков.
InMemoryEmbeddingStore<TextSegment> embeddingStore =
new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
storeIngestor.ingest(document);
Экземпляр InMemoryEmbeddingStore
, векторной базы данных в памяти, создан для хранения векторных вторжений.
Документ разделен в куски благодаря классу DocumentSplitters
. Он собирается разделить текст файла PDF на фрагменты 500 символов, с перекрытием 100 символов (со следующей частью, чтобы избежать разрезания слов или предложений, в кусочках).
Магазин Ingestor связывает сплиттер документа, модель встраивания для расчета векторов и векторную базу данных в памяти. Затем метод ingest()
позаботится о проглатывании.
Теперь первое этап закончился, документ был преобразован в текстовые куски с связанными с ними векторными встроениями и хранится в векторной базе данных.
Задавать вопросы
Пора подготовиться к вопросам! Создайте модель чата, чтобы начать разговор:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-002")
.maxOutputTokens(1000)
.build();
Вам также нужен класс ретривера, чтобы связать векторную базу данных (в переменной embeddingStore
) с моделью встраивания. Его задача - запросить векторную базу данных путем вычисления векторного встраивания для запроса пользователя, чтобы найти аналогичные векторы в базе данных:
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
Создайте интерфейс, который представляет помощника эксперта автомобиля, это интерфейс, который класс AiServices
будет реализован для вас, чтобы взаимодействовать с моделью:
interface CarExpert {
Result<String> ask(String question);
}
Интерфейс CarExpert
возвращает ответ строки, завернутый в класс Result
Langchain4J. Зачем использовать эту обертку? Потому что не только это даст вам ответ, но и позволит вам проверить куски из базы данных, которые были возвращены Content Retriever. Таким образом, вы можете отобразить источники документа, которые используются для заземления пользователя.
На этом этапе вы можете настроить новую службу искусственного интеллекта:
CarExpert expert = AiServices.builder(CarExpert.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(retriever)
.build();
Эта услуга связывается вместе:
- Модель языка чата , которую вы настроили ранее.
- Память чата , чтобы отслеживать разговор.
- Ретривер сравнивает векторный запрос в встраиваемость с векторами в базе данных.
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("""
You are an expert in car automotive, and you answer concisely.
Here is the question: {{userMessage}}
Answer using the following information:
{{contents}}
the following information:
{{contents}}
"""))
.build())
.contentRetriever(retriever)
.build())
Вы наконец -то готовы задать свои вопросы!
List.of(
"What is the cargo capacity of Cymbal Starlight?",
"What's the emergency roadside assistance phone number?",
"Are there some special kits available on that car?"
).forEach(query -> {
Result<String> response = expert.ask(query);
System.out.printf("%n=== %s === %n%n %s %n%n", query, response.content());
System.out.println("SOURCE: " + response.sources().getFirst().textSegment().text());
});
Полный исходный код находится в RAG.java
в app/src/main/java/gemini/workshop
Directory.
Запустите образец:
./gradlew -q run -DjavaMainClass=gemini.workshop.RAG
В результате вы должны увидеть ответы на свои вопросы:
=== What is the cargo capacity of Cymbal Starlight? === The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. SOURCE: Cargo The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. The cargo area is located in the trunk of the vehicle. To access the cargo area, open the trunk lid using the trunk release lever located in the driver's footwell. When loading cargo into the trunk, be sure to distribute the weight evenly. Do not overload the trunk, as this could affect the vehicle's handling and stability. Luggage === What's the emergency roadside assistance phone number? === The emergency roadside assistance phone number is 1-800-555-1212. SOURCE: Chapter 18: Emergencies Roadside Assistance If you experience a roadside emergency, such as a flat tire or a dead battery, you can call roadside assistance for help. Roadside assistance is available 24 hours a day, 7 days a week. To call roadside assistance, dial the following number: 1-800-555-1212 When you call roadside assistance, be prepared to provide the following information: Your name and contact information Your vehicle's make, model, and year Your vehicle's location === Are there some special kits available on that car? === Yes, the Cymbal Starlight comes with a tire repair kit. SOURCE: Lane keeping assist: This feature helps to keep you in your lane by gently steering the vehicle back into the lane if you start to drift. Adaptive cruise control: This feature automatically adjusts your speed to maintain a safe following distance from the vehicle in front of you. Forward collision warning: This feature warns you if you are approaching another vehicle too quickly. Automatic emergency braking: This feature can automatically apply the brakes to avoid a collision.
11. Функциональный вызов
Существуют ситуации, когда вы хотите, чтобы LLM имел доступ к внешним системам, таким как удаленный веб -API, который получает информацию или имеет действие, или услуги, которые выполняют какие -то вычисления. Например:
Удаленные веб -API:
- Отслеживать и обновлять заказы клиентов.
- Найдите или создайте билет в трекере выпуска.
- Получите данные в реальном времени, такие как котировки с запасами или измерения датчика IoT.
- Отправьте электронное письмо.
Инструменты вычисления:
- Калькулятор для более продвинутых математических задач.
- Интерпретация кода для запуска кода, когда LLMS нуждается в логике рассуждений.
- Преобразовать запросы естественного языка в запросы SQL, чтобы LLM мог запросить базу данных.
Вызовов функций (иногда называемые инструментами или использование инструментов) - это возможность для модели запросить один или несколько вызовов функций, выполняемых от его имени, поэтому она может правильно ответить на подсказку пользователя с более свежими данными.
Учитывая конкретную подсказку от пользователя и знание существующих функций, которые могут иметь отношение к этому контексту, LLM может ответить с помощью запроса вызова функции. Приложение, интегрирующее LLM, может затем вызвать функцию от ее имени, а затем ответить на LLM ответом, а затем LLM интерпретирует обратно, отвечая с текстовым ответом.
Четыре этапа вызова функции
Давайте посмотрим на пример функционального вызова: получение информации о прогнозе погоды.
Если вы спросите Gemini или любой другой LLM о погоде в Париже, они отвечали бы, сказав, что у него нет информации о текущем прогнозе погоды. Если вы хотите, чтобы LLM имел доступ в режиме реального времени к данным погоды, вам необходимо определить некоторые функции, которые он может запросить для использования.
Взгляните на следующую диаграмму:
1⃣ Во -первых, пользователь спрашивает о погоде в Париже. Приложение Chatbot (с использованием langchain4j) знает, что есть одна или несколько функций, которые находятся в его распоряжении, чтобы помочь LLM выполнить запрос. Чатбот отправляет первоначальную подсказку, а также список функций, которые можно вызвать. Здесь функция называется getWeather()
которая принимает параметр строки для местоположения.
Поскольку LLM не знает о прогнозах погоды, вместо того, чтобы отвечать через текст, он отправляет обратно запрос на выполнение функции. Чатбот должен вызвать функцию getWeather()
с "Paris"
в качестве параметра местоположения.
2⃣ Чатбот вызывает, что функционирует от имени LLM, извлекает ответ функции. Здесь мы представляем, что ответ {"forecast": "sunny"}
.
3⃣ Приложение Chatbot отправляет ответ JSON обратно в LLM.
4⃣ LLM смотрит на ответ JSON, интерпретирует эту информацию и в конечном итоге отвечает на текст, что погода солнечная в Париже.
Каждый шаг как код
Во -первых, вы настроите модель Gemini как обычно:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-002")
.maxOutputTokens(100)
.build();
Вы определяете спецификацию инструмента, которая описывает функцию, которую можно назвать:
ToolSpecification weatherToolSpec = ToolSpecification.builder()
.name("getWeather")
.description("Get the weather forecast for a given location or city")
.parameters(JsonObjectSchema.builder()
.addStringProperty(
"location",
"the location or city to get the weather forecast for")
.build())
.build();
Имя функции определено, а также имя и тип параметра, но обратите внимание, что как функции, так и параметры приведены описания. Описания очень важны и помогают LLM действительно понять, что может выполнять функция, и, таким образом, судить, должна ли эта функция быть вызвана в контексте разговора.
Давайте начнем шаг № 1, отправив первоначальный вопрос о погоде в Париже:
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 мы передаем инструмент, который мы хотели бы использовать модель, и модель отвечает с помощью запроса на выполнение:
// 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. На этом этапе мы знаем, какую функцию LLM хотела бы, чтобы мы позвонили. В коде мы не делаем реального призывания к внешнему API, мы просто возвращаем гипотетический прогноз погоды напрямую:
// 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 LLM узнает о результате выполнения функции, а затем может синтезировать текстовый ответ:
// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());
Вывод:
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.
Вы можете увидеть в выводе выше запроса выполнения инструмента, а также ответ.
Полный исходный код находится в FunctionCalling.java
app/src/main/java/gemini/workshop
Запустите образец:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling
Вы должны увидеть выход, похожий на следующее:
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 обрабатывает функции вызова функции
На предыдущем этапе вы увидели, как обычные текстовые вопросы/ответа и запрос функции/ответ/ответ взаимодействуют, и между ними вы напрямую предоставили запрошенную функцию ответа, не вызывая реальную функцию.
Тем не менее, Langchain4J также предлагает абстракцию более высокого уровня, которая может обрабатывать функцию прозрачно для вас, одновременно обрабатывая разговор, как обычно.
Однофункциональный вызов
Давайте посмотрим на FunctionCallingAssistant.java
, кусок по кусочкам.
Во -первых, вы создаете запись, которая будет представлять структуру данных ответа функции:
record WeatherForecast(String location, String forecast, int temperature) {}
Ответ содержит информацию о местоположении, прогнозе и температуре.
Затем вы создаете класс, который содержит фактическую функцию, которую вы хотите сделать для модели:
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);
}
}
}
Обратите внимание, что этот класс содержит одну функцию, но он аннотирован с аннотацией @Tool
, которая соответствует описанию функции, которую модель может запросить для вызова.
Параметры функции (единственная здесь) также аннотируются, но с этой короткой аннотацией @P
, которая также дает описание параметра. Вы можете добавить столько функций, сколько вы хотели, чтобы сделать их доступными для модели для более сложных сценариев.
В этом классе вы возвращаете некоторые консервированные ответы, но если вы хотите назвать реальную службу прогноза с внешней погодой, это в организме того метода, который вы бы призывали к этой услуге.
Как мы видели, когда вы создали ToolSpecification
в предыдущем подходе, важно документировать, что делает функция, и описать, к чему соответствуют параметры. Это помогает модели понять, как и когда эта функция может быть использована.
Затем Langchain4J позволяет предоставить интерфейс, который соответствует контракту, который вы хотите использовать для взаимодействия с моделью. Здесь это простой интерфейс, который принимает строку, представляющую сообщение пользователя, и возвращает строку, соответствующую ответу модели:
interface WeatherAssistant {
String chat(String userMessage);
}
Также можно использовать более сложные подписи, которые включают в себя UserMessage
(для пользовательского сообщения) или AiMessage
(для ответа на модель) или даже TokenStream
, если вы хотите справиться с более продвинутыми ситуациями, так как эти более сложные объекты также содержат дополнительные Информация, такая как количество потребляемых токенов и т. Д., Но ради простоты мы просто возьмем строку в вводе и строку в выводе.
Давайте закончим с помощью метода main()
который связывает все части вместе:
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-pro-002")
.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. Затем вы создаете экземпляр своей службы прогноза погоды, которая содержит «функцию», которую модель попросит нас позвонить.
Теперь вы снова используете класс AiServices
, чтобы связать модель чата, память чата и инструмент (т.е. служба прогноза погоды с ее функцией). AiServices
возвращает объект, который реализует ваш определенный интерфейс WeatherAssistant
. Единственное, что осталось, - это вызвать метод chat()
этого помощника. При его вызове вы увидите только текстовые ответы, но запросы вызова функции и ответы вызова функции не будут видны у разработчика, и эти запросы будут обрабатываться автоматически и прозрачно. Если Gemini считает, что должна быть вызвана функция, она ответит с помощью запроса вызова функции, и Langchain4J позаботится о вызове локальной функции от вашего имени.
Запустите образец:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant
Вы должны увидеть выход, похожий на следующее:
OK. The weather in Paris is sunny with a temperature of 20 degrees.
Это был пример одной функции.
Несколько вызовов функций
Вы также можете иметь несколько функций и позволить Langchain4J обрабатывать несколько вызовов функций от вашего имени. Взгляните на MultiFunctionCallingAssistant.java
для множественного примера функции.
Он имеет функцию для преобразования валют:
@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;
}
Другая функция для получения стоимости акции:
@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;
}
Другая функция, чтобы применить процент к данной сумме:
@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;
}
Затем вы можете объединить все эти функции и класс с несколькими кругами и задать такие вопросы, как «Что такое 10% цены акций AAPL, преобразованную из USD в EUR?» »
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-002")
.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?"));
}
Запустите его следующим образом:
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
И вы должны увидеть несколько функций, называемых:
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.
К агентам
Функциональный вызов - это отличный механизм расширения для крупных языковых моделей, таких как Близнецы. Это позволяет нам создавать более сложные системы, которые часто называют «агентами» или «помощниками искусственного интеллекта». Эти агенты могут взаимодействовать с внешним миром с помощью внешних API и услуг, которые могут иметь побочные эффекты на внешней среде (например, отправка электронных писем, создание билетов и т. Д.)
При создании таких влиятельных агентов вы должны сделать это ответственно. Вы должны рассмотреть человеку в петле, прежде чем совершать автоматические действия. Важно помнить о безопасности при проектировании агентов с LLM, которые взаимодействуют с внешним миром.
13. Запуск Джеммы с олламой и тестовых контейнерами
До сих пор мы использовали Близнецы, но есть и Джемма , ее младшая сестра модель.
Gemma -это семейство легких, современных открытых моделей, созданных из тех же исследований и технологий, используемых для создания моделей Gemini. Gemma доступна в двух вариациях Gemma1 и Gemma2 с различными размерами. GEMMA1 доступен в двух размерах: 2B и 7B. GEMMA2 доступен в двух размерах: 9B и 27B. Их веса свободно доступны, а их небольшие размеры означает, что вы можете запустить его самостоятельно, даже на своем ноутбуке или в облачной оболочке.
Как вы управляете Джеммой?
Есть много способов запустить Gemma: в облаке, через AI Vertex AI с нажатием кнопки или GKE с некоторыми графическими процессорами, но вы также можете запустить ее локально.
Один хороший вариант для запуска Gemma Locally - это Ollama , инструмент, который позволяет вам запускать небольшие модели, такие как Llama 2, Mistral и многие другие на вашей местной машине. Это похоже на Docker, но для LLMS.
Установите Ollama после инструкции для вашей операционной системы.
Если вы используете среду Linux, вам нужно сначала включить Ollama после ее установки.
ollama serve > /dev/null 2>&1 &
После установки локально вы можете запустить команды, чтобы вытащить модель:
ollama pull gemma:2b
Подождите, пока модель будет вытянута. Это может занять некоторое время.
Запустите модель:
ollama run gemma:2b
Теперь вы можете взаимодействовать с моделью:
>>> Hello! Hello! It's nice to hear from you. What can I do for you today?
Чтобы выйти из приглашения нажатия Ctrl+D
Запуск Gemma в Ollama на TestContainers
Вместо того, чтобы устанавливать и запускать Ollama на локальном уровне, вы можете использовать Ollama в контейнере, обрабатываемые TestContainers .
TestContainers не только полезны для тестирования, но и вы можете использовать его для выполнения контейнеров. Есть даже конкретный OllamaContainer
которым вы можете воспользоваться!
Вот целая картина:
Выполнение
Давайте посмотрим на GemmaWithOllamaContainer.java
, кусок по кусочкам.
Во -первых, вам нужно создать производный контейнер Ollama, который втягивает модель Gemma. Это изображение либо уже существует из предыдущего запуска, либо будет создано. Если изображение уже существует, вы просто сообщите TestContainers, что вы хотите заменить изображение Ollama по умолчанию вашим вариантом, способствующим 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"));
}
}
Затем вы создаете и запускаете тестовый контейнер Ollama, а затем создаете модель чата Ollama, указывая на адрес и порт контейнера с помощью модели, которую вы хотите использовать. Наконец, вы только что вызываете model.generate(yourPrompt)
как обычно:
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);
}
Запустите его следующим образом:
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
Первый пробег займет некоторое время, чтобы создать и запустить контейнер, но как только сделано, вы должны увидеть, как Джемма отвечает:
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.
У вас есть Джемма, работающая в облачной оболочке!
14. Поздравляю
Поздравляем, вы успешно создали свое первое генеративное приложение для AI в Java, используя Langchain4J и API Gemini! Вы обнаружили, что мультимодальные крупные языковые модели довольно мощные и способны выполнять различные задачи, такие как вопрос/ответ, даже по вашей собственной документации, извлечение данных, взаимодействие с внешними API и многое другое.
Что дальше?
У вас есть очередь, чтобы улучшить ваши приложения с мощными интеграциями LLM!
Дальнейшее чтение
- Генеративные варианты общего использования ИИ
- Учебные ресурсы по генеративному ИИ
- Взаимодействовать с Близнецами через генеративную студию ИИ
- Ответственный ИИ