Skip to content

Commit

Permalink
Add GraalPy OpenAI Starter.
Browse files Browse the repository at this point in the history
  • Loading branch information
fniephaus committed Oct 1, 2024
1 parent 4ddc1ab commit 36f6f96
Show file tree
Hide file tree
Showing 10 changed files with 790 additions and 0 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/graalpy-openai-starter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test GraalPy OpenAI Starter
on:
push:
paths:
- 'graalpy/graalpy-openai-starter/**'
- '.github/workflows/graalpy-openai-starter.yml'
pull_request:
paths:
- 'graalpy/graalpy-openai-starter/**'
- '.github/workflows/graalpy-openai-starter.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
run:
name: 'graalpy-openai-starter'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
java-version: '23.0.0'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
cache: 'maven'
native-image-job-reports: 'true'
- name: Build, test, and run 'graalpy-openai-starter' using Maven
run: |
cd graalpy/graalpy-openai-starter
./mvnw --no-transfer-progress test
./mvnw --no-transfer-progress exec:java || true
# Gradle plugin not available yet
# - name: Build, test, and run 'graalpy-openai-starter' using Gradle
# run: |
# cd graalpy/graalpy-openai-starter
# ./gradlew test
# ./gradlew run || true
12 changes: 12 additions & 0 deletions graalpy/graalpy-openai-starter/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

# Binary files should be left untouched
*.jar binary

8 changes: 8 additions & 0 deletions graalpy/graalpy-openai-starter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build

# Ignore maven build output directory
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
62 changes: 62 additions & 0 deletions graalpy/graalpy-openai-starter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# GraalPy OpenAI Starter

A minimal Java application that embeds [the official Python library for the OpenAI API](https://github.com/openai/openai-python) with GraalPy.

## Preparation

Install GraalVM for JDK 23 and set the value of `JAVA_HOME` accordingly.
We recommend using [SDKMAN!](https://sdkman.io/). (For other download options, see [GraalVM Downloads](https://www.graalvm.org/downloads/).)

```bash
sdk install java 23-graal
```

This project also requires that an OpenAI API key is set via the `OPENAI_API_KEY` environment variable.
For more information on how to generate and set such a key, check out [the Developer quickstart from OpenAI](https://platform.openai.com/docs/quickstart/create-and-export-an-api-key).

## Run the Application Using Maven

To build and test the demo, run:

```bash
./mvnw test
```

To execute the main method, run:

```bash
./mvnw exec:java -Dexec.args="'Say Hello from GraalPy'"
```

## Run the Application Using Gradle

To build and test the demo, run:

```bash
./gradlew test
```

To execute the main method, run:

```bash
./gradlew run --args="'Say Hello from GraalPy'"
```

## Implementation Details

The [App.java](src/main/java/com/example/App.java) embeds a `create_chat_completion()` function based on [the official usage example](https://github.com/openai/openai-python?tab=readme-ov-file#usage).
The Java text block that contains the Python code is annotated with `// language=python`, which triggers a [language injection in IntelliJ IDEA](https://www.jetbrains.com/help/idea/using-language-injections.html).
The `create_chat_completion()` function is returned when the Python code is evaluated.
Afterward, the Python function is called with `userInput` from Java, and the result is mapped to the `ChatCompletion` interface.
The interfaces `ChatCompletion`, `Choice`, and `ChatComplectionMessage` are ported from [the official API](https://github.com/openai/openai-python/blob/main/api.md), and only contain the functionality exercised by the example code.
These interfaces can be extended appropriately when needed.

Moreover, the [_pom.xml_](pom.xml) contains the `openai` package and all its transitive dependencies, and locks their version.
This is recommended because it provides a similar experience to Maven (all versions are well-defined), while `pip`, the package installer for Python, also supports version ranges (versions are defined at compile time).
In particular, the `jiter` and `pydantic_core` make use of native extensions which must be built for GraalPy.
For the selected versions of these two packages, there are pre-built binary wheels for Linux x86_64 in the [GraalPy PyPI repository](https://www.graalvm.org/python/wheels/).
This means that on Linux x86_64 machines, the native extensions do not need to be built from source and are instead fetched from the PyPI repository.

> Note: Although `jiter`, `pydantic_core`, and many other Python packages that use native extensions work with GraalPy, their use is currently considered experimental.
> Also note that when using multiple contexts, only one context can load and access a native extension at a time.
> We are exploring approaches to support multi-context setups with native extensions.
Loading

0 comments on commit 36f6f96

Please sign in to comment.