Quarkus extension for integrating IBM watsonx.ai foundation models with LangChain4j. Provides chat models, generation models, streaming models, embedding models, and scoring models for IBM watsonx.ai. Includes comprehensive configuration options, support for tool/function calling, text extraction from documents in Cloud Object Storage, and experimental built-in services for Google search, weather, and web crawling. Designed for enterprise Java applications using the Quarkus framework with built-in dependency injection and native compilation support.
Experimental utility services that integrate with Watsonx's utility agent tools API. These services provide Google search, weather information, and web page content extraction capabilities. All services are marked with the @Experimental annotation and may change in future versions.
Important: These services are experimental and subject to change. APIs, behavior, and availability may be modified or removed in future releases.
Search online content using Google and retrieve ranked results.
@Experimental
public class GoogleSearchService {
public GoogleSearchService(UtilityAgentToolsRestApi client, GoogleSearchConfig config);
public List<GoogleSearchResult> search(String input);
public List<GoogleSearchResult> search(String input, Integer maxResults);
}Example Usage:
import io.quarkiverse.langchain4j.watsonx.services.GoogleSearchService;
import io.quarkiverse.langchain4j.watsonx.bean.GoogleSearchResult;
import jakarta.inject.Inject;
import java.util.List;
@ApplicationScoped
public class SearchService {
@Inject
GoogleSearchService googleSearch;
public List<GoogleSearchResult> searchWeb(String query) {
// Search with default max results (configured in application.properties)
return googleSearch.search(query);
}
public List<GoogleSearchResult> searchWebLimit(String query, int limit) {
// Search with custom max results (max 20)
return googleSearch.search(query, Math.min(limit, 20));
}
}With LangChain4j Tools:
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;
public class SearchTools {
@Inject
GoogleSearchService googleSearch;
@Tool("Search Google for information")
public String searchGoogle(
@P("Search query") String query,
@P("Maximum number of results") Integer maxResults
) {
List<GoogleSearchResult> results = googleSearch.search(query, maxResults);
return results.stream()
.map(result -> String.format(
"Title: %s\nURL: %s\nSnippet: %s\n",
result.title(),
result.url(),
result.description()
))
.collect(Collectors.joining("\n---\n"));
}
}Retrieve current weather information for cities worldwide.
@Experimental
public class WeatherService {
public WeatherService(UtilityAgentToolsRestApi client);
public String find(String name, String country)
throws WeatherServiceException, NoCityFoundException;
}Exception Classes:
public class WeatherServiceException extends Exception {
public WeatherServiceException(String message);
public WeatherServiceException(String message, Throwable cause);
}
public class NoCityFoundException extends WeatherServiceException {
public NoCityFoundException(String message);
}Example Usage:
import io.quarkiverse.langchain4j.watsonx.services.WeatherService;
import io.quarkiverse.langchain4j.watsonx.services.WeatherService.WeatherServiceException;
import io.quarkiverse.langchain4j.watsonx.services.WeatherService.NoCityFoundException;
import jakarta.inject.Inject;
@ApplicationScoped
public class WeatherInfoService {
@Inject
WeatherService weatherService;
public String getWeather(String city, String country) {
try {
return weatherService.find(city, country);
} catch (NoCityFoundException e) {
return "City not found: " + city + ", " + country;
} catch (WeatherServiceException e) {
return "Error retrieving weather: " + e.getMessage();
}
}
}With LangChain4j Tools:
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;
public class WeatherTools {
@Inject
WeatherService weatherService;
@Tool("Get current weather for a city")
public String getCurrentWeather(
@P("City name") String city,
@P("Country code (e.g., US, FR, JP)") String country
) {
try {
return weatherService.find(city, country);
} catch (NoCityFoundException e) {
return "City not found: " + city;
} catch (WeatherServiceException e) {
return "Error: " + e.getMessage();
}
}
}Fetch and extract content from web pages.
@Experimental
public class WebCrawlerService {
public WebCrawlerService(UtilityAgentToolsRestApi client);
public WebCrawlerResult process(String url) throws BuiltinServiceException;
}Example Usage:
import io.quarkiverse.langchain4j.watsonx.services.WebCrawlerService;
import io.quarkiverse.langchain4j.watsonx.bean.WebCrawlerResult;
import io.quarkiverse.langchain4j.watsonx.exception.BuiltinServiceException;
import jakarta.inject.Inject;
@ApplicationScoped
public class WebContentService {
@Inject
WebCrawlerService webCrawler;
public String fetchWebContent(String url) {
try {
WebCrawlerResult result = webCrawler.process(url);
return result.content();
} catch (BuiltinServiceException e) {
return "Error fetching content: " + e.getMessage() +
" (Status: " + e.statusCode() + ")";
}
}
public WebCrawlerResult fetchWebPage(String url) {
try {
return webCrawler.process(url);
} catch (BuiltinServiceException e) {
throw new RuntimeException("Failed to fetch page: " + url, e);
}
}
}With LangChain4j Tools:
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;
public class WebTools {
@Inject
WebCrawlerService webCrawler;
@Tool("Fetch and extract content from a web page")
public String fetchWebPage(
@P("Web page URL") String url
) {
try {
WebCrawlerResult result = webCrawler.process(url);
return String.format(
"URL: %s\n\nContent:\n%s",
result.url(),
result.content()
);
} catch (BuiltinServiceException e) {
return "Error: " + e.getMessage();
}
}
}Configure built-in services in application.properties:
# Built-in services base URL (auto-calculated if empty)
quarkus.langchain4j.watsonx.built-in-service.base-url=https://api.watsonx.ai/services
# API key (inherits from watsonx.api-key if empty)
quarkus.langchain4j.watsonx.built-in-service.api-key=your-api-key
# Request timeout (default: 10s)
quarkus.langchain4j.watsonx.built-in-service.timeout=15s
# Logging configuration
quarkus.langchain4j.watsonx.built-in-service.log-requests=false
quarkus.langchain4j.watsonx.built-in-service.log-responses=false
# Google search configuration
quarkus.langchain4j.watsonx.built-in-service.google-search.max-results=10Services are automatically created and injected by Quarkus:
import jakarta.inject.Inject;
import io.quarkiverse.langchain4j.watsonx.services.*;
@ApplicationScoped
public class UtilityService {
@Inject
GoogleSearchService googleSearch;
@Inject
WeatherService weatherService;
@Inject
WebCrawlerService webCrawler;
}public record GoogleSearchResult(
String title,
String url,
String description
) {}Fields:
Example:
List<GoogleSearchResult> results = googleSearch.search("machine learning");
for (GoogleSearchResult result : results) {
System.out.println("Title: " + result.title());
System.out.println("URL: " + result.url());
System.out.println("Snippet: " + result.description());
System.out.println("---");
}public record WebCrawlerResult(
String url,
String contentType,
String content
) {}Fields:
Example:
WebCrawlerResult result = webCrawler.process("https://example.com");
System.out.println("URL: " + result.url());
System.out.println("Content length: " + result.content().length());
System.out.println("Content: " + result.content());public class BuiltinServiceException extends RuntimeException {
public BuiltinServiceException(String message, Integer statusCode);
public Integer statusCode();
public String details();
}Example:
try {
WebCrawlerResult result = webCrawler.process(url);
} catch (BuiltinServiceException e) {
System.err.println("Status: " + e.statusCode());
System.err.println("Details: " + e.details());
if (e.statusCode() == 404) {
// Handle not found
} else if (e.statusCode() >= 500) {
// Handle server error
}
}import dev.langchain4j.service.AiServices;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;
// Define AI service interface
interface SearchAssistant {
String chat(String message);
}
// Define tools
public class SearchAgentTools {
@Inject
GoogleSearchService googleSearch;
@Inject
WebCrawlerService webCrawler;
@Tool("Search Google for information")
public String searchWeb(
@P("Search query") String query,
@P("Number of results") Integer maxResults
) {
List<GoogleSearchResult> results = googleSearch.search(
query,
Math.min(maxResults, 10)
);
return results.stream()
.map(r -> String.format("Title: %s\nURL: %s\nSnippet: %s",
r.title(), r.url(), r.description()))
.collect(Collectors.joining("\n\n"));
}
@Tool("Fetch full content from a web page")
public String fetchPage(@P("URL to fetch") String url) {
try {
WebCrawlerResult result = webCrawler.process(url);
return result.content();
} catch (BuiltinServiceException e) {
return "Error: " + e.getMessage();
}
}
}
// Create agent
@ApplicationScoped
public class SearchAgent {
@Inject
ChatModel chatModel;
@Inject
SearchAgentTools tools;
public String search(String userQuery) {
SearchAssistant assistant = AiServices.builder(SearchAssistant.class)
.chatModel(chatModel)
.tools(tools)
.build();
return assistant.chat(userQuery);
}
}Usage:
SearchAgent agent = CDI.current().select(SearchAgent.class).get();
String response = agent.search("What are the latest developments in AI?");
// Agent will:
// 1. Search Google for relevant information
// 2. Optionally fetch full pages for more detail
// 3. Synthesize a comprehensive answerinterface WeatherAssistant {
String chat(String message);
}
public class WeatherAgentTools {
@Inject
WeatherService weatherService;
@Tool("Get current weather for a city")
public String getWeather(
@P("City name") String city,
@P("Country code") String country
) {
try {
return weatherService.find(city, country);
} catch (NoCityFoundException e) {
return "City not found: " + city;
} catch (WeatherServiceException e) {
return "Error: " + e.getMessage();
}
}
}
@ApplicationScoped
public class WeatherAgent {
@Inject
ChatModel chatModel;
@Inject
WeatherAgentTools tools;
public String chat(String userMessage) {
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.chatModel(chatModel)
.tools(tools)
.build();
return assistant.chat(userMessage);
}
}Usage:
WeatherAgent agent = CDI.current().select(WeatherAgent.class).get();
String response = agent.chat("What's the weather like in Paris today?");
// Agent will call weather service and provide formatted responseinterface ResearchAssistant {
String research(String topic);
}
public class ResearchTools {
@Inject
GoogleSearchService googleSearch;
@Inject
WebCrawlerService webCrawler;
@Inject
WeatherService weatherService;
@Tool("Search for information online")
public String search(@P("Query") String query) {
return googleSearch.search(query, 5).stream()
.map(r -> String.format("%s: %s", r.title(), r.description()))
.collect(Collectors.joining("\n"));
}
@Tool("Fetch detailed content from a URL")
public String fetchContent(@P("URL") String url) {
try {
return webCrawler.process(url).content();
} catch (BuiltinServiceException e) {
return "Error: " + e.getMessage();
}
}
@Tool("Get weather information")
public String getWeather(
@P("City") String city,
@P("Country") String country
) {
try {
return weatherService.find(city, country);
} catch (Exception e) {
return "Weather unavailable";
}
}
}
@ApplicationScoped
public class ResearchAgent {
@Inject
ChatModel chatModel;
@Inject
ResearchTools tools;
public String research(String topic) {
ResearchAssistant assistant = AiServices.builder(ResearchAssistant.class)
.chatModel(chatModel)
.tools(tools)
.build();
return assistant.research(topic);
}
}@ApplicationScoped
public class RobustUtilityService {
@Inject
GoogleSearchService googleSearch;
@Inject
WebCrawlerService webCrawler;
@Inject
WeatherService weatherService;
public List<GoogleSearchResult> searchSafely(String query) {
try {
return googleSearch.search(query, 10);
} catch (Exception e) {
logger.error("Search failed for query: " + query, e);
return List.of();
}
}
public Optional<WebCrawlerResult> fetchSafely(String url) {
try {
return Optional.of(webCrawler.process(url));
} catch (BuiltinServiceException e) {
logger.error("Failed to fetch URL: " + url, e);
return Optional.empty();
}
}
public Optional<String> getWeatherSafely(String city, String country) {
try {
return Optional.of(weatherService.find(city, country));
} catch (NoCityFoundException e) {
logger.warn("City not found: " + city + ", " + country);
return Optional.empty();
} catch (WeatherServiceException e) {
logger.error("Weather service error", e);
return Optional.empty();
}
}
}import java.util.concurrent.Semaphore;
import java.time.Duration;
@ApplicationScoped
public class RateLimitedSearchService {
@Inject
GoogleSearchService googleSearch;
private final Semaphore semaphore = new Semaphore(5); // Max 5 concurrent requests
public List<GoogleSearchResult> searchWithRateLimit(String query) {
try {
semaphore.acquire();
try {
return googleSearch.search(query);
} finally {
// Release after delay
Thread.sleep(1000); // 1 second between requests
semaphore.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted", e);
}
}
}import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
@ApplicationScoped
public class CachedUtilityService {
@Inject
GoogleSearchService googleSearch;
@Inject
WebCrawlerService webCrawler;
private final Map<String, List<GoogleSearchResult>> searchCache = new ConcurrentHashMap<>();
private final Map<String, WebCrawlerResult> contentCache = new ConcurrentHashMap<>();
public List<GoogleSearchResult> searchWithCache(String query) {
return searchCache.computeIfAbsent(query, q -> googleSearch.search(q));
}
public WebCrawlerResult fetchWithCache(String url) {
return contentCache.computeIfAbsent(url, u -> {
try {
return webCrawler.process(u);
} catch (BuiltinServiceException e) {
throw new RuntimeException(e);
}
});
}
public void clearCache() {
searchCache.clear();
contentCache.clear();
}
}# Increase timeout for slow operations
quarkus.langchain4j.watsonx.built-in-service.timeout=30s
# Different timeouts per environment
%dev.quarkus.langchain4j.watsonx.built-in-service.timeout=60s
%prod.quarkus.langchain4j.watsonx.built-in-service.timeout=15s@ApplicationScoped
public class FilteredWebService {
@Inject
WebCrawlerService webCrawler;
public String fetchAndFilter(String url, int maxLength) {
try {
WebCrawlerResult result = webCrawler.process(url);
String content = result.content();
// Truncate if too long
if (content.length() > maxLength) {
return content.substring(0, maxLength) + "...";
}
return content;
} catch (BuiltinServiceException e) {
return "Error: " + e.getMessage();
}
}
public String fetchMainContent(String url) {
try {
WebCrawlerResult result = webCrawler.process(url);
// Remove common noise (adjust based on your needs)
return result.content()
.replaceAll("(?i)(cookie|privacy policy|terms of service).*", "")
.trim();
} catch (BuiltinServiceException e) {
return "";
}
}
}Since these services are experimental, be prepared for:
Recommendation: Wrap experimental services in your own abstraction layer to isolate changes:
public interface SearchProvider {
List<SearchResult> search(String query, int maxResults);
}
@ApplicationScoped
public class WatsonxSearchProvider implements SearchProvider {
@Inject
GoogleSearchService googleSearch;
@Override
public List<SearchResult> search(String query, int maxResults) {
// Adapter pattern isolates changes
return googleSearch.search(query, maxResults).stream()
.map(r -> new SearchResult(r.title(), r.link(), r.snippet()))
.toList();
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-watsonx