Skip to content

sergiobayona/easy_talk

Repository files navigation

EasyTalk

EasyTalk is a Ruby library that simplifies defining and generating JSON Schema documents, and validates that JSON data conforms to these schemas.

Key Features

  • Intuitive Schema Definition: Use Ruby classes and methods to define JSON Schema documents easily.
  • LLM Function Support: Ideal for integrating with Large Language Models (LLMs) such as OpenAI’s GPT series. EasyTalk enables you to effortlessly create JSON Schema documents describing the inputs and outputs of LLM function calls.
  • Schema Composition: Define EasyTalk models and reference them in other EasyTalk models to create complex schemas.
  • Validation: Write validations using ActiveModel’s validations.

Inspiration Inspired by Python's Pydantic library, EasyTalk brings similar functionality to the Ruby ecosystem, providing a Ruby-friendly approach to JSON Schema operations.

Example Use:

class User
  include EasyTalk::Model

  validates :name, :email, :group, presence: true
  validates :age, numericality: { greater_than_or_equal_to: 18, less_than_or_equal_to: 100 }

  define_schema do
    title "User"
    description "A user of the system"
    property :name, String, description: "The user's name", title: "Full Name"
    property :email, Hash do
      property :address, String, format: "email", description: "The user's email", title: "Email Address"
      property :verified, T::Boolean, description: "Whether the email is verified"
    end
    property :group, Integer, enum: [1, 2, 3], default: 1, description: "The user's group"
    property :age, Integer, minimum: 18, maximum: 100, description: "The user's age"
    property :tags, T::Array[String], min_items: 1, unique_items: true, description: "The user's tags"
  end
end

Calling User.json_schema will return the Ruby representation of the JSON Schema for the User class:

{
  "type" => "object",
  "title" => "User",
  "description" => "A user of the system",
  "properties" => {
    "name" => {
      "type" => "string", "title" => "Full Name", "description" => "The user's name"
    },
    "email" => {
      "type" => "object",
      "properties" => {
        "address" => {
          "type" => "string", "title" => "Email Address", "description" => "The user's email", "format" => "email"
        },
        "verified" => {
          "type" => "boolean", "description" => "Whether the email is verified"
        }
      },
      "required" => ["address", "verified"]
    },
    "group" => {
      "type" => "integer", "description" => "The user's group", "enum" => [1, 2, 3], "default" => 1
    },
    "age" => {
      "type" => "integer", "description" => "The user's age", "minimum" => 18, "maximum" => 100
    },
    "tags" => {
      "type" => "array",
      "items" => { "type" => "string" },
      "description" => "The user's tags",
      "minItems" => 1,
      "uniqueItems" => true
    }
  },
  "required" => ["name", "email", "group", "age", "tags"]
}

Instantiate a User object and validate it with ActiveModel validations:

user = User.new(name: "John Doe", email: { address: "[email protected]", verified: true }, group: 1, age: 25, tags: ["tag1", "tag2"])
user.valid? # => true

user.name = nil
user.valid? # => false

user.errors.full_messages # => ["Name can't be blank"]
user.errors["name"]       # => ["can't be blank"]

Installation

install the gem by running the following command in your terminal:

$ gem install easy_talk

Usage

Simply include the EasyTalk::Model module in your Ruby class, define the schema using the define_schema block, and call the json_schema class method to generate the JSON Schema document.

Schema Definition

In the example above, the define_schema method adds a title and description to the schema. The property method defines properties of the schema document. property accepts:

  • A name (symbol)
  • A type (generic Ruby type like String/Integer, a Sorbet type like T::Boolean, or one of the custom types like T::AnyOf[...])
  • A hash of constraints (e.g., minimum: 18, enum: [1, 2, 3], etc.)

Why Sortbet-style types?

Ruby doesn’t natively allow complex types like Array[String] or Array[Integer]. Sorbet-style types let you define these compound types clearly. EasyTalk uses this style to handle property types such as T::Array[String] or T::AnyOf[ClassA, ClassB].

Property Constraints

Property constraints are type-dependent. Refer to the CONSTRAINTS.md file for a list of constraints supported by the JSON Schema generator.

Schema Composition

EasyTalk supports schema composition. You can define a schema for a nested object by defining a new class that includes EasyTalk::Model. You can then reference the nested schema in the parent using special types:

T::OneOf[Model1, Model2, ...] — The property must match at least one of the specified schemas T::AnyOf[Model1, Model2, ...] — The property can match any of the specified schemas T::AllOf[Model1, Model2, ...] — The property must match all of the specified schemas

Example: A Payment object that can be a credit card, PayPal, or bank transfer:

