Skip to main content

GASP (Gee Another Schema Parser) - A validator for WAIL (Widely Applicable Interface Language) schemas and JSON

Project description

GASP (Gee Another Schema Parser)

GASP is a high-performance Rust-based parser and validator for WAIL (Widely Applicable Interface Language) schemas and JSON responses. It's specifically designed to work with Large Language Models (LLMs) by providing robust error recovery for common LLM response quirks.

What is WAIL?

WAIL (Widely Applicable Interface Language) is a schema language designed for:

  1. Generating type-validated LLM prompts
  2. Validating JSON responses from LLMs
  3. Providing clear error messages for schema violations

Why

In our experience the ergonomics around tool calling kind of suck right now and in the lowest common denominator settings are down right painful.

If you're using OpenRouter (which is great) they choose not to support some platform specific features (understandable) like the "ANY" parameter from Anthropic so you wind up with super verbose output, the occasional no tool call, missing params even when specified in "required" and so we decided to implement this prompt creator and schema validator because what are tool calls other than type interfaces.

Honestly, BAML is a really sick tool and more feature complete than this, with like people actually paid to work on it. However, they require you to use their code gen'd inference clients for sending messages to the LLM. That let's them do some really powerful things like validation mid streaming, but you have to be all in on them.

GASP and WAIL let you separate out prompt creation, inference and prompt validation from one another so you can apply GASP to whatever client floats your boat with the trade off that we aren't intending to make this work for every streaming format under the sun (at least I'm not, feel free to contribute!) so it's only applicable to fully generated outputs.

I didn't need all that especially because I along with my friend and co-founder have written Asimov a framework for building Agents that includes all of our own inference machinery I'm not looking to give up.

Anyway both Asimov and now GASP/WAIL are built for supporting Bismuth a programming agent that can help businesses find and patch bugs on your Github PRs before you ever know about them.

Features

  • Robust Error Recovery: Handles common LLM response issues like trailing commas, unquoted identifiers, and malformed JSON
  • Type Coercion: Attempt to fix type mismatches like Number -> String, single items to arrays, object types if a unique set of fields can be matched to a schema.
  • Type Validation: Strong type checking for both schema definitions and JSON responses
  • High Performance: Written in Rust with Python bindings for optimal speed
  • Developer Friendly: Clear error messages (except for syntax errors see below) and intuitive schema syntax
  • LLM-Optimized: Specifically designed to work with the quirks and inconsistencies of LLM outputs

Anti-Features

  • Limited syntax error messages - Right now syntax errors will tell you where the parser failed but messages aren't more helpful than that so sometimes it's hard to figure out what is wrong in the parser syntax.

Caveats

  • Output parsing is assumed to be sequential - That is we assume output happens in the same order as the template variable binding so like if binding1 doesn't correspond to output1 things will be wacky.

Installation

pip install gasp-py

Usage

from gasp import WAILGenerator

# Create a validator with your WAIL schema
generator = WAILGenerator()

schema = r'''
object Response {
    name: String
    age: Number
    interests: String[]
}

template GenerateResponse(desc: String) -> Response {
    prompt: """
    Based on {{desc}} generate a response that returns 
    
    {{return_type}}      
    """
}

main {
    template_args {
        desc: String
    }

    let res = GenerateResponse(desc: $desc);

    prompt {
        {{res}}
    }
}
'''

generator.load_wail(schema)

# Get the prompt to send to the LLM
(prompt, warnings, errors) = generator.get_prompt(desc="A totally good response")

print(prompt)

# Use your favorite LLM client to send the prompt and get a response
# response = your_fav_client.generate(prompt) # or whatever your interface is
examples_res = """
<action>
{
    name: "Alice",
    "age": 25,
    "interests": [coding, 'AI', "music"]
}
</action>
"""

# Parse and validate JSON responses
generator.parse_llm_output(examples_res)

# Note the ability to handle malformed JSON

Error Recovery

GASP includes built-in error recovery for common LLM response issues:

  • Trailing commas in arrays and objects
  • Unquoted identifiers in object keys
  • Missing quotes around strings
  • Inconsistent whitespace and formatting

License

Apache License, Version 2.0 - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Complete Example

Here's a complete example showing how to use GASP in Python:

from gasp import WAILGenerator
import json

