diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..83e55a2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: +- package-ecosystem: maven + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + ignore: + # Only allow for patch release upgrades + - dependency-name: '*' + update-types: ['version-update:semver-major', 'version-update:semver-minor'] +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/project.yml b/.github/project.yml new file mode 100644 index 0000000..1a8fee0 --- /dev/null +++ b/.github/project.yml @@ -0,0 +1,4 @@ +name: SmallRye LLM +release: + current-version: 0.0.1 + next-version: 1.0.0-SNAPSHOT diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3fc559f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,64 @@ +name: SmallRye Build + +on: + push: + branches: + - main + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - 'NOTICE' + - 'README*' + pull_request: + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - 'NOTICE' + - 'README*' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [17, 21] + name: build with jdk ${{matrix.java}} + + steps: + - uses: actions/checkout@v4 + name: checkout + + - uses: actions/setup-java@v4 + name: set up jdk ${{matrix.java}} + with: + distribution: 'temurin' + java-version: ${{matrix.java}} + cache: 'maven' + cache-dependency-path: '**/pom.xml' + + - name: build with maven + run: mvn -B formatter:validate verify --file pom.xml + + build-windows: + runs-on: windows-latest + strategy: + matrix: + java: [17, 21] + name: build with jdk ${{matrix.java}} windows + + steps: + - uses: actions/checkout@v4 + name: checkout + + - uses: actions/setup-java@v4 + name: set up jdk ${{matrix.java}} + with: + distribution: 'temurin' + java-version: ${{matrix.java}} + cache: 'maven' + cache-dependency-path: '**/pom.xml' + + - name: build with maven + run: mvn -B formatter:validate verify --file pom.xml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..3d61f16 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,18 @@ +name: SmallRye Prepare Release + +on: + pull_request: + types: [ closed ] + paths: + - '.github/project.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + prepare-release: + name: Prepare Release + if: ${{ github.event.pull_request.merged == true}} + uses: smallrye/.github/.github/workflows/prepare-release.yml@main + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7c2252c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: SmallRye Release +run-name: Release ${{github.event.inputs.tag || github.ref_name}} +on: + push: + tags: + - '*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to release' + required: true + +permissions: + attestations: write + id-token: write + # Needed for the publish-* workflows + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + perform-release: + name: Perform Release + uses: smallrye/.github/.github/workflows/perform-release.yml@main + secrets: inherit + with: + version: ${{github.event.inputs.tag || github.ref_name}} diff --git a/.github/workflows/review-release.yml b/.github/workflows/review-release.yml new file mode 100644 index 0000000..b6645f0 --- /dev/null +++ b/.github/workflows/review-release.yml @@ -0,0 +1,31 @@ +name: SmallRye Review Release + +on: + pull_request: + paths: + - '.github/project.yml' + +jobs: + release: + runs-on: ubuntu-latest + name: pre release + + steps: + - uses: radcortez/project-metadata-action@main + name: retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - name: Validate version + if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT') + run: | + echo '::error::Cannot release a SNAPSHOT version.' + exit 1 + + - uses: radcortez/milestone-review-action@main + name: milestone review + with: + github-token: ${{secrets.GITHUB_TOKEN}} + milestone-title: ${{steps.metadata.outputs.current-version}} diff --git a/.github/workflows/update-milestone.yml b/.github/workflows/update-milestone.yml new file mode 100644 index 0000000..5a1f335 --- /dev/null +++ b/.github/workflows/update-milestone.yml @@ -0,0 +1,17 @@ +name: Update Milestone + +on: + pull_request_target: + types: [closed] + +jobs: + update: + runs-on: ubuntu-latest + name: update-milestone + if: ${{github.event.pull_request.merged == true}} + + steps: + - uses: radcortez/milestone-set-action@main + name: milestone set + with: + github-token: ${{secrets.GITHUB_TOKEN}} diff --git a/.gitignore b/.gitignore index 7879fe1..3d5de90 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ buildNumber.properties .project # JDT-specific (Eclipse Java Development Tools) .classpath - +nb-configuration.xml .idea diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/DigmaJCEFProjectPersistence.xml b/.idea/DigmaJCEFProjectPersistence.xml deleted file mode 100644 index 7106797..0000000 --- a/.idea/DigmaJCEFProjectPersistence.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/aws.xml b/.idea/aws.xml deleted file mode 100644 index b63b642..0000000 --- a/.idea/aws.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index da2d3e9..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 92519c8..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index 712ab9d..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 408f87e..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 2b63946..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..17c2244 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +.github @smallrye/llm \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..531490a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement: +Farah Juma - fjuma@redhat.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..83110a6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +Contributing to smallrye-llm +================================== + +Welcome to the smallrye-llm project! We welcome contributions from the community. This guide will walk you through the steps for getting started on our project. + +- [Contributing Guidelines](#contributing-guidelines) +- [Issues](#issues) + - [Good First Issues](#good-first-issues) +- [Setting up your Developer Environment](#setting-up-your-developer-environment) +- [Community](#community) + +## Contributing Guidelines + +Please refer to our Wiki for the https://github.com/smallrye/smallrye/[Contribution Guidelines]. + + +## Issues +The smallrye-llm project uses GitHub to manage issues. All issues can be found [here](https://github.com/smallrye/smallrye-llm/issues). + +To create a new issue, comment on an existing issue, or assign an issue to yourself, you'll need to first [create a GitHub account](https://github.com/). + + +### Good First Issues +Want to contribute to the smallrye-llm project but aren't quite sure where to start? Check out our issues with the `good first issue` label. These are a triaged set of issues that are great for getting started on our project. These can be found [here](https://github.com/smallrye/smallrye-llm/labels/good%20first%20issue). + +Once you have selected an issue you'd like to work on, make sure it's not already assigned to someone else, and assign it to yourself. + +It is recommended that you use a separate branch for every issue you work on. To keep things straightforward and memorable, you can name each branch using the GitHub issue number. This way, you can have multiple PRs open for different issues. For example, if you were working on [issue-125](https://github.com/smallrye/smallrye-llm/issues/125), you could use issue-125 as your branch name. + +## Setting up your Developer Environment +You will need: + +* Python 3.12+ +* Git +* An [IDE](https://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments#Java) +(e.g., [Apache NetBeans](https://netbeans.apache.org/)) + +To setup your development environment you need to: + +1. First `cd` to the directory where you cloned the project (eg: `cd smallrye-llm`) + +2. To build `smallrye-llm` run: + + ``` + mvn clean install + ``` + +3. To run the tests: + + ``` + mvn test + ``` + +## Code Reviews + +All submissions, including submissions by project members, need to be reviewed by at least one smallrye-llm committer before being merged. + +The [GitHub Pull Request Review Process](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews) is followed for every pull request. \ No newline at end of file diff --git a/README.md b/README.md index e1ff4ea..060da27 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,41 @@ -# smallrye-llm +# 🚀 Smallrye LLM + Experimentation around LLM and MicroProfile -## How to run examples. +## How to run examples + +### Use LM Studio -### Install LM Sutdio +#### Install LM Sutdio https://lmstudio.ai/ -### Download model +#### Download model Mistral 7B Instruct v0.2 -### Run +#### Run + +On left goto "local server", select the model in dropdown combo on the top, then start server + +### Use Ollama + +Running Ollama with the llama3.1 model: + +```shell +$CONTAINER_ENGINE= podman | docker +$CONTAINER_ENGINE run -d --rm --name ollama --replace --pull=always -p 11434:11434 -v ollama:/root/.ollama --stop-signal=SIGKILL docker.io/ollama/ollama +$CONTAINER_ENGINE exec -it ollama ollama run llama3.1 +``` + +### Run the examples + +Go to each example READEM.md to see how to execute the example. + +## Contributing +If you want to contribute, please have a look to [CONTRIBUTING.md](CONTRIBUTING.md) + +## License + +This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. -On left goto "local server", select the model in dropdown combo on the top, then start server \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..0382d7e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Reporting of CVEs and Security Issues + +## The Smallrye LLM community takes security bugs very seriously + +We aim to take immediate action to address serious security-related problems that involve our project. + +Note that we will only fix such issues in the most recent minor release of Smallrye LLM. + +## Reporting of Security Issues + +When reporting a security vulnerability it is important to not accidentally broadcast to the world that +the issue exists, as this makes it easier for people to exploit it. The software industry uses the term +embargo to describe the time a +security issue is known internally until it is public knowledge. + +Our preferred way of reporting security issues in Smallrye LLM is listed below. + +### Email the Smallrye LLM team + +To report a security issue, please email ehugonne@redhat.com and/or +yann.blazart@bycode.fr. A member of the Smallrye LLM team will +open the required issues. + +### Other considerations + +If you would like to work with us on a fix for the security vulnerability, please include your GitHub username +in the above email, and we will provide you access to a temporary private fork where we can collaborate on a +fix without it being disclosed publicly, **including in your own publicly visible git repository**. + +Do not open a public issue, send a pull request, or disclose any information about the suspected vulnerability +publicly, **including in your own publicly visible git repository**. If you discover any publicly disclosed +security vulnerabilities, please notify us immediately through the emails listed in the section above. diff --git a/examples/glassfish-car-booking/pom.xml b/examples/glassfish-car-booking/pom.xml index a8942c3..9373056 100644 --- a/examples/glassfish-car-booking/pom.xml +++ b/examples/glassfish-car-booking/pom.xml @@ -1,23 +1,23 @@ - + 4.0.0 io.smallrye.llm.examples examples 1.0.0-SNAPSHOT + glassfish-car-booking war - glassfish-car-booking Maven Webapp - http://maven.apache.org + SmallRye LLM Examples: Glassfish Car Booking + UTF-8 - 17 10.0.0 true 8080 ${project.build.directory}/../installs + jakarta.platform @@ -25,10 +25,13 @@ ${jakarta.jakartaee-api.version} provided + + io.smallrye.llm + mp-ai-api + io.smallrye.llm smallrye-llm-langchain4j-portable-extension - 1.0.0-SNAPSHOT jakarta.enterprise @@ -66,7 +69,6 @@ dev.langchain4j langchain4j - ${dev.langchain4j.version} dev.langchain4j @@ -88,12 +90,17 @@ org.slf4j slf4j-jdk14 runtime - 2.0.9 + 2.0.16 glassfish-car-booking + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + org.codehaus.cargo cargo-maven3-plugin diff --git a/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/BookingService.java b/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/BookingService.java index 4ee5ff5..3e38e4c 100644 --- a/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/BookingService.java +++ b/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/BookingService.java @@ -6,9 +6,9 @@ import java.util.Map; import java.util.stream.Collectors; -import jakarta.enterprise.context.ApplicationScoped; +import org.eclipse.microprofile.ai.llm.Tool; -import dev.langchain4j.agent.tool.Tool; +import jakarta.enterprise.context.ApplicationScoped; import lombok.extern.java.Log; @ApplicationScoped diff --git a/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java b/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java index d35c5c3..594b8f1 100644 --- a/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java +++ b/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java @@ -1,9 +1,7 @@ package io.jefrajames.booking; -import dev.langchain4j.service.SystemMessage; -import io.smallrye.llm.spi.RegisterAIService; - -import java.time.temporal.ChronoUnit; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(tools = BookingService.class, chatMemoryMaxMessages = 10, chatLanguageModelName = "chat-model") diff --git a/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java b/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java index 68488de..021bf3c 100644 --- a/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java +++ b/examples/glassfish-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java @@ -1,13 +1,12 @@ package io.jefrajames.booking; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; -import io.smallrye.llm.spi.RegisterAIService; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; +import org.eclipse.microprofile.ai.llm.UserMessage; +import org.eclipse.microprofile.ai.llm.V; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(chatMemoryMaxMessages = 5, - chatLanguageModelName = "chat-model") public interface FraudAiService { diff --git a/examples/helidon-car-booking-portable-ext/pom.xml b/examples/helidon-car-booking-portable-ext/pom.xml index 64edfe9..7b5d79c 100644 --- a/examples/helidon-car-booking-portable-ext/pom.xml +++ b/examples/helidon-car-booking-portable-ext/pom.xml @@ -1,35 +1,52 @@ - + 4.0.0 - io.helidon.applications - helidon-mp - 4.0.7 - + io.smallrye.llm.examples + examples + 1.0.0-SNAPSHOT helidon-car-booking-portable-ext - io.smallrye.llm.examples - 1.0 + SmallRye LLM Examples: Helidon Car Booking Portable Extension - 17 - UTF-8 - 0.34.0 + 4.0.7 + io.helidon.Main + 0.35.0 + + + + io.helidon + helidon-bom + ${version.io.helidon} + pom + import + + + io.helidon + helidon-dependencies + ${version.io.helidon} + pom + import + + + + + + io.smallrye.llm + mp-ai-api + io.smallrye.llm smallrye-llm-langchain4j-portable-extension - 1.0.0-SNAPSHOT io.smallrye.llm smallrye-llm-langchain4j-config-mpconfig - 1.0.0-SNAPSHOT @@ -60,6 +77,19 @@ jersey-media-json-binding runtime + + jakarta.inject + jakarta.inject-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + compile + + + jakarta.enterprise + jakarta.enterprise.lang-model + io.smallrye @@ -76,7 +106,6 @@ dev.langchain4j langchain4j - ${dev.langchain4j.version} dev.langchain4j @@ -132,6 +161,7 @@ + ${project.artifactId} src/main/resources @@ -149,9 +179,38 @@ copy-libs + prepare-package + + copy-dependencies + + + ${project.build.directory}/libs + false + false + true + true + runtime + + okio + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + libs + + ${mainClass} + false + + + + io.smallrye jandex-maven-plugin diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/Booking.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/Booking.java index b1621fd..a8e03c1 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/Booking.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/Booking.java @@ -1,11 +1,11 @@ package io.jefrajames.booking; +import java.time.LocalDate; + import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDate; - @Data @NoArgsConstructor @AllArgsConstructor diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java index 4280877..740a903 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java @@ -5,5 +5,5 @@ public class BookingAlreadyCanceledException extends RuntimeException { public BookingAlreadyCanceledException(String bookingNumber) { super("Booking " + bookingNumber + " already canceled"); } - + } diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java index 057f92b..5bafda7 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java @@ -5,5 +5,5 @@ public class BookingCannotBeCanceledException extends RuntimeException { public BookingCannotBeCanceledException(String bookingNumber) { super("Booking " + bookingNumber + " cannot be canceled"); } - + } diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingService.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingService.java index d4caa93..4ee5ff5 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingService.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/BookingService.java @@ -33,7 +33,7 @@ public class BookingService { } // Simulate database accesses - private Booking checkBookingExists(String bookingNumber,String name, String surname) { + private Booking checkBookingExists(String bookingNumber, String name, String surname) { Booking booking = BOOKINGS.get(bookingNumber); if (booking == null || !booking.getCustomer().getName().equals(name) || !booking.getCustomer().getSurname().equals(surname)) { diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/CarBookingResource.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/CarBookingResource.java index 67dfb03..d4ca739 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/CarBookingResource.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/CarBookingResource.java @@ -29,14 +29,7 @@ public class CarBookingResource { @Operation(summary = "Chat with an asssitant.", description = "Ask any car booking related question.", operationId = "chatWithAssistant") @APIResponse(responseCode = "200", description = "Anwser provided by assistant", content = @Content(mediaType = "text/plain")) public String chatWithAssistant( - @Parameter( - description = "The question to ask the assistant", - required = true, - example = "I want to book a car how can you help me?" - ) - @QueryParam("question") - String question - ) { + @Parameter(description = "The question to ask the assistant", required = true, example = "I want to book a car how can you help me?") @QueryParam("question") String question) { String answer; try { @@ -55,19 +48,9 @@ public String chatWithAssistant( @Operation(summary = "Detect for a customer.", description = "Detect fraud for a customer given his name and surname.", operationId = "detectFraudForCustomer") @APIResponse(responseCode = "200", description = "Anwser provided by assistant", content = @Content(mediaType = "application/json")) public FraudResponse detectFraudForCustomer( - @Parameter( - description = "Name of the customer to detect fraud for.", - required = true, - example = "Bond") - @QueryParam("name") - String name, - - @QueryParam("surname") - @Parameter( - description = "Surname of the customer to detect fraud for.", - required = true, - example = "James") - String surname) { + @Parameter(description = "Name of the customer to detect fraud for.", required = true, example = "Bond") @QueryParam("name") String name, + + @QueryParam("surname") @Parameter(description = "Surname of the customer to detect fraud for.", required = true, example = "James") String surname) { return fraudService.detectFraudForCustomer(name, surname); } diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/ChatAiService.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/ChatAiService.java index 01a10d2..407ee89 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/ChatAiService.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/ChatAiService.java @@ -1,45 +1,41 @@ package io.jefrajames.booking; -import dev.langchain4j.service.SystemMessage; +import java.time.temporal.ChronoUnit; + +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import io.smallrye.llm.spi.RegisterAIService; - -import java.time.temporal.ChronoUnit; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") -@RegisterAIService( - tools = BookingService.class, - chatMemoryMaxMessages = 10, - chatLanguageModelName = "chat-model" -) +@RegisterAIService(tools = BookingService.class, chatMemoryMaxMessages = 10, chatLanguageModelName = "chat-model") public interface ChatAiService { - @SystemMessage(""" - You are a customer support agent of a car rental company named 'Miles of Smiles'. - Before providing information about booking or canceling a booking, you MUST always check: - booking number, customer name and surname. - You should not answer to any request not related to car booking or Miles of Smiles company general information. - When a customer wants to cancel a booking, you must check his name and the Miles of Smiles cancellation policy first. - Any cancelation request must comply with cancellation policy both for the delay and the duration. - Today is {{current_date}}. - """) - @Timeout(unit = ChronoUnit.MINUTES, value = 5) - @Retry(abortOn = { BookingCannotBeCanceledException.class, - BookingAlreadyCanceledException.class, - BookingNotFoundException.class }, maxRetries = 2) - @Fallback(fallbackMethod = "chatFallback", skipOn = { - BookingCannotBeCanceledException.class, - BookingAlreadyCanceledException.class, - BookingNotFoundException.class }) - // String chat(@V("question") @UserMessage String question); - String chat(String question); + @SystemMessage(""" + You are a customer support agent of a car rental company named 'Miles of Smiles'. + Before providing information about booking or canceling a booking, you MUST always check: + booking number, customer name and surname. + You should not answer to any request not related to car booking or Miles of Smiles company general information. + When a customer wants to cancel a booking, you must check his name and the Miles of Smiles cancellation policy first. + Any cancelation request must comply with cancellation policy both for the delay and the duration. + Today is {{current_date}}. + """) + @Timeout(unit = ChronoUnit.MINUTES, value = 5) + @Retry(abortOn = { BookingCannotBeCanceledException.class, + BookingAlreadyCanceledException.class, + BookingNotFoundException.class }, maxRetries = 2) + @Fallback(fallbackMethod = "chatFallback", skipOn = { + BookingCannotBeCanceledException.class, + BookingAlreadyCanceledException.class, + BookingNotFoundException.class }) + // String chat(@V("question") @UserMessage String question); + String chat(String question); - default String chatFallback(String question) { - return String.format( - "Sorry, I am not able to answer your request %s at the moment. Please try again later.", - question); - } + default String chatFallback(String question) { + return String.format( + "Sorry, I am not able to answer your request %s at the moment. Please try again later.", + question); + } } diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/DocRagIngestor.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/DocRagIngestor.java index 1f164c8..d15bd24 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/DocRagIngestor.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/DocRagIngestor.java @@ -11,16 +11,16 @@ import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; + import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.parser.TextDocumentParser; import dev.langchain4j.data.document.splitter.DocumentSplitters; -import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import lombok.extern.java.Log; -import org.eclipse.microprofile.config.inject.ConfigProperty; - @Log @ApplicationScoped @@ -41,7 +41,7 @@ public class DocRagIngestor { private List loadDocs() { return loadDocuments(docs.getPath(), new TextDocumentParser()); } - + public void ingest(@Observes @Initialized(ApplicationScoped.class) Object pointless) { long start = System.currentTimeMillis(); @@ -56,7 +56,7 @@ public void ingest(@Observes @Initialized(ApplicationScoped.class) Object pointl ingestor.ingest(docs); log.info(String.format("DEMO %d documents ingested in %d msec", docs.size(), - System.currentTimeMillis() - start)); + System.currentTimeMillis() - start)); } public static void main(String[] args) { diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudAiService.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudAiService.java index 651b98a..a22deaa 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudAiService.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudAiService.java @@ -1,66 +1,61 @@ package io.jefrajames.booking; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; +import java.time.temporal.ChronoUnit; + +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; +import org.eclipse.microprofile.ai.llm.UserMessage; +import org.eclipse.microprofile.ai.llm.V; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import io.smallrye.llm.spi.RegisterAIService; - -import java.time.temporal.ChronoUnit; - @SuppressWarnings("CdiManagedBeanInconsistencyInspection") -@RegisterAIService( - chatMemoryMaxMessages = 5, - - chatLanguageModelName = "chat-model" -) +@RegisterAIService(chatMemoryMaxMessages = 5, chatLanguageModelName = "chat-model") public interface FraudAiService { - @SystemMessage(""" - You are a car booking fraud detection AI for Miles of Smiles. - You have to detect customer fraud in bookings. - """) - @UserMessage(""" - Your task is to detect whether a fraud was committed for the customer {{name}} {{surname}}. - - To detect a fraud, perform the following actions: - 1 - Retrieve all bookings for the customer with name {{name}} and surname {{surname}}. - 2 - If there are no bookings, return the fraud status as 'false'. - 3 - Otherwise, Determine if there is an overlap between several bookings. - 4 - If there is an overlap, a fraud is detected. - 5 - If a fraud is detected, return the fraud status and the bookings that overlap. - - A booking overlap (and hence a fraud) occurs when there are several bookings for a given date. - For instance: - -there is no overlap if a given customer has the following bookings: - - Booking number 345-678 with the period from 2024-03-25 to 2024-03-31. - - Booking number 234-567 with the period from 2024-03-21 to 2024-03-23. - -there is an overlap if a given customer has the following bookings: - - Booking number 456-789 with the period from 2024-03-21 to 2024-03-31. - - Booking number 567-890 with the period from 2024-03-22 to 2024-03-27. - - Answer with the following information in a valid JSON document: - - the customer-name key set to {{name}} - - the customer-surname key set to {{surname}} - - the fraud-detected key set to 'true' or 'false' - - in case of fraud, the explanation of the fraud in the fraud-explanation key - - in case of fraud, the reservation ids that overlap. - You must respond in a valid JSON format. - - You must not wrap JSON response in backticks, markdown, or in any other way, but return it as plain text. - """) - @Timeout(unit = ChronoUnit.MINUTES, value = 5) - @Retry(maxRetries = 2) - @Fallback(fallbackMethod = "fraudFallback") - FraudResponse detectFraudForCustomer(@V("name") String name, @V("surname") String surname); - - default FraudResponse fraudFallback(String name, String surname) { - throw new RuntimeException( - "Sorry, I am not able to detect fraud for customer " + name + " " + surname - + " at the moment. Please try again later."); - } + @SystemMessage(""" + You are a car booking fraud detection AI for Miles of Smiles. + You have to detect customer fraud in bookings. + """) + @UserMessage(""" + Your task is to detect whether a fraud was committed for the customer {{name}} {{surname}}. + + To detect a fraud, perform the following actions: + 1 - Retrieve all bookings for the customer with name {{name}} and surname {{surname}}. + 2 - If there are no bookings, return the fraud status as 'false'. + 3 - Otherwise, Determine if there is an overlap between several bookings. + 4 - If there is an overlap, a fraud is detected. + 5 - If a fraud is detected, return the fraud status and the bookings that overlap. + + A booking overlap (and hence a fraud) occurs when there are several bookings for a given date. + For instance: + -there is no overlap if a given customer has the following bookings: + - Booking number 345-678 with the period from 2024-03-25 to 2024-03-31. + - Booking number 234-567 with the period from 2024-03-21 to 2024-03-23. + -there is an overlap if a given customer has the following bookings: + - Booking number 456-789 with the period from 2024-03-21 to 2024-03-31. + - Booking number 567-890 with the period from 2024-03-22 to 2024-03-27. + + Answer with the following information in a valid JSON document: + - the customer-name key set to {{name}} + - the customer-surname key set to {{surname}} + - the fraud-detected key set to 'true' or 'false' + - in case of fraud, the explanation of the fraud in the fraud-explanation key + - in case of fraud, the reservation ids that overlap. + You must respond in a valid JSON format. + + You must not wrap JSON response in backticks, markdown, or in any other way, but return it as plain text. + """) + @Timeout(unit = ChronoUnit.MINUTES, value = 5) + @Retry(maxRetries = 2) + @Fallback(fallbackMethod = "fraudFallback") + FraudResponse detectFraudForCustomer(@V("name") String name, @V("surname") String surname); + + default FraudResponse fraudFallback(String name, String surname) { + throw new RuntimeException( + "Sorry, I am not able to detect fraud for customer " + name + " " + surname + + " at the moment. Please try again later."); + } } diff --git a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudResponse.java b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudResponse.java index bb58d0b..a1663ec 100644 --- a/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudResponse.java +++ b/examples/helidon-car-booking-portable-ext/src/main/java/io/jefrajames/booking/FraudResponse.java @@ -7,8 +7,8 @@ // Warning: Java Record not supported by Google JSON (used by LangChain4J) @Data public class FraudResponse { - private String customerName; - private String customerSurname; + private String customerName; + private String customerSurname; private boolean fraudDetected; - private List bookingIds; + private List bookingIds; } diff --git a/examples/helidon-car-booking/pom.xml b/examples/helidon-car-booking/pom.xml index 604369a..79c94cb 100644 --- a/examples/helidon-car-booking/pom.xml +++ b/examples/helidon-car-booking/pom.xml @@ -1,35 +1,56 @@ - + 4.0.0 - io.helidon.applications - helidon-mp - 4.0.7 - + io.smallrye.llm.examples + examples + 1.0.0-SNAPSHOT helidon-car-booking - io.smallrye.llm.examples - 1.0 + SmallRye LLM Examples: Helidon Car Booking - 17 - UTF-8 - 0.34.0 + 4.0.7 + io.helidon.Main + 0.35.0 + + + + io.helidon + helidon-bom + ${version.io.helidon} + pom + import + + + io.helidon + helidon-dependencies + ${version.io.helidon} + pom + import + + + + + + io.smallrye.llm + mp-ai-api + + + io.smallrye.llm + mp-ai-api + io.smallrye.llm smallrye-llm-langchain4j-buildcompatible-extension - 1.0.0-SNAPSHOT io.smallrye.llm smallrye-llm-langchain4j-config-mpconfig - 1.0.0-SNAPSHOT @@ -55,6 +76,23 @@ jakarta.json.bind jakarta.json.bind-api + + jakarta.annotation + jakarta.annotation-api + + + jakarta.inject + jakarta.inject-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + compile + + + jakarta.enterprise + jakarta.enterprise.lang-model + org.glassfish.jersey.media jersey-media-json-binding @@ -76,7 +114,6 @@ dev.langchain4j langchain4j - ${dev.langchain4j.version} dev.langchain4j @@ -132,6 +169,7 @@ + ${project.artifactId} src/main/resources @@ -144,20 +182,49 @@ - org.apache.maven.plugins - maven-dependency-plugin + io.smallrye + jandex-maven-plugin - copy-libs + make-index - io.smallrye - jandex-maven-plugin + org.apache.maven.plugins + maven-jar-plugin + + + + true + libs + + ${mainClass} + false + + + + + + org.apache.maven.plugins + maven-dependency-plugin - make-index + copy-libs + prepare-package + + copy-dependencies + + + ${project.build.directory}/libs + false + false + true + true + runtime + + okio + diff --git a/examples/helidon-car-booking/run-local.sh b/examples/helidon-car-booking/run-local.sh new file mode 100755 index 0000000..31a23c1 --- /dev/null +++ b/examples/helidon-car-booking/run-local.sh @@ -0,0 +1,11 @@ +#bin/bash + +ENGINE="${CONTAINER_ENGINE:-podman}" + +$ENGINE stop ollama +$ENGINE run -d --name ollama --replace --pull=always --restart=always -p 11434:11434 -v ollama:/root/.ollama --stop-signal=SIGKILL docker.io/ollama/ollama +$ENGINE exec -it ollama ollama run llama3.1 << EOF +/bye +EOF + +java -Dsun.misc.URLClassPath.disableJarChecking=true --add-opens jdk.naming.rmi/com.sun.jndi.rmi.registry=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.net=ALL-UNNAMED -jar target/helidon-car-booking.jar \ No newline at end of file diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/Booking.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/Booking.java index b1621fd..a8e03c1 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/Booking.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/Booking.java @@ -1,11 +1,11 @@ package io.jefrajames.booking; +import java.time.LocalDate; + import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDate; - @Data @NoArgsConstructor @AllArgsConstructor diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java index 4280877..740a903 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingAlreadyCanceledException.java @@ -5,5 +5,5 @@ public class BookingAlreadyCanceledException extends RuntimeException { public BookingAlreadyCanceledException(String bookingNumber) { super("Booking " + bookingNumber + " already canceled"); } - + } diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java index 057f92b..5bafda7 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingCannotBeCanceledException.java @@ -5,5 +5,5 @@ public class BookingCannotBeCanceledException extends RuntimeException { public BookingCannotBeCanceledException(String bookingNumber) { super("Booking " + bookingNumber + " cannot be canceled"); } - + } diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingService.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingService.java index d4caa93..3e38e4c 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingService.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/BookingService.java @@ -6,9 +6,9 @@ import java.util.Map; import java.util.stream.Collectors; -import jakarta.enterprise.context.ApplicationScoped; +import org.eclipse.microprofile.ai.llm.Tool; -import dev.langchain4j.agent.tool.Tool; +import jakarta.enterprise.context.ApplicationScoped; import lombok.extern.java.Log; @ApplicationScoped @@ -33,7 +33,7 @@ public class BookingService { } // Simulate database accesses - private Booking checkBookingExists(String bookingNumber,String name, String surname) { + private Booking checkBookingExists(String bookingNumber, String name, String surname) { Booking booking = BOOKINGS.get(bookingNumber); if (booking == null || !booking.getCustomer().getName().equals(name) || !booking.getCustomer().getSurname().equals(surname)) { diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/CarBookingResource.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/CarBookingResource.java index 67dfb03..d4ca739 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/CarBookingResource.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/CarBookingResource.java @@ -29,14 +29,7 @@ public class CarBookingResource { @Operation(summary = "Chat with an asssitant.", description = "Ask any car booking related question.", operationId = "chatWithAssistant") @APIResponse(responseCode = "200", description = "Anwser provided by assistant", content = @Content(mediaType = "text/plain")) public String chatWithAssistant( - @Parameter( - description = "The question to ask the assistant", - required = true, - example = "I want to book a car how can you help me?" - ) - @QueryParam("question") - String question - ) { + @Parameter(description = "The question to ask the assistant", required = true, example = "I want to book a car how can you help me?") @QueryParam("question") String question) { String answer; try { @@ -55,19 +48,9 @@ public String chatWithAssistant( @Operation(summary = "Detect for a customer.", description = "Detect fraud for a customer given his name and surname.", operationId = "detectFraudForCustomer") @APIResponse(responseCode = "200", description = "Anwser provided by assistant", content = @Content(mediaType = "application/json")) public FraudResponse detectFraudForCustomer( - @Parameter( - description = "Name of the customer to detect fraud for.", - required = true, - example = "Bond") - @QueryParam("name") - String name, - - @QueryParam("surname") - @Parameter( - description = "Surname of the customer to detect fraud for.", - required = true, - example = "James") - String surname) { + @Parameter(description = "Name of the customer to detect fraud for.", required = true, example = "Bond") @QueryParam("name") String name, + + @QueryParam("surname") @Parameter(description = "Surname of the customer to detect fraud for.", required = true, example = "James") String surname) { return fraudService.detectFraudForCustomer(name, surname); } diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java index 01a10d2..407ee89 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java @@ -1,45 +1,41 @@ package io.jefrajames.booking; -import dev.langchain4j.service.SystemMessage; +import java.time.temporal.ChronoUnit; + +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import io.smallrye.llm.spi.RegisterAIService; - -import java.time.temporal.ChronoUnit; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") -@RegisterAIService( - tools = BookingService.class, - chatMemoryMaxMessages = 10, - chatLanguageModelName = "chat-model" -) +@RegisterAIService(tools = BookingService.class, chatMemoryMaxMessages = 10, chatLanguageModelName = "chat-model") public interface ChatAiService { - @SystemMessage(""" - You are a customer support agent of a car rental company named 'Miles of Smiles'. - Before providing information about booking or canceling a booking, you MUST always check: - booking number, customer name and surname. - You should not answer to any request not related to car booking or Miles of Smiles company general information. - When a customer wants to cancel a booking, you must check his name and the Miles of Smiles cancellation policy first. - Any cancelation request must comply with cancellation policy both for the delay and the duration. - Today is {{current_date}}. - """) - @Timeout(unit = ChronoUnit.MINUTES, value = 5) - @Retry(abortOn = { BookingCannotBeCanceledException.class, - BookingAlreadyCanceledException.class, - BookingNotFoundException.class }, maxRetries = 2) - @Fallback(fallbackMethod = "chatFallback", skipOn = { - BookingCannotBeCanceledException.class, - BookingAlreadyCanceledException.class, - BookingNotFoundException.class }) - // String chat(@V("question") @UserMessage String question); - String chat(String question); + @SystemMessage(""" + You are a customer support agent of a car rental company named 'Miles of Smiles'. + Before providing information about booking or canceling a booking, you MUST always check: + booking number, customer name and surname. + You should not answer to any request not related to car booking or Miles of Smiles company general information. + When a customer wants to cancel a booking, you must check his name and the Miles of Smiles cancellation policy first. + Any cancelation request must comply with cancellation policy both for the delay and the duration. + Today is {{current_date}}. + """) + @Timeout(unit = ChronoUnit.MINUTES, value = 5) + @Retry(abortOn = { BookingCannotBeCanceledException.class, + BookingAlreadyCanceledException.class, + BookingNotFoundException.class }, maxRetries = 2) + @Fallback(fallbackMethod = "chatFallback", skipOn = { + BookingCannotBeCanceledException.class, + BookingAlreadyCanceledException.class, + BookingNotFoundException.class }) + // String chat(@V("question") @UserMessage String question); + String chat(String question); - default String chatFallback(String question) { - return String.format( - "Sorry, I am not able to answer your request %s at the moment. Please try again later.", - question); - } + default String chatFallback(String question) { + return String.format( + "Sorry, I am not able to answer your request %s at the moment. Please try again later.", + question); + } } diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java index 79e0cf1..d15bd24 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java @@ -1,5 +1,18 @@ package io.jefrajames.booking; +import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments; + +import java.io.File; +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.parser.TextDocumentParser; import dev.langchain4j.data.document.splitter.DocumentSplitters; @@ -7,19 +20,7 @@ import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Initialized; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; import lombok.extern.java.Log; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.io.File; -import java.util.List; - -import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments; - @Log @ApplicationScoped @@ -40,7 +41,7 @@ public class DocRagIngestor { private List loadDocs() { return loadDocuments(docs.getPath(), new TextDocumentParser()); } - + public void ingest(@Observes @Initialized(ApplicationScoped.class) Object pointless) { long start = System.currentTimeMillis(); @@ -55,7 +56,7 @@ public void ingest(@Observes @Initialized(ApplicationScoped.class) Object pointl ingestor.ingest(docs); log.info(String.format("DEMO %d documents ingested in %d msec", docs.size(), - System.currentTimeMillis() - start)); + System.currentTimeMillis() - start)); } public static void main(String[] args) { diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java index 651b98a..a22deaa 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java @@ -1,66 +1,61 @@ package io.jefrajames.booking; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; +import java.time.temporal.ChronoUnit; + +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; +import org.eclipse.microprofile.ai.llm.UserMessage; +import org.eclipse.microprofile.ai.llm.V; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import io.smallrye.llm.spi.RegisterAIService; - -import java.time.temporal.ChronoUnit; - @SuppressWarnings("CdiManagedBeanInconsistencyInspection") -@RegisterAIService( - chatMemoryMaxMessages = 5, - - chatLanguageModelName = "chat-model" -) +@RegisterAIService(chatMemoryMaxMessages = 5, chatLanguageModelName = "chat-model") public interface FraudAiService { - @SystemMessage(""" - You are a car booking fraud detection AI for Miles of Smiles. - You have to detect customer fraud in bookings. - """) - @UserMessage(""" - Your task is to detect whether a fraud was committed for the customer {{name}} {{surname}}. - - To detect a fraud, perform the following actions: - 1 - Retrieve all bookings for the customer with name {{name}} and surname {{surname}}. - 2 - If there are no bookings, return the fraud status as 'false'. - 3 - Otherwise, Determine if there is an overlap between several bookings. - 4 - If there is an overlap, a fraud is detected. - 5 - If a fraud is detected, return the fraud status and the bookings that overlap. - - A booking overlap (and hence a fraud) occurs when there are several bookings for a given date. - For instance: - -there is no overlap if a given customer has the following bookings: - - Booking number 345-678 with the period from 2024-03-25 to 2024-03-31. - - Booking number 234-567 with the period from 2024-03-21 to 2024-03-23. - -there is an overlap if a given customer has the following bookings: - - Booking number 456-789 with the period from 2024-03-21 to 2024-03-31. - - Booking number 567-890 with the period from 2024-03-22 to 2024-03-27. - - Answer with the following information in a valid JSON document: - - the customer-name key set to {{name}} - - the customer-surname key set to {{surname}} - - the fraud-detected key set to 'true' or 'false' - - in case of fraud, the explanation of the fraud in the fraud-explanation key - - in case of fraud, the reservation ids that overlap. - You must respond in a valid JSON format. - - You must not wrap JSON response in backticks, markdown, or in any other way, but return it as plain text. - """) - @Timeout(unit = ChronoUnit.MINUTES, value = 5) - @Retry(maxRetries = 2) - @Fallback(fallbackMethod = "fraudFallback") - FraudResponse detectFraudForCustomer(@V("name") String name, @V("surname") String surname); - - default FraudResponse fraudFallback(String name, String surname) { - throw new RuntimeException( - "Sorry, I am not able to detect fraud for customer " + name + " " + surname - + " at the moment. Please try again later."); - } + @SystemMessage(""" + You are a car booking fraud detection AI for Miles of Smiles. + You have to detect customer fraud in bookings. + """) + @UserMessage(""" + Your task is to detect whether a fraud was committed for the customer {{name}} {{surname}}. + + To detect a fraud, perform the following actions: + 1 - Retrieve all bookings for the customer with name {{name}} and surname {{surname}}. + 2 - If there are no bookings, return the fraud status as 'false'. + 3 - Otherwise, Determine if there is an overlap between several bookings. + 4 - If there is an overlap, a fraud is detected. + 5 - If a fraud is detected, return the fraud status and the bookings that overlap. + + A booking overlap (and hence a fraud) occurs when there are several bookings for a given date. + For instance: + -there is no overlap if a given customer has the following bookings: + - Booking number 345-678 with the period from 2024-03-25 to 2024-03-31. + - Booking number 234-567 with the period from 2024-03-21 to 2024-03-23. + -there is an overlap if a given customer has the following bookings: + - Booking number 456-789 with the period from 2024-03-21 to 2024-03-31. + - Booking number 567-890 with the period from 2024-03-22 to 2024-03-27. + + Answer with the following information in a valid JSON document: + - the customer-name key set to {{name}} + - the customer-surname key set to {{surname}} + - the fraud-detected key set to 'true' or 'false' + - in case of fraud, the explanation of the fraud in the fraud-explanation key + - in case of fraud, the reservation ids that overlap. + You must respond in a valid JSON format. + + You must not wrap JSON response in backticks, markdown, or in any other way, but return it as plain text. + """) + @Timeout(unit = ChronoUnit.MINUTES, value = 5) + @Retry(maxRetries = 2) + @Fallback(fallbackMethod = "fraudFallback") + FraudResponse detectFraudForCustomer(@V("name") String name, @V("surname") String surname); + + default FraudResponse fraudFallback(String name, String surname) { + throw new RuntimeException( + "Sorry, I am not able to detect fraud for customer " + name + " " + surname + + " at the moment. Please try again later."); + } } diff --git a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudResponse.java b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudResponse.java index bb58d0b..a1663ec 100644 --- a/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudResponse.java +++ b/examples/helidon-car-booking/src/main/java/io/jefrajames/booking/FraudResponse.java @@ -7,8 +7,8 @@ // Warning: Java Record not supported by Google JSON (used by LangChain4J) @Data public class FraudResponse { - private String customerName; - private String customerSurname; + private String customerName; + private String customerSurname; private boolean fraudDetected; - private List bookingIds; + private List bookingIds; } diff --git a/examples/liberty-car-booking/README.md b/examples/liberty-car-booking/README.md index 92561c7..7eb643f 100644 --- a/examples/liberty-car-booking/README.md +++ b/examples/liberty-car-booking/README.md @@ -10,7 +10,7 @@ These are the steps to run this service. ## Application requirements: - JDK 17 and higher - Maven 3.9.9 and higher -- LangChain4j 0.33.0 or higher. +- LangChain4j 0.35.0 or higher. - Testing against GPT 3.5 and 4.0 on a dedicated Azure instance (to be customized in your context). Then you can access the application through the browser of your choice. @@ -39,7 +39,7 @@ To package the application in JVM mode run: `mvn package`. ## Configuration -All configuration is centralized in `microprofile-config.properties`(found is `resources\META-INF` folder) and can be redefined using environment variables. +All configuration is centralized in `microprofile-config.properties` (found is `resources\META-INF` folder) and can be redefined using environment variables. ## Running the application diff --git a/examples/liberty-car-booking/pom.xml b/examples/liberty-car-booking/pom.xml index f5ef78b..6d01fd4 100644 --- a/examples/liberty-car-booking/pom.xml +++ b/examples/liberty-car-booking/pom.xml @@ -1,45 +1,47 @@ - 4.0.0 - io.smallrye.llm.examples - 1.0.0-SNAPSHOT - liberty-car-booking - war + 4.0.0 + + io.smallrye.llm.examples + examples + 1.0.0-SNAPSHOT + ../pom.xml + - - - Buhake Sindi - +2 - - PROJECT LEAD - - - + liberty-car-booking + war + SmallRye LLM Examples: Liberty Car Booking + + + + Buhake Sindi + +2 + + PROJECT LEAD + + + - - UTF-8 - UTF-8 - 17 - 10.0.0 - 6.1 - 3.13.0 - 3.4.0 - 0.34.0 - 3.13.0 - - - ${project.build.directory}/liberty/wlp/usr/shared/resources/lib/ - + + UTF-8 + UTF-8 + 10.0.0 + 6.1 + 3.4.0 + + + ${project.build.directory}/liberty/wlp/usr/shared/resources/lib/ + - - - - - jakarta.platform - jakarta.jakartaee-api - ${jakartaee-api.version} - provided - - + + + + + jakarta.platform + jakarta.jakartaee-api + ${jakartaee-api.version} + provided + + org.eclipse.microprofile microprofile @@ -57,116 +59,81 @@ --> - + io.smallrye.llm - smallrye-llm-langchain4j-config-mpconfig + mp-ai-api 1.0.0-SNAPSHOT - - - io.smallrye.llm - smallrye-llm-langchain4j-portable-extension - 1.0.0-SNAPSHOT - - - dev.langchain4j - langchain4j - ${dev.langchain4j.version} - - - - dev.langchain4j - langchain4j-hugging-face - ${dev.langchain4j.version} + io.smallrye.llm + smallrye-llm-langchain4j-config-mpconfig + 1.0.0-SNAPSHOT + + + dev.langchain4j + langchain4j-hugging-face + ${dev.langchain4j.version} + - - - dev.langchain4j - langchain4j-azure-open-ai - ${dev.langchain4j.version} - + + + dev.langchain4j + langchain4j-azure-open-ai + ${dev.langchain4j.version} + - - dev.langchain4j - langchain4j-open-ai - ${dev.langchain4j.version} - + + dev.langchain4j + langchain4j-open-ai + ${dev.langchain4j.version} + - - dev.langchain4j - langchain4j-embeddings-all-minilm-l6-v2 - ${dev.langchain4j.version} - + + dev.langchain4j + langchain4j-embeddings-all-minilm-l6-v2 + ${dev.langchain4j.version} + - - - ai.djl.huggingface - tokenizers - 0.30.0 - + + + ai.djl.huggingface + tokenizers + 0.30.0 + - - - org.slf4j - slf4j-jdk14 - runtime - 2.0.9 - - - + + + org.slf4j + slf4j-jdk14 + runtime + 2.0.9 + + + - - - - - - org.eclipse.microprofile - microprofile - pom - + + + org.eclipse.microprofile + microprofile + pom + + + io.smallrye.llm + mp-ai-api + io.smallrye.llm smallrye-llm-langchain4j-config-mpconfig - - io.smallrye.llm - smallrye-llm-langchain4j-portable-extension - - - - - - + + io.smallrye.llm + smallrye-llm-langchain4j-portable-extension + + org.projectlombok lombok @@ -186,10 +153,10 @@ - - dev.langchain4j - langchain4j-azure-open-ai - + + dev.langchain4j + langchain4j-azure-open-ai + dev.langchain4j @@ -202,10 +169,10 @@ - - ai.djl.huggingface - tokenizers - + + ai.djl.huggingface + tokenizers + @@ -213,45 +180,38 @@ slf4j-jdk14 runtime - + - - ${project.artifactId} - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${maven.compiler.release} - - - - - io.openliberty.tools - liberty-maven-plugin - 3.10.3 - - - ${project.build.finalName} - ${project.basedir}/docs-for-rag - - - - - + + ${project.artifactId} + + + + + io.openliberty.tools + liberty-maven-plugin + 3.10.3 + + + ${project.build.finalName} + ${project.basedir}/docs-for-rag + + + + + - - - org.apache.maven.plugins - maven-compiler-plugin - - - - io.openliberty.tools - liberty-maven-plugin - - - + + + io.openliberty.tools + liberty-maven-plugin + + + + org.apache.maven.plugins + maven-war-plugin + ${war-plugin.version} + + + \ No newline at end of file diff --git a/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java b/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java index bc6892f..6bdb4e6 100644 --- a/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java +++ b/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java @@ -2,13 +2,12 @@ import java.time.temporal.ChronoUnit; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import dev.langchain4j.service.SystemMessage; -import io.smallrye.llm.spi.RegisterAIService; - //@SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(tools = BookingService.class, chatMemoryMaxMessages = 10) public interface ChatAiService { @@ -38,5 +37,4 @@ default String chatFallback(String question) { "Sorry, I am not able to answer your request %s at the moment. Please try again later.", question); } - } diff --git a/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java b/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java index 58cb628..70d7ff1 100644 --- a/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java +++ b/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/DocRagIngestor.java @@ -11,21 +11,21 @@ import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; + import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.parser.TextDocumentParser; import dev.langchain4j.data.document.splitter.DocumentSplitters; -import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import lombok.extern.java.Log; -import org.eclipse.microprofile.config.inject.ConfigProperty; - @Log @ApplicationScoped public class DocRagIngestor { - + // Used by ContentRetriever @Produces private EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); @@ -34,8 +34,8 @@ public class DocRagIngestor { @Produces private InMemoryEmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>(); -// private File docs = new File(System.getProperty("docragdir")); - + // private File docs = new File(System.getProperty("docragdir")); + @Inject @ConfigProperty(name = "app.docs-for-rag.dir") private File docs; diff --git a/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java b/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java index 68488de..0376b0e 100644 --- a/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java +++ b/examples/liberty-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java @@ -1,9 +1,9 @@ package io.jefrajames.booking; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; -import io.smallrye.llm.spi.RegisterAIService; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; +import org.eclipse.microprofile.ai.llm.UserMessage; +import org.eclipse.microprofile.ai.llm.V; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(chatMemoryMaxMessages = 5, diff --git a/examples/pom.xml b/examples/pom.xml index b6fb2da..1bea750 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 io.smallrye.llm @@ -9,6 +8,7 @@ examples io.smallrye.llm.examples + SmallRye LLM Examples: Parent pom diff --git a/examples/quarkus-car-booking/pom.xml b/examples/quarkus-car-booking/pom.xml index a0fbb32..feecdc3 100644 --- a/examples/quarkus-car-booking/pom.xml +++ b/examples/quarkus-car-booking/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 examples @@ -9,12 +7,12 @@ 1.0.0-SNAPSHOT ../pom.xml + quarkus-car-booking - 1.0 + SmallRye LLM Examples: Quarkus Car Booking 3.12.1 - 17 UTF-8 UTF-8 quarkus-bom @@ -37,15 +35,22 @@ + + io.smallrye.llm + mp-ai-api + 1.0.0-SNAPSHOT + + + io.smallrye.llm + mp-ai-api + io.smallrye.llm smallrye-llm-langchain4j-buildcompatible-extension - 1.0.0-SNAPSHOT io.smallrye.llm smallrye-llm-langchain4j-config-mpconfig - 1.0.0-SNAPSHOT org.projectlombok @@ -56,7 +61,6 @@ dev.langchain4j langchain4j - ${dev.langchain4j.version} dev.langchain4j diff --git a/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/BookingService.java b/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/BookingService.java index 4ee5ff5..8704807 100644 --- a/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/BookingService.java +++ b/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/BookingService.java @@ -8,7 +8,8 @@ import jakarta.enterprise.context.ApplicationScoped; -import dev.langchain4j.agent.tool.Tool; +import org.eclipse.microprofile.ai.llm.Tool; + import lombok.extern.java.Log; @ApplicationScoped diff --git a/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java b/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java index 7e00c9b..7ddaa0b 100644 --- a/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java +++ b/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/ChatAiService.java @@ -2,13 +2,12 @@ import java.time.temporal.ChronoUnit; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import dev.langchain4j.service.SystemMessage; -import io.smallrye.llm.spi.RegisterAIService; - @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(tools = BookingService.class, chatMemoryMaxMessages = 10) public interface ChatAiService { diff --git a/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java b/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java index 61fdeef..143b440 100644 --- a/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java +++ b/examples/quarkus-car-booking/src/main/java/io/jefrajames/booking/FraudAiService.java @@ -2,15 +2,14 @@ import java.time.temporal.ChronoUnit; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; +import org.eclipse.microprofile.ai.llm.UserMessage; +import org.eclipse.microprofile.ai.llm.V; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; -import io.smallrye.llm.spi.RegisterAIService; - @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(chatMemoryMaxMessages = 5) public interface FraudAiService { diff --git a/mp-ai-api/pom.xml b/mp-ai-api/pom.xml new file mode 100644 index 0000000..4ba2ccd --- /dev/null +++ b/mp-ai-api/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + io.smallrye.llm + smallrye-llm-parent + 1.0.0-SNAPSHOT + ../pom.xml + + mp-ai-api + + + + jakarta.enterprise + jakarta.enterprise.cdi-api + provided + + + \ No newline at end of file diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/MemoryId.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/MemoryId.java new file mode 100644 index 0000000..f1cbea5 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/MemoryId.java @@ -0,0 +1,20 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The value of a method parameter annotated with @MemoryId will be used to find the memory belonging to that user/conversation. + * A parameter annotated with @MemoryId can be of any type, provided it has properly implemented equals() and hashCode() + * methods. + */ +@Documented +@Retention(RUNTIME) +@Target(PARAMETER) +public @interface MemoryId { + +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/Moderate.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/Moderate.java new file mode 100644 index 0000000..58628a5 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/Moderate.java @@ -0,0 +1,24 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * When a method in the AI Service is annotated with @Moderate, each invocation of this method will call not only the LLM, + * but also the moderation model (which must be provided during the construction of the AI Service) in parallel. + * This ensures that no malicious content is supplied by the user. + * Before the method returns an answer from the LLM, it will wait until the moderation model returns a result. + * If the moderation model flags the content, a ModerationException will be thrown. + * There is also an option to moderate user input *before* sending it to the LLM. If you require this functionality, + * please open an issue. + */ +@Documented +@Retention(RUNTIME) +@Target(METHOD) +public @interface Moderate { + +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/P.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/P.java new file mode 100644 index 0000000..c261d1c --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/P.java @@ -0,0 +1,32 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Parameter of a Tool + */ +@Documented +@Retention(RUNTIME) +@Target({ PARAMETER }) +public @interface P { + + /** + * Description of a parameter + * + * @return the description of a parameter + */ + String value(); + + /** + * Whether the parameter is required + * + * @return true if the parameter is required, false otherwise + * Default is true. + */ + boolean required() default true; +} diff --git a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/spi/RegisterAIService.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/RegisterAIService.java similarity index 95% rename from smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/spi/RegisterAIService.java rename to mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/RegisterAIService.java index b48605f..2c3f8c5 100644 --- a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/spi/RegisterAIService.java +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/RegisterAIService.java @@ -1,4 +1,4 @@ -package io.smallrye.llm.spi; +package org.eclipse.microprofile.ai.llm; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/StructuredPrompt.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/StructuredPrompt.java new file mode 100644 index 0000000..4c0d469 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/StructuredPrompt.java @@ -0,0 +1,32 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Represents a structured prompt. + */ +@Documented +@Retention(RUNTIME) +@Target(TYPE) +public @interface StructuredPrompt { + + /** + * Prompt template can be defined in one line or multiple lines. + * If the template is defined in multiple lines, the lines will be joined with a delimiter defined below. + * + * @return the prompt template lines. + */ + String[] value(); + + /** + * The delimiter to join the lines of the prompt template. + * + * @return the delimiter. + */ + String delimiter() default "\n"; +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/SystemMessage.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/SystemMessage.java new file mode 100644 index 0000000..1f050c3 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/SystemMessage.java @@ -0,0 +1,62 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Specifies either a complete system message (prompt) or a system message template to be used each time an AI service is + * invoked. + *
+ * An example: + * + *
+ * interface Assistant {
+ *
+ *     {@code @SystemMessage}("You are a helpful assistant")
+ *     String chat(String userMessage);
+ * }
+ * 
+ * + * The system message can contain template variables, + * which will be resolved with values from method parameters annotated with @{@link V}. + *
+ * An example: + * + *
+ * interface Assistant {
+ *
+ *     {@code @SystemMessage}("You are a {{characteristic}} assistant")
+ *     String chat(@UserMessage String userMessage, @V("characteristic") String characteristic);
+ * }
+ * 
+ * + * @see UserMessage + */ +@Documented +@Retention(RUNTIME) +@Target({ TYPE, METHOD }) +public @interface SystemMessage { + + /** + * Prompt template can be defined in one line or multiple lines. + * If the template is defined in multiple lines, the lines will be joined with a delimiter defined below. + */ + String[] value() default ""; + + String delimiter() default "\n"; + + /** + * The resource from which to read the prompt template. + * If no resource is specified, the prompt template is taken from {@link #value()}. + * If the resource is not found, an {@link IllegalConfigurationException} is thrown. + *

+ * The resource will be read by calling {@link Class#getResourceAsStream(String)} + * on the AI Service class (interface). + */ + String fromResource() default ""; +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/TokenStream.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/TokenStream.java new file mode 100644 index 0000000..76e9713 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/TokenStream.java @@ -0,0 +1,59 @@ +package org.eclipse.microprofile.ai.llm; + +import java.util.function.Consumer; + +/** + * Represents a token stream from language model to which you can subscribe and receive updates + * when a new token is available, when language model finishes streaming, or when an error occurs during streaming. + * It is intended to be used as a return type in AI Service. + */ +public interface TokenStream { + + /** + * The provided consumer will be invoked when/if contents have been retrieved using {@link RetrievalAugmentor}. + *

+ * The invocation happens before any call is made to the language model. + * + * @param contentHandler lambda that consumes all retrieved contents + * @return token stream instance used to configure or start stream processing + */ + // TokenStream onRetrieved(Consumer> contentHandler); + + /** + * The provided consumer will be invoked every time a new token from a language model is available. + * + * @param tokenHandler lambda that consumes tokens of the response + * @return token stream instance used to configure or start stream processing + */ + TokenStream onNext(Consumer tokenHandler); + + /** + * The provided consumer will be invoked when a language model finishes streaming a response. + * + * @param completionHandler lambda that will be invoked when language model finishes streaming + * @return token stream instance used to configure or start stream processing + */ + // TokenStream onComplete(Consumer> completionHandler); + + /** + * The provided consumer will be invoked when an error occurs during streaming. + * + * @param errorHandler lambda that will be invoked when an error occurs + * @return token stream instance used to configure or start stream processing + */ + TokenStream onError(Consumer errorHandler); + + /** + * All errors during streaming will be ignored (but will be logged with a WARN log level). + * + * @return token stream instance used to configure or start stream processing + */ + TokenStream ignoreErrors(); + + /** + * Completes the current token stream building and starts processing. + *

+ * Will send a request to LLM and start response streaming. + */ + void start(); +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/TokenStreamAdapter.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/TokenStreamAdapter.java new file mode 100644 index 0000000..fd7a70b --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/TokenStreamAdapter.java @@ -0,0 +1,14 @@ +package org.eclipse.microprofile.ai.llm; + +import java.lang.reflect.Type; + +/** + * @author Buhake Sindi + * @since 11 October 2024 + */ +public interface TokenStreamAdapter { + + boolean canAdaptTokenStreamTo(Type type); + + Object adapt(TokenStream tokenStream); +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/Tool.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/Tool.java new file mode 100644 index 0000000..8655b33 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/Tool.java @@ -0,0 +1,36 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Java methods annotated with {@code @Tool} are considered tools/functions that language model can execute/call. + * Tool/function calling LLM capability (e.g., see OpenAI + * function calling documentation) + * is used under the hood. + * If LLM decides to call the tool, the arguments are automatically parsed and injected as method arguments. + */ +@Documented +@Retention(RUNTIME) +@Target(METHOD) +public @interface Tool { + + /** + * Name of the tool. If not provided, method name will be used. + * + * @return name of the tool. + */ + String name() default ""; + + /** + * Description of the tool. + * It should be clear and descriptive to allow language model to understand the tool's purpose and its intended use. + * + * @return description of the tool. + */ + String[] value() default ""; +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/ToolMemoryId.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/ToolMemoryId.java new file mode 100644 index 0000000..af46700 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/ToolMemoryId.java @@ -0,0 +1,19 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * If a {@link Tool} method parameter is annotated with this annotation, + * memory id (parameter annotated with @MemoryId in AI Service) will be injected automatically. + */ +@Documented +@Retention(RUNTIME) +@Target(PARAMETER) +public @interface ToolMemoryId { + +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/UserMessage.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/UserMessage.java new file mode 100644 index 0000000..172d081 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/UserMessage.java @@ -0,0 +1,63 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Specifies either a complete user message or a user message template to be used each time an AI service is invoked. + * The user message can contain template variables, + * which will be resolved with values from method parameters annotated with @{@link V}. + *
+ * An example: + * + *

+ * interface Assistant {
+ *
+ *     {@code @UserMessage}("Say hello to {{name}}")
+ *     String greet(@V("name") String name);
+ * }
+ * 
+ * + * {@code @UserMessage} can also be used with method parameters: + * + *
+ * interface Assistant {
+ *
+ *     {@code @SystemMessage}("You are a {{characteristic}} assistant")
+ *     String chat(@UserMessage String userMessage, @V("characteristic") String characteristic);
+ * }
+ * 
+ * + * In this case {@code String userMessage} can contain unresolved template variables (e.g. "{{characteristic}}"), + * which will be resolved using the values of method parameters annotated with @{@link V}. + * + * @see SystemMessage + */ +@Documented +@Retention(RUNTIME) +@Target({ METHOD, PARAMETER }) +public @interface UserMessage { + + /** + * Prompt template can be defined in one line or multiple lines. + * If the template is defined in multiple lines, the lines will be joined with a delimiter defined below. + */ + String[] value() default ""; + + String delimiter() default "\n"; + + /** + * The resource from which to read the prompt template. + * If no resource is specified, the prompt template is taken from {@link #value()}. + * If the resource is not found, an {@link IllegalConfigurationException} is thrown. + *

+ * The resource will be read by calling {@link Class#getResourceAsStream(String)} + * on the AI Service class (interface). + */ + String fromResource() default ""; +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/UserName.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/UserName.java new file mode 100644 index 0000000..4e93838 --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/UserName.java @@ -0,0 +1,18 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The value of a method parameter annotated with @UserName will be injected into the field 'name' of a UserMessage. + */ +@Documented +@Retention(RUNTIME) +@Target(PARAMETER) +public @interface UserName { + +} diff --git a/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/V.java b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/V.java new file mode 100644 index 0000000..4110a4a --- /dev/null +++ b/mp-ai-api/src/main/java/org/eclipse/microprofile/ai/llm/V.java @@ -0,0 +1,42 @@ +package org.eclipse.microprofile.ai.llm; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * When a parameter of a method in an AI Service is annotated with {@code @V}, + * it becomes a prompt template variable. Its value will be injected into prompt templates defined + * via @{@link UserMessage}, @{@link SystemMessage}. + *

+ * Example: + * + *

+ * {@code @UserMessage("Hello, my name is {{name}}. I am {{age}} years old.")}
+ * String chat(@V("name") String name, @V("age") int age);
+ * 
+ *

+ * Example: + * + *

+ * {@code @UserMessage("Hello, my name is {{name}}. I am {{age}} years old.")}
+ * String chat(@V String name, @V int age);
+ * 
+ *

+ * + * @see UserMessage + * @see SystemMessage + */ +@Documented +@Retention(RUNTIME) +@Target(PARAMETER) +public @interface V { + + /** + * Name of a variable (placeholder) in a prompt template. + */ + String value(); +} diff --git a/pom.xml b/pom.xml index 88a3405..1b24b2d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,29 +1,28 @@ - - +~ Copyright 2017 Red Hat, Inc. +~ +~ Licensed under the Apache License, Version 2.0 (the "License"); +~ you may not use this file except in compliance with the License. +~ You may obtain a copy of the License at +~ +~ http://www.apache.org/licenses/LICENSE-2.0 +~ +~ Unless required by applicable law or agreed to in writing, software +~ distributed under the License is distributed on an "AS IS" BASIS, +~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +~ See the License for the specific language governing permissions and +~ limitations under the License. +--> + + 4.0.0 io.smallrye smallrye-parent - 43 + 46 io.smallrye.llm @@ -31,6 +30,7 @@ 1.0.0-SNAPSHOT pom + SmallRye LLM: Parent https://smallrye.io @@ -38,19 +38,13 @@ 2.4.0 9.7 2.3.0 - UTF-8 - UTF-8 - UTF-8 4.1.0 4.0.0.Final 5.1.3.Final 3.5.3.Final - 0.34.0 - 3.9.9 - 3.13.0 - 3.5.0 - 3.4.0 - + 0.35.0 + 17 + ${maven.compiler.release} SmallRye LLM @@ -64,11 +58,10 @@ GitHub - https://github.com/smallrye/smallrye-config/issues + https://github.com/smallrye/smallrye-llm/issues - + scm:git:git@github.com:smallrye/smallrye-llm.git scm:git:git@github.com:smallrye/smallrye-llm.git https://github.com/smallrye/smallrye-llm/ @@ -81,6 +74,7 @@ smallrye-llm-langchain4j-buildcompatible-extension smallrye-llm-langchain4j-core smallrye-llm-langchain4j-config-mpconfig + mp-ai-api @@ -97,15 +91,43 @@ import pom + + org.jboss.logging + jboss-logging + ${version.org.jboss.logging} + + + dev.langchain4j + langchain4j + ${dev.langchain4j.version} + - - io.smallrye.testing - smallrye-testing-bom - ${version.smallrye.testing} - import - pom + io.smallrye.llm + mp-ai-api + ${project.version} + + + io.smallrye.llm + smallrye-llm-langchain4j-buildcompatible-extension + ${project.version} + + + io.smallrye.llm + smallrye-llm-langchain4j-config-mpconfig + ${project.version} + + + io.smallrye.llm + smallrye-llm-langchain4j-core + ${project.version} + + io.smallrye.llm + smallrye-llm-langchain4j-portable-extension + ${project.version} + + jakarta.enterprise jakarta.enterprise.cdi-api @@ -118,89 +140,63 @@ ${version.weld} runtime - - org.jboss.weld - weld-junit5 - ${weld-junit5.version} - test - org.jboss.weld.se weld-se-core ${version.weld} runtime + + - org.jboss.logging - jboss-logging - ${version.org.jboss.logging} + io.smallrye.testing + smallrye-testing-bom + ${version.smallrye.testing} + import + pom - dev.langchain4j - langchain4j - ${dev.langchain4j.version} + org.jboss.weld + weld-junit5 + ${weld-junit5.version} + test + + + io.smallrye.config + smallrye-config + 3.8.1 + test - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${maven.compiler.source} - ${maven.compiler.target} - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - org.apache.maven.plugins - maven-enforcer-plugin - ${maven-enforcer-plugin.version} - - - enforce-maven - - enforce - - - - - [${maven.version},) - Check for Maven version >=${maven.version} failed. - Update your Maven install. - - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.apache.maven.plugins - maven-surefire-plugin - + + + + org.apache.maven.plugins + maven-compiler-plugin + - - org.apache.maven.plugins - maven-enforcer-plugin - - - + + org.apache.maven.plugins + maven-surefire-plugin + + + + + + + unpublished + + + performRelease + !true + + + + examples + + + \ No newline at end of file diff --git a/smallrye-llm-langchain4j-buildcompatible-extension/pom.xml b/smallrye-llm-langchain4j-buildcompatible-extension/pom.xml index 1d055e0..33a17bb 100644 --- a/smallrye-llm-langchain4j-buildcompatible-extension/pom.xml +++ b/smallrye-llm-langchain4j-buildcompatible-extension/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 io.smallrye.llm @@ -7,13 +6,15 @@ 1.0.0-SNAPSHOT ../pom.xml + smallrye-llm-langchain4j-buildcompatible-extension + SmallRye LLM: LangChain4j Build Compatible Extension + org.jacoco jacoco-maven-plugin - 0.8.12 @@ -35,10 +36,6 @@ @{jacocoArgLine} - - org.apache.maven.plugins - maven-enforcer-plugin - @@ -65,20 +62,17 @@ io.smallrye.llm smallrye-llm-langchain4j-core - ${project.version} io.smallrye.llm smallrye-llm-langchain4j-config-mpconfig - ${project.version} test io.smallrye.config smallrye-config - 3.8.1 test diff --git a/smallrye-llm-langchain4j-buildcompatible-extension/src/main/java/io/smallrye/llm/aiservice/Langchain4JAIServiceBuildCompatibleExtension.java b/smallrye-llm-langchain4j-buildcompatible-extension/src/main/java/io/smallrye/llm/aiservice/Langchain4JAIServiceBuildCompatibleExtension.java index 2cdbeb2..24c6d90 100644 --- a/smallrye-llm-langchain4j-buildcompatible-extension/src/main/java/io/smallrye/llm/aiservice/Langchain4JAIServiceBuildCompatibleExtension.java +++ b/smallrye-llm-langchain4j-buildcompatible-extension/src/main/java/io/smallrye/llm/aiservice/Langchain4JAIServiceBuildCompatibleExtension.java @@ -20,10 +20,9 @@ import jakarta.enterprise.lang.model.types.ClassType; import jakarta.inject.Named; +import org.eclipse.microprofile.ai.llm.RegisterAIService; import org.jboss.logging.Logger; -import io.smallrye.llm.spi.RegisterAIService; - public class Langchain4JAIServiceBuildCompatibleExtension implements BuildCompatibleExtension { private static final Logger LOGGER = Logger.getLogger(Langchain4JAIServiceBuildCompatibleExtension.class); private static final Set> detectedAIServicesDeclaredInterfaces = new HashSet<>(); diff --git a/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java b/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java index fb87b1f..5d656c1 100644 --- a/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java +++ b/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java @@ -1,9 +1,9 @@ package io.smallrye.llm.core; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; -import io.smallrye.llm.spi.RegisterAIService; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; +import org.eclipse.microprofile.ai.llm.UserMessage; +import org.eclipse.microprofile.ai.llm.V; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService diff --git a/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java b/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java index 81deb7e..c86cf42 100644 --- a/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java +++ b/smallrye-llm-langchain4j-buildcompatible-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java @@ -2,7 +2,7 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.llm.spi.RegisterAIService; +import org.eclipse.microprofile.ai.llm.RegisterAIService; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(scope = ApplicationScoped.class) diff --git a/smallrye-llm-langchain4j-config-mpconfig/pom.xml b/smallrye-llm-langchain4j-config-mpconfig/pom.xml index 84680df..9c60d2c 100644 --- a/smallrye-llm-langchain4j-config-mpconfig/pom.xml +++ b/smallrye-llm-langchain4j-config-mpconfig/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 io.smallrye.llm @@ -7,14 +6,14 @@ 1.0.0-SNAPSHOT ../pom.xml + smallrye-llm-langchain4j-config-mpconfig - http://maven.apache.org + SmallRye LLM: LangChain4j Config io.smallrye.config smallrye-config - 3.8.1 test @@ -25,7 +24,6 @@ io.smallrye.llm smallrye-llm-langchain4j-core - ${project.version} diff --git a/smallrye-llm-langchain4j-core/pom.xml b/smallrye-llm-langchain4j-core/pom.xml index 014aadc..2550701 100644 --- a/smallrye-llm-langchain4j-core/pom.xml +++ b/smallrye-llm-langchain4j-core/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 io.smallrye.llm @@ -7,10 +6,20 @@ 1.0.0-SNAPSHOT ../pom.xml + smallrye-llm-langchain4j-core - http://maven.apache.org + SmallRye LLM: LangChain4j Core + + io.smallrye.llm + mp-ai-api + ${project.version} + + + io.smallrye.llm + mp-ai-api + org.jboss.logging jboss-logging diff --git a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/aiservice/CommonAIServiceCreator.java b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/aiservice/CommonAIServiceCreator.java index 35546d9..a2a4faf 100644 --- a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/aiservice/CommonAIServiceCreator.java +++ b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/aiservice/CommonAIServiceCreator.java @@ -7,13 +7,13 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.literal.NamedLiteral; +import org.eclipse.microprofile.ai.llm.RegisterAIService; import org.jboss.logging.Logger; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.service.AiServices; import io.smallrye.llm.core.langchain4j.core.config.spi.ChatMemoryFactoryProvider; -import io.smallrye.llm.spi.RegisterAIService; public class CommonAIServiceCreator { diff --git a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeAiServiceTokenStream.java b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeAiServiceTokenStream.java new file mode 100644 index 0000000..775d9dc --- /dev/null +++ b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeAiServiceTokenStream.java @@ -0,0 +1,89 @@ +package io.smallrye.llm.core.langchain4j.services; + +import java.util.function.Consumer; + +import org.eclipse.microprofile.ai.llm.TokenStream; + +/** + * @author Buhake Sindi + * @since 10 October 2024 + */ +public class SmallRyeAiServiceTokenStream implements TokenStream { + + private dev.langchain4j.service.TokenStream delegateTokenStream; + + /** + * @param delegateTokenStream + */ + public SmallRyeAiServiceTokenStream(dev.langchain4j.service.TokenStream delegateTokenStream) { + super(); + this.delegateTokenStream = delegateTokenStream; + } + + // /* (non-Javadoc) + // * @see io.smallrye.llm.service.TokenStream#onRetrieved(java.util.function.Consumer) + // */ + // @Override + // public TokenStream onRetrieved(Consumer> contentHandler) { + // // TODO Auto-generated method stub + // delegateTokenStream.onRetrieved(contentHandler); + // return this; + // } + + /* + * (non-Javadoc) + * + * @see io.smallrye.llm.service.TokenStream#onNext(java.util.function.Consumer) + */ + @Override + public TokenStream onNext(Consumer tokenHandler) { + // TODO Auto-generated method stub + delegateTokenStream.onNext(tokenHandler); + return this; + } + + // /* (non-Javadoc) + // * @see io.smallrye.llm.service.TokenStream#onComplete(java.util.function.Consumer) + // */ + // @Override + // public TokenStream onComplete(Consumer> completionHandler) { + // // TODO Auto-generated method stub + // delegateTokenStream.onComplete(completionHandler); + // return this; + // } + + /* + * (non-Javadoc) + * + * @see io.smallrye.llm.service.TokenStream#onError(java.util.function.Consumer) + */ + @Override + public TokenStream onError(Consumer errorHandler) { + // TODO Auto-generated method stub + delegateTokenStream.onError(errorHandler); + return this; + } + + /* + * (non-Javadoc) + * + * @see io.smallrye.llm.service.TokenStream#ignoreErrors() + */ + @Override + public TokenStream ignoreErrors() { + // TODO Auto-generated method stub + delegateTokenStream.ignoreErrors(); + return this; + } + + /* + * (non-Javadoc) + * + * @see io.smallrye.llm.service.TokenStream#start() + */ + @Override + public void start() { + // TODO Auto-generated method stub + delegateTokenStream.start(); + } +} diff --git a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeLang4JAiServices.java b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeLang4JAiServices.java new file mode 100644 index 0000000..b4296c2 --- /dev/null +++ b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeLang4JAiServices.java @@ -0,0 +1,612 @@ +package io.smallrye.llm.core.langchain4j.services; + +import static dev.langchain4j.exception.IllegalConfigurationException.illegalConfiguration; +import static dev.langchain4j.internal.Exceptions.illegalArgument; +import static dev.langchain4j.internal.Exceptions.runtime; +import static dev.langchain4j.internal.Utils.isNotNullOrBlank; +import static dev.langchain4j.model.chat.Capability.RESPONSE_FORMAT_JSON_SCHEMA; +import static dev.langchain4j.model.chat.request.ResponseFormatType.JSON; +import static dev.langchain4j.service.TypeUtils.typeHasRawClass; +import static dev.langchain4j.service.output.JsonSchemas.jsonSchemaFrom; +import static dev.langchain4j.spi.ServiceHelper.loadFactories; + +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Scanner; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.eclipse.microprofile.ai.llm.MemoryId; +import org.eclipse.microprofile.ai.llm.Moderate; +import org.eclipse.microprofile.ai.llm.StructuredPrompt; +import org.eclipse.microprofile.ai.llm.TokenStream; +import org.eclipse.microprofile.ai.llm.TokenStreamAdapter; +import org.eclipse.microprofile.ai.llm.Tool; +import org.eclipse.microprofile.ai.llm.UserName; +import org.eclipse.microprofile.ai.llm.V; +import org.jboss.logging.Logger; + +import dev.langchain4j.agent.tool.ToolExecutionRequest; +import dev.langchain4j.agent.tool.ToolSpecification; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.ToolExecutionResultMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.memory.ChatMemory; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.request.ResponseFormat; +import dev.langchain4j.model.chat.request.json.JsonSchema; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.model.input.Prompt; +import dev.langchain4j.model.input.PromptTemplate; +import dev.langchain4j.model.input.structured.StructuredPromptProcessor; +import dev.langchain4j.model.moderation.Moderation; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.model.output.TokenUsage; +import dev.langchain4j.rag.AugmentationRequest; +import dev.langchain4j.rag.AugmentationResult; +import dev.langchain4j.rag.query.Metadata; +import dev.langchain4j.service.AiServiceContext; +import dev.langchain4j.service.AiServiceTokenStream; +import dev.langchain4j.service.AiServices; +import dev.langchain4j.service.Result; +import dev.langchain4j.service.TypeUtils; +import dev.langchain4j.service.output.ServiceOutputParser; +import dev.langchain4j.service.tool.DefaultToolExecutor; +import dev.langchain4j.service.tool.ToolExecution; +import dev.langchain4j.service.tool.ToolExecutor; +import dev.langchain4j.service.tool.ToolProviderRequest; +import dev.langchain4j.service.tool.ToolProviderResult; + +/** + * @author Buhake Sindi + * @since 10 October 2024 + */ +public class SmallRyeLang4JAiServices extends AiServices { + + private static final Logger LOGGER = Logger.getLogger(SmallRyeLang4JAiServices.class); + + private final ServiceOutputParser serviceOutputParser = new ServiceOutputParser(); + private final Collection tokenStreamAdapters = loadFactories(TokenStreamAdapter.class); + private static final int MAX_SEQUENTIAL_TOOL_EXECUTIONS = 10; + + /** + * @param context + */ + public SmallRyeLang4JAiServices(AiServiceContext context) { + super(context); + //TODO Auto-generated constructor stub + } + + /* + * (non-Javadoc) + * + * @see dev.langchain4j.service.AiServices#tools(java.util.List) + */ + @Override + public AiServices tools(List objectsWithTools) { + // TODO Auto-generated method stub + if (objectsWithTools != null) { + if (context.toolSpecifications == null) { + context.toolSpecifications = new ArrayList<>(); + } + if (context.toolExecutors == null) { + context.toolExecutors = new HashMap<>(); + } + + for (Object objectWithTool : objectsWithTools) { + if (objectWithTool instanceof Class) { + throw illegalConfiguration("Tool '%s' must be an object, not a class", objectWithTool); + } + + for (Method method : objectWithTool.getClass().getDeclaredMethods()) { + if (method.isAnnotationPresent(Tool.class)) { + ToolSpecification toolSpecification = ToolSpecifications.toolSpecificationFrom(method); + context.toolSpecifications.add(toolSpecification); + context.toolExecutors.put(toolSpecification.name(), new DefaultToolExecutor(objectWithTool, method)); + } + } + } + + return super.tools(objectsWithTools); + } + + return this; + } + + @SuppressWarnings("unchecked") + @Override + public T build() { + // TODO Auto-generated method stub + performBasicValidation(); + + for (Method method : context.aiServiceClass.getMethods()) { + if (method.isAnnotationPresent(Moderate.class) && context.moderationModel == null) { + throw illegalConfiguration("The @Moderate annotation is present, but the moderationModel is not set up. " + + "Please ensure a valid moderationModel is configured before using the @Moderate annotation."); + } + if (method.getReturnType() == Result.class || + method.getReturnType() == List.class || + method.getReturnType() == Set.class) { + TypeUtils.validateReturnTypesAreProperlyParametrized(method.getName(), method.getGenericReturnType()); + } + } + + Object proxyInstance = Proxy.newProxyInstance( + context.aiServiceClass.getClassLoader(), + new Class[] { context.aiServiceClass }, + new InvocationHandler() { + + private final ExecutorService executor = Executors.newCachedThreadPool(); + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Exception { + + if (method.getDeclaringClass() == Object.class) { + // methods like equals(), hashCode() and toString() should not be handled by this proxy + return method.invoke(this, args); + } + + validateParameters(method); + + Object memoryId = findMemoryId(method, args).orElse(DEFAULT); + + Optional systemMessage = prepareSystemMessage(memoryId, method, args); + UserMessage userMessage = prepareUserMessage(method, args); + AugmentationResult augmentationResult = null; + if (context.retrievalAugmentor != null) { + List chatMemory = context.hasChatMemory() + ? context.chatMemory(memoryId).messages() + : null; + Metadata metadata = Metadata.from(userMessage, memoryId, chatMemory); + AugmentationRequest augmentationRequest = new AugmentationRequest(userMessage, metadata); + augmentationResult = context.retrievalAugmentor.augment(augmentationRequest); + userMessage = (UserMessage) augmentationResult.chatMessage(); + } + + // TODO give user ability to provide custom OutputParser + Type returnType = method.getGenericReturnType(); + + boolean streaming = returnType == TokenStream.class || canAdaptTokenStreamTo(returnType); + + boolean supportsJsonSchema = supportsJsonSchema(); + Optional jsonSchema = Optional.empty(); + if (supportsJsonSchema && !streaming) { + jsonSchema = jsonSchemaFrom(returnType); + } + + if ((!supportsJsonSchema || !jsonSchema.isPresent()) && !streaming) { + // TODO append after storing in the memory? + userMessage = appendOutputFormatInstructions(returnType, userMessage); + } + + if (context.hasChatMemory()) { + ChatMemory chatMemory = context.chatMemory(memoryId); + systemMessage.ifPresent(chatMemory::add); + chatMemory.add(userMessage); + } + + List messages; + if (context.hasChatMemory()) { + messages = context.chatMemory(memoryId).messages(); + } else { + messages = new ArrayList<>(); + systemMessage.ifPresent(messages::add); + messages.add(userMessage); + } + + Future moderationFuture = triggerModerationIfNeeded(method, messages); + + List toolSpecifications = context.toolSpecifications; + Map toolExecutors = context.toolExecutors; + + if (context.toolProvider != null) { + toolSpecifications = new ArrayList<>(); + toolExecutors = new HashMap<>(); + ToolProviderRequest toolProviderRequest = new ToolProviderRequest(memoryId, userMessage); + ToolProviderResult toolProviderResult = context.toolProvider.provideTools(toolProviderRequest); + if (toolProviderResult != null) { + Map tools = toolProviderResult.tools(); + for (ToolSpecification toolSpecification : tools.keySet()) { + toolSpecifications.add(toolSpecification); + toolExecutors.put(toolSpecification.name(), tools.get(toolSpecification)); + } + } + } + + if (streaming) { + TokenStream tokenStream = new SmallRyeAiServiceTokenStream(new AiServiceTokenStream( + messages, + toolSpecifications, + toolExecutors, + augmentationResult != null ? augmentationResult.contents() : null, + context, + memoryId)); + // TODO moderation + if (returnType == TokenStream.class) { + return tokenStream; + } else { + return adapt(tokenStream, returnType); + } + } + + Response response; + if (supportsJsonSchema && jsonSchema.isPresent()) { + ChatRequest chatRequest = ChatRequest.builder() + .messages(messages) + .toolSpecifications(toolSpecifications) + .responseFormat(ResponseFormat.builder() + .type(JSON) + .jsonSchema(jsonSchema.get()) + .build()) + .build(); + + ChatResponse chatResponse = context.chatModel.chat(chatRequest); + + response = new Response<>( + chatResponse.aiMessage(), + chatResponse.tokenUsage(), + chatResponse.finishReason()); + } else { + // TODO migrate to new API + response = toolSpecifications == null || toolSpecifications.isEmpty() + ? context.chatModel.generate(messages) + : context.chatModel.generate(messages, toolSpecifications); + } + + TokenUsage tokenUsageAccumulator = response.tokenUsage(); + + verifyModerationIfNeeded(moderationFuture); + + int executionsLeft = MAX_SEQUENTIAL_TOOL_EXECUTIONS; + List toolExecutions = new ArrayList<>(); + while (true) { + + if (executionsLeft-- == 0) { + throw runtime("Something is wrong, exceeded %s sequential tool executions", + MAX_SEQUENTIAL_TOOL_EXECUTIONS); + } + + AiMessage aiMessage = response.content(); + + if (context.hasChatMemory()) { + context.chatMemory(memoryId).add(aiMessage); + } else { + messages = new ArrayList<>(messages); + messages.add(aiMessage); + } + + if (!aiMessage.hasToolExecutionRequests()) { + break; + } + + for (ToolExecutionRequest toolExecutionRequest : aiMessage.toolExecutionRequests()) { + ToolExecutor toolExecutor = toolExecutors.get(toolExecutionRequest.name()); + String toolExecutionResult = toolExecutor.execute(toolExecutionRequest, memoryId); + toolExecutions.add(ToolExecution.builder() + .request(toolExecutionRequest) + .result(toolExecutionResult) + .build()); + ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from( + toolExecutionRequest, + toolExecutionResult); + if (context.hasChatMemory()) { + context.chatMemory(memoryId).add(toolExecutionResultMessage); + } else { + messages.add(toolExecutionResultMessage); + } + } + + if (context.hasChatMemory()) { + messages = context.chatMemory(memoryId).messages(); + } + + response = context.chatModel.generate(messages, toolSpecifications); + tokenUsageAccumulator = TokenUsage.sum(tokenUsageAccumulator, response.tokenUsage()); + } + + response = Response.from(response.content(), tokenUsageAccumulator, response.finishReason()); + + Object parsedResponse = serviceOutputParser.parse(response, returnType); + if (typeHasRawClass(returnType, Result.class)) { + return Result.builder() + .content(parsedResponse) + .tokenUsage(tokenUsageAccumulator) + .sources(augmentationResult == null ? null : augmentationResult.contents()) + .finishReason(response.finishReason()) + .toolExecutions(toolExecutions) + .build(); + } else { + return parsedResponse; + } + } + + private boolean canAdaptTokenStreamTo(Type returnType) { + for (TokenStreamAdapter tokenStreamAdapter : tokenStreamAdapters) { + if (tokenStreamAdapter.canAdaptTokenStreamTo(returnType)) { + return true; + } + } + return false; + } + + private Object adapt(TokenStream tokenStream, Type returnType) { + for (TokenStreamAdapter tokenStreamAdapter : tokenStreamAdapters) { + if (tokenStreamAdapter.canAdaptTokenStreamTo(returnType)) { + return tokenStreamAdapter.adapt(tokenStream); + } + } + throw new IllegalStateException("Can't find suitable TokenStreamAdapter"); + } + + private boolean supportsJsonSchema() { + return context.chatModel != null + && context.chatModel.supportedCapabilities().contains(RESPONSE_FORMAT_JSON_SCHEMA); + } + + private UserMessage appendOutputFormatInstructions(Type returnType, UserMessage userMessage) { + String outputFormatInstructions = serviceOutputParser.outputFormatInstructions(returnType); + String text = userMessage.singleText() + outputFormatInstructions; + if (isNotNullOrBlank(userMessage.name())) { + userMessage = UserMessage.from(userMessage.name(), text); + } else { + userMessage = UserMessage.from(text); + } + return userMessage; + } + + private Future triggerModerationIfNeeded(Method method, List messages) { + if (method.isAnnotationPresent(Moderate.class)) { + return executor.submit(() -> { + List messagesToModerate = removeToolMessages(messages); + return context.moderationModel.moderate(messagesToModerate).content(); + }); + } + return null; + } + }); + + return (T) proxyInstance; + } + + static void validateParameters(Method method) { + Parameter[] parameters = method.getParameters(); + if (parameters == null || parameters.length < 2) { + return; + } + + for (Parameter parameter : parameters) { + V v = parameter.getAnnotation(V.class); + org.eclipse.microprofile.ai.llm.UserMessage userMessage = parameter + .getAnnotation(org.eclipse.microprofile.ai.llm.UserMessage.class); + MemoryId memoryId = parameter.getAnnotation(MemoryId.class); + UserName userName = parameter.getAnnotation(UserName.class); + if (v == null && userMessage == null && memoryId == null && userName == null) { + throw illegalConfiguration( + "Parameter '%s' of method '%s' should be annotated with @V or @UserMessage " + + "or @UserName or @MemoryId", + parameter.getName(), method.getName()); + } + } + } + + private static Optional findMemoryId(Method method, Object[] args) { + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].isAnnotationPresent(MemoryId.class)) { + Object memoryId = args[i]; + if (memoryId == null) { + throw illegalArgument( + "The value of parameter '%s' annotated with @MemoryId in method '%s' must not be null", + parameters[i].getName(), method.getName()); + } + return Optional.of(memoryId); + } + } + return Optional.empty(); + } + + private Optional prepareSystemMessage(Object memoryId, Method method, Object[] args) { + return findSystemMessageTemplate(memoryId, method) + .map(systemMessageTemplate -> PromptTemplate.from(systemMessageTemplate) + .apply(findTemplateVariables(systemMessageTemplate, method, args)) + .toSystemMessage()); + } + + private Optional findSystemMessageTemplate(Object memoryId, Method method) { + org.eclipse.microprofile.ai.llm.SystemMessage annotation = method + .getAnnotation(org.eclipse.microprofile.ai.llm.SystemMessage.class); + if (annotation != null) { + return Optional + .of(getTemplate(method, "System", annotation.fromResource(), annotation.value(), annotation.delimiter())); + } + + return context.systemMessageProvider.apply(memoryId); + } + + private static Map findTemplateVariables(String template, Method method, Object[] args) { + Parameter[] parameters = method.getParameters(); + + Map variables = new HashMap<>(); + for (int i = 0; i < parameters.length; i++) { + V annotation = parameters[i].getAnnotation(V.class); + if (annotation != null) { + String variableName = annotation.value(); + Object variableValue = args[i]; + variables.put(variableName, variableValue); + } + } + + if (template.contains("{{it}}") && !variables.containsKey("it")) { + String itValue = getValueOfVariableIt(parameters, args); + variables.put("it", itValue); + } + + return variables; + } + + private static String getValueOfVariableIt(Parameter[] parameters, Object[] args) { + if (parameters.length == 1) { + Parameter parameter = parameters[0]; + if (!parameter.isAnnotationPresent(MemoryId.class) + && !parameter.isAnnotationPresent(org.eclipse.microprofile.ai.llm.UserMessage.class) + && !parameter.isAnnotationPresent(UserName.class) + && (!parameter.isAnnotationPresent(V.class) || isAnnotatedWithIt(parameter))) { + return toString(args[0]); + } + } + + for (int i = 0; i < parameters.length; i++) { + if (isAnnotatedWithIt(parameters[i])) { + return toString(args[i]); + } + } + + throw illegalConfiguration("Error: cannot find the value of the prompt template variable \"{{it}}\"."); + } + + private static boolean isAnnotatedWithIt(Parameter parameter) { + V annotation = parameter.getAnnotation(V.class); + return annotation != null && "it".equals(annotation.value()); + } + + private static UserMessage prepareUserMessage(Method method, Object[] args) { + + String template = getUserMessageTemplate(method, args); + Map variables = findTemplateVariables(template, method, args); + + Prompt prompt = PromptTemplate.from(template).apply(variables); + + Optional maybeUserName = findUserName(method.getParameters(), args); + return maybeUserName.map(userName -> UserMessage.from(userName, prompt.text())) + .orElseGet(prompt::toUserMessage); + } + + private static String getUserMessageTemplate(Method method, Object[] args) { + + Optional templateFromMethodAnnotation = findUserMessageTemplateFromMethodAnnotation(method); + Optional templateFromParameterAnnotation = findUserMessageTemplateFromAnnotatedParameter(method.getParameters(), + args); + + if (templateFromMethodAnnotation.isPresent() && templateFromParameterAnnotation.isPresent()) { + throw illegalConfiguration( + "Error: The method '%s' has multiple @UserMessage annotations. Please use only one.", + method.getName()); + } + + if (templateFromMethodAnnotation.isPresent()) { + return templateFromMethodAnnotation.get(); + } + if (templateFromParameterAnnotation.isPresent()) { + return templateFromParameterAnnotation.get(); + } + + Optional templateFromTheOnlyArgument = findUserMessageTemplateFromTheOnlyArgument(method.getParameters(), args); + if (templateFromTheOnlyArgument.isPresent()) { + return templateFromTheOnlyArgument.get(); + } + + throw illegalConfiguration("Error: The method '%s' does not have a user message defined.", method.getName()); + } + + private static Optional findUserMessageTemplateFromMethodAnnotation(Method method) { + return Optional.ofNullable(method.getAnnotation(org.eclipse.microprofile.ai.llm.UserMessage.class)) + .map(a -> getTemplate(method, "User", a.fromResource(), a.value(), a.delimiter())); + } + + private static Optional findUserMessageTemplateFromAnnotatedParameter(Parameter[] parameters, Object[] args) { + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].isAnnotationPresent(org.eclipse.microprofile.ai.llm.UserMessage.class)) { + return Optional.of(toString(args[i])); + } + } + return Optional.empty(); + } + + private static Optional findUserMessageTemplateFromTheOnlyArgument(Parameter[] parameters, Object[] args) { + if (parameters != null && parameters.length == 1 && parameters[0].getAnnotations().length == 0) { + return Optional.of(toString(args[0])); + } + return Optional.empty(); + } + + private static Optional findUserName(Parameter[] parameters, Object[] args) { + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].isAnnotationPresent(UserName.class)) { + return Optional.of(args[i].toString()); + } + } + return Optional.empty(); + } + + private static String getTemplate(Method method, String type, String resource, String[] value, String delimiter) { + String messageTemplate; + if (!resource.trim().isEmpty()) { + messageTemplate = getResourceText(method.getDeclaringClass(), resource); + if (messageTemplate == null) { + throw illegalConfiguration("@%sMessage's resource '%s' not found", type, resource); + } + } else { + messageTemplate = String.join(delimiter, value); + } + if (messageTemplate.trim().isEmpty()) { + throw illegalConfiguration("@%sMessage's template cannot be empty", type); + } + return messageTemplate; + } + + private static String getResourceText(Class clazz, String resource) { + InputStream inputStream = clazz.getResourceAsStream(resource); + if (inputStream == null) { + inputStream = clazz.getResourceAsStream("/" + resource); + } + return getText(inputStream); + } + + private static String getText(InputStream inputStream) { + if (inputStream == null) { + return null; + } + try (Scanner scanner = new Scanner(inputStream); + Scanner s = scanner.useDelimiter("\\A")) { + return s.hasNext() ? s.next() : ""; + } + } + + private static String toString(Object arg) { + if (arg.getClass().isArray()) { + return arrayToString(arg); + } else if (arg.getClass().isAnnotationPresent(StructuredPrompt.class)) { + return StructuredPromptProcessor.toPrompt(arg).text(); + } else { + return arg.toString(); + } + } + + private static String arrayToString(Object arg) { + StringBuilder sb = new StringBuilder("["); + int length = Array.getLength(arg); + for (int i = 0; i < length; i++) { + sb.append(toString(Array.get(arg, i))); + if (i < length - 1) { + sb.append(", "); + } + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeLang4JchainAiServicesFactory.java b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeLang4JchainAiServicesFactory.java new file mode 100644 index 0000000..f74fe16 --- /dev/null +++ b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeLang4JchainAiServicesFactory.java @@ -0,0 +1,23 @@ +package io.smallrye.llm.core.langchain4j.services; + +import dev.langchain4j.service.AiServiceContext; +import dev.langchain4j.service.AiServices; +import dev.langchain4j.spi.services.AiServicesFactory; + +/** + * @author Buhake Sindi + * @since 10 October 2024 + */ +public class SmallRyeLang4JchainAiServicesFactory implements AiServicesFactory { + + /* + * (non-Javadoc) + * + * @see dev.langchain4j.spi.services.AiServicesFactory#create(dev.langchain4j.service.AiServiceContext) + */ + @Override + public AiServices create(AiServiceContext context) { + // TODO Auto-generated method stub + return new SmallRyeLang4JAiServices(context); + } +} diff --git a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeStructuredPromptFactory.java b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeStructuredPromptFactory.java new file mode 100644 index 0000000..3b47182 --- /dev/null +++ b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/SmallRyeStructuredPromptFactory.java @@ -0,0 +1,82 @@ +package io.smallrye.llm.core.langchain4j.services; + +import java.util.Map; + +import org.eclipse.microprofile.ai.llm.StructuredPrompt; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; +import com.google.gson.reflect.TypeToken; + +import dev.langchain4j.internal.ValidationUtils; +import dev.langchain4j.model.input.Prompt; +import dev.langchain4j.model.input.PromptTemplate; +import dev.langchain4j.spi.prompt.structured.StructuredPromptFactory; + +/** + * @author Buhake Sindi + * @since 10 October 2024 + */ +public class SmallRyeStructuredPromptFactory implements StructuredPromptFactory { + private static final Gson GSON = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create(); + + /* + * (non-Javadoc) + * + * @see dev.langchain4j.spi.prompt.structured.StructuredPromptFactory#toPrompt(java.lang.Object) + */ + + @Override + public Prompt toPrompt(Object structuredPrompt) { + // TODO Auto-generated method stub + StructuredPrompt annotation = validateStructuredPrompt(structuredPrompt); + + String promptTemplateString = join(annotation); + PromptTemplate promptTemplate = PromptTemplate.from(promptTemplateString); + + Map variables = extractVariables(structuredPrompt); + + return promptTemplate.apply(variables); + } + + /** + * Extracts the variables from the structured prompt. + * + * @param structuredPrompt The structured prompt. + * @return The variables map. + */ + private static Map extractVariables(Object structuredPrompt) { + String json = GSON.toJson(structuredPrompt); + TypeToken> mapType = new TypeToken>() { + }; + return GSON.fromJson(json, mapType); + } + + /** + * Validates that the given object is annotated with {@link StructuredPrompt}. + * + * @param structuredPrompt the object to validate. + * @return the annotation. + */ + private static StructuredPrompt validateStructuredPrompt(Object structuredPrompt) { + ValidationUtils.ensureNotNull(structuredPrompt, "structuredPrompt"); + + Class cls = structuredPrompt.getClass(); + + return ValidationUtils.ensureNotNull( + cls.getAnnotation(StructuredPrompt.class), + "%s should be annotated with @StructuredPrompt to be used as a structured prompt", + cls.getName()); + } + + /** + * Joins the lines of the prompt template. + * + * @param structuredPrompt the structured prompt. + * @return the joined prompt template. + */ + private static String join(StructuredPrompt structuredPrompt) { + return String.join(structuredPrompt.delimiter(), structuredPrompt.value()); + } +} diff --git a/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/ToolSpecifications.java b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/ToolSpecifications.java new file mode 100644 index 0000000..ec3d2a9 --- /dev/null +++ b/smallrye-llm-langchain4j-core/src/main/java/io/smallrye/llm/core/langchain4j/services/ToolSpecifications.java @@ -0,0 +1,279 @@ +package io.smallrye.llm.core.langchain4j.services; + +import static dev.langchain4j.agent.tool.JsonSchemaProperty.ARRAY; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.BOOLEAN; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.INTEGER; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.NUMBER; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.OBJECT; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.STRING; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.description; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.enums; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.from; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.items; +import static dev.langchain4j.agent.tool.JsonSchemaProperty.objectItems; +import static dev.langchain4j.internal.TypeUtils.isJsonBoolean; +import static dev.langchain4j.internal.TypeUtils.isJsonInteger; +import static dev.langchain4j.internal.TypeUtils.isJsonNumber; +import static dev.langchain4j.internal.Utils.isNullOrBlank; +import static java.lang.String.format; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toList; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.microprofile.ai.llm.P; +import org.eclipse.microprofile.ai.llm.Tool; +import org.eclipse.microprofile.ai.llm.ToolMemoryId; + +import dev.langchain4j.agent.tool.JsonSchemaProperty; +import dev.langchain4j.agent.tool.ToolSpecification; +import dev.langchain4j.model.output.structured.Description; + +/** + * @author Buhake Sindi + * @since 10 October 2024 + */ +public class ToolSpecifications { + + private ToolSpecifications() { + } + + /** + * Returns {@link ToolSpecification}s for all methods annotated with @{@link Tool} within the specified class. + * + * @param classWithTools the class. + * @return the {@link ToolSpecification}s. + */ + public static List toolSpecificationsFrom(Class classWithTools) { + List toolSpecifications = stream(classWithTools.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(Tool.class)) + .map(ToolSpecifications::toolSpecificationFrom) + .collect(toList()); + validateSpecifications(toolSpecifications); + return toolSpecifications; + } + + /** + * Returns {@link ToolSpecification}s for all methods annotated with @{@link Tool} + * within the class of the specified object. + * + * @param objectWithTools the object. + * @return the {@link ToolSpecification}s. + */ + public static List toolSpecificationsFrom(Object objectWithTools) { + return toolSpecificationsFrom(objectWithTools.getClass()); + } + + /** + * Validates all the {@link ToolSpecification}s. The validation checks for duplicate method names. + * Throws {@link IllegalArgumentException} if validation fails + * + * @param toolSpecifications list of ToolSpecification to be validated. + */ + public static void validateSpecifications(List toolSpecifications) throws IllegalArgumentException { + + // Checks for duplicates methods + Set names = new HashSet<>(); + for (ToolSpecification toolSpecification : toolSpecifications) { + if (!names.add(toolSpecification.name())) { + throw new IllegalArgumentException( + format("Tool names must be unique. The tool '%s' appears several times", toolSpecification.name())); + } + } + } + + /** + * Returns the {@link ToolSpecification} for the given method annotated with @{@link Tool}. + * + * @param method the method. + * @return the {@link ToolSpecification}. + */ + public static ToolSpecification toolSpecificationFrom(Method method) { + Tool annotation = method.getAnnotation(Tool.class); + + String name = isNullOrBlank(annotation.name()) ? method.getName() : annotation.name(); + String description = String.join("\n", annotation.value()); // TODO provide null instead of "" ? + + ToolSpecification.Builder builder = ToolSpecification.builder() + .name(name) + .description(description); + + for (Parameter parameter : method.getParameters()) { + if (parameter.isAnnotationPresent(ToolMemoryId.class)) { + continue; + } + + boolean required = Optional.ofNullable(parameter.getAnnotation(P.class)) + .map(P::required) + .orElse(true); + + if (required) { + builder.addParameter(parameter.getName(), toJsonSchemaProperties(parameter)); + } else { + builder.addOptionalParameter(parameter.getName(), toJsonSchemaProperties(parameter)); + } + } + + return builder.build(); + } + + /** + * Convert a {@link Parameter} to a {@link JsonSchemaProperty}. + * + * @param parameter the parameter. + * @return the {@link JsonSchemaProperty}. + */ + static Iterable toJsonSchemaProperties(Parameter parameter) { + + Class type = parameter.getType(); + + P annotation = parameter.getAnnotation(P.class); + JsonSchemaProperty description = annotation == null ? null : description(annotation.value()); + + Iterable simpleType = toJsonSchemaProperties(type, description); + + if (simpleType != null) { + return simpleType; + } + + if (Collection.class.isAssignableFrom(type)) { + return removeNulls(ARRAY, arrayTypeFrom(parameter.getParameterizedType()), description); + } + + return removeNulls(OBJECT, schema(type), description); + } + + static JsonSchemaProperty schema(Class structured) { + return schema(structured, new HashMap<>()); + } + + private static JsonSchemaProperty schema(Class structured, HashMap, JsonSchemaProperty> visited) { + if (visited.containsKey(structured)) { + return visited.get(structured); + } + + // Mark the class as visited by inserting it in the visited map with a null value initially. + visited.put(structured, null); + Map properties = new HashMap<>(); + for (Field field : structured.getDeclaredFields()) { + String name = field.getName(); + if (name.equals("this$0") || java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + // Skip inner class reference. + continue; + } + Iterable schemaProperties = toJsonSchemaProperties(field, visited); + Map objectMap = new HashMap<>(); + for (JsonSchemaProperty jsonSchemaProperty : schemaProperties) { + objectMap.put(jsonSchemaProperty.key(), jsonSchemaProperty.value()); + } + properties.put(name, objectMap); + } + JsonSchemaProperty jsonSchemaProperty = from("properties", properties); + // Update the visited map with the final JsonSchemaProperty for the current class + visited.put(structured, jsonSchemaProperty); + return jsonSchemaProperty; + } + + private static Iterable toJsonSchemaProperties(Field field, + HashMap, JsonSchemaProperty> visited) { + + Class type = field.getType(); + + Description annotation = field.getAnnotation(Description.class); + JsonSchemaProperty description = annotation == null ? null : description(String.join(" ", annotation.value())); + + Iterable simpleType = toJsonSchemaProperties(type, description); + + if (simpleType != null) { + return simpleType; + } + + if (Collection.class.isAssignableFrom(type)) { + return removeNulls(ARRAY, + arrayTypeFrom((Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]), + description); + } + + return removeNulls(OBJECT, schema(type, visited), description); + } + + private static Iterable toJsonSchemaProperties(Class type, JsonSchemaProperty description) { + + if (type == String.class) { + return removeNulls(STRING, description); + } + + if (isJsonBoolean(type)) { + return removeNulls(BOOLEAN, description); + } + + if (isJsonInteger(type)) { + return removeNulls(INTEGER, description); + } + + if (isJsonNumber(type)) { + return removeNulls(NUMBER, description); + } + + if (type.isArray()) { + return removeNulls(ARRAY, arrayTypeFrom(type.getComponentType()), description); + } + + if (type.isEnum()) { + return removeNulls(STRING, enums((Class) type), description); + } + + return null; + } + + private static JsonSchemaProperty arrayTypeFrom(Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length == 1) { + return arrayTypeFrom((Class) actualTypeArguments[0]); + } + } + return items(JsonSchemaProperty.OBJECT); + } + + private static JsonSchemaProperty arrayTypeFrom(Class clazz) { + if (clazz == String.class) { + return items(JsonSchemaProperty.STRING); + } + if (isJsonBoolean(clazz)) { + return items(JsonSchemaProperty.BOOLEAN); + } + if (isJsonInteger(clazz)) { + return items(JsonSchemaProperty.INTEGER); + } + if (isJsonNumber(clazz)) { + return items(JsonSchemaProperty.NUMBER); + } + return objectItems(schema(clazz)); + } + + /** + * Remove nulls from the given array. + * + * @param items the array + * @return an iterable of the non-null items. + */ + static Iterable removeNulls(JsonSchemaProperty... items) { + return stream(items) + .filter(Objects::nonNull) + .collect(toList()); + } +} diff --git a/smallrye-llm-langchain4j-core/src/main/resources/META-INF/services/dev.langchain4j.spi.prompt.structured.StructuredPromptFactory b/smallrye-llm-langchain4j-core/src/main/resources/META-INF/services/dev.langchain4j.spi.prompt.structured.StructuredPromptFactory new file mode 100644 index 0000000..c5471d4 --- /dev/null +++ b/smallrye-llm-langchain4j-core/src/main/resources/META-INF/services/dev.langchain4j.spi.prompt.structured.StructuredPromptFactory @@ -0,0 +1 @@ +io.smallrye.llm.core.langchain4j.services.SmallRyeStructuredPromptFactory \ No newline at end of file diff --git a/smallrye-llm-langchain4j-core/src/main/resources/META-INF/services/dev.langchain4j.spi.services.AiServicesFactory b/smallrye-llm-langchain4j-core/src/main/resources/META-INF/services/dev.langchain4j.spi.services.AiServicesFactory new file mode 100644 index 0000000..d5e51a5 --- /dev/null +++ b/smallrye-llm-langchain4j-core/src/main/resources/META-INF/services/dev.langchain4j.spi.services.AiServicesFactory @@ -0,0 +1 @@ +io.smallrye.llm.core.langchain4j.services.SmallRyeLang4JchainAiServicesFactory \ No newline at end of file diff --git a/smallrye-llm-langchain4j-portable-extension/effective-pom.xml b/smallrye-llm-langchain4j-portable-extension/effective-pom.xml deleted file mode 100644 index d27aa22..0000000 --- a/smallrye-llm-langchain4j-portable-extension/effective-pom.xml +++ /dev/null @@ -1,1156 +0,0 @@ - - - - - - - - - - - - - - - 4.0.0 - - io.smallrye.llm - smallrye-llm-parent - 1.0.0-SNAPSHOT - - io.smallrye.llm - smallrye-llm-langchain4j-portable-extension - 1.0.0-SNAPSHOT - SmallRye Parent POM - http://maven.apache.org - 2018 - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - - - - - radcortez - Roberto Cortez - radcortez@redhat.com - Red Hat - https://www.redhat.com/en - - - - scm:git:git@github.com:smallrye/smallrye-llm.git - scm:git:git@github.com:smallrye/smallrye-llm.git - https://github.com/smallrye/smallrye-llm/ - - - GitHub - https://github.com/smallrye/smallrye-config/issues - - - - oss.sonatype - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - - - oss.sonatype - https://s01.oss.sonatype.org/content/repositories/snapshots/ - - - - 0.34.0 - false - 4.0.1 - 3.13.0 - 3.5.0 - 3.4.0 - 11 - 11 - 11 - 11 - 11 - 3.9.9 - 3.0.3 - UTF-8 - UTF-8 - https://sonarcloud.io - smallrye - SmallRye LLM - 9.7 - 3.25.3 - 4.2.1 - 3.2.0 - 3.3.2 - 3.13.0 - 3.1.1 - 3.1 - 3.2.5 - 2.22.0 - 3.2.2 - 1.9.0 - 3.1.1 - 0.8.12 - 2.1.1 - 10.0.0 - 4.0.1 - 3.0.0 - 2.1.1 - 3.1.0 - 3.3.0 - 3.6.3 - 3.Final - 5.10.2 - 1.6.13 - 3.0.5 - 3.5.3.Final - 2.2.1.Final - 3.0.1 - 3.3.1 - 4.5.1 - 3.12.1 - 2 - 2.4.0 - 1.0.0 - 2.3.0 - 3.3.0 - 3.2.5 - 3.2.5 - 1.19.7 - 2.16.2 - 5.1.3.Final - 4.0.0.Final - - - - - org.eclipse.microprofile.config - microprofile-config-api - 3.0.3 - - - jakarta.enterprise - jakarta.enterprise.cdi-api - 4.0.1 - - - org.jboss.weld - weld-junit5 - 4.0.0.Final - test - - - org.jboss.weld.se - weld-se-core - 5.1.3.Final - runtime - - - org.jboss.logging - jboss-logging - 3.5.3.Final - - - dev.langchain4j - langchain4j - 0.34.0 - - - jakarta.platform - jakarta.jakartaee-core-api - 10.0.0 - - - jakarta.ws.rs - jakarta.ws.rs-api - 3.1.0 - - - jakarta.annotation - jakarta.annotation-api - 2.1.1 - - - jakarta.json.bind - jakarta.json.bind-api - 3.0.0 - - - jakarta.json - jakarta.json-api - 2.1.1 - - - org.jboss.logging - jboss-logging-annotations - 2.2.1.Final - provided - - - org.jboss.logging - jboss-logging-processor - 2.2.1.Final - provided - - - org.jboss - jandex - 3.0.5 - - - org.junit.jupiter - junit-jupiter - 5.10.2 - test - - - org.junit.vintage - junit-vintage-engine - 5.10.2 - test - - - org.assertj - assertj-core - 3.25.3 - test - - - org.awaitility - awaitility - 4.2.1 - test - - - io.rest-assured - rest-assured - 4.5.1 - test - - - org.testcontainers - testcontainers - 1.19.7 - test - - - io.smallrye.common - smallrye-common-annotation - 2.4.0 - - - io.smallrye.common - smallrye-common-classloader - 2.4.0 - - - io.smallrye.common - smallrye-common-constraint - 2.4.0 - - - io.smallrye.common - smallrye-common-cpu - 2.4.0 - - - io.smallrye.common - smallrye-common-function - 2.4.0 - - - io.smallrye.common - smallrye-common-expression - 2.4.0 - - - io.smallrye.common - smallrye-common-io - 2.4.0 - - - io.smallrye.common - smallrye-common-net - 2.4.0 - - - io.smallrye.common - smallrye-common-os - 2.4.0 - - - io.smallrye.common - smallrye-common-ref - 2.4.0 - - - io.smallrye.common - smallrye-common-version - 2.4.0 - - - io.smallrye.common - smallrye-common-vertx-context - 2.4.0 - - - io.smallrye.testing - smallrye-testing-utilities - 2.3.0 - test - - - org.testng - testng - 7.6.0 - test - - - org.jboss.weld - weld-spi - 5.0.SP3 - - - org.jboss.weld - weld-api - 5.0.SP3 - - - org.jboss.weld - weld-core-impl - 5.1.0.Final - - - org.jboss.logmanager - jboss-logmanager - 2.1.19.Final - test - - - org.junit.jupiter - junit-jupiter-api - 5.9.2 - - - org.junit.jupiter - junit-jupiter-engine - 5.9.2 - - - org.junit.jupiter - junit-jupiter-migrationsupport - 5.9.2 - - - org.junit.jupiter - junit-jupiter-params - 5.9.2 - - - org.junit.platform - junit-platform-commons - 1.9.2 - - - org.junit.platform - junit-platform-console - 1.9.2 - - - org.junit.platform - junit-platform-engine - 1.9.2 - - - org.junit.platform - junit-platform-jfr - 1.9.2 - - - org.junit.platform - junit-platform-launcher - 1.9.2 - - - org.junit.platform - junit-platform-reporting - 1.9.2 - - - org.junit.platform - junit-platform-runner - 1.9.2 - - - org.junit.platform - junit-platform-suite - 1.9.2 - - - org.junit.platform - junit-platform-suite-api - 1.9.2 - - - org.junit.platform - junit-platform-suite-commons - 1.9.2 - - - org.junit.platform - junit-platform-suite-engine - 1.9.2 - - - org.junit.platform - junit-platform-testkit - 1.9.2 - - - - - - io.smallrye.llm - smallrye-llm-langchain4j-core - 1.0.0-SNAPSHOT - compile - - - dev.langchain4j - langchain4j - 0.34.0 - compile - - - jakarta.enterprise - jakarta.enterprise.cdi-api - 4.0.1 - provided - - - org.jboss.logging - jboss-logging - 3.5.3.Final - compile - - - io.smallrye.llm - smallrye-llm-langchain4j-config-mpconfig - 1.0.0-SNAPSHOT - test - - - io.smallrye.config - smallrye-config - 3.8.1 - test - - - org.jboss.weld - weld-junit5 - 4.0.0.Final - test - - - - - - true - - - false - - amq-cr-repo - AMQ-7.12.1-CR1 Brew maven Repository - https://download.hosts.prod.upshift.rdu2.redhat.com/devel/candidates/amq/amq-broker-7.12.1.CR1/maven-repository/maven-repository/ - - - - true - - - false - - redhat-qa - Red Hat QA Maven Repository - https://repository.engineering.redhat.com/nexus/content/repositories/jboss-qa/ - - - - true - - - true - - redhat-qa-snapshots - Red Hat QA Maven Repository - https://repository.engineering.redhat.com/nexus/content/repositories/jboss-qa-snapshots/ - - - - true - - redhat-maven-repo-ga - Red Hat Maven Repository - https://maven.repository.redhat.com/ga/all - - - - true - - - true - - eap8-internal-repository - https://download.eng.bos.redhat.com/brewroot/repos/jb-eap-8.0-maven-build/latest/maven/ - - - - true - - - true - - eap74-internal-repository - https://download.eng.bos.redhat.com/brewroot/repos/jb-eap-7.4-maven-build/latest/maven/ - - - - true - - - false - - jboss-community-repository - https://repository.jboss.org/nexus/content/groups/public/ - - - - false - - central - Central Repository - https://repo.maven.apache.org/maven2 - - - - - - true - - - false - - redhat-qa - Red Hat QA Maven Repository - https://repository.engineering.redhat.com/nexus/content/repositories/jboss-qa/ - - - - true - - - true - - redhat-qa-snapshots - Red Hat QA Maven Repository - https://repository.engineering.redhat.com/nexus/content/repositories/jboss-qa-snapshots/ - - - - true - - - true - - eap8-internal-repository - https://download.eng.bos.redhat.com/brewroot/repos/jb-eap-8.0-maven-build/latest/maven/ - - - - true - - - true - - eap74-internal-repository - https://download.eng.bos.redhat.com/brewroot/repos/jb-eap-7.4-maven-build/latest/maven/ - - - - true - - - false - - jboss-community-repository - https://repository.jboss.org/nexus/content/groups/public/ - - - - false - - central - Central Repository - https://repo.maven.apache.org/maven2 - - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/src/main/java - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/src/main/scripts - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/src/test/java - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/classes - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/test-classes - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/src/main/resources - - - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/src/test/resources - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target - smallrye-llm-langchain4j-portable-extension-1.0.0-SNAPSHOT - - - - maven-antrun-plugin - 3.1.0 - - - maven-assembly-plugin - 3.7.1 - - - maven-dependency-plugin - 3.7.0 - - - maven-release-plugin - 3.0.1 - - - maven-clean-plugin - 3.3.2 - - - maven-compiler-plugin - 3.13.0 - - 11 - 11 - - - - maven-resources-plugin - 3.3.1 - - - maven-gpg-plugin - 3.2.2 - - - --pinentry-mode - loopback - - - - - maven-install-plugin - 3.1.1 - - - maven-jar-plugin - 3.3.0 - - - true - - true - true - - - http://maven.apache.org - 17.0.12 - https://github.com/smallrye/smallrye-llm/ - scm:git:git@github.com:smallrye/smallrye-llm.git - ${buildNumber} - - - - - - maven-javadoc-plugin - 3.6.3 - - - - apiNote - a - API Note: - - - implSpec - a - Implementation Requirements: - - - implNote - a - Implementation Note: - - - param - - - return - - - throws - - - since - - - version - - - serialData - - - see - - - - - - maven-source-plugin - 3.3.0 - - - maven-site-plugin - 3.12.1 - - - maven-surefire-plugin - 3.4.0 - - - maven-surefire-report-plugin - 3.2.5 - - - maven-failsafe-plugin - 3.2.5 - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 - - 15 - - - - maven-deploy-plugin - 3.1.1 - - - org.jacoco - jacoco-maven-plugin - 0.8.12 - - - prepare-agent - generate-test-resources - - prepare-agent - - - jacocoArgLine - true - - META-INF/** - - - - - - jacocoArgLine - true - - META-INF/** - - - - - io.smallrye - smallrye-maven-plugin - 1.0.0 - - - maven-enforcer-plugin - 3.5.0 - - - enforce-maven - - enforce - - - - - [3.9.9,) - Check for Maven version >=3.9.9 failed. - Update your Maven install. - - - - - - - - - - - maven-compiler-plugin - 3.13.0 - - - default-compile - compile - - compile - - - - -Aorg.jboss.logging.tools.addGeneratedAnnotation=false - - 11 - 11 - - - - default-testCompile - test-compile - - testCompile - - - 11 - 11 - - - - - 11 - 11 - - - - maven-release-plugin - 3.0.1 - - true - @{project.version} - verify - false - true - false - -DskipTests ${release.arguments} - - - - maven-source-plugin - 3.3.0 - - - attach-sources - - jar-no-fork - - - - - - net.revelc.code.formatter - formatter-maven-plugin - 2.22.0 - - - format-sources - process-sources - - format - - - io/smallrye/coderules/eclipse-format.xml - false - - - - - - io.smallrye - smallrye-code-rules - 2 - compile - - - - io/smallrye/coderules/eclipse-format.xml - false - - - - net.revelc.code - impsort-maven-plugin - 1.9.0 - - - sort-imports - - sort - - - java.,javax.,jakarta.,org.,com. - * - false - true - - - - - java.,javax.,jakarta.,org.,com. - * - false - true - - - - org.codehaus.mojo - buildnumber-maven-plugin - 3.2.0 - - - get-scm-revision - initialize - - create - - - false - false - UNKNOWN - true - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.16.2 - - false - - - - maven-surefire-plugin - 3.4.0 - - - default-test - test - - test - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/classes/META-INF/versions/17 - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/classes - - - - - - - maven-enforcer-plugin - 3.5.0 - - - enforce-maven - - enforce - - - - - [3.9.9,) - Check for Maven version >=3.9.9 failed. - Update your Maven install. - - - - - - - - maven-clean-plugin - 3.3.2 - - - default-clean - clean - - clean - - - - - - maven-resources-plugin - 3.3.1 - - - default-testResources - process-test-resources - - testResources - - - - default-resources - process-resources - - resources - - - - - - maven-jar-plugin - 3.3.0 - - - default-jar - package - - jar - - - - true - - true - true - - - http://maven.apache.org - 17.0.12 - https://github.com/smallrye/smallrye-llm/ - scm:git:git@github.com:smallrye/smallrye-llm.git - ${buildNumber} - - - - - - - - true - - true - true - - - http://maven.apache.org - 17.0.12 - https://github.com/smallrye/smallrye-llm/ - scm:git:git@github.com:smallrye/smallrye-llm.git - ${buildNumber} - - - - - - maven-install-plugin - 3.1.1 - - - default-install - install - - install - - - - - - maven-deploy-plugin - 3.1.1 - - - default-deploy - deploy - - deploy - - - - - - maven-site-plugin - 3.12.1 - - - default-site - site - - site - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/site - - - org.apache.maven.plugins - maven-project-info-reports-plugin - - - - - - default-deploy - site-deploy - - deploy - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/site - - - org.apache.maven.plugins - maven-project-info-reports-plugin - - - - - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/site - - - org.apache.maven.plugins - maven-project-info-reports-plugin - - - - - - - - /home/ehugonne/dev/AI/smallrye-llm/smallrye-llm-langchain4j-portable-extension/target/site - - diff --git a/smallrye-llm-langchain4j-portable-extension/pom.xml b/smallrye-llm-langchain4j-portable-extension/pom.xml index 4690195..17c206b 100644 --- a/smallrye-llm-langchain4j-portable-extension/pom.xml +++ b/smallrye-llm-langchain4j-portable-extension/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 io.smallrye.llm @@ -7,8 +6,9 @@ 1.0.0-SNAPSHOT ../pom.xml + smallrye-llm-langchain4j-portable-extension - http://maven.apache.org + SmallRye LLM: LangChain4j Portable Extension @@ -42,7 +42,6 @@ io.smallrye.config smallrye-config - 3.8.1 test diff --git a/smallrye-llm-langchain4j-portable-extension/src/main/java/io/smallrye/llm/core/langchain4j/portableextension/LangChain4JAIServicePortableExtension.java b/smallrye-llm-langchain4j-portable-extension/src/main/java/io/smallrye/llm/core/langchain4j/portableextension/LangChain4JAIServicePortableExtension.java index 304de6c..44c8800 100644 --- a/smallrye-llm-langchain4j-portable-extension/src/main/java/io/smallrye/llm/core/langchain4j/portableextension/LangChain4JAIServicePortableExtension.java +++ b/smallrye-llm-langchain4j-portable-extension/src/main/java/io/smallrye/llm/core/langchain4j/portableextension/LangChain4JAIServicePortableExtension.java @@ -17,10 +17,10 @@ import jakarta.enterprise.inject.spi.ProcessInjectionPoint; import jakarta.enterprise.inject.spi.WithAnnotations; +import org.eclipse.microprofile.ai.llm.RegisterAIService; import org.jboss.logging.Logger; import io.smallrye.llm.aiservice.CommonAIServiceCreator; -import io.smallrye.llm.spi.RegisterAIService; public class LangChain4JAIServicePortableExtension implements Extension { private static final Logger LOGGER = Logger.getLogger(LangChain4JAIServicePortableExtension.class); diff --git a/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java b/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java index fb87b1f..5d656c1 100644 --- a/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java +++ b/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyAIService.java @@ -1,9 +1,9 @@ package io.smallrye.llm.core; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; -import io.smallrye.llm.spi.RegisterAIService; +import org.eclipse.microprofile.ai.llm.RegisterAIService; +import org.eclipse.microprofile.ai.llm.SystemMessage; +import org.eclipse.microprofile.ai.llm.UserMessage; +import org.eclipse.microprofile.ai.llm.V; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService diff --git a/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java b/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java index 81deb7e..c86cf42 100644 --- a/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java +++ b/smallrye-llm-langchain4j-portable-extension/src/test/java/io/smallrye/llm/core/MyDummyApplicationScopedAIService.java @@ -2,7 +2,7 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.llm.spi.RegisterAIService; +import org.eclipse.microprofile.ai.llm.RegisterAIService; @SuppressWarnings("CdiManagedBeanInconsistencyInspection") @RegisterAIService(scope = ApplicationScoped.class)