Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validated Schema #417

Closed
jxnl opened this issue Dec 29, 2023 · 7 comments
Closed

Validated Schema #417

jxnl opened this issue Dec 29, 2023 · 7 comments

Comments

@jxnl
Copy link

jxnl commented Dec 29, 2023

Is your feature request related to a problem? Please describe.

I'm the maintainer of Instructor which uses pydantic (python's validation) library to improve the sdk usaibility. If I wanted to do something like this in ruby, do you recommend ruby-openai support it? or should there be some seperate library that patchs this one, and adds the various new keywords?

In python it looks like

import instructor
from openai import OpenAI
from pydantic import BaseModel

# This enables response_model keyword
# from client.chat.completions.create
client = instructor.patch(OpenAI())

class UserDetail(BaseModel):
    name: str
    age: int

user = client.chat.completions.create(
    model="gpt-3.5-turbo",
    response_model=UserDetail,
    messages=[
        {"role": "user", "content": "Extract Jason is 25 years old"},
    ]
)

assert isinstance(user, UserDetail)
assert user.name == "Jason"
assert user.age == 25

In ruby you can imagine using

UserSchema = Dry::Validation.Schema do
  required(:name).filled(:str?)
  required(:age).filled(:int?, gt?: 0)
end

response = client.chat(
    parameters: {
        model: "gpt-3.5-turbo", # Required.
        messages: [{ role: "user", content: "Extract Jason is 25 years old"}], # Required.
        temperature: 0.7,
    },
    response_model: UserSchema
)
    
 if response.success?
  # The response is valid, assert each attribute
  user = validation_result.output
  raise 'Name is incorrect' unless user[:name] == "Jason"
  raise 'Age is incorrect'  unless user[:age] == 25

Instructor is mostly a bunch of docs on how to 'think' about the idea. like: https://jxnl.github.io/instructor/concepts/prompting/

@kaiwren
Copy link

kaiwren commented Jan 3, 2024

Somewhat tangentially - I personally tend to favour encapsulating the API call inside a factory method on a PORO domain class that then builds an instance from the response. I then use ActiveModel::Validations for validations on that instance. Something like:

user = User.extract_from('Jason is 25 years old')
if user.valid?
  puts user.name, user.age
else
  puts user.errors.full_messages.join("\n") user.valid?
end

@jxnl
Copy link
Author

jxnl commented Jan 3, 2024

Somewhat tangentially - I personally tend to favour encapsulating the API call inside a factory method on a PORO domain class that then builds an instance from the response. I then use ActiveModel::Validations for validations on that instance. Something like:

user = User.extract_from('Jason is 25 years old')
if user.valid?
  puts user.name, user.age
else
  puts user.errors.full_messages.join("\n") user.valid?
end

ok, you should check out marvin i think they have the cleanest version of that.

I think its importnat to give the users constrol of the whole messages array.

@sergiobayona
Copy link

@jxnl seems like ruby-openai would be able to support it with some patching. Although I wouldn't use dry-validation. I would use ActiveModel.

Something else to consider is that these libs don't output json schema so that'd need to be added... I think.

btw fantastic work you're doing with Instructor. I'm following closely. Here's how it might look like in Ruby:

class UserDetail
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :age, :integer
end

client = OpenAI::Client.new

user = client.chat(
  parameters: {
    model: "gpt-3.5-turbo",
    response_model: UserDetail,
    messages: [{ "role": "user", "content": "Extract Jason is 25 years old" }]
  }
)

RSpec.describe "Attribute Assignment" do
  it "assigns a value to an attribute" do
    expect(user).to be_instance_of(UserDetail)
    expect(user.name).to eq("Jason")
    expect(user.age).to eq(25)
  end
end

@jxnl
Copy link
Author

jxnl commented Jan 18, 2024

I think that would be awesome. good go everyone.

@jxnl seems like ruby-openai would be able to support it with some patching. Although I wouldn't use dry-validation. I would use ActiveModel.

Something else to consider is that these libs don't output json schema so that'd need to be added... I think.

btw fantastic work you're doing with Instructor. I'm following closely. Here's how it might look like in Ruby:

class UserDetail
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :age, :integer
end

client = OpenAI::Client.new

user = client.chat(
  parameters: {
    model: "gpt-3.5-turbo",
    response_model: UserDetail,
    messages: [{ "role": "user", "content": "Extract Jason is 25 years old" }]
  }
)

RSpec.describe "Attribute Assignment" do
  it "assigns a value to an attribute" do
    expect(user).to be_instance_of(UserDetail)
    expect(user.name).to eq("Jason")
    expect(user.age).to eq(25)
  end
end

That would be awesome. I'm not much of a Ruby developer, but I think it could benefit a lot of folks. We've had a lot of progress on the JavaScript side.

@sergiobayona
Copy link

@jxnl I'd like to take a stab at a solution for Ruby for the problem you are solving with Instructor. Would you be interested in collaborating?

@jxnl
Copy link
Author

jxnl commented Jan 18, 2024

For sure! Are you on Twitter dm me @jxnlco

@sergiobayona
Copy link

I think this issue can be closed since instructor-rb solves it. cc @alexrudall

@jxnl jxnl closed this as completed May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants