CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-watsonx

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.

Overview
Eval results
Files

builtin-services.mddocs/

Built-in Services (Experimental)

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.

Capabilities

Google Search Service

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

Weather Service

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

Web Crawler Service

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

Configuration

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=10

Dependency Injection

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

Types

Google Search Result

public record GoogleSearchResult(
    String title,
    String url,
    String description
) {}

Fields:

  • title (String): Page title
  • url (String): URL of the result
  • description (String): Text snippet/description

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

Web Crawler Result

public record WebCrawlerResult(
    String url,
    String contentType,
    String content
) {}

Fields:

  • url (String): The URL that was crawled
  • contentType (String): Content type of the crawled page
  • content (String): Extracted text content from the page

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

Built-in Service Exception

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

AI Agent Integration

Complete Search Agent

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 answer

Weather Information Agent

interface 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 response

Multi-Service Research Agent

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

Best Practices

Error Handling

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

Rate Limiting

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

Caching Results

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

Timeout Configuration

# 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

Content Filtering

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

Limitations

Experimental Status

  • APIs may change without notice
  • Features may be added or removed
  • Service availability depends on Watsonx backend

Google Search

  • Maximum 20 results per request
  • Results depend on Google's availability
  • May have rate limits or quotas

Weather Service

  • Requires exact city names and country codes
  • May not cover all locations
  • Throws NoCityFoundException for unknown cities

Web Crawler

  • May fail on JavaScript-heavy sites
  • Respects robots.txt and rate limits
  • Content extraction quality varies by site structure

Migration Notes

Since these services are experimental, be prepared for:

  1. API Changes: Method signatures may change
  2. Configuration Changes: Property names may be updated
  3. Behavior Changes: Service behavior may be modified
  4. Deprecation: Services may be deprecated or replaced

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@1.7.0

docs

builtin-services.md

chat-models.md

configuration.md

embedding-scoring.md

exceptions.md

generation-models.md

index.md

request-parameters.md

text-extraction.md

tile.json