From 050d4448a65216edecd5e3bb2aab1475b98f4d86 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 12 Dec 2024 17:13:27 +0200 Subject: [PATCH] Add weather agent sample --- samples/pom.xml | 1 + samples/weather-agent/README.md | 63 ++++++++ samples/weather-agent/pom.xml | 135 ++++++++++++++++++ .../weather/agent/CityExtractorAgent.java | 21 +++ .../weather/agent/WeatherForecastAgent.java | 24 ++++ .../weather/agent/WeatherResource.java | 27 ++++ .../weather/agent/geo/GeoCodingService.java | 22 +++ .../weather/agent/geo/GeoResult.java | 4 + .../weather/agent/geo/GeoResults.java | 11 ++ .../weather/agent/weather/Daily.java | 29 ++++ .../weather/agent/weather/DailyUnits.java | 7 + .../agent/weather/DailyWeatherData.java | 23 +++ .../agent/weather/WeatherForecast.java | 6 + .../agent/weather/WeatherForecastService.java | 27 ++++ .../weather/agent/weather/WmoCode.java | 31 ++++ .../src/main/resources/application.properties | 5 + 16 files changed, 436 insertions(+) create mode 100644 samples/weather-agent/README.md create mode 100644 samples/weather-agent/pom.xml create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/CityExtractorAgent.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherForecastAgent.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherResource.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoCodingService.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResult.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResults.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/Daily.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyUnits.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyWeatherData.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecast.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecastService.java create mode 100644 samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WmoCode.java create mode 100644 samples/weather-agent/src/main/resources/application.properties diff --git a/samples/pom.xml b/samples/pom.xml index c2acc9c41..b04dd2cc7 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -22,6 +22,7 @@ secure-poem-multiple-models secure-sql-chatbot sql-chatbot + weather-agent diff --git a/samples/weather-agent/README.md b/samples/weather-agent/README.md new file mode 100644 index 000000000..407545337 --- /dev/null +++ b/samples/weather-agent/README.md @@ -0,0 +1,63 @@ +# Chatbot example + +This example demonstrates how to create an AI agent using Quarkus LangChain4j. + +## Running the example + +A prerequisite to running this example is to provide your OpenAI API key. + +``` +export QUARKUS_LANGCHAIN4J_OPENAI_API_KEY= +``` + +Then, simply run the project in Dev mode: + +``` +mvn quarkus:dev +``` + +## Using the example + +Execute: + +``` +curl http://localhost:8080/weather?city=Athens +``` + +and you should get a response a like so: + +``` +The weather in Athens today is mostly cloudy, with a maximum temperature of 15.6°C and a minimum of 7.4°C. There is no expected precipitation and wind speeds can reach up to 8.1 km/h +``` + +## Using other model providers + +### Compatible OpenAI serving infrastructure + +Add `quarkus.langchain4j.openai.base-url=http://yourerver` to `application.properties`. + +In this case, `quarkus.langchain4j.openai.api-key` is generally not needed. + +### Ollama + + +Replace: + +```xml + + io.quarkiverse.langchain4j + quarkus-langchain4j-openai + ${quarkus-langchain4j.version} + +``` + +with + +```xml + + io.quarkiverse.langchain4j + quarkus-langchain4j-ollama + ${quarkus-langchain4j.version} + +``` + diff --git a/samples/weather-agent/pom.xml b/samples/weather-agent/pom.xml new file mode 100644 index 000000000..4ebd9b3a6 --- /dev/null +++ b/samples/weather-agent/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + io.quarkiverse.langchain4j + quarkus-langchain4j-sample-weather-agent + Quarkus LangChain4j - Sample - Weather Agent + 1.0-SNAPSHOT + + + 3.13.0 + true + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + 3.15.1 + true + 3.2.5 + 999-SNAPSHOT + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + io.quarkus + quarkus-rest-jackson + + + io.quarkiverse.langchain4j + quarkus-langchain4j-openai + ${quarkus-langchain4j.version} + + + io.quarkus + quarkus-cache + + + + + io.quarkiverse.langchain4j + quarkus-langchain4j-openai-deployment + ${quarkus-langchain4j.version} + test + pom + + + * + * + + + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.platform.version} + + + + build + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + maven-surefire-plugin + 3.5.1 + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + 3.5.1 + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + + + + diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/CityExtractorAgent.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/CityExtractorAgent.java new file mode 100644 index 000000000..ee911a066 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/CityExtractorAgent.java @@ -0,0 +1,21 @@ +package io.quarkiverse.langchain4j.weather.agent; + +import dev.langchain4j.agent.tool.Tool; +import dev.langchain4j.service.UserMessage; +import io.quarkiverse.langchain4j.RegisterAiService; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +@RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.NoChatMemoryProviderSupplier.class) +public interface CityExtractorAgent { + + @UserMessage(""" + You are given one question and you have to extract city name from it + Only reply the city name if it exists or reply 'unknown_city' if there is no city name in question + + Here is the question: {question} + """) + @Tool("Extracts the city from a question") + String extractCity(String question); + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherForecastAgent.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherForecastAgent.java new file mode 100644 index 000000000..e050e9372 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherForecastAgent.java @@ -0,0 +1,24 @@ +package io.quarkiverse.langchain4j.weather.agent; + +import dev.langchain4j.service.SystemMessage; +import io.quarkiverse.langchain4j.RegisterAiService; +import io.quarkiverse.langchain4j.weather.agent.geo.GeoCodingService; +import io.quarkiverse.langchain4j.weather.agent.weather.WeatherForecastService; + +@RegisterAiService(tools = { CityExtractorAgent.class, WeatherForecastService.class, GeoCodingService.class}) +public interface WeatherForecastAgent { + + @SystemMessage(""" + You are a meteorologist, and you need to answer questions asked by the user about weather using at most 3 lines. + + The weather information is a JSON object and has the following fields: + + maxTemperature is the maximum temperature of the day in Celsius degrees + minTemperature is the minimum temperature of the day in Celsius degrees + precipitation is the amount of water in mm + windSpeed is the speed of wind in kilometers per hour + weather is the overall weather. + """) + String chat(String query); + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherResource.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherResource.java new file mode 100644 index 000000000..95ece59af --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/WeatherResource.java @@ -0,0 +1,27 @@ +package io.quarkiverse.langchain4j.weather.agent; + +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.jboss.resteasy.reactive.RestQuery; + + +@Path("/weather") +public class WeatherResource { + + private final WeatherForecastAgent agent; + + public WeatherResource(WeatherForecastAgent agent) { + this.agent = agent; + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getWeather(@RestQuery @DefaultValue("Manilla") String city) { + return agent.chat(String.format("What is the weather in %s ?", city)); + } + + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoCodingService.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoCodingService.java new file mode 100644 index 000000000..6e23ce893 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoCodingService.java @@ -0,0 +1,22 @@ +package io.quarkiverse.langchain4j.weather.agent.geo; + +import dev.langchain4j.agent.tool.Tool; +import io.quarkus.cache.CacheResult; +import io.quarkus.rest.client.reactive.ClientQueryParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.RestQuery; + +@RegisterRestClient(configKey = "geocoding") +@Path("/v1") +public interface GeoCodingService { + + @GET + @Path("/search") + @CacheResult(cacheName = "geo-results") + @ClientQueryParam(name = "count", value = "1") + @Tool("Finds the latitude and longitude of a given city") + GeoResults search(@RestQuery String name); + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResult.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResult.java new file mode 100644 index 000000000..404eb810f --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResult.java @@ -0,0 +1,4 @@ +package io.quarkiverse.langchain4j.weather.agent.geo; + +public record GeoResult(double latitude, double longitude) { +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResults.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResults.java new file mode 100644 index 000000000..03322a5c6 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/geo/GeoResults.java @@ -0,0 +1,11 @@ +package io.quarkiverse.langchain4j.weather.agent.geo; + +import java.util.List; + +public record GeoResults(List results) { + + public GeoResult getFirst() { + return results.get(0); + } + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/Daily.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/Daily.java new file mode 100644 index 000000000..cf16c72e3 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/Daily.java @@ -0,0 +1,29 @@ +package io.quarkiverse.langchain4j.weather.agent.weather; + +import java.util.Arrays; + +public record Daily(double[] temperature_2m_max, + double[] temperature_2m_min, + double[] precipitation_sum, + double[] wind_speed_10m_max, + int[] weather_code) { + + public DailyWeatherData getFirstDay() { + return new DailyWeatherData(temperature_2m_max[0], + temperature_2m_min[0], + precipitation_sum[0], + wind_speed_10m_max[0], + weather_code[0]); + } + + @Override + public String toString() { + return "Daily{" + "temperature_2m_max=" + Arrays.toString(temperature_2m_max) + + ", temperature_2m_min=" + Arrays.toString(temperature_2m_min) + + ", precipitation_sum=" + Arrays.toString(precipitation_sum) + + ", wind_speed_10m_max=" + Arrays.toString(wind_speed_10m_max) + + ", weather_code=" + Arrays.toString(weather_code) + + '}'; + } + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyUnits.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyUnits.java new file mode 100644 index 000000000..f6d9799f0 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyUnits.java @@ -0,0 +1,7 @@ +package io.quarkiverse.langchain4j.weather.agent.weather; + +public record DailyUnits(String time, + String temperature_2m_max, + String precipitation_sum, + String wind_speed_10m_max) { +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyWeatherData.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyWeatherData.java new file mode 100644 index 000000000..25f46a4e6 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/DailyWeatherData.java @@ -0,0 +1,23 @@ +package io.quarkiverse.langchain4j.weather.agent.weather; + +import io.vertx.core.json.JsonObject; + +public record DailyWeatherData(double temperature_2m_max, + double temperature_2m_min, + double precipitation_sum, + double wind_speed_10m_max, + int weather_code) { + + + public JsonObject toJson() { + JsonObject json = new JsonObject(); + json.put("maxTemperature", temperature_2m_max()); + json.put("minTemperature", temperature_2m_min()); + json.put("precipitation", precipitation_sum()); + json.put("windSpeed", wind_speed_10m_max()); + json.put("weather", WmoCode.translate(weather_code())); + + return json; + } + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecast.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecast.java new file mode 100644 index 000000000..fbae14887 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecast.java @@ -0,0 +1,6 @@ +package io.quarkiverse.langchain4j.weather.agent.weather; + +public record WeatherForecast(DailyUnits daily_units, Daily daily) { + + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecastService.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecastService.java new file mode 100644 index 000000000..4827f18b0 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WeatherForecastService.java @@ -0,0 +1,27 @@ +package io.quarkiverse.langchain4j.weather.agent.weather; + +import dev.langchain4j.agent.tool.Tool; +import io.quarkus.rest.client.reactive.ClientQueryParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.RestQuery; + +@RegisterRestClient(configKey = "openmeteo") +@Path("/v1") +public interface WeatherForecastService { + + @GET + @Path("/forecast") + @Tool("Forecasts the weather for the given latitude and longitude") + @ClientQueryParam(name = "forecast_days", value = "1") + @ClientQueryParam(name = "daily", value = { + "temperature_2m_max", + "temperature_2m_min", + "precipitation_sum", + "wind_speed_10m_max", + "weather_code" + }) + WeatherForecast forecast(@RestQuery double latitude, @RestQuery double longitude); + +} diff --git a/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WmoCode.java b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WmoCode.java new file mode 100644 index 000000000..195b0bcb8 --- /dev/null +++ b/samples/weather-agent/src/main/java/io/quarkiverse/langchain4j/weather/agent/weather/WmoCode.java @@ -0,0 +1,31 @@ +package io.quarkiverse.langchain4j.weather.agent.weather; + +import java.util.Arrays; + +public enum WmoCode { + + CLEAR_SKY(0), MAINLY_CLEAR(1), PARTLY_CLOUDY(2), OVERCAST(3), + FOG(45), DEPOSITING_RIME_FOG(46), DRIZZLE_LIGHT(51), DRIZZLE_MEDIUM(53), + DRIZZLE_DENSE(55), FREEZING_DRIZZLE_LIGHT(56), FREEZING_DRIZZLE_DENSE(57), + RAIN_SLIGHT(61), RAIN_MODERATE(63), RAIN_HEAVY(65), FREEZING_RAIN_LIGHT(66), FREEZING_RAIN_HEAVY(67), + SNOW_FALL_SLIGHT(71), SNOW_FALL_MODERATE(73), SNOW_FALL_HEAVY(75), SNOW_GRAINS(77), + RAIN_SHOWERS_SLIGHT(80), RAIN_SHOWERS_MODERATE(81), RAIN_SHOWERS_VIOLENT(82), + SNOW_SHOWERS_SLIGHT(85), SNOW_SHOWERS_HEAVY(86), THUNDERSTORM(95), + THUNDERSTORM_SLIGHT_HAIL(96), THUNDERSTORM_HEAVY_HAIL(99); + + final int code; + + WmoCode(int code) { + this.code = code; + } + + public static WmoCode translate(int code) { + WmoCode[] values = WmoCode.values(); + + return Arrays.stream(values) + .filter(wmoCode -> code == wmoCode.code) + .findFirst() + .orElse(null); + } + +} diff --git a/samples/weather-agent/src/main/resources/application.properties b/samples/weather-agent/src/main/resources/application.properties new file mode 100644 index 000000000..d25dc6921 --- /dev/null +++ b/samples/weather-agent/src/main/resources/application.properties @@ -0,0 +1,5 @@ +quarkus.langchain4j.log-requests=true +quarkus.langchain4j.log-responses=true + +quarkus.rest-client.geocoding.url=https://geocoding-api.open-meteo.com +quarkus.rest-client.openmeteo.url=https://api.open-meteo.com