2025 рік — рік Агентів ШІ.
Для цілей цієї статті, Агент ШІ — це система, яка може використовувати ШІ для досягнення мети, виконуючи низку кроків, можливо, міркуючи над своїми результатами та вносячи корективи. Насправді, кроки, які виконує агент, можуть складати граф.
Ми створимо реактивного агента (який реагує на стимул, в нашому випадку на введення від користувача), щоб допомогти людям знайти ідеальний відпочинок.
Наш агент шукатиме найкраще місто в заданій країні, враховуючи їжу, море та активності, вказані користувачем.
Агент виглядатиме так:
На першій фазі він збирає інформацію паралельно, оцінюючи міста за однією характеристикою. Потім останній крок використовує цю інформацію для вибору найкращого міста.
Ви могли б використовувати пошукову систему для збору інформації, але ми використаємо ChatGPT для всіх етапів, хоча використовуватимемо різні моделі.
Ви можете написати весь код вручну або використати деякі бібліотеки для полегшення роботи з кодом. Сьогодні ми використаємо нову функцію, яку я додав до Fibry, моєї системи акторів, щоб реалізувати граф та контролювати паралелізм з великою деталізацією.
Fibry — це проста та компактна система акторів, яка надає простий спосіб використання акторів для спрощення багатозадачності, і не має залежностей. Fibry також реалізує кінцевий автомат, тому я вирішив розширити її, щоб спростити написання агентів на Java. Моєю натхненням був LangGraph.
Оскільки Fibry орієнтована на багатозадачність, нові функції дають велику гнучкість у визначенні рівня паралелізму, при цьому зберігаючи все якнайпростішим.
Ви повинні використовувати Fibry 3.0.2, наприклад:
compile group: 'eu.lucaventuri', name: 'fibry', version: '3.0.2'
Визначення запитів
Перший крок — це визначення запитів, які нам потрібні для LLM:
public static class AiAgentVacations {
private static final String promptFood = "You are a foodie from {country}. Please tell me the top 10 cities for food in {country}.";
private static final String promptActivity = "You are from {country}, and know it inside out. Please tell me the top 10 cities in {country} where I can {goal}";
private static final String promptSea = "You are an expert traveler, and you {country} inside out. Please tell me the top 10 cities for sea vacations in {country}.";
private static final String promptChoice = """
You enjoy traveling, eating good food and staying at the sea, but you also want to {activity}. Please analyze the following suggestions from your friends for a vacation in {country} and choose the best city to visit, offering the best mix of food and sea and where you can {activity}.
Food suggestions: {food}.
Activity suggestions: {activity}.
Sea suggestions: {sea}.
""";
}
Визначення станів
Зазвичай ви б визначили чотири стани, по одному для кожного етапу. Однак, оскільки розгалуження та повернення досить поширені, я додав функцію, яка дозволяє це обробляти за допомогою лише одного стану. Як результат, нам потрібно лише два стани: CITIES, де ми збираємо інформацію, та CHOICE, де ми вибираємо місто.
enum VacationStates {
CITIES, CHOICE
}
Визначення контексту
Різні кроки агента будуть збирати інформацію, яку потрібно десь зберігати, давайте назвемо це контекстом.
Ідеально ви хочете, щоб кожен крок був незалежним і знав якнайменше про інші, але досягти цього простим способом з низькою кількістю коду, зберігаючи типову безпеку та підтримуючи безпеку потоків, не так вже й просто.
Як результат, я вирішив змусити контекст бути записом (record), надаючи деякі функціональні можливості для оновлення значень запису (використовуючи рефлексію під капотом), поки ми чекаємо на реалізацію JEP 468 (Derived Record Creation).
public record VacationContext(String country, String goal, String food, String activity, String sea, String proposal) {
public static VacationContext from(String country, String goal) {
return new VacationContext(country, goal, null, null, null, null);
}
}
Визначення вузлів
Тепер ми можемо визначити логіку агента. Ми дозволимо користувачу використовувати дві різні моделі LLM, наприклад, “звичайну” модель LLM для пошуку та “модель міркувань” для кроку вибору.
Тут стає трохи складніше, оскільки це досить щільно:
AgentNode nodeFood = state -> state.setAttribute("food", modelSearch.call("user", replaceField(promptFood, state.data(), "country")));
AgentNode nodeActivity = state -> state.setAttribute("activity", modelSearch.call("user", replaceField(promptActivity, state.data(), "country")));
AgentNode nodeSea = state -> state.setAttribute("sea", modelSearch.call("user", replaceField(promptSea, state.data(), "country")));
AgentNode nodeChoice = state -> {
var prompt = replaceAllFields(promptChoice, state.data());
System.out.println("***** CHOICE PROMPT: " + prompt);
return state.setAttribute("proposal", modelThink.call("user", prompt));
};
Як ви могли здогадатися, modelSearch — це модель, яка використовується для пошуку (наприклад, ChatGPT 4o), а modelThink може бути “моделлю міркувань” (наприклад, ChatGPT o1). Fibry надає простий інтерфейс для LLM та просту реалізацію для ChatGPT, доступну через клас ChatGpt.
Зверніть увагу, що виклик API ChatGPT вимагає наявності ключа API, який потрібно визначити за допомогою параметра JVM “-DOPENAIAPIKEY=xxxx”.
Існують також більш складні випадки використання, які потребують спеціальних реалізацій.
Є також невелика проблема, пов'язана з філософією Fibry, оскільки Fibry створена без залежностей, і це стає проблемою з JSON. Як результат, наразі Fibry може працювати двома способами:
- Якщо виявлений Jackson, Fibry використовуватиме його з рефлексією для парсингу JSON.
- Якщо Jackson не виявлений, використовується дуже простий власний парсер (який працює з результатами ChatGPT). Це рекомендується лише для швидких тестів, а не для виробництва.
- Або ви можете надати власну реалізацію процесора JSON і викликати JsonUtils.setProcessor(), можливо, перевіривши JacksonProcessor для натхнення.
Методи replaceField() та replaceAllFields() визначені в RecordUtils і є зручними методами для заміни тексту в запиті, щоб ми могли передати наші дані до LLM.
Функція setAttribute() використовується для встановлення значення атрибута в стані, не потрібно вручну створювати запис або визначати список методів “withers”. Є й інші методи, які можна використовувати, такі як mergeAttribute(), addToList(), addToSet() та addToMap().
Створення агента
Тепер, коли ми маємо логіку, потрібно описати граф залежностей між станами та вказати рівень паралелізму, який ми хочемо досягти. Якщо уявити велику систему багатьох агентів в продукції, можливість виразити потрібний паралелізм для максимізації продуктивності без виснаження ресурсів, досягнення обмежень швидкості або перевищення дозволеного паралелізму зовнішніми системами, це критична функція.
І ось тут Fibry може допомогти, роблячи все явним, але досить простим для налаштування.
Давайте почнемо створювати будівельник агента:
var builder = AiAgent.builder(true);
Параметр autoGuards використовується для автоматичного встановлення охоронців для станів, що означає, що вони виконуються за логікою AND, і стан виконується тільки після того, як всі вхідні стани будуть оброблені.
Якщо параметр false, стан викликається один раз для кожного вхідного стану.
У попередньому прикладі, якщо мета — виконати D один раз після A та один раз після C, тоді autoGuards має бути false, а якщо ви хочете, щоб його викликали тільки після того, як обидва будуть виконані, тоді autoGuards має бути true.
Але давайте продовжимо з агентом для відпустки.
builder.addState(VacationStates.CHOICE, null, 1, nodeChoice, null);
Давайте почнемо з методу addState(). Він використовується для того, щоб вказати, що певний стан повинен бути виконаний після іншого стану і виконати певну логіку. Крім того, можна вказати паралелізм (більше про це скоро) та охоронці.
У цьому випадку:
- Стан — це CHOICE
- Немає наступного стану за замовчуванням (наприклад, це фінальний стан)
- Паралелізм — 1
- Немає охоронця
Наступний стан просто є за замовчуванням, оскільки вузол має можливість перезаписати наступний стан, що означає, що граф може динамічно змінюватися під час виконання, і зокрема може виконувати цикли, наприклад, якщо деякі кроки потрібно повторити, щоб зібрати більше або кращу інформацію. Це складний випадок використання.
Несподіваним концептом може бути паралелізм. Це не має наслідків при одному запуску агента, але має значення в продуктивному середовищі, на великому масштабі.
У Fibry кожен вузол підтримується актором, який з практичної точки зору є потоком з переліком повідомлень для обробки. Кожне повідомлення — це крок виконання. Отже, паралелізм — це кількість повідомлень, які можуть бути виконані одночасно. На практиці:
- паралелізм == 1 означає, що є лише один потік, який керує кроком, тобто лише одне виконання за раз
- паралелізм > 1 означає, що існує пул потоків, що підтримує актора, з кількістю потоків, зазначеною користувачем. За замовчуванням використовуються віртуальні потоки.
- паралелізм == 0 означає, що кожне повідомлення створює нового актора, підтримуваного віртуальним потоком, тому паралелізм може бути таким високим, як це необхідно
Кожен крок можна налаштувати незалежно, що дозволяє налаштувати продуктивність і використання ресурсів досить добре. Зверніть увагу, що якщо паралелізм != 1, то у вас може бути багатопотоковість, оскільки зазвичай поточне обмеження, яке зв'язує з акторами, втрачається.
Це багато для засвоєння. Якщо це зрозуміло, ви можете перевірити стиснення станів.
Стиснення станів
Як згадувалося раніше, досить часто є кілька станів, які пов'язані один з одним, їх потрібно виконати паралельно і об'єднати перед переходом до спільного стану.
У цьому випадку не потрібно визначати кілька станів, а можна використовувати лише один:
builder.addStateParallel(VacationStates.CITIES, VacationStates.CHOICE, 1, List.of(nodeFood, nodeActivity, nodeSea), null);
У цьому випадку ми бачимо, що стан CITIES визначений трьома вузлами, а addStateParallel() займається їх виконанням паралельно та чекає, поки всі вони завершаться. У цьому випадку паралелізм застосовується до кожного вузла, тому в цьому випадку ви отримаєте 3 однониткових актора.
Зверніть увагу, що якщо ви не використовуєте autoGuards, це фактично дозволяє змішувати логіку OR і AND.
Якщо ви хочете об'єднати деякі вузли в одному стані, але вони мають бути виконані послідовно (наприклад,
Оскільки вони потребують інформації, згенерованої попереднім вузлом), також доступний метод addStateSerial().
Створення AiAgent є простим, але є кілька параметрів, які потрібно вказати:
- Початковий стан
- Кінцевий стан (який може бути null)
- Прапорець для виконання станів паралельно, коли це можливо
var vacationAgent = builder.build(VacationStates.CITIES, null, true);
Тепер у нас є агент, і ми можемо його використовувати, викликаючи process:
vacationsAgent.process(AiAgentVacations.VacationContext.from("Italy", "Dance Salsa and Bachata"), (state, info) -> System.out.println(state + ": " + info));
Ця версія process() приймає два параметри:
- початковий стан, який містить інформацію, необхідну агенту для виконання його дій
- необов'язковий слухач, наприклад, якщо ви хочете вивести результат кожного кроку
Якщо ви хочете дізнатися більше про варіанти паралелізму, я рекомендую перевірити модульний тест TestAiAgent. Він імітує агента з вузлами, які сплять деякий час, і може допомогти вам побачити вплив кожного вибору:
Але я ж обіцяв вам мульті-агента, чи не так?
Розширення до мульти-агентів
AiAgent, який ви щойно створили, є актором, тому він працює на своєму власному потоці (плюс усі потоки, що використовуються вузлами), і він також реалізує інтерфейс Function, якщо це вам потрібно.
Насправді немає нічого особливого в мульти-агенті, просто один або більше вузлів агента запитують у іншого агента виконати дію. Однак ви можете створити бібліотеку агентів і комбінувати їх найкращим чином, спрощуючи всю систему.
Уявімо, що ми хочемо використати результат нашого попереднього агента та використовувати його для розрахунку вартості цієї відпустки, щоб користувач міг вирішити, чи є вона достатньо доступною. Як справжній Туристичний агент!
Ось що ми хочемо створити:
Спочатку нам потрібні підказки для вилучення місця призначення та обчислення вартості.
private static final String promptDestination = "Прочитайте наступний текст, що описує місце для відпустки, і витягніть місце призначення як просте місто та країну, без вступу. Тільки місто та країна. {proposal}";
private static final String promptCost = "Ви експерт з подорожей.";
Клієнт попросив вас оцінити вартість подорожі з {startCity}, {startCountry} до {destination}, для {adults} дорослих і {kids} дітей";
Нам потрібні лише два стани: один для дослідження міст, що здійснюється попереднім агентом, і один для обчислення вартості.
enum TravelStates {
SEARCH, CALCULATE
}
```
Нам також потрібен контекст, який повинен містити пропозицію від попереднього агента.
public record TravelContext(String startCity, String startCountry, int adults, int kids, String destination, String cost, String proposal) { }
Тепер ми можемо визначити логіку агента, яка вимагає як параметр іншого агента.
Перший вузол викликає попереднього агента для отримання пропозиції.
var builder = AiAgent.builder(false);
AgentNode nodeSearch = state -> {
var vacationProposal = vacationsAgent.process(AiAgentVacations.VacationContext.from(country, goal), 1, TimeUnit.MINUTES, (st, info) -> System.out.print(debugSubAgentStates ? st + ": " + info : ""));
return state.setAttribute("proposal", vacationProposal.proposal())
.setAttribute("destination", model.call(promptDestination.replaceAll("\\{proposal\\}", vacationProposal.proposal())));
};
Другий вузол обчислює вартість:
AgentNode nodeCalculateCost = state -> state.setAttribute("cost", model.call(replaceAllFields(promptCost, state.data())));
Тепер ми можемо визначити граф і побудувати агента
builder.addState(TravelStates.SEARCH, TravelStates.CALCULATE, 1, nodeSearch, null);
builder.addState(TravelStates.CALCULATE, null, 1, nodeCalculateCost, null);
var agent = builder.build(TravelStates.SEARCH, null, false);
Тепер ми можемо створити два агенти (я вибрав використання ChatGPT 4o і ChatGPT 01-mini) і використовувати їх:
try (var vacationsAgent = AiAgentVacations.buildAgent(ChatGPT.GPT_MODEL_4O, ChatGPT.GPT_MODEL_O1_MINI)) {
try (var travelAgent = AiAgentTravelAgency.buildAgent(ChatGPT.GPT_MODEL_4O, vacationsAgent, "Italy", "Dance Salsa and Bachata", true)) {
var result = travelAgent.process(new AiAgentTravelAgency.TravelContext("Oslo", "Norway", 2, 2, null, null, null), (state, info) -> System.out.println(state + ": " + info));
System.out.println("*** Proposal: " + result.proposal());
System.out.println("\n\n\n*** Destination: " + result.destination());
System.out.println("\n\n\n*** Cost: " + result.cost());
}
}
Остаточні результати
Якщо ви цікавитесь, ось результат, який ви можете отримати, коли заявляєте, що хочете танцювати Сальсу та Бачату:
Місце призначення:
Наполі, Італія
Пропозиція:
Згідно з детальним аналізом пропозицій ваших друзів, **Наполі** виявляється ідеальним містом для вашої відпустки в Італії. Ось чому Наполі виділяється як найкращий вибір, пропонуючи чудову комбінацію смачної їжі, прекрасних пляжних розваг та яскравої сцени сальси та бачати:
### **1. Жива танцювальна сцена**
- **Танцювальні майданчики:** У Наполі є безліч місць та заходів, присвячених сальсі та бачате, що дозволяє регулярно насолоджуватись танцювальними вечорами.
- **Пристрасть культури:** Місто відзначається пристрасною та енергійною атмосферою, що робить його ідеальним місцем для шанувальників латиноамериканських танців.
### **2. Кулінарна досконалість**
- **Аутентична неаполітанська піца:** Як батьківщина піци, Наполі пропонує деякі з найкращих і найавтентичніших піцерій у світі.
- **Свіжі морепродукти:** Як прибережне місто, Наполі забезпечує доступ до широкого вибору свіжих морепродуктів, що додають смаку вашим кулінарним пригодам.
- **Смачні десерти:** Не пропустіть місцеві спеціалітети, такі як **сфольятелла**, знаменитий неаполітанський десерт, який обов'язково треба спробувати.
### **3.**
**Вражаюче прибережне місце**
- **Затока Неаполя:** Насолоджуйтесь захоплюючими краєвидами та активностями вздовж затоки Неаполя, включаючи поїздки на човнах та мальовничі заходи сонця.
- **Близькість до Амальфійського узбережжя:** Неаполь є воротами до знаменитого Амальфійського узбережжя, дозволяючи вам легко досліджувати чудові прибережні міста, такі як Амальфі, Позитано та Сорренто.
- **Красиві пляжі:** Відпочивайте на прекрасних пляжах міста або здійснюйте короткі поїздки до найближчих прибережних напрямків для ідеального поєднання відпочинку та досліджень.
### **4. Культурне багатство**
- **Історичні пам'ятки:** Досліджуйте багату історію Неаполя через численні музеї, історичні місця та об'єкти Світової спадщини ЮНЕСКО, такі як Історичний центр Неаполя.
- **Жваве нічне життя:** Окрім танців, Неаполь пропонує жваву нічну сцену з різноманітними барами, клубами та варіантами розваг для будь-яких смаків.
### **5. Доступність та зручність**
- **Транспортний вузол:** Неаполь добре з'єднаний авіаційним, залізничним та автомобільним транспортом, що робить подорожі по інших частинах Італії та за її межі простими.
- **Опції для розміщення:** Від розкішних готелів до затишних бутик-готелів, Неаполь пропонує широкий вибір місць для проживання, що відповідають вашим уподобанням та бюджету.
### **Висновок**
Неаполь ідеально поєднує процвітаючу танцювальну сцену, відмінну кулінарію та прекрасні прибережні атракції. Його унікальне поєднання культури, історії та жвавого нічного життя робить його найкращим містом в Італії для здійснення ваших бажань щодо подорожей, смачної їжі та яскравих танцювальних вражень. Незалежно від того, чи танцюєте ви всю ніч, смакуєте справжню піцу біля моря або досліджуєте найближчі прибережні перлини, Неаполь обіцяє незабутню відпустку.
### **Додаткові рекомендації**
- **Одноденні поїздки:** Розгляньте можливість відвідати найближчі пам'ятки, такі як Помпеї, острів Капрі та дивовижне Амальфійське узбережжя, щоб збагатити вашу подорож.
- **Місцеві враження:** Залучайтеся до місцевих танцювальних класів або відвідуйте фестивалі, щоб глибше зануритися в яскраву культурну сцену Неаполя.
Насолоджуйтесь вашою подорожжю до Італії, і нехай Неаполь надасть вам ідеальне поєднання всього, що ви шукаєте!
Вартість:
Для того щоб оцінити вартість подорожі з Осло, Норвегія, до Неаполя, Італія, для двох дорослих та двох дітей, потрібно врахувати кілька основних складових поїздки: авіаквитки, проживання, місцевий транспорт, їжу та активності. Ось розподіл можливих витрат:
1. **Авіаквитки:**
- Квитки туди-назад з Осло до Неаполя зазвичай коштують від 100 до 300 доларів за особу, залежно від часу бронювання, сезону та авіакомпанії. Бюджетні авіакомпанії можуть запропонувати нижчі ціни, тоді як авіакомпанії з повним сервісом можуть бути дорожчими.
- Для сім'ї з чотирьох осіб вартість може коливатися від 400 до 1 200 доларів.
2. **Проживання:**
- Готелі в Неаполі можуть значно варіюватися. Очікуйте, що вартість проживання в номері середнього рівня для сім'ї буде приблизно від 70 до 150 доларів за ніч. Оренда будинків або квартир може бути більш гнучким варіантом і потенційно дешевшим.
- Для типової п’ятиденнної поїздки це буде від 350 до 750 доларів.
3. **Місцевий транспорт:**
- Громадський транспорт в Неаполі (автобуси, метро, трамваї) є доступним, і квитки на день коштують близько 4 доларів за особу.
- Для місцевого транспорту для сім'ї на всю поїздку варто очікувати від 50 до 100 доларів, залежно від використання.
4. **Їжа:**
- Вартість їжі може варіюватися. Бюджет на їжу може бути від 10 до 20 доларів з особи за прийом їжі в неформальних ресторанах, в той час як середній клас ресторанів може коштувати від 20 до 40 доларів з особи.
- Для сім'ї з чотирьох осіб на день це може скласти від 50 до 100 доларів, що в сумі становить від 250 до 500 доларів за п’ять днів.
5. **Активності:**
- Вартість вхідних квитків на атракціони може варіюватися. Деякі музеї та археологічні пам'ятки коштують від 10 до 20 доларів за дорослого, знижки для дітей.
- Очікуйте витрати від 100 до 200 доларів на сімейні активності та вхідні квитки.
6. **Різні витрати:**
- Завжди варто мати трохи додаткових коштів для сувенірів, перекусів та непередбачених витрат.
Типовий буфер може становити від 100 до 200 доларів.
**Оціночна загальна вартість**:
- **Низька оцінка**: $1,250
- **Висока оцінка**: $2,950
Це загальні оцінки, і фактичні витрати можуть змінюватися залежно від того, коли ви подорожуєте, як рано ви бронюєте, а також від ваших особистих уподобань щодо проживання та активностей. Для найбільш точних оцінок рекомендується звернутися до авіакомпаній для актуальних цін на квитки, готелів для тарифів на номери та ознайомитися з конкретними атракціями, які ви хочете відвідати.
Це багато інформації, і це лише результат роботи двох моделей "міркування"!
Але результат досить цікавий. Неаполь є в моєму списку місць, які я хочу відвідати, і мені цікаво, чи вірно працює агент!
Давайте також перевіримо проміжні результати, щоб побачити, як він досяг цього висновку, що здається мені розумним.
Проміжні результати
Якщо вам цікаво, ось проміжні результати.
Їжа:
Як гурман, що досліджує Італію, ви потрапляєте в рай, оскільки країна славиться багатою кулінарною спадщиною та регіональними спеціалітетами. Ось список 10 кращих міст Італії, відомих своєю їжею:
1. **Болонья** - Часто називається гастрономічним серцем Італії, Болонья славиться своїм багатим соусом Болоньєзе, смачною мортаделлою та свіжими тальятелле.
2. **Неаполь** - Батьківщина піци, Неаполь пропонує автентичну неаполітанську піцу, а також смачні морепродукти та випічку, таку як сфольятелла.
3. **Флоренція** - Відома своїм флорентійським стейком, ріболлітою (наполегливий суп з хлібом та овочами) та смачними винами з навколишнього регіону Тоскана.
4. **Рим** - Насолоджуйтесь класичними римськими стравами, такими як карбонара, качіо е пе́пе та римські артишоки в буремній столиці.
5. **Мілан** - Місто, що поєднує традиції та інновації, Мілан пропонує ризотто алла міланезе, оссобуко та ряд розкішних ресторанних вражень.
6. **Турин** - Відомий своєю шоколадною та кавовою культурою, а також традиційними стравами, такими як бенья кауда та агнолотті.
7. **Палермо** - Скуштуйте яскраву вуличну їжу, таку як арачіні, панелле та сфінчіоне, а також свіжі місцеві морепродукти в цьому сицілійському місті.
8. **Венеція** - Відома своїм морепродуктовим ризотто, сарде ін саор (солодко-кислі сардини) та чічетті (венеціанські тапаси), які можна скуштувати з бокалом просекко.
9. **Парма** - Домівка знаменитого сиру Парміджіано-Реджано та прошутто ді Парма, це місце для любителів копчених м'яс та сирів.
10. **Генуя** - Відомий своїм песто Дженовезе, фокаччею та різноманіттям свіжих морепродуктів, Генуя пропонує унікальний смак лігурійської кухні.
Кожне з цих міст пропонує унікальний кулінарний досвід, що впливає на місцеві традиції та інгредієнти, роблячи їх обов'язковими місцями для відвідування для будь-якого гурмана, який досліджує Італію.
Море:
Італія славиться своїми чудовими узбережжями та прекрасними прибережними містами. Ось десять кращих міст і регіонів для морських відпусток:
1. **Амальфі** - Розташоване на знаменитому Амальфійському узбережжі, це місто відоме своїми драматичними скелями, блакитними водами та чарівними прибережними селами.
2. **Позитано** - Також на Амальфійському узбережжі, Позитано славиться своїми кольоровими будівлями, крутішими вулицями та мальовничими пляжами з галькою.
3. **Сорренто** - З чудовими видами на затоку Неаполя, Сорренто служить воротами до Амальфійського узбережжя та пропонує спокійну прибережну атмосферу.
4. **Капрі** - Острів Капрі відомий своїм скелястим ландшафтом, розкішними готелями та знаменитою Блакитною Гроттою, вражаючою морською печерою.
5. **Портофіно** - Це затишне рибальське селище на Італійській Рив'єрі відоме своєю мальовничою гаванью, пастельними будинками та розкішним прибережним оточенням.
6. **Чінкве-Терре** - Складається з п'яти вражаючих селищ на Лігурійському узбережжі, Чінкве-Терре є об'єктом Світової спадщини ЮНЕСКО, відомим своїми драматичними прибережними ландшафтами та пішохідними маршрутами.
7. **Таорміна** - Розташована на пагорбі на східному узбережжі Сицилії, Таорміна пропонує захоплюючі види на Іонічне море та чудові пляжі, такі як Ізола Белла.
8.
**Ріміні** - Розташований на узбережжі Адріатики, Ріміні відомий своїми довгими піщаними пляжами та яскравим нічним життям, що робить його улюбленим місцем для любителів пляжного відпочинку та вечірок.
9. **Альгеро** - Місто на північному заході Сардинії, Альгеро славиться середньовічною архітектурою, вражаючими пляжами та каталонською культурою.
10. **Леріці** - Близько до Лігурійського моря, Леріці є частиною чудової затоки Поетів і відоме своєю красивою бухтою, історичним замком та кришталево чистими водами.
Кожне з цих місць пропонує унікальне поєднання прекрасних пляжів, культурних пам'яток і місцевої кухні, що робить Італію чудовим вибором для морського відпочинку.
Активності:
Італія славиться яскравою танцювальною сценою, і багато міст пропонують чудові можливості для насолоди сальсою та бачато. Ось десять міст, де ви можете зануритися в ці захоплюючі танцювальні стилі:
1. **Рим** - Столиця має жваву танцювальну сцену з численними клубами сальси та регулярними подіями.
2. **Мілан** - Відомий своїм нічним життям, Мілан пропонує різні клуби та події, що орієнтовані на любителів сальси та бачато.
3. **Флоренція** - Культурний центр, Флоренція має кілька танцювальних студій та клубів, де ви можете насолоджуватися латинськими танцями.
4. **Неаполь** - Відомий своєю пристрасною культурою, Неаполь пропонує кілька майданчиків та подій для любителів сальси та бачато.
5. **Турин** - Це місто на півночі має зростаючу спільноту сальси з подіями та соціальними танцями.
6. **Болонья** - Відома своїм активним студентським населенням, Болонья має кілька клубів та подій для сальси та бачато.
7. **Венеція** - Хоча відома своїми романтичними каналами, Венеція також проводить різноманітні танцювальні події протягом року.
8. **Палермо** - На Сицилії Палермо має яскраву латинську танцювальну сцену, що відображає святкову культуру острова.
9. **Верона** - Відомий своєю романтичною атмосферою, Верона має кілька танцювальних студій та клубів для сальси та бачато.
10. **Барі** - Це прибережне місто на півдні пропонує танцювальні фестивалі та клуби, ідеальні для любителів сальси та бачато.
Ці міста пропонують поєднання культурних вражень та активних танцювальних майданчиків, що дозволяє насолоджуватися сальсою та бачато по всій Італії.
Цікаво, що Неаполь не очолює жоден з цих списків, хоча перші чотири міста з переліку морських відпусток знаходяться поруч з Неаполем.
Ліцензійні умови
Перед завершенням статті, кілька слів про ліцензію Fibry. Fibry більше не розповсюджується за чистою ліцензією MIT. Основна відмінність тепер у тому, що якщо ви хочете створити систему для генерації коду в масштабах для третіх осіб (як, наприклад, агент для розробки програмного забезпечення), вам потрібно отримати комерційну ліцензію. Також заборонено включати його в будь-які набори даних для навчання систем, що генерують код (наприклад, ChatGPT не повинно бути натреновано на вихідному коді Fibry). Все інше, ви можете використовувати без обмежень.
Я можу надати комерційну підтримку та розробляти функції за запитом.
Висновки
Сподіваюся, що вам сподобалося і ви змогли зрозуміти, як використовувати Fibry для створення AI-агентів.
Якщо ви вважаєте, що багатокористувацька система повинна бути розподіленою та працювати на кількох вузлах, Fibry вас покриває! Хоча ми збережемо деталі для іншої статті, варто зазначити, що налаштування акторів Fibry в розподіленій системі є простим, і ваші агенти вже є акторами: коли ви викликаєте process() або processAsync(), повідомлення надсилається до підлеглого актора.
У Fibry відправка та отримання повідомлень через мережу абстраговані, тому вам навіть не потрібно змінювати логіку агента для увімкнення розподілу.
Щасливого кодування!
Перекладено з: Designing AI Multi Agent Systems in Java