Skip to content

Add AiModelService for improving JabRef handling of language models (#13860)#14197

Merged
Siedlerchr merged 25 commits into
JabRef:mainfrom
st-rm-ng:fix-for-issue-13860
Jan 17, 2026
Merged

Add AiModelService for improving JabRef handling of language models (#13860)#14197
Siedlerchr merged 25 commits into
JabRef:mainfrom
st-rm-ng:fix-for-issue-13860

Conversation

@st-rm-ng

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

Copy link
Copy Markdown
Contributor

Contributes to #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

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • I added screenshots in the PR description (if change is visible to the user)
  • I described the change in CHANGELOG.md in a way that is understandable for the average user (if change is visible to the user)
  • [/] I checked the user documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request updating file(s) in https://github.com/JabRef/user-documentation/tree/main/en.

@github-actions

Copy link
Copy Markdown
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

@InAnYan InAnYan left a comment

Copy link
Copy Markdown
Member

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

Comment thread jablib/src/main/java/org/jabref/logic/ai/models/AiModelService.java Outdated
@st-rm-ng

Copy link
Copy Markdown
Contributor 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 😄

@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

@koppor koppor left a comment

Copy link
Copy Markdown
Member

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

st-rm-ng commented Nov 20, 2025

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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

koppor commented Nov 21, 2025

Copy link
Copy Markdown
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

@st-rm-ng please follow that advice.

@InAnYan

InAnYan commented Nov 21, 2025

Copy link
Copy Markdown
Member

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

@koppor koppor left a comment

Copy link
Copy Markdown
Member

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

InAnYan commented Nov 21, 2025

Copy link
Copy Markdown
Member

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

InAnYan commented Nov 21, 2025

Copy link
Copy Markdown
Member

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

@koppor

koppor commented Nov 21, 2025

Copy link
Copy Markdown
Member

@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
Copy Markdown
Contributor Author

@koppor I've made modifications to use Nullmarked and Nullable as you hinted me. Is this what you had in your mind?

@koppor

koppor commented Nov 29, 2025

Copy link
Copy Markdown
Member

Currently no user-visible change, is it?\n\nI think, the model list in the preferences should be updated, shouldn't hey? Did I miss something?

@st-rm-ng what's the answer for that?

@st-rm-ng

Copy link
Copy Markdown
Contributor Author

Currently no user-visible change, is it?\n\nI think, the model list in the preferences should be updated, shouldn't hey? Did I miss something?

@st-rm-ng what's the answer for that?

I haven't connected this back-end code to UI yet, still working on it

@koppor

koppor commented Dec 5, 2025

Copy link
Copy Markdown
Member

@koppor I've made modifications to use Nullmarked and Nullable as you hinted me. Is this what you had in your mind?

Nice one thank you. Looking forward for the update.

}
}
}
} catch (Exception e) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No catching of global exception, needs to be a more specific ones

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Siedlerchr Maybe this can be fixed before merging?

@Siedlerchr

Copy link
Copy Markdown
Member

Although the UI is missing I think it would be good start to have this
@InAnYan what yoo you tink?

@koppor

koppor commented Jan 17, 2026

Copy link
Copy Markdown
Member

Although the UI is missing I think it would be good start to have this @InAnYan what yoo you tink?

I agree. We can add a follow-up issue.

"test-key"
);

assertTrue(models.isEmpty());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI generated

Normally,

assertEquals(List.of(), models);

* upstream/main: (64 commits)
  New Crowdin updates (JabRef#14862)
  Make JDK25 available (JabRef#14861)
  Fix empty entries array when exporting group chat to JSON (JabRef#14814)
  feat: add right-click copy context menu to AI chat messages (JabRef#14722)
  FIX : generic error dialog and icon in Source Tab parsing (JabRef#14828)
  Factor out setup-* actions (JabRef#14859)
  Link .http files.
  Update dependency org.postgresql:postgresql to v42.7.9 (JabRef#14857)
  Add more commands to JabSrv (JabRef#14855)
  Fix JabRef#14821: Hide identifier action buttons when field is empty (JabRef#14831)
  Add GH_TOKEN to closed issues/PRs processing step
  New Crowdin updates (JabRef#14854)
  New Crowdin updates (JabRef#14849)
  Chore(deps): Bump jablib/src/main/resources/csl-styles from `0201999` to `f345aa8` (JabRef#14833)
  Add support for book front covers, again (JabRef#14777)
  Readd min width to button in new enty dialog (JabRef#14791)
  Replace plugin impl from jbang plugin (JabRef#14846)
  Revise AI policy wording
  Chore(deps): Bump jablib/src/main/resources/csl-locales (JabRef#14677)
  Update dependency com.konghq:unirest-modules-gson to v4.7.1 (JabRef#14845)
  ...
@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Jan 17, 2026
@github-actions github-actions Bot removed the status: changes-required Pull requests that are not yet complete label Jan 17, 2026

@koppor koppor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would say: Good enough, we can fix things later.

Merged via the queue into JabRef:main with commit 3cdaf0d Jan 17, 2026
50 checks passed
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.

5 participants