def create_person_prompt():
    """Define the WAIL schema for person generation."""
    return r'''
    # Define our Person type
    object Person {
        name: String
        age: Number
        interests: String[]
    }

    # Define a template for generating a person from a description
    template GetPersonFromDescription(description: String) -> Person {
        prompt: """
        Given this description of a person: "{{description}}"

        Create a Person object with their name, age, and interests.
        Return in this format: 
        {{return_type}}
        """
    }

    main {
        # This is a comment
        let person_prompt = GetPersonFromDescription(
            description: "Alice is a 25-year-old software engineer who loves coding, AI, and hiking."
        );

        # This is the prompt we'll send to the LLM
        prompt {
            This is an example of a prompt generated by WAIL:
            {{person_prompt}}
        }
    }
    '''

def main():
    # Initialize our validator with the schema
    generator = WAILGenerator()
    generator.load_wail(create_person_prompt())

    warnings, errors = generator.validate_wail()

    print("Validation Results:")
    print("\nWarnings:")
    print(warnings)
    print("\nErrors:")
    print(errors)

    # Get the generated prompt - this is what you'd send to your LLM
    (prompt, warnings, errors) = generator.get_prompt()
    print("Generated Prompt:")
    print(prompt)
    print("Warnings:")
    print(warnings)
    print("Errors:")
    print(errors)

    # In a real application, you would send this prompt to your LLM
    # Here we'll simulate an LLM response with some typical quirks
    llm_response = """
    <action>
    {
        'name': 'Alice',
        'age': 25,
        'interests': [
            "coding",
            'AI',
            hiking,
        ]
    }
    </action>
    """

    try:
        # Validate the LLM's response and get the parsed JSON as a Python dict
        result = generator.parse_llm_output(llm_response)
        print("✓ Response validation successful!")

        result = result["person_prompt"]
        

        # Work with the validated data
        print("\nParsed Person:")
        print(f"Name: {result['name']}")
        print(f"Age: {result['age']}")
        print(f"Interests: {', '.join(result['interests'])}")
        
        # # You can also convert it to standard JSON
        # print("\nAs standard JSON:")
        # print(json.dumps(result, indent=2))
        
    except Exception as e:
        print(f"❌ Validation error: {e}")

if __name__ == "__main__":
    main() 

This example demonstrates:

  1. Creating a WAIL schema with proper Python string formatting
  2. Defining object types and templates in WAIL
  3. Generating a type-aware prompt for your LLM
  4. Handling common LLM response quirks automatically
  5. Validating and parsing the response
  6. Working with the validated data in Python

When run, this script will output:

Validation Results:
Warnings:
[]
Errors:
[]
Generated Prompt:

This is an example of a prompt generated by WAIL:

Given this description of a person: "Alice is a 25-year-old software engineer who loves coding, AI, and hiking."

Create a Person object with their name, age, and interests.
Return in this format: 

{
  name: string
  age: number
  interests: String[]>
}


Warnings:
[]
Errors:
[]
✓ Response validation successful!

Parsed Person:
Name: Alice
Age: 25
Interests: coding, AI, hiking

Changelog

0.9.0 - Complete parser rewrite to an incremental non recursive parser. All original tests pass but parsing semantics may be slightly different. Considering this a breaking change because there are likely parser edge cases I can't account for.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

gasp_py-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

gasp_py-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

