Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
37 changes: 37 additions & 0 deletions .github/workflows/prompty-java-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: prompty Java build and test
on:
pull_request:
paths:
- 'runtime/java/**'
- '.github/workflows/prompty-java-check.yml'

workflow_call:

jobs:
java-tests:
name: test Java ${{ matrix.java }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
java: ["21"]
permissions:
contents: read
defaults:
run:
working-directory: runtime/java
steps:
- uses: actions/checkout@v7

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4

- name: Build and test (live tests excluded)
run: ./gradlew build --console=plain
21 changes: 21 additions & 0 deletions runtime/java/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# OpenAI
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o-mini
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
OPENAI_IMAGE_MODEL=dall-e-2

# Azure OpenAI / Foundry
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-4o-mini
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small

# Azure AI Foundry project (agent / model discovery)
FOUNDRY_PROJECT_ENDPOINT=

# Azure Entra ID (for keyless auth tests)
AZURE_TENANT_ID=

# Anthropic
ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=claude-sonnet-4-20250514
11 changes: 11 additions & 0 deletions runtime/java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Gradle
.gradle/
build/

# Local credentials for live provider tests — never commit.
.env

# IDE
.idea/
*.iml
.vscode/
78 changes: 78 additions & 0 deletions runtime/java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Prompty for Java

The Java implementation of the Prompty runtime.

Prompty is a markdown-based asset format (`.prompty`) for LLM prompts: YAML
frontmatter describes the model, inputs, outputs, tools and template
configuration, and the markdown body becomes the prompt instructions. The
runtime loads, renders, parses, executes and processes those assets.

> **Status:** in development. The generated model layer and its example suites
> are complete; the loader, renderers, parser, pipeline and providers are being
> ported from the Rust reference implementation.

## Layout

| Module | Contents |
| ------------------- | --------------------------------------------------------------- |
| `prompty` | Canonical generated model + loader, renderers, parser, pipeline |
| `prompty-openai` | OpenAI provider (executor + processor) |
| `prompty-anthropic` | Anthropic provider (executor + processor) |
| `prompty-foundry` | Azure AI Foundry provider (executor + processor) |

## The model layer is generated — do not edit it

`prompty/src/main/java/com/microsoft/prompty/model/` and
`prompty/src/test/java/com/microsoft/prompty/model/` are emitted from the
TypeSpec definition in [`schema/`](../../schema) by the Typra emitter. They are
the single canonical model layer for this runtime; there is no hand-written
duplicate. Regenerate with:

```bash
cd schema
npm install # first time only
npm run generate
```

`npm run generate` runs `tsp compile` followed by
`schema/scripts/normalize-typra-output.mjs`, which applies a deterministic,
idempotent normalization pass to the emitted Java (see
`schema/scripts/normalize-java-output.mjs`). That shim exists only because the
Typra Java backend currently emits source that does not compile and that
diverges from the C#/Rust/Go/Python backends; every defect it works around is
documented at the top of the shim and has been reported upstream. The shim is
part of the generation pipeline, not a manual edit — running the pipeline twice
from a clean tree produces byte-identical output.

The emitter also produces example-driven suites next to the model. They are
package-private, so `GeneratedExamplesTest` discovers the compiled
`*GeneratedTest` classes by reflection and runs each as a named dynamic test,
which keeps the suite in step with the schema without a checked-in registry.
`ModelNormalizationTest` covers the behaviour the shim adds on top of the
emitter output, which the generated examples do not exercise.

## Building

The Gradle wrapper provisions Gradle itself; a JDK is required to run it. The
build pins a Java 21 toolchain, so a newer JDK works as long as Gradle can
locate or provision a 21 toolchain.

```bash
cd runtime/java
./gradlew build # compile + unit tests
./gradlew :prompty:test # unit tests for the core module only
```

## Live provider tests

Tests tagged `live` call real providers and are excluded from the default `test`
task. They read credentials from the process environment; the test support code
also reads `runtime/java/.env` when present so the file does not have to be
exported manually.

```bash
cp .env.example .env # then fill in credentials
./gradlew test -PliveTests
```

`.env` is git-ignored and must never be committed.
48 changes: 48 additions & 0 deletions runtime/java/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
plugins {
id("java-library")
}

allprojects {
group = "com.microsoft.prompty"
version = "2.0.0-beta.4"
}

subprojects {
apply(plugin = "java-library")

repositories {
mavenCentral()
}

extensions.configure<JavaPluginExtension> {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
withSourcesJar()
}

tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
// Generated model sources are emitted by Typra and are not warning-clean by
// design; keep the build strict for hand-written code only.
options.compilerArgs.addAll(listOf("-Xlint:all", "-Xlint:-serial", "-Xlint:-this-escape"))
}

tasks.withType<Test>().configureEach {
// Live provider tests call real endpoints; they are opt-in via -PliveTests.
val liveTests = providers.gradleProperty("liveTests").isPresent
useJUnitPlatform {
if (!liveTests) {
excludeTags("live")
}
}
testLogging {
events("failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
}

tasks.withType<Javadoc>().configureEach {
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
}
}
Binary file added runtime/java/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
9 changes: 9 additions & 0 deletions runtime/java/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading