From de26c4507febbaba64608520b1fa1fc23365b854 Mon Sep 17 00:00:00 2001 From: hellovai Date: Thu, 13 Jun 2024 11:45:39 -0700 Subject: [PATCH] Replace with a single github action that runs if anything in action changes (#649) Single action attempts to build: * Python * Node * VSCode Extension * Prompt Fiddle --- .github/dependabot.yaml | 2 +- .../{workflows => examples}/bump_versions.yml | 0 .github/{workflows => examples}/cli.yml | 0 .../release_language_client_python.yaml | 0 .../release_language_client_ruby.yaml | 0 .../release_language_client_typescript.yaml | 0 .../tests/chron-event.json | 0 .../tests/test-vscode-extention.sh | 0 .../tests/version-bump-event.json | 0 .../{workflows => examples}/vscode_ext.yml | 0 .github/workflows/primary.yml | 97 +++ .github/workflows/release.yml | 311 ++++++++ engine/Cargo.lock | 1 - .../validation_files/class/secure_types.baml | 55 +- .../client/missing_template_args.baml | 4 +- .../client/required_provider.baml | 10 +- .../validation_files/enum/invalid_commas.baml | 7 +- .../validation_files/generators/error.baml | 8 +- .../tests/validation_files/generators/v0.baml | 16 +- .../tests/validation_files/generators/v1.baml | 49 +- .../strings/unquoted_strings.baml | 50 +- engine/baml-lib/jinja/src/lib.rs | 64 +- .../baml-lib/jinja/src/output_format/mod.rs | 2 +- .../coercer/ir_ref/coerce_class.rs | 28 +- .../jsonish/src/deserializer/coercer/mod.rs | 48 +- engine/baml-lib/jsonish/src/jsonish/mod.rs | 4 +- engine/baml-lib/jsonish/src/tests/mod.rs | 1 + .../baml-lib/jsonish/src/tests/test_basics.rs | 749 ++++++++++++++++++ .../baml-lib/parser-database/src/types/mod.rs | 2 +- engine/baml-schema-wasm/Cargo.toml | 1 - .../language_client_typescript/package.json | 2 +- root.code-workspace | 2 +- typescript/package.json | 1 - 33 files changed, 1398 insertions(+), 116 deletions(-) rename .github/{workflows => examples}/bump_versions.yml (100%) rename .github/{workflows => examples}/cli.yml (100%) rename .github/{workflows => examples}/release_language_client_python.yaml (100%) rename .github/{workflows => examples}/release_language_client_ruby.yaml (100%) rename .github/{workflows => examples}/release_language_client_typescript.yaml (100%) rename .github/{workflows => examples}/tests/chron-event.json (100%) rename .github/{workflows => examples}/tests/test-vscode-extention.sh (100%) rename .github/{workflows => examples}/tests/version-bump-event.json (100%) rename .github/{workflows => examples}/vscode_ext.yml (100%) create mode 100644 .github/workflows/primary.yml create mode 100644 .github/workflows/release.yml create mode 100644 engine/baml-lib/jsonish/src/tests/test_basics.rs diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 7b3bebd8c..6a97bfbe7 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -9,4 +9,4 @@ updates: directory: "/" schedule: # Check for updates to GitHub Actions every week - interval: "weekly" + interval: "monthly" diff --git a/.github/workflows/bump_versions.yml b/.github/examples/bump_versions.yml similarity index 100% rename from .github/workflows/bump_versions.yml rename to .github/examples/bump_versions.yml diff --git a/.github/workflows/cli.yml b/.github/examples/cli.yml similarity index 100% rename from .github/workflows/cli.yml rename to .github/examples/cli.yml diff --git a/.github/workflows/release_language_client_python.yaml b/.github/examples/release_language_client_python.yaml similarity index 100% rename from .github/workflows/release_language_client_python.yaml rename to .github/examples/release_language_client_python.yaml diff --git a/.github/workflows/release_language_client_ruby.yaml b/.github/examples/release_language_client_ruby.yaml similarity index 100% rename from .github/workflows/release_language_client_ruby.yaml rename to .github/examples/release_language_client_ruby.yaml diff --git a/.github/workflows/release_language_client_typescript.yaml b/.github/examples/release_language_client_typescript.yaml similarity index 100% rename from .github/workflows/release_language_client_typescript.yaml rename to .github/examples/release_language_client_typescript.yaml diff --git a/.github/workflows/tests/chron-event.json b/.github/examples/tests/chron-event.json similarity index 100% rename from .github/workflows/tests/chron-event.json rename to .github/examples/tests/chron-event.json diff --git a/.github/workflows/tests/test-vscode-extention.sh b/.github/examples/tests/test-vscode-extention.sh similarity index 100% rename from .github/workflows/tests/test-vscode-extention.sh rename to .github/examples/tests/test-vscode-extention.sh diff --git a/.github/workflows/tests/version-bump-event.json b/.github/examples/tests/version-bump-event.json similarity index 100% rename from .github/workflows/tests/version-bump-event.json rename to .github/examples/tests/version-bump-event.json diff --git a/.github/workflows/vscode_ext.yml b/.github/examples/vscode_ext.yml similarity index 100% rename from .github/workflows/vscode_ext.yml rename to .github/examples/vscode_ext.yml diff --git a/.github/workflows/primary.yml b/.github/workflows/primary.yml new file mode 100644 index 000000000..e486692ff --- /dev/null +++ b/.github/workflows/primary.yml @@ -0,0 +1,97 @@ +name: BAML Runtime + +on: + pull_request: + paths: + - "engine/**" + - ".github/workflows/primary.yml" + branches: + - canary + push: + paths: + - "engine/**" + - ".github/workflows/primary.yml" + branches: + - canary + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: read + repository-projects: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 + with: + version: 9.0.6 + run_install: false + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: | + typescript/**/pnpm-lock.yaml + - name: Install Node + run: pnpm install --frozen-lockfile + working-directory: typescript + - name: Check TS Lint + run: pnpm biome ci . --organize-imports-enabled=false + working-directory: typescript + test_node_generator: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 + with: + version: 9.0.6 + run_install: false + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: | + engine/language_client_typescript/pnpm-lock.yaml + integ-tests/typescript/pnpm-lock.yaml + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + with: + workspaces: engine + - name: Install Node + run: pnpm install --frozen-lockfile + working-directory: engine/language_client_typescript + - name: Build Node + run: pnpm build + working-directory: engine/language_client_typescript + - name: Install Node + run: pnpm install --frozen-lockfile + working-directory: integ-tests/typescript + - name: Test Node Generator + run: pnpm generate + working-directory: integ-tests/typescript + - name: Ensure No Changes + run: git diff --exit-code + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + with: + workspaces: engine + - name: Build Rust + run: cargo build + working-directory: engine + - name: Test Rust + run: cargo test + working-directory: engine diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..924e04521 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,311 @@ +name: BAML Release + +on: + push: + tags: + - "test-release/*.*" + - "*.*" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write + +jobs: + build-wasm: + runs-on: ubuntu-latest + name: Build WASM + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + with: + workspaces: engine + - name: Bindgen + run: cargo install -f wasm-bindgen-cli@0.2.92 + working-directory: engine/baml-schema-wasm + - uses: pnpm/action-setup@v3 + with: + version: 8 + run_install: false + # Set up Node.js + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + cache: "pnpm" + node-version: 18 + cache-dependency-path: | + typescript/**/pnpm-lock.yaml + - name: Install Dependencies + run: pnpm install --frozen-lockfile + working-directory: typescript/ + - name: Build Typescript Project + run: pnpm run build + working-directory: typescript/ + + # Build the VSCode Extension + - name: Build VSCode Extension + id: build + run: | + pnpm run vscode:package + VERSION=$(cat package.json| grep version | cut -d ' ' -f 4 | sed 's/[",]//g') + echo "version=$VERSION" >> $GITHUB_OUTPUT + working-directory: typescript/vscode-ext/packages + + # Upload the artifact (helpful for debugging and manual downloads) + - name: Upload VSCode Extension Artifact + uses: actions/upload-artifact@v4 + with: + name: baml-vscode.vsix + path: typescript/vscode-ext/packages/baml-${{ steps.build.outputs.version }}.vsix + + # Upload the artifact (helpful for debugging and manual downloads) + - name: Upload VSCode Extension Artifact + uses: actions/upload-artifact@v4 + with: + name: baml-out + path: typescript/vscode-ext/packages/vscode/out + + # upload the lang server artifact + - name: Upload VSCode Lang Server Extension Artifact + uses: actions/upload-artifact@v4 + with: + name: language-server + path: typescript/vscode-ext/packages/language-server/out + + - name: VSCode Playground Artifact + uses: actions/upload-artifact@v4 + with: + name: vscode-playground + path: typescript/vscode-ext/packages/web-panel/dist + + build-release: + strategy: + fail-fast: true + matrix: + _: + - target: aarch64-apple-darwin + host: macos-14 + node_build: pnpm build --target aarch64-apple-darwin + + # Disabled as python is not supported on aarch64 windows + # - target: aarch64-pc-windows-msvc + # host: windows-latest + # node_build: pnpm build --target aarch64-pc-windows-msvc + + - target: aarch64-unknown-linux-gnu + host: ubuntu-latest + node_build: pnpm build --target aarch64-unknown-linux-gnu --use-napi-cross + + - target: x86_64-apple-darwin + host: macos-latest + node_build: pnpm build --target x86_64-apple-darwin + + - target: x86_64-pc-windows-msvc + host: windows-latest + node_build: pnpm build --target x86_64-pc-windows-msvc + + - target: x86_64-unknown-linux-gnu + host: ubuntu-latest + node_build: pnpm build --target x86_64-unknown-linux-gnu --use-napi-cross + + name: Build ${{ matrix._.target }} + runs-on: ${{ matrix._.host }} + steps: + - uses: actions/checkout@v4 + # Install python set up + - uses: actions/setup-python@v5 + with: + python-version: "3.8" + architecture: ${{ matrix._.host == 'windows-latest' && 'x64' || null }} + # Install node set up + - uses: pnpm/action-setup@v3 + with: + version: 9.0.6 + run_install: false + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: | + engine/language_client_typescript/pnpm-lock.yaml + typescript/**/pnpm-lock.yaml + # Install rust + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: ${{ matrix._.target }} + # Install steps + - name: PNPM Install + run: pnpm install --frozen-lockfile + working-directory: engine/language_client_typescript + # Rust caching + - uses: Swatinem/rust-cache@v2 + with: + workspaces: engine + - name: Install aarch64-linux-gnu + if: matrix._.target == 'aarch64-unknown-linux-gnu' + run: sudo apt-get install gcc-aarch64-linux-gnu -y + + # This doesn't work aarch64, so we build each target separately + # - name: Build Rust + # run: cargo build --release --target ${{ matrix._.target }} + # working-directory: engine + + # Build Node + - name: PNPM Build + run: ${{ matrix._.node_build }} + working-directory: engine/language_client_typescript + + # Build Python + - name: Build Python + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix._.target }} + command: build + args: --release --out engine/language_client_python/dist --manifest-path engine/language_client_python/Cargo.toml + sccache: "true" + manylinux: ${{ matrix._.host == 'ubuntu-latest' && 'auto' || null }} + before-script-linux: | + if command -v yum &> /dev/null; then + yum update -y && yum install -y perl-core openssl openssl-devel pkgconfig libatomic + else + # If we're running on debian-based system. + apt update -y && apt-get install -y libssl-dev openssl pkg-config + fi + + # Upload + - name: Upload Python + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix._.target }} + path: engine/language_client_python/dist + if-no-files-found: error + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: bindings-${{ matrix._.target }} + path: engine/language_client_typescript/*.node + if-no-files-found: error + + publish-to-pypi: + environment: release + needs: [build-release, build-wasm] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.8" + - uses: actions/download-artifact@v4 + with: + name: wheels-* + + # authz is managed via OIDC configured at https://pypi.org/manage/project/baml-py/settings/publishing/ + # it is pinned to this github actions filename, so renaming this file is not safe!! + - name: Publish package to PyPI + if: ${{ !startsWith(github.ref, 'refs/tags/test-release') }} + uses: pypa/gh-action-pypi-publish@release/v1 + + publish-to-npm: + environment: release + needs: [build-release, build-wasm] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9.0.6 + package_json_file: engine/language_client_typescript/package.json + run_install: false + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: engine/language_client_typescript/pnpm-lock.yaml + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + run: pnpm install + working-directory: engine/language_client_typescript + + - uses: actions/download-artifact@v4 + with: + name: bindings-* + + - name: create npm dirs + run: pnpm napi create-npm-dirs + working-directory: engine/language_client_typescript + + - name: Move artifacts + run: pnpm artifacts + working-directory: engine/language_client_typescript + + - name: Publish + if: ${{ !startsWith(github.ref, 'refs/tags/test-release') }} + run: | + npm publish --access public + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + publish-vscode: + environment: release + needs: [build-release, build-wasm] + if: ${{ !startsWith(github.ref, 'refs/tags/test-release') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Get all the artifacts + - name: Get artifact + uses: actions/download-artifact@v4 + with: + name: baml-vscode.vsix + path: typescript/vscode-ext/packages + - name: Get artifact + uses: actions/download-artifact@v4 + with: + name: baml-out + path: typescript/vscode-ext/packages/vscode/out + - name: Get artifact + uses: actions/download-artifact@v4 + with: + name: language-server + path: typescript/vscode-ext/packages/language-server/out + - name: Get artifact + uses: actions/download-artifact@v4 + with: + name: vscode-playground + path: typescript/vscode-ext/packages/web-panel/dist + + # Set up Node.js + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + cache: "pnpm" + node-version: 18 + cache-dependency-path: typescript/pnpm-lock.yaml + + - name: Install Dependencies + run: pnpm install --frozen-lockfile + working-directory: typescript/ + - name: Publish + if: ${{ !startsWith(github.ref, 'refs/tags/test-release') }} + run: | + pnpm run vscode:publish --pre-release --no-git-tag-version -p ${{ secrets.VSCODE_PAT }} + working-directory: typescript/vscode-ext/packages \ No newline at end of file diff --git a/engine/Cargo.lock b/engine/Cargo.lock index 1088d800a..7287fce5d 100644 --- a/engine/Cargo.lock +++ b/engine/Cargo.lock @@ -517,7 +517,6 @@ name = "baml-schema-build" version = "0.39.0" dependencies = [ "anyhow", - "baml-fmt", "baml-runtime", "baml-types", "console_error_panic_hook", diff --git a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml index af2f14619..8c1885e90 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml @@ -17,19 +17,18 @@ class ComplexTypes { o (((int | string) | bool[]), (float, double) | long_long_identifier_123) } - - - - - - - // error: Type `apple_pie` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `string`, `int`? // --> class/secure_types.baml:3 // | // 2 | class ComplexTypes { // 3 | a {string[]: (int | bool[]) | apple_pie[][]} // | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:4 +// | +// 3 | a {string[]: (int | bool[]) | apple_pie[][]} +// 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) +// | // error: Type `long_word_123.foobar` does not exist. Did you mean one of these: `float`, `bool`, `ComplexTypes`, `string`, `int`? // --> class/secure_types.baml:4 // | @@ -48,6 +47,24 @@ class ComplexTypes { // 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) // 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] // | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:5 +// | +// 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) +// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] +// | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:6 +// | +// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] +// 6 | d {int[][]: ((int | float) | char[])} +// | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:7 +// | +// 6 | d {int[][]: ((int | float) | char[])} +// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][]) +// | // error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`? // --> class/secure_types.baml:7 // | @@ -66,12 +83,24 @@ class ComplexTypes { // 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][]) // 8 | f VeryLongWord_With_123_Numbers[][][][] // | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:9 +// | +// 8 | f VeryLongWord_With_123_Numbers[][][][] +// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] +// | // error: Type `tuple_inside_tuple` does not exist. Did you mean one of these: `ComplexTypes`, `int`, `string`, `float`, `bool`? // --> class/secure_types.baml:9 // | // 8 | f VeryLongWord_With_123_Numbers[][][][] // 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] // | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:10 +// | +// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] +// 10 | h (((int | string)[]) | {bool[][]: char[]}) +// | // error: Type `apple` does not exist. Did you mean one of these: `bool`, `int`, `float`, `string`, `ComplexTypes`? // --> class/secure_types.baml:11 // | @@ -102,6 +131,12 @@ class ComplexTypes { // 10 | h (((int | string)[]) | {bool[][]: char[]}) // 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] // | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:12 +// | +// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] +// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) +// | // error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`? // --> class/secure_types.baml:12 // | @@ -126,6 +161,12 @@ class ComplexTypes { // 13 | k {string[]: (int | long[])} | {float[][]: double[][]} // 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] // | +// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// --> class/secure_types.baml:14 +// | +// 13 | k {string[]: (int | long[])} | {float[][]: double[][]} +// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] +// | // error: Type `tuple_1` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`? // --> class/secure_types.baml:15 // | diff --git a/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml b/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml index 9bc69ecaf..b20366e4d 100644 --- a/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml +++ b/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml @@ -2,11 +2,11 @@ client MyClient { provider baml-openai-chat } - - // error: Error validating: Missing template for client. (did you forget ) // --> client/missing_template_args.baml:1 // | // | // 1 | client MyClient { +// 2 | provider baml-openai-chat +// 3 | } // | diff --git a/engine/baml-lib/baml/tests/validation_files/client/required_provider.baml b/engine/baml-lib/baml/tests/validation_files/client/required_provider.baml index f0cd4516b..095148209 100644 --- a/engine/baml-lib/baml/tests/validation_files/client/required_provider.baml +++ b/engine/baml-lib/baml/tests/validation_files/client/required_provider.baml @@ -4,13 +4,13 @@ client MyClient { } } - - - - -// error: Error validating: Missing `provider` field in client. e.g. `provider baml-openai-chat` +// error: Error validating: Missing `provider` field in client. e.g. `provider openai` // --> client/required_provider.baml:1 // | // | // 1 | client MyClient { +// 2 | options { +// 3 | max_tokens 100 +// 4 | } +// 5 | } // | diff --git a/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml b/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml index c16c91488..e55dd3ec6 100644 --- a/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml +++ b/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml @@ -4,22 +4,21 @@ enum Test { C, } - -// error: Error validating: This line is not an enum value definition. Check for extra commas +// error: Error validating: This line is not an enum value definition. BAML enums don't have commas, and all values must be all caps // --> enum/invalid_commas.baml:2 // | // 1 | enum Test { // 2 | A, // 3 | B, // | -// error: Error validating: This line is not an enum value definition. Check for extra commas +// error: Error validating: This line is not an enum value definition. BAML enums don't have commas, and all values must be all caps // --> enum/invalid_commas.baml:3 // | // 2 | A, // 3 | B, // 4 | C, // | -// error: Error validating: This line is not an enum value definition. Check for extra commas +// error: Error validating: This line is not an enum value definition. BAML enums don't have commas, and all values must be all caps // --> enum/invalid_commas.baml:4 // | // 3 | B, diff --git a/engine/baml-lib/baml/tests/validation_files/generators/error.baml b/engine/baml-lib/baml/tests/validation_files/generators/error.baml index 5d1b36750..8ec987546 100644 --- a/engine/baml-lib/baml/tests/validation_files/generators/error.baml +++ b/engine/baml-lib/baml/tests/validation_files/generators/error.baml @@ -3,7 +3,13 @@ generator default { o o } -// error: Property not known: "o". Did you mean one of these: "language", "project_root", "test_command", "install_command"? +// error: Property not known: "language". Did you mean one of these: "output_type", "output_dir"? +// --> generators/error.baml:2 +// | +// 1 | generator default { +// 2 | language python +// | +// error: Property not known: "o". Did you mean one of these: "output_dir", "output_type"? // --> generators/error.baml:3 // | // 2 | language python diff --git a/engine/baml-lib/baml/tests/validation_files/generators/v0.baml b/engine/baml-lib/baml/tests/validation_files/generators/v0.baml index dc08b480c..f678e8dd6 100644 --- a/engine/baml-lib/baml/tests/validation_files/generators/v0.baml +++ b/engine/baml-lib/baml/tests/validation_files/generators/v0.baml @@ -6,17 +6,19 @@ generator default { pkg_manager poetry } - -// warning: The generator format is deprecated. Please use the new format. +// warning: This generator format is deprecated. Please use the new format. // generator default { -// language "python" -// project_root "generators/../" -// test_command "poetry run python -m pytest" -// install_command "poetry add baml@latest" -// package_version_command "poetry show baml" +// output_type "python/pydantic" +// output_dir "../" // } // --> generators/v0.baml:1 // | // | // 1 | generator default { +// 2 | // Right now only python is supported, but soon TS +// 3 | language python +// 4 | +// 5 | // Uncomment this line if you use poetry +// 6 | pkg_manager poetry +// 7 | } // | diff --git a/engine/baml-lib/baml/tests/validation_files/generators/v1.baml b/engine/baml-lib/baml/tests/validation_files/generators/v1.baml index 0a92c7338..82831331a 100644 --- a/engine/baml-lib/baml/tests/validation_files/generators/v1.baml +++ b/engine/baml-lib/baml/tests/validation_files/generators/v1.baml @@ -24,4 +24,51 @@ generator lang_python_0 { // dependencies to your language environment install_command "poetry add baml@latest" package_version_command "poetry show baml" -} \ No newline at end of file +} + +// warning: This generator format is deprecated. Please use the new format. +// +// generator lang_python_1 { +// output_type "python/pydantic" +// output_dir "../" +// } +// --> generators/v1.baml:1 +// | +// | +// 1 | generator lang_python_1 { +// 2 | language python +// 3 | // This is where your non-baml source code located +// 4 | // (relative directory where pyproject.toml, package.json, etc. lives) +// 5 | project_root "../" +// 6 | // This command is used by "baml test" to run tests +// 7 | // defined in the playground +// 8 | test_command ". ./venv/bin/activate && python -m pytest" +// 9 | // This command is used by "baml update-client" to install +// 10 | // dependencies to your language environment +// 11 | install_command ". ./venv/bin/activate && pip install --upgrade baml" +// 12 | package_version_command ". ./venv/bin/activate && pip show baml" +// 13 | } +// | +// warning: This generator format is deprecated. Please use the new format. +// +// generator lang_python_0 { +// output_type "python/pydantic" +// output_dir "../" +// } +// --> generators/v1.baml:15 +// | +// 14 | +// 15 | generator lang_python_0 { +// 16 | language python +// 17 | // This is where your non-baml source code located +// 18 | // (relative directory where pyproject.toml, package.json, etc. lives) +// 19 | project_root "../" +// 20 | // This command is used by "baml test" to run tests +// 21 | // defined in the playground +// 22 | test_command "poetry run pytest" +// 23 | // This command is used by "baml update-client" to install +// 24 | // dependencies to your language environment +// 25 | install_command "poetry add baml@latest" +// 26 | package_version_command "poetry show baml" +// 27 | } +// | diff --git a/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml b/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml index 7228dec3b..41997accc 100644 --- a/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml +++ b/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml @@ -1,4 +1,3 @@ - client Hello { provider baml-openai-chat options { @@ -9,39 +8,38 @@ client Hello { } } - -// error: Error validating: This line is not a valid field or attribute definition. -// --> strings/unquoted_strings.baml:4 +// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons. +// --> strings/unquoted_strings.baml:3 // | -// 3 | provider baml-openai-chat -// 4 | options { -// 5 | thing hello'world +// 2 | provider baml-openai-chat +// 3 | options { +// 4 | thing hello'world // | -// error: Error validating: This line is not a valid field or attribute definition. -// --> strings/unquoted_strings.baml:5 +// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons. +// --> strings/unquoted_strings.baml:4 // | -// 4 | options { -// 5 | thing hello'world -// 6 | banned @helloworld +// 3 | options { +// 4 | thing hello'world +// 5 | banned @helloworld // | -// error: Error validating: This line is not a valid field or attribute definition. -// --> strings/unquoted_strings.baml:7 +// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons. +// --> strings/unquoted_strings.baml:6 // | -// 6 | banned @helloworld -// 7 | banned2 #helloworld -// 8 | banned3 hello(world) +// 5 | banned @helloworld +// 6 | banned2 #helloworld +// 7 | banned3 hello(world) // | -// error: Error validating: This line is not a valid field or attribute definition. -// --> strings/unquoted_strings.baml:8 +// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons. +// --> strings/unquoted_strings.baml:7 // | -// 7 | banned2 #helloworld -// 8 | banned3 hello(world) -// 9 | } +// 6 | banned2 #helloworld +// 7 | banned3 hello(world) +// 8 | } // | // error: Error validating: This line is invalid. It does not start with any known Baml schema keyword. -// --> strings/unquoted_strings.baml:10 +// --> strings/unquoted_strings.baml:9 // | -// 9 | } -// 10 | } -// 11 | +// 8 | } +// 9 | } +// 10 | // | diff --git a/engine/baml-lib/jinja/src/lib.rs b/engine/baml-lib/jinja/src/lib.rs index 0d41aee7b..4e4716f67 100644 --- a/engine/baml-lib/jinja/src/lib.rs +++ b/engine/baml-lib/jinja/src/lib.rs @@ -628,8 +628,7 @@ mod render_tests { role: "john doe".to_string(), parts: vec![ChatMessagePart::Text( vec![ - "Tell me a haiku about sakura. Answer in JSON using this schema:", - "iambic pentameter", + "Tell me a haiku about sakura. ", "", "End the haiku with a line about your maker, openai.", ] @@ -713,12 +712,7 @@ mod render_tests { &vec![], )?; - assert_eq!( - rendered, - RenderedPrompt::Completion( - "Answer in JSON using this schema:\niambic pentameter".to_string() - ) - ); + assert_eq!(rendered, RenderedPrompt::Completion("".to_string())); Ok(()) } @@ -733,7 +727,7 @@ mod render_tests { )])); let rendered = render_prompt( - "{{ ctx.output_format() }}", + "HI! {{ ctx.output_format }}", &args, RenderContext { client: RenderContext_Client { @@ -746,12 +740,7 @@ mod render_tests { &vec![], )?; - assert_eq!( - rendered, - RenderedPrompt::Completion( - "Answer in JSON using this schema:\niambic pentameter".to_string() - ) - ); + assert_eq!(rendered, RenderedPrompt::Completion("HI! ".to_string())); Ok(()) } @@ -779,7 +768,7 @@ mod render_tests { &vec![], )?; - assert_eq!(rendered, RenderedPrompt::Completion("string".into())); + assert_eq!(rendered, RenderedPrompt::Completion("".into())); Ok(()) } @@ -809,7 +798,7 @@ mod render_tests { assert_eq!( rendered, - RenderedPrompt::Completion("custom format:\niambic pentameter".to_string()) + RenderedPrompt::Completion("custom format:string".to_string()) ); Ok(()) @@ -832,17 +821,17 @@ mod render_tests { and also outputs this word 4 times after giving a response: {{ haiku_subject }} - {{ _.chat(role=ctx.env.ROLE) }} + {{ _.role(role=ctx.tags.ROLE) }} Tell me a haiku about {{ haiku_subject }} in {{ ctx.output_format }}. - {{ _.chat(ctx.env.ROLE) }} + {{ _.role(ctx.tags.ROLE) }} End the haiku with a line about your maker, {{ ctx.client.provider }}. - {{ _.chat("a", role="aa") }} + {{ _.role("a", role="aa") }} hi! - {{ _.chat() }} + {{ _.role() }} hi! "#, &args, @@ -863,7 +852,7 @@ mod render_tests { } Err(e) => assert!(e .to_string() - .contains("chat() called with two roles: 'aa' and 'a'")), + .contains("role() called with two roles: 'aa' and 'a'")), } Ok(()) @@ -887,11 +876,11 @@ mod render_tests { and also outputs this word 4 times after giving a response: {{ haiku_subject }} - {{ _.chat(role=ctx.env.ROLE) }} + {{ _.chat(role=ctx.tags.ROLE) }} Tell me a haiku about {{ haiku_subject }}. {{ ctx.output_format }} - {{ _.chat(ctx.env.ROLE) }} + {{ _.chat(ctx.tags.ROLE) }} End the haiku with a line about your maker, {{ ctx.client.provider }}. "#, &args, @@ -911,24 +900,27 @@ mod render_tests { RenderedPrompt::Chat(vec![ RenderedChatMessage { role: "system".to_string(), - parts: vec![ChatMessagePart::Text(vec![ - "You are an assistant that always responds", - "in a very excited way with emojis", - "and also outputs this word 4 times", - "after giving a response: sakura", - ].join("\n"))] + parts: vec![ChatMessagePart::Text( + vec![ + "You are an assistant that always responds", + "in a very excited way with emojis", + "and also outputs this word 4 times", + "after giving a response: sakura", + ] + .join("\n") + )] }, RenderedChatMessage { role: "john doe".to_string(), - parts: vec![ - ChatMessagePart::Text("Tell me a haiku about sakura. Answer in JSON using this schema:\niambic pentameter".to_string()) - ] + parts: vec![ChatMessagePart::Text( + "Tell me a haiku about sakura.".to_string() + )] }, RenderedChatMessage { role: "john doe".to_string(), - parts: vec![ - ChatMessagePart::Text("End the haiku with a line about your maker, openai.".to_string()) - ] + parts: vec![ChatMessagePart::Text( + "End the haiku with a line about your maker, openai.".to_string() + )] } ]) ); diff --git a/engine/baml-lib/jinja/src/output_format/mod.rs b/engine/baml-lib/jinja/src/output_format/mod.rs index 089544e62..c65f4caf2 100644 --- a/engine/baml-lib/jinja/src/output_format/mod.rs +++ b/engine/baml-lib/jinja/src/output_format/mod.rs @@ -127,7 +127,7 @@ impl minijinja::value::Object for OutputFormat { match content { Some(content) => Ok(Value::from_safe_string(content)), - None => Ok(Value::from_serializable(&None::)), + None => Ok(Value::from_serialize("")), } } fn call_method( diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_class.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_class.rs index 81a72fb72..ea6c23a89 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_class.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_class.rs @@ -187,20 +187,30 @@ impl TypeCoercer for Class { } log::trace!("----"); + let unparsed_required_fields = required_values + .iter() + .filter_map(|(k, v)| match v { + Some(Ok(_)) => None, + Some(Err(e)) => Some((k.clone(), e.to_string())), + None => None, + }) + .collect::>(); let missing_required_fields = required_values .iter() - .filter(|(_, v)| !v.as_ref().map(|v| v.is_ok()).unwrap_or(false)) - .map(|(k, _)| k.clone()) + .filter_map(|(k, v)| match v { + Some(Ok(_)) => None, + Some(Err(e)) => None, + None => Some(k.clone()), + }) .collect::>(); - if !missing_required_fields.is_empty() { - log::trace!( - "Missing required fields: {:?} in {:?}", - missing_required_fields, - value - ); + if !missing_required_fields.is_empty() || !unparsed_required_fields.is_empty() { if completed_cls.is_empty() { - return Err(ctx.error_missing_required_field(&missing_required_fields, value)); + return Err(ctx.error_missing_required_field( + &unparsed_required_fields, + &missing_required_fields, + value, + )); } } else { let merged_errors = required_values diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs index bc769d1a7..85192f4b5 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs @@ -123,19 +123,51 @@ impl ParsingContext<'_> { pub(crate) fn error_missing_required_field>( &self, - fields: &[T], + unparsed_fields: &[(T, T)], + missing_fields: &[T], item: Option<&crate::jsonish::Value>, ) -> ParsingError { - ParsingError { - reason: format!( - "Missing required fields: {}\nGot: {:#?}", - fields + let fields = missing_fields + .iter() + .map(|c| c.as_ref()) + .collect::>() + .join(", "); + let missing_error = match missing_fields.len() { + 0 => None, + 1 => Some(format!("Missing required field: {}", fields)), + _ => Some(format!("Missing required fields: {}", fields)), + }; + + let unparsed = unparsed_fields + .iter() + .map(|(k, v)| format!("{}: {}", k.as_ref(), v.as_ref().replace("\n", "\n "))) + .collect::>() + .join("\n"); + let unparsed_error = match unparsed_fields.len() { + 0 => None, + 1 => Some(format!( + "Unparsed field: {}\n {}", + unparsed_fields[0].0.as_ref(), + unparsed_fields[0].1.as_ref().replace("\n", "\n ") + )), + _ => Some(format!( + "Unparsed fields:\n{}\n {}", + unparsed_fields .iter() - .map(|c| c.as_ref()) + .map(|(k, _)| k.as_ref()) .collect::>() .join(", "), - item - ), + unparsed.replace("\n", "\n ") + )), + }; + + ParsingError { + reason: match (missing_error, unparsed_error) { + (Some(m), Some(u)) => format!("{}\n{}", m, u), + (Some(m), None) => m, + (None, Some(u)) => u, + (None, None) => "Unexpected error".to_string(), + }, scope: self.scope.clone(), } } diff --git a/engine/baml-lib/jsonish/src/jsonish/mod.rs b/engine/baml-lib/jsonish/src/jsonish/mod.rs index 0db9426ba..c926e0eb9 100644 --- a/engine/baml-lib/jsonish/src/jsonish/mod.rs +++ b/engine/baml-lib/jsonish/src/jsonish/mod.rs @@ -1,8 +1,8 @@ mod iterative_parser; mod parser; -#[cfg(test)] -mod test_iterative_parser; +// #[cfg(test)] +// mod test_iterative_parser; mod value; pub use value::{Fixes, Value}; diff --git a/engine/baml-lib/jsonish/src/tests/mod.rs b/engine/baml-lib/jsonish/src/tests/mod.rs index f02f6084b..88d03baeb 100644 --- a/engine/baml-lib/jsonish/src/tests/mod.rs +++ b/engine/baml-lib/jsonish/src/tests/mod.rs @@ -4,6 +4,7 @@ use internal_baml_jinja::types::{Class, Enum, Name, OutputFormatContent}; #[macro_use] pub mod macros; +mod test_basics; mod test_class; mod test_enum; mod test_lists; diff --git a/engine/baml-lib/jsonish/src/tests/test_basics.rs b/engine/baml-lib/jsonish/src/tests/test_basics.rs new file mode 100644 index 000000000..4413da604 --- /dev/null +++ b/engine/baml-lib/jsonish/src/tests/test_basics.rs @@ -0,0 +1,749 @@ +use super::*; + +test_deserializer!(test_null, EMPTY_FILE, "null", FieldType::null(), null); + +test_deserializer!(test_number, EMPTY_FILE, "12111", FieldType::int(), 12111); + +test_deserializer!( + test_string, + EMPTY_FILE, + r#""hello""#, + FieldType::string(), + "\"hello\"" +); + +test_deserializer!(test_bool, EMPTY_FILE, "true", FieldType::bool(), true); + +test_deserializer!( + test_array, + EMPTY_FILE, + r#"[1, 2, 3]"#, + FieldType::int().as_list(), + [1, 2, 3] +); + +test_deserializer!( + test_array_1, + EMPTY_FILE, + r#"[1, 2, 3]"#, + FieldType::string().as_list(), + ["1", "2", "3"] +); + +test_deserializer!( + test_array_3, + EMPTY_FILE, + r#"[1, 2, 3]"#, + FieldType::float().as_list(), + [1., 2., 3.] +); + +test_deserializer!( + test_object, + r#" + class Test { + key string + } + "#, + r#"{"key": "value"}"#, + FieldType::class("Test"), + {"key": "value"} +); + +test_deserializer!( + test_nested, + r#" + class Test { + key int[] + } + "#, + r#"{"key": [1, 2, 3]}"#, + FieldType::class("Test"), + {"key": [1, 2, 3]} +); + +// now with whitespace +test_deserializer!( + test_nested_whitespace, + r#" + class Test { + key int[] + } + "#, + r#" { "key" : [ 1 , 2 , 3 ] } "#, + FieldType::class("Test"), + {"key": [1, 2, 3]} +); + +// Now with leading and suffix text. +test_deserializer!( + test_nested_whitespace_prefix_suffix, + r#" + class Test { + key int[] + } + "#, + r#"prefix { "key" : [ 1 , 2 , 3 ] } suffix"#, + FieldType::class("Test"), + {"key": [1, 2, 3]} +); + +// Now with multiple top level objects +test_deserializer!( + test_multiple_top_level_1, + r#" + class Test { + key string + } + "#, + r#"{"key": "value1"} {"key": "value2"}"#, + FieldType::class("Test"), + {"key": "value1"} +); + +test_deserializer!( + test_multiple_top_level_2, + r#" + class Test { + key string + } + "#, + r#"{"key": "value1"} {"key": "value2"}"#, + FieldType::class("Test").as_list(), + [{"key": "value1"}, {"key": "value2"}] +); + +// With prefix and suffix +test_deserializer!( + test_multiple_top_level_prefix_suffix_1, + r#" + class Test { + key string + } + "#, + r#"prefix {"key": "value1"} some random text {"key": "value2"} suffix"#, + FieldType::class("Test"), + {"key": "value1"} +); + +test_deserializer!( + test_multiple_top_level_prefix_suffix_2, + r#" + class Test { + key string + } + "#, + r#"prefix {"key": "value1"} some random text {"key": "value2"} suffix"#, + FieldType::class("Test").as_list(), + [{"key": "value1"}, {"key": "value2"}] +); + +// Trailing comma +// The jsonish parser will return the value as a string as we do our best not to cast or modify the input when types are not clear. +test_deserializer!( + test_trailing_comma_array_2, + EMPTY_FILE, + r#"[1, 2, 3,]"#, + FieldType::int().as_list(), + [1, 2, 3] +); + +test_deserializer!( + test_trailing_comma_array_3, + EMPTY_FILE, + r#"[1, 2, 3,]"#, + FieldType::string().as_list(), + ["1", "2", "3"] +); + +test_deserializer!( + test_trailing_comma_object, + r#" + class Test { + key string + } + "#, + r#"{"key": "value",}"#, + FieldType::class("Test"), + {"key": "value"} +); + +// Test cases for invalid JSONish +test_deserializer!( + test_invalid_array, + EMPTY_FILE, + r#"[1, 2, 3"#, + FieldType::int().as_list(), + [1, 2, 3] +); + +test_deserializer!( + test_invalid_array_in_object, + r#" + class Test { + key int[] + } + "#, + r#"{"key": [1, 2, 3"#, + FieldType::class("Test"), + {"key": [1, 2, 3]} +); + +// Extra quote is not allowed +test_deserializer!( + test_incomplete_string, + EMPTY_FILE, + r#""hello"#, + FieldType::string(), + "\"hello" +); + +test_deserializer!( + test_incomplete_string_in_object, + r#" + class Test { + key string + } + "#, + r#"{"key": "value"#, + FieldType::class("Test"), + {"key": "value"} +); + +// This is un-changed +test_deserializer!( + test_prefixed_incompleted_string, + EMPTY_FILE, + r#"prefix "hello"#, + FieldType::string(), + "prefix \"hello" +); + +test_deserializer!( + test_large_object, + r#" + class Test { + key string + array int[] + object Foo + } + + class Foo { + key string + } + "#, + r#"{"key": "value", "array": [1, 2, 3], "object": {"key": "value"}}"#, + FieldType::class("Test"), + {"key": "value", "array": [1, 2, 3], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_md_example_1, + r#" + class Test { + key string + array int[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + some text + ```json + { + "key": "value", + "array": [1, 2, 3], + "object": { + "key": "value" + } + } + ``` + "#, + FieldType::class("Test"), + {"key": "value", "array": [1, 2, 3], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_md_example_2, + r#" + class Test { + key string + array int[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + some text + ```json + { + "key": "value", + "array": [1, 2, 3], + "object": { + "key": "value" + } + } + ``` + + + ```json + ["1", "2"] + ``` + "#, + FieldType::class("Test"), + {"key": "value", "array": [1, 2, 3], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_md_example_3, + r#" + class Test { + key string + array int[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + some text + ```json + { + "key": "value", + "array": [1, 2, 3], + "object": { + "key": "value" + } + } + ``` + + + ```json + ["1", "2"] + ``` + "#, + FieldType::int().as_list(), + [1, 2] +); + +test_deserializer!( + test_json_md_example_1_bad_inner_json, + r#" + class Test { + key string + array int[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + some text + ```json + { + "key": "value", + "array": [1, 2, 3,], + "object": { + "key": "value" + } + } + ``` + "#, + FieldType::class("Test"), + {"key": "value", "array": [1, 2, 3], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_md_example_1_bad_inner_json_2, + r#" + class Test { + key string + array (int | string)[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + some text + ```json + { + "key": "value", + "array": [1, 2, 3, "somet"string with quotes"], + "object": { + "key": "value" + } + } + ``` + "#, + FieldType::class("Test"), + {"key": "value", "array": [1, 2, 3, "somet\"string with quotes"], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_md_example_1_bad_inner_json_3, + r#" + class Test { + key string + array (int | string)[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + some text + ```json + { + "key": "value", + "array": [1, 2, 3, 'some stinrg' with quotes' /* test */], + "object": { // Test comment + "key": "value" + }, + } + ``` + "#, + FieldType::class("Test"), + {"key": "value", "array": [1, 2, 3, "some stinrg' with quotes"], "object": {"key": "value"}} +); + +test_deserializer!( + test_unquoted_keys, + r#" + class Test { + key string + array (int | string)[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + some text + ```json + { + key: "value", + array: [1, 2, 3, 'some stinrg' with quotes' /* test */], + object: { // Test comment + key: "value" + }, + } + ``` + "#, + FieldType::class("Test"), + {"key": "value", "array": [1, 2, 3, "some stinrg' with quotes"], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_with_unquoted_values_with_spaces, + r#" + class Test { + key string + array (int | string)[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + { + key: value with space, + array: [1, 2, 3], + object: { + key: value + } + } + "#, + FieldType::class("Test"), + {"key": "value with space", "array": [1, 2, 3], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_with_unquoted_values_with_spaces_and_new_lines, + r#" + class Test { + key string + array (int | string)[] + object Foo + } + + class Foo { + key string + } + "#, + r#" + { + key: "test a long +thing with new + +lines", + array: [1, 2, 3], + object: { + key: value + } + } + "#, + FieldType::class("Test"), + {"key": "test a long\nthing with new\n\nlines", "array": [1, 2, 3], "object": {"key": "value"}} +); + +test_deserializer!( + test_json_with_markdown_without_quotes, + r#" + class Test { + my_field_0 bool + my_field_1 string + } + "#, + r#" + { + "my_field_0": true, + "my_field_1": **First fragment, Another fragment** + +Frag 2, frag 3. Frag 4, Frag 5, Frag 5. + +Frag 6, the rest, of the sentence. Then i would quote something "like this" or this. + +Then would add a summary of sorts. + } + "#, + FieldType::class("Test"), + { + "my_field_0": true, + "my_field_1": "**First fragment, Another fragment**\n\nFrag 2, frag 3. Frag 4, Frag 5, Frag 5.\n\nFrag 6, the rest, of the sentence. Then i would quote something \"like this\" or this.\n\nThen would add a summary of sorts." + } +); + +test_partial_deserializer!( + test_mal_formed_json_sequence, + r#" + class Test { + foo1 Foo1 + foo2 Foo2[] + foo3 Foo3 + } + + class Foo1 { + field1 string + field2 string + field3 string + field4 string + field5 string + field6 string + } + + class Foo2 { + field7 string + field8 string + field9 string + field10 string + field11 string + field12 string + field13 string + field14 string + field15 string + field16 string + field17 string + field18 string + field19 string + field20 string + field21 string + field22 string + field23 string + field24 string + field25 string + } + + class Foo3 { + field28 string + field29 string[] + field30 string[] + field31 string[] + field32 string[] + field33 string + field34 string + field35 string + field36 string + } + "#, + r#"```json +{ +"foo1": { +"field1": "Something horrible has happened!!", +"field2": null, +"field3": null, +"field4": null, +"field5": null, +"field6": null +}, +"foo2": { +"field7": null, +"field8": null, +"field9": null, +"field10": null, +"field11": null, +"field12": null, +"field13": null{ +"foo1": { +"field1": "A thing has been going on poorly", +"field2": null, +"field3": null, +"field4": null, +"field5": null, +"field6": null +}, +"foo2": { +"field7": null, +"field8": null, +"field9": null, +"field10": null, +"field11": null, +"field12": null, +"field13": null, +"field14": null, +"field15": null, +"field16": null, +"field17": null, +"field18": null, +"field19": null, +"field20": null, +"field21": null, +"field22": null, +"field23": null, +"field24": null, +"field25": null +}, +"foo2": [ +{ + "field26": "The bad thing is confirmed.", + "field27": null +} +], +"foo3": { +"field28": "We are really going to try and take care of the bad thing.", +"field29": [], +"field30": [], +"field31": [], +"field32": [], +"field33": null, +"field34": null, +"field35": null, +"field36": null +} +}"#, +FieldType::class("Test"), +{ + "foo1": { + "field1": "Something horrible has happened!!", + "field2": null, + "field3": null, + "field4": null, + "field5": null, + "field6": null + }, + "foo2": [ + { + "field7": null, + "field8": null, + "field9": null, + "field10": null, + "field11": null, + "field12": null, + "field13": null, + "field14": null, + "field15": null, + "field16": null, + "field17": null, + "field18": null, + "field19": null, + "field20": null, + "field21": null, + "field22": null, + "field23": null, + "field24": null, + "field25": null, + } + ], + "foo3": { + "field28": "We are really going to try and take care of the bad thing.", + "field29": [], + "field30": [], + "field31": [], + "field32": [], + "field33": null, + "field34": null, + "field35": null, + "field36": null + } +}); + +test_deserializer!( + test_localization, + r#" + class Test { + id string + English string + Portuguese string + } + "#, + r#" +To effectively localize these strings for a Portuguese-speaking audience, I will focus on maintaining the original tone and meaning while ensuring that the translations sound natural and culturally appropriate. For the game title "Arcadian Atlas," I will keep it unchanged as it is a proper noun and likely a branded term within the game. For the other strings, I will adapt them to resonate with Portuguese players, using idiomatic expressions if necessary and ensuring that the sense of adventure and urgency is conveyed. + +For the string with the placeholder {player_name}, I will ensure that the placeholder is kept intact and that the surrounding text is grammatically correct and flows naturally in Portuguese. The name "Jonathan" will remain unchanged as it is a proper noun and recognizable in Portuguese. + +JSON Output: +``` +[ + { + "id": "CH1_Welcome", + "English": "Welcome to Arcadian Atlas", + "Portuguese": "Bem-vindo ao Arcadian Atlas" + }, + { + "id": "CH1_02", + "English": "Arcadia is a vast land, with monsters and dangers!", + "Portuguese": "Arcadia é uma terra vasta, repleta de monstros e perigos!" + }, + { + "id": "CH1_03", + "English": "Find him {player_name}. Find him and save Arcadia. Jonathan will save us all. It is the only way.", + "Portuguese": "Encontre-o {player_name}. Encontre-o e salve Arcadia. Jonathan nos salvará a todos. É a única maneira." + } +] +``` + "#.trim(), + FieldType::class("Test").as_list(), + [{ + "id": "CH1_Welcome", + "English": "Welcome to Arcadian Atlas", + "Portuguese": "Bem-vindo ao Arcadian Atlas" + }, + { + "id": "CH1_02", + "English": "Arcadia is a vast land, with monsters and dangers!", + "Portuguese": "Arcadia é uma terra vasta, repleta de monstros e perigos!" + }, + { + "id": "CH1_03", + "English": "Find him {player_name}. Find him and save Arcadia. Jonathan will save us all. It is the only way.", + "Portuguese": "Encontre-o {player_name}. Encontre-o e salve Arcadia. Jonathan nos salvará a todos. É a única maneira." + }] +); diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 8e319a734..e4936cd12 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -673,7 +673,7 @@ fn visit_client<'db>(idx: ClientId, client: &'db ast::Client, ctx: &mut Context< } } (None, _) => ctx.push_error(DatamodelError::new_validation_error( - "Missing `provider` field in client. e.g. `provider baml-openai-chat`", + "Missing `provider` field in client. e.g. `provider openai`", client.span().clone(), )), } diff --git a/engine/baml-schema-wasm/Cargo.toml b/engine/baml-schema-wasm/Cargo.toml index abf475d63..32e0cf61c 100644 --- a/engine/baml-schema-wasm/Cargo.toml +++ b/engine/baml-schema-wasm/Cargo.toml @@ -11,7 +11,6 @@ crate-type = ["cdylib", "rlib"] [dependencies] anyhow.workspace = true -baml-fmt = { path = "../baml-fmt" } baml-runtime = { path = "../baml-runtime", features = [ "internal", ], default-features = false } diff --git a/engine/language_client_typescript/package.json b/engine/language_client_typescript/package.json index 975c4d3b5..8651fa702 100644 --- a/engine/language_client_typescript/package.json +++ b/engine/language_client_typescript/package.json @@ -53,7 +53,7 @@ }, "scripts": { "artifacts": "napi artifacts", - "build": "pnpm build:napi-release --release && pnpm build:ts_build", + "build": "pnpm build:napi-release && pnpm build:ts_build", "build:debug": "pnpm build:napi-debug && pnpm build:ts_build", "build:napi-release": "pnpm build:napi-debug --release", "build:napi-debug": "napi build --js ./native.js --dts ./native.d.ts --platform", diff --git a/root.code-workspace b/root.code-workspace index 146e44203..f5486343f 100644 --- a/root.code-workspace +++ b/root.code-workspace @@ -31,7 +31,7 @@ "editor.formatOnSave": true }, "[python]": { - "editor.defaultFormatter": "charliermarsh.ruff" + "editor.defaultFormatter": "ms-python.black-formatter" }, "[javascript]": { "editor.defaultFormatter": "biomejs.biome" diff --git a/typescript/package.json b/typescript/package.json index 66d176ac5..c4d6ca2fa 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -20,7 +20,6 @@ "tsx": "^3.7.1", "turbo": "^1.10.16" }, - "packageManager": "pnpm@8.6.1", "pnpm": { "patchedDependencies": { "vite-plugin-wasm@3.3.0": "patches/vscode-inline-wasm-as-base64.patch"