gasp_py-0.10.0-cp312-cp312-macosx_11_0_arm64.whl (921.5 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

gasp_py-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ x86-64

gasp_py-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

gasp_py-0.10.0-cp311-cp311-macosx_11_0_arm64.whl (921.2 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

gasp_py-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ x86-64

gasp_py-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

gasp_py-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl (984.1 kB view details)

Uploaded CPython 3.10macOS 10.12+ x86-64

gasp_py-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ x86-64

gasp_py-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.17+ x86-64

gasp_py-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl (983.5 kB view details)

Uploaded CPython 3.9macOS 10.12+ x86-64

gasp_py-0.10.0-cp38-cp38-musllinux_1_2_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.8musllinux: musl 1.2+ x86-64

gasp_py-0.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.8manylinux: glibc 2.17+ x86-64

gasp_py-0.10.0-cp38-cp38-macosx_10_12_x86_64.whl (984.0 kB view details)

Uploaded CPython 3.8macOS 10.12+ x86-64

File details

Details for the file gasp_py-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 c1a3c4cbe260c968a8624a7aa2b5001b1b15c56acd62088858cc2fe307240179
MD5 373ce25d4f11bad548d4b28e805315e2
BLAKE2b-256 fcf6f0b59ccdf033c09905692038794a40546b1fc0e764ed5341462857457fb1

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 26fe80fcc45f666f34dd4a4cb23ad8df29c6e4afd822c3b304a9cab12cf076b3
MD5 c377c4d20d988f55b871bfed29856657
BLAKE2b-256 26197c273e3b0e27be9fcb2a9e7c5d5fb74f675d51860399f6f8143290228979

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 51eae07df5f037d9ee1acdbcabe88a0e6ab144e5c3b6aa49c45f0d6c95925298
MD5 784ba83d2ec55896f484c1e913b0c1af
BLAKE2b-256 5a9ca76323154a32c52012689c496ce0250145c9cba95a41ccd32d32b65f9334

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 b7f92afdf9bf2ca61612132c1bce9ede02fa5c878df47474a2ed790e5abd9a82
MD5 13b13d861932b0baf88e9735d5b9d17d
BLAKE2b-256 3e73bf0f792610c10a08be08ebc12070bff6627a89c06d4bedfe505e95ef849c

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 552970fac315ef6eed2a10da6ca319d6ab42494cf9ebe48b05c5dfb93c16b4c3
MD5 376c90f05305edc849fb2360a7c16483
BLAKE2b-256 27dacd02bf62b625380e855884c5f6bd0fea5c8363e5f6b6a41cdfa730b6b88d

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d19c2aa284e1f8210aaec0e27f14828b97380d81bf4d127a58b65b7dcfc71760
MD5 e590f6f0f92087664aaeca699248b5e6
BLAKE2b-256 bab0596697cb55940fe666d87696913c8b4ed7c62d5cd18ebac9f814db126874

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 958c7d4c15d8947e34b03dd6b4980d23cdb52cada9f111ac0b5965793ca6eb16
MD5 ba46d2919b4d3babdc098bfd77add6a5
BLAKE2b-256 13277078306645752c6014e28eaf0e609ec462c21f592771aece6621c8f002e5

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0188d7f11bf7f7862c5928742ffcec741ae87d39b86d7a81a6ee014fbe053456
MD5 39165c11214265d2d7d3cdf677af2dd3
BLAKE2b-256 3ce6ff9fffb310d4fc10b7c2556124ab2311dcf9222c57561884e042761c4a8e

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 7197bb7128839cdae37a5141c1b0a68b3168af0b98943a485cbd07eea309884d
MD5 46b4665e8fef346365ef00490797cd32
BLAKE2b-256 2d60d9b6779a3436f823d0d5c01bf42df77e563bba1539d6e66d53f8516e7458

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 4a3f59343da0ccdc7d5303ebafd22af09b90874c98ae6b38f582912b91cda52c
MD5 3c1f45d5aa40209d63a933b643333d4b
BLAKE2b-256 4370668a609341a4d0b8c3fe9717856921ac61fd4da62fbbfd986c2d415618a8

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 af6242ccb5702efa6211803506cf24377f224ab2918e638c018195101b6ffeca
MD5 f4635c49a068803edc28fbea0da1e369
BLAKE2b-256 92165fe0f56a5e93c6993ac8e0c82a49c3f6d6ebc2fb81e1a0a62264c15d9e0f

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 9d7e06af8a188be104c4248701ab1ef2323c98aa181431a35aa97a5ed0aed8c7
MD5 afcde63f6f881e87ca36e57e287094be
BLAKE2b-256 a5bd7cdebdf5aab67858d95cf0a6d1d4c53e996b7fe11668d5b7b903f1c62f1b

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp38-cp38-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp38-cp38-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 537f24af07fdfc621075e9e6be12f4198f7e4b2d1addeb5438e61f0844fe33f5
MD5 3b9970acac6e3449519ff19aab24c352
BLAKE2b-256 94b36c4e2735a7af82a5eb2ad45ea2f9a078922c0b3d13c6c739ed52d48ac138

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 32e94ebfe28e01b25dd7736522a2c0e97af75b2b4a99fbfb3cf71e66075b238a
MD5 a55825a111831b6af45143bff01b749f
BLAKE2b-256 3df56e7881e01804e78bdf660c902536e243e48c860705fb50ed5ff052465ad9

See more details on using hashes here.

File details

Details for the file gasp_py-0.10.0-cp38-cp38-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for gasp_py-0.10.0-cp38-cp38-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 1ca1095b089646b2d61036d39273e9435066168e98abb9a842554941a8899b70
MD5 36446db2117c812b6245702e91bceb46
BLAKE2b-256 9cac0d13c55e6e346beef8ecfa3850699f106ccdec3ff397a471e9caed27c380

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page