spring-projects / spring-ai

An Application Framework for AI Engineering
https://docs.spring.io/spring-ai/reference/index.html
Apache License 2.0
3.36k stars 866 forks source link

Support for registering Supplier functions in addition to Function interfaces for LLM function calling #1277

Closed jsilverman26 closed 1 week ago

jsilverman26 commented 3 months ago

Please do a quick search on GitHub issues first, the feature you are about to request might have already been requested.

Expected Behavior I expect that Spring AI should support registering java.util.function.Supplier functions for LLM (Large Language Model) function calling, in addition to the currently supported java.util.function.Function interface. This would allow developers to register functions that do not take any arguments but return a dynamic result, such as a random number generator, the current time, or the server's health status.

Here are a few examples where Supplier functions would be useful:

  1. Random Value Generation:

    • Generate a random integer within a specified range:
      Supplier<Integer> randomNumberSupplier = () -> new Random().nextInt(100); // Generates a random number between 0 and 99
    • Generate a random UUID string:
      Supplier<String> randomStringSupplier = () -> UUID.randomUUID().toString();
  2. Time-Related Information:

    • Retrieve the current timestamp:
      Supplier<LocalDateTime> currentTimeSupplier = () -> LocalDateTime.now();
    • Retrieve the current date in a specific format:
      Supplier<String> formattedDateSupplier = () -> LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
  3. Server Status Monitoring:

    • Return the server's current health status, such as "OK" or "FAIL":
      Supplier<String> serverStatusSupplier = () -> "OK"; // In a real scenario, this would check the actual server status

By supporting Supplier functions, Spring AI would allow for more dynamic and versatile function calling capabilities in LLM-powered applications. These functions are essential for generating real-time or context-dependent data without requiring any input parameters.

Current Behavior

Currently, it appears that Spring AI only supports registering functions that implement the java.util.function.Function interface for LLM function calling. This limits the functionality to only those functions that take an input and return an output. However, there are use cases where functions that take no arguments but return a value (like Supplier) would be more appropriate. This limitation makes it impossible to implement certain types of functionality using the current version.

Context This issue has affected our ability to fully utilize Spring AI for dynamic content generation, such as generating random numbers or strings within the context of an LLM-powered application. We have considered alternative approaches, such as wrapping a Supplier within a Function, but this feels like a workaround rather than a proper solution. We believe that direct support for Supplier would be a more elegant and useful addition to the framework.

Are there any plans to support this feature, or is there a recommended workaround that we may not be aware of?

ullenboom commented 3 months ago

Assuming the FunctionCallbackWrapper class has a new builder(Supplier<O> function) method, is there really much difference between writing:

FunctionCallbackWrapper.builder(() -> LocalDate.now());

and:

FunctionCallbackWrapper.builder(_ -> LocalDate.now());  // Java 22; use __ for earlier versions

Or, you could just use your simple utility method:

public static <Void, O> FunctionCallbackWrapper.Builder<Void, O> builder(Supplier<O> function) {
  return new FunctionCallbackWrapper.Builder<>(__ -> function.get());
}
jsilverman26 commented 3 months ago

@ullenboom Thank you for the detailed explanation! I understand the utility of converting a Supplier to a Function using a utility method, and it certainly works without adding new methods. However, my concern is to simplify the API for cases where Supplier would be more natural. For example, in a Spring Boot application where I need to retrieve the current time, the current implementation requires using a Function like this:

@GetMapping("/sample")
public AssistantMessage sample() {
    return chatClient.prompt()
            .function("GetCurrentTime", "Use this function to get the current time.",  ... some Function code  .... )
            .system("You are a helpful AI assistant.")
            .call()
            .chatResponse()
            .getResult()
            .getOutput();

However, if Supplier were supported directly, the code could be more concise and intuitive:

@GetMapping("/sample")
public AssistantMessage sample() {
    return chatClient.prompt()
            .function("GetCurrentTime", "Use this function to get the current time.", () -> LocalDateTime.now().toString())
            .system("You are a helpful AI bot.")
            .call()
            .chatResponse()
            .getResult()
            .getOutput();
}

Supporting Supplier directly in FunctionCallbackWrapper would make such scenarios simpler and enhance the developer experience.

Feel free to share any other thoughts or suggestions; I welcome them!

tzolov commented 3 weeks ago

This issue is part of the more generic one #1718

markpollack commented 1 week ago

This functionality was added in M4, closing. See 432954dad7f7c3a33117848a4259bc7076865c5f commit