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 = """
<result>
{
    name: "Alice",
    "age": 25,
    "interests": [coding, 'AI', "music"]
}
</result>
"""

# 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 = """
    <result>
    {
        'name': 'Alice',
        'age': 25,
        'interests': [
            "coding",
            'AI',
            hiking,
        ]
    }
    </result>
    """

    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.9.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.9.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.9.0-cp312-cp312-macosx_11_0_arm64.whl (915.7 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

gasp_py-0.9.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.9.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.9.0-cp311-cp311-macosx_11_0_arm64.whl (915.9 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

gasp_py-0.9.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.9.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.9.0-cp310-cp310-macosx_10_12_x86_64.whl (984.3 kB view details)

Uploaded CPython 3.10macOS 10.12+ x86-64

gasp_py-0.9.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.9.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.9.0-cp39-cp39-macosx_10_12_x86_64.whl (984.3 kB view details)

Uploaded CPython 3.9macOS 10.12+ x86-64

gasp_py-0.9.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.9.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.9.0-cp38-cp38-macosx_10_12_x86_64.whl (984.7 kB view details)

Uploaded CPython 3.8macOS 10.12+ x86-64

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 f35e30d9f956a6e0224777cf00d001531b46018b3d97b8d6cf2578b2d1ed99bc
MD5 aaa56724040f2c917148dc0c504c0385
BLAKE2b-256 964bb5086a1ccf0b165609613d0335cb17fff54fe832e5de61ea64a2e4c9791a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 695b6e6386a06535c9dc4dabe67d02fe95ff81f4f585211f41932d0515992315
MD5 8a7d855d546b2dbab78202fac18c7859
BLAKE2b-256 d7dce030aa626ae5de8011587be3a66113127804d51f1c9a24be30ca9f7ade24

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 44b5b916fa4a8d38742e7744460cf99af275c3d5e4cf1d5cebd0e02a95c3e1b8
MD5 d77398f43eae8274f207773310e3b14c
BLAKE2b-256 7a2ccb7a68468c9688d4353b013f1aa3ae00e11cf27e52980d08f0e08f6e38fb

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 c95eb02082abe65a3dcfdb2ed79122497740ef4b3d179ef77df973dd8f9fdf22
MD5 90b66f54ec6ee67eb78f6b9c2b5ba15c
BLAKE2b-256 9f027a90e89f7afa54e71b92a649ad2abb6cedaa31b4d05e5c97140021f1fe11

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 eda1a8ac64d59cb5a3f24ab8827153dfa78f423c36d7dab3926b7e020603d3ff
MD5 ee99f9edad352067ffafe6ebd9463912
BLAKE2b-256 6ea5797c28fa5cf9abb03f64dfa31a56414d9326ddf272f08027dd03c0e01629

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b1b50463bff3f501cbfcde8139ea775d15a27032737dc4b461852474ec4e086e
MD5 b158bc723252eb55b3d0adbfe5b69436
BLAKE2b-256 20867dae80c19a6a4daf78fca64acb2701d53ac186f2bc98f824a575391ab75d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 6000f7605de2086e571f977abcdb6c1d9932a9f0066d9137160a3f53bea6f987
MD5 51a66162a6b56df80ecdad07b1660483
BLAKE2b-256 60e4d12006322f8029ed94bcb68e029b2ca49403ac2f82f67c36624a0d9a8d24

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e792feb29fede845b52fd341e5eb7c14e89cc902c9ccd7eaccdcdc5844ca315e
MD5 579469476eeaf553ed8bf0a4a212463c
BLAKE2b-256 0371c38d005109115beba73d531ffba9f7a6714b9a98e10b2c2170bcb163b5a8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 d653e0669a130741bede149ae7502bd8f69b022e4a0ade0ab4316d2c4978a769
MD5 5ed2a046b61078a47d830c12af2ea66d
BLAKE2b-256 d2c19a12fc4874a9ed09641014195a5ee66562054bb7e05c0e5c7c5cd4ee65e3

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 fd71b41e5335bce35e8454b19661efa906a58214b69699aeb12afd127b2de709
MD5 28d9ae5999a4ef9328048404ee212a8e
BLAKE2b-256 0a5f0c6b03d7196df994d6785b1adf44fb21ad5ace796af69b4d4c6819acf07c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 49f14dcef46b372f338a201c2bbb93f0e80063911bf6f2522754e92cd7677139
MD5 cc9ac2c6aa4cd519c4a1d3f1221de9fc
BLAKE2b-256 f949959b245f7b1056c5452c7570f2bf61944038e96c232c41d388b021795b12

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 879f2b8bdc836ab14fb7e34b78fe78ff99ee635247ba06d1cc9b4bfb505e59e4
MD5 7f6bb948d13adac0fda2803b8ece29c9
BLAKE2b-256 1a68d284ee8a27c2a4682e8981b94787cd6d079ed9f031d9d58fda01dc2fb0e9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp38-cp38-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 3c01ce5fa2ce76bbb1596a30ad4ce4f6a2e24d6ac91af29fd46cf00f42f3428f
MD5 4c1edc46eac7dc020a6ef309a90bb6de
BLAKE2b-256 bbca49dd90f0ade8c12764c88172fac3d33c3ceda5fdcaeb2ac50ad4746883c5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 52ea50a2d78bdf39502c6f3b2e1176ddf88aef34b65fb27628bbbdf3fe1c7b6b
MD5 99e677797457a83fae722efcb382e75a
BLAKE2b-256 2bcde0176e473ca474a2295d4519fdebef21bc27ebf168b3d2d726fa30c6fedf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for gasp_py-0.9.0-cp38-cp38-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a2d75c9ef59240857b1f76fdd6234d94e2c8dc60a219130fa6452b61ff50d4c5
MD5 f1f435ee92353558b16a3a51cc368e4f
BLAKE2b-256 900bf2eb0fe01d330710aa3fbb0f2aea39a626acc1ef4c40001aabed06e885ce

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