Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue 4153] Adds a backoff strategy to OpenAI API calls #3

Open
wants to merge 4 commits into
base: 5.25
Choose a base branch
from

Conversation

mpetrini-larus
Copy link
Owner

Fixes neo4j-contrib#4153
Adds a backoff strategy to OpenAI API calls.

Proposed Changes (Mandatory)

A brief list of proposed changes in order to fix the issue:

  • To better handle API failures caused by excessive load (HTTP 429 - Too Many Requests), the OpenAI API calls have been wrapped in a method that implements a backoff strategy.
  • This approach efficiently manages retries, reducing the likelihood of overwhelming the API and enhancing system resilience under high load conditions.

Copy link

@vga91 vga91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:emoji-tauromachia: giusto qualche modifichetta qua e la.

Oltre a quelle di sotto, metti pure la documentazione nel file docs/asciidoc/modules/ROOT/pages/ml/openai.adoc, nella tabella .Common configuration parameter,
quindi tipo

| enableBackOffRetries | <descrizione> (default: false)
| backOffRetries | <descrizione> (default: 5)
| exponentialBackoff | <descrizione> (default: false)
...

@@ -36,7 +36,8 @@ public OpenAIIT() {

@Before
public void setUp() throws Exception {
openaiKey = System.getenv("OPENAI_KEY");
openaiKey = "OPENAI_KEY";
// openaiKey = System.getenv("OPENAI_KEY");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questo si può togliere

@@ -35,6 +37,8 @@ public class OpenAI {
public static final String JSON_PATH_CONF_KEY = "jsonPath";
public static final String PATH_CONF_KEY = "path";
public static final String GPT_4O_MODEL = "gpt-4o";
public static final String ENABLE_BACK_OFF_RETRIES = "enableBackOffRetries";
public static final String BACK_OFF_RETRIES = "backOffRetries";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static final String BACK_OFF_RETRIES = "backOffRetries";
public static final String BACK_OFF_RETRIES_CONF_KEY = "backOffRetries";

Supplier<T> func, boolean retry, Integer backoffRetry,
Consumer<Exception> exceptionHandler
){
return withBackOffRetries(func, retry, backoffRetry, exceptionHandler, Boolean.FALSE);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forse se invece di Boolean mettiamo boolean si semplifica un po', perché non serve requireNonNullElse?

Comment on lines 380 to 388
long sleepMultiplier = Objects.requireNonNullElse(exponential, Boolean.FALSE) ?
(long) Math.pow(2, backoffRetry - countDown) : // Exponential retry progression
backoffRetry - countDown; // Linear retry progression
long delay = Math.min(
Duration.ofSeconds(1)
.multipliedBy(sleepMultiplier)
.toMillis(),
Duration.ofSeconds(30).toMillis() // Max 30s
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lo raggrupperei in un metodo, quindi farei così:

long delay = getDelay(backoffRetry, exponential, countDown);

e poi

    private static long getDelay(Integer backoffRetry, Boolean exponential, Integer countDown) {
        long sleepMultiplier = Objects.requireNonNullElse(exponential, Boolean.FALSE) ?
                (long) Math.pow(2, backoffRetry - countDown) : // Exponential retry progression
                backoffRetry - countDown; // Linear retry progression
        long delay = Math.min(
                Duration.ofSeconds(1)
                        .multipliedBy(sleepMultiplier)
                        .toMillis(),
                Duration.ofSeconds(30).toMillis() // Max 30s
        );
        return delay;
    }

@@ -58,6 +62,8 @@ public EmbeddingResult(long index, String text, List<Double> embedding) {

static Stream<Object> executeRequest(String apiKey, Map<String, Object> configuration, String path, String model, String key, Object inputs, String jsonPath, ApocConfig apocConfig, URLAccessChecker urlAccessChecker) throws JsonProcessingException, MalformedURLException {
apiKey = (String) configuration.getOrDefault(APIKEY_CONF_KEY, apocConfig.getString(APOC_OPENAI_KEY, apiKey));
Boolean enableBackOffRetries = (Boolean) configuration.getOrDefault(ENABLE_BACK_OFF_RETRIES, Boolean.TRUE);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anche se non è proprio proprio una breaking-change, meglio stare tranquilli,
la metterei con default a false.

Dovrebbe funzionare se fai così:

Util.toBoolean( configuration.get(ENABLE_BACK_OFF_RETRIES_CONF_KEY) )

@@ -35,6 +37,8 @@ public class OpenAI {
public static final String JSON_PATH_CONF_KEY = "jsonPath";
public static final String PATH_CONF_KEY = "path";
public static final String GPT_4O_MODEL = "gpt-4o";
public static final String ENABLE_BACK_OFF_RETRIES = "enableBackOffRetries";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static final String ENABLE_BACK_OFF_RETRIES = "enableBackOffRetries";
public static final String ENABLE_BACK_OFF_RETRIES_CONF_KEY = "enableBackOffRetries";

@@ -58,6 +62,8 @@ public EmbeddingResult(long index, String text, List<Double> embedding) {

static Stream<Object> executeRequest(String apiKey, Map<String, Object> configuration, String path, String model, String key, Object inputs, String jsonPath, ApocConfig apocConfig, URLAccessChecker urlAccessChecker) throws JsonProcessingException, MalformedURLException {
apiKey = (String) configuration.getOrDefault(APIKEY_CONF_KEY, apocConfig.getString(APOC_OPENAI_KEY, apiKey));
Boolean enableBackOffRetries = (Boolean) configuration.getOrDefault(ENABLE_BACK_OFF_RETRIES, Boolean.TRUE);
Integer backOffRetries = (Integer) configuration.getOrDefault(BACK_OFF_RETRIES, 5);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visto che sta pagato, metterei anche un

boolean exponentialBackoff = Util.toBoolean( configuration.get(BACK_OFF_EXPONENTIAL_CONF_KEY) )

)
);
long time = System.currentTimeMillis() - start;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scrivi pure qua qualche riga di commento come sopra:

// The method will attempt to execute the operation with an exponential backoff strategy,
...etc

Copy link

@vga91 vga91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇 giusto queste due modifichine e siamo a cavallo.

@@ -77,7 +85,7 @@ static Stream<Object> executeRequest(String apiKey, Map<String, Object> configur
path = (String) configuration.getOrDefault(PATH_CONF_KEY, path);
OpenAIRequestHandler apiType = type.get();

jsonPath = (String) configuration.getOrDefault(JSON_PATH_CONF_KEY, jsonPath);
String sJsonPath = (String) configuration.getOrDefault(JSON_PATH_CONF_KEY, jsonPath);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Farei anch'io un'altra variabile, ma visto che l'implementazione viene dall'alto meglio rimettere jsonPath come prima

Copy link
Owner Author

@mpetrini-larus mpetrini-larus Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

si ma poi non lo posso passare alla lambda sotto 😕
se la lascio com'era non è "effettivamente-statica" e la lambda non la digerisce

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java maledetto, allora va bene così

extended/src/test/java/apoc/util/ExtendedUtilTest.java Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants