Skip to content

Conversation

@st-rm-ng
Copy link

@st-rm-ng st-rm-ng commented Oct 30, 2025

Closes #13860

  • This pull request adds an AiModelService to improve how JabRef handles and manages language models for AI features.
  • Created new AiModelService class to centralize AI model management

Steps to test

./gradlew :jablib:test --tests "org.jabref.logic.ai.models.*Test"

Mandatory checks

@github-actions
Copy link
Contributor

Hey @st-rm-ng!

Thank you for contributing to JabRef! Your help is truly appreciated ❤️.

We have automatic checks in place, based on which you will soon get automated feedback if any of them are failing. We also use TragBot with custom rules that scans your changes and provides some preliminary comments, before a maintainer takes a look. TragBot is still learning, and may not always be accurate. In the "Files changed" tab, you can go through its comments and just click on "Resolve conversation" if you are sure that it is incorrect, or comment on the conversation if you are doubtful.

Please re-check our contribution guide in case of any other doubts related to our contribution workflow.

@koppor koppor requested a review from InAnYan October 30, 2025 15:14
Copy link
Member

@InAnYan InAnYan left a comment

Choose a reason for hiding this comment

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

Hi!

Thanks very much for looking into this issue. I left some comments to change. Currently I'm on phone and I wasn't able to test the PR

@st-rm-ng
Copy link
Author

Hello @koppor and @InAnYan,

this is just a Draft PR, and it's not ready to be tested or anything. Think of this as my initial attempt or establishment to view all changes. I will let you know when it will be in ready for review state.

Thank you very much for your quick response to this PR 😄

@jabref-machine
Copy link
Collaborator

Your code currently does not meet JabRef's code guidelines. IntelliJ auto format covers some cases. There seem to be issues with your code style and autoformat configuration. Please reformat your code (Ctrl+Alt+L) and commit, then push.

In special cases, consider using // formatter:off and // formatter:on annotations to allow deviation from the code style.

@st-rm-ng st-rm-ng marked this pull request as ready for review November 18, 2025 22:31
@st-rm-ng st-rm-ng requested review from InAnYan and koppor November 18, 2025 22:32
Copy link
Member

@koppor koppor left a comment

Choose a reason for hiding this comment

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

The threading thing looks complicated. There at least should be a note why a "synchronous" nested thread-spawning thing is nested in a Background-Task.

Not sure if this is the call for using RxJava to enable easy parallel processing: https://github.com/ReactiveX/RxJava#parallel-processing

@github-actions github-actions bot added the status: changes-required Pull requests that are not yet complete label Nov 19, 2025
@st-rm-ng
Copy link
Author

st-rm-ng commented Nov 20, 2025

The threading thing looks complicated. There at least should be a note why a "synchronous" nested thread-spawning thing is nested in a Background-Task.

Not sure if this is the call for using RxJava to enable easy parallel processing: https://github.com/ReactiveX/RxJava#parallel-processing

The reason for this complexity is to implement a timeout mechanism that we need to avoid blocking the BackgroundTask thread indefinitely. You are right, this solution is maybe overly complicated and from my perspective it would be maybe better to use HttpClient.sendAsync() and CompletableFuture.orTimeout() to avoid nested thread spawning.

Regarding RxJava - I think that in this case I am only fetching models from one provider at a time and maybe it would be better for the future to fetch models from multiple providers in parallel using RxJava. Maybe something like Observable.fromIterable(providers).flatMap(...).timeout(...) which would look more elegant.

@koppor Should I use RxJava approach instead? What would be your advice?

@github-actions github-actions bot removed the status: changes-required Pull requests that are not yet complete label Nov 20, 2025
@st-rm-ng st-rm-ng requested a review from koppor November 20, 2025 21:33
@Siedlerchr
Copy link
Member

The threading thing looks complicated. There at least should be a note why a "synchronous" nested thread-spawning thing is nested in a Background-Task.
Not sure if this is the call for using RxJava to enable easy parallel processing: https://github.com/ReactiveX/RxJava#parallel-processing

The reason for this complexity is to implement a timeout mechanism that we need to avoid blocking the BackgroundTask thread indefinitely. You are right, this solution is maybe overly complicated and from my perspective it would be maybe better to use HttpClient.sendAsync() and CompletableFuture.orTimeout() to avoid nested thread spawning.

Regarding RxJava - I think that in this case I am only fetching models from one provider at a time and maybe it would be better for the future to fetch models from multiple providers in parallel using RxJava. Maybe something like Observable.fromIterable(providers).flatMap(...).timeout(...) which would look more elegant.

@koppor Should I use RxJava approach instead? What would be your advice?

For timeouts you could leverage the functionality of the Completable Future https://www.baeldung.com/java-completablefuture-timeout
I don't think rxjava would be needed here

@koppor
Copy link
Member

koppor commented Nov 21, 2025

The threading thing looks complicated. There at least should be a note why a "synchronous" nested thread-spawning thing is nested in a Background-Task.
Not sure if this is the call for using RxJava to enable easy parallel processing: https://github.com/ReactiveX/RxJava#parallel-processing

The reason for this complexity is to implement a timeout mechanism that we need to avoid blocking the BackgroundTask thread indefinitely. You are right, this solution is maybe overly complicated and from my perspective it would be maybe better to use HttpClient.sendAsync() and CompletableFuture.orTimeout() to avoid nested thread spawning.

Regarding RxJava - I think that in this case I am only fetching models from one provider at a time and maybe it would be better for the future to fetch models from multiple providers in parallel using RxJava. Maybe something like Observable.fromIterable(providers).flatMap(...).timeout(...) which would look more elegant.

@koppor Should I use RxJava approach instead? What would be your advice?

For timeouts you could leverage the functionality of the Completable Future https://www.baeldung.com/java-completablefuture-timeout
I don't think rxjava would be needed here

@st-rm-ng please follow that advice.

@InAnYan
Copy link
Member

InAnYan commented Nov 21, 2025

Okay, I only reviewed the code, but I find it very clear and easy to read. But may I ask this question: why there is at all some complicated logic for fetching the models? Why do you need to create a fetch thread and check that it's alive?

I think 1 BackgroundTask (which you already have) that is executed with a TaskExecutor on the start of JabRef is enough. And in case user wants to refresh, you can start that task when preferences window is open, or add a ⟳ button near the chat model field

Copy link
Member

@koppor koppor left a comment

Choose a reason for hiding this comment

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

Okay, I only reviewed the code, but I find it very clear and easy to read. But may I ask this question: why there is at all some complicated logic for fetching the models? Why do you need to create a fetch thread and check that it's alive?

The contributor answered:

The reason for this complexity is to implement a timeout mechanism that we need to avoid blocking the BackgroundTask thread indefinitely

Therefore siedlerchr recommended https://www.baeldung.com/java-completablefuture-timeout

Hope this works.

@github-actions github-actions bot added the status: changes-required Pull requests that are not yet complete label Nov 21, 2025
@InAnYan
Copy link
Member

InAnYan commented Nov 21, 2025

Sorry, maybe I miss something, but I still don't get why implementing timeout is that hard and how it can block the background tasks? (I think we use a thread pool)

Here is an example how to send http request with timeout that ChatGPT generated me:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class SimpleHttp {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(5))  // connection timeout
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://example.com"))
                .timeout(Duration.ofSeconds(3))        // request timeout
                .GET()
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}

When it reaches timeout, an exception will be thrown.

BackgroundTask may process it by (if I recall correctly) task.onError(e -> {...})

@InAnYan
Copy link
Member

InAnYan commented Nov 21, 2025

Sorry, maybe I just don't know something about Java

@koppor
Copy link
Member

koppor commented Nov 21, 2025

@InAnYan Thank you for pointing out. Your code should solve the issue. @st-rm-ng Please incorporate that idea into your solution!

@github-actions github-actions bot removed the status: changes-required Pull requests that are not yet complete label Nov 22, 2025
@st-rm-ng
Copy link
Author

st-rm-ng commented Nov 22, 2025

Thank you for advices @Siedlerchr, @koppor and @InAnYan. I've removed redundant FetchThread timeout mechanism for simpler synchronous model fetching and I've used built-in timeout for HTTP Client fetching. I didn't use CompletableFuture and it's just simply relying on HTTP Client timeout. Is this what you had in mind @InAnYan?

@st-rm-ng st-rm-ng requested a review from koppor November 22, 2025 12:05
@jabref-machine
Copy link
Collaborator

JUnit tests of jablib are failing. You can see which checks are failing by locating the box "Some checks were not successful" on the pull request page. To see the test output, locate "Source Code Tests / Unit tests (pull_request)" and click on it.

You can then run these tests in IntelliJ to reproduce the failing tests locally. We offer a quick test running howto in the section Final build system checks in our setup guide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automatically determine latest language models

5 participants