class CreditCard
  include EasyTalk::Model

  define_schema do
    property :CardNumber, String
    property :CardType, String, enum: %w[Visa MasterCard AmericanExpress]
    property :CardExpMonth, Integer, minimum: 1, maximum: 12
    property :CardExpYear, Integer, minimum: Date.today.year, maximum: Date.today.year + 10
    property :CardCVV, String, pattern: '^[0-9]{3,4}$'
    additional_properties false
  end
end

class Paypal
  include EasyTalk::Model

  define_schema do
    property :PaypalEmail, String, format: 'email'
    property :PaypalPasswordEncrypted, String
    additional_properties false
  end
end

class BankTransfer
  include EasyTalk::Model

  define_schema do
    property :BankName, String
    property :AccountNumber, String
    property :RoutingNumber, String
    property :AccountType, String, enum: %w[Checking Savings]
    additional_properties false
  end
end

class Payment
  include EasyTalk::Model

  define_schema do
    title 'Payment'
    description 'Payment info'
    property :PaymentMethod, String, enum: %w[CreditCard Paypal BankTransfer]
    property :Details, T::AnyOf[CreditCard, Paypal, BankTransfer]
  end
end

Additional Properties

EasyTalk supports the JSON Schema additionalProperties keyword, allowing you to control whether instances of your model can accept properties beyond those explicitly defined in the schema.

Usage

Use the additional_properties keyword in your schema definition to specify whether additional properties are allowed:

class Company
  include EasyTalk::Model

  define_schema do
    property :name, String
    additional_properties true  # Allow additional properties
  end
end

# Additional properties are allowed
company = Company.new
company.name = "Acme Corp"        # Defined property
company.location = "New York"     # Additional property
company.employee_count = 100      # Additional property

company.as_json
# => {
#      "name" => "Acme Corp",
#      "location" => "New York",
#      "employee_count" => 100
#    }

Behavior

When additional_properties true:

  • Instances can accept properties beyond those defined in the schema
  • Additional properties can be set both via the constructor and direct assignment
  • Additional properties are included in JSON serialization
  • Attempting to access an undefined additional property raises NoMethodError
# Setting via constructor
company = Company.new(
  name: "Acme Corp",
  location: "New York"  # Additional property
)

# Setting via assignment
company.rank = 1        # Additional property

# Accessing undefined properties
company.undefined_prop  # Raises NoMethodError

When additional_properties false or not specified:

  • Only properties defined in the schema are allowed
  • Attempting to set or get undefined properties raises NoMethodError
class RestrictedCompany
  include EasyTalk::Model

  define_schema do
    property :name, String
    additional_properties false  # Restrict to defined properties only
  end
end

company = RestrictedCompany.new
company.name = "Acme Corp"     # OK - defined property
company.location = "New York"  # Raises NoMethodError

JSON Schema

The additional_properties setting is reflected in the generated JSON Schema:

Company.json_schema
# => {
#      "type" => "object",
#      "properties" => {
#        "name" => { "type" => "string" }
#      },
#      "required" => ["name"],
#      "additionalProperties" => true
#    }

Best Practices

  1. Default to Restrictive: Unless you specifically need additional properties, it's recommended to leave additional_properties as false (the default) to maintain schema integrity.

  2. Documentation: If you enable additional properties, document the expected additional property types and their purpose.

  3. Validation: Consider implementing custom validation for additional properties if they need to conform to specific patterns or types.

  4. Error Handling: When working with instances that allow additional properties, use respond_to? or try to handle potentially undefined properties safely:

# Safe property access
value = company.try(:optional_property)
# or
value = company.optional_property if company.respond_to?(:optional_property)

Type Checking and Schema Constraints

EasyTalk uses a combination of standard Ruby types (String, Integer), Sorbet types (T::Boolean, T::Array[String], etc.), and custom Sorbet-style types (T::AnyOf[], T::OneOf[]) to perform basic type checking. For example:

If you specify enum: [1,2,3] but the property type is String, EasyTalk raises a type error. If you define minimum: 1 on a String property, it raises an error because minimum applies only to numeric types.

Schema Validation

You can instantiate an EasyTalk model with a hash of attributes and validate it using standard ActiveModel validations. EasyTalk does not automatically validate instances; you must explicitly define ActiveModel validations in your EasyTalk model. See [spec/easy_talk/activemodel_integration_spec.rb](ActiveModel Integration Spec) for examples.

JSON Schema Specifications

EasyTalk is currently loose about JSON Schema versions. It doesn’t strictly enforce or adhere to any particular version of the specification. The goal is to add more robust support for the latest JSON Schema specs in the future.

To learn about current capabilities, see the spec/easy_talk/examples folder. The examples illustrate how EasyTalk generates JSON Schema in different scenarios.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that lets you experiment.

To install this gem onto your local machine, run:

bundle exec rake install

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/easy_talk.

License

The gem is available as open source under the terms of the MIT License.

About

Ruby gem for defining and generating JSON Schema

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages