Skip to content
This repository has been archived by the owner on Aug 4, 2024. It is now read-only.

Commit

Permalink
Working version
Browse files Browse the repository at this point in the history
  • Loading branch information
nhorton committed Aug 11, 2023
1 parent 7bf9898 commit 7efcf8f
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 15 deletions.
4 changes: 2 additions & 2 deletions examples/agents-prettier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
democrat.call!(input: republican["conversation"][-2]["response"].gsub('Republican: ', ''))
# This is an extra demo section to demonstrate serializing and deserializing programs as it is an important
# part of the flow that Guidance itself never really shows
# stored_republican = republican.serialize
# republican = Guidance.deserialize(stored_republican)
stored_republican = Guidance::Serializer.serialize republican
republican = Guidance::Serializer.deserialize stored_republican
end
puts('Democrat: ' + first_question)
democrat['conversation'][0..-2].map do |x|
Expand Down
19 changes: 8 additions & 11 deletions lib/guidance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@
# that we want in modules and then include them in the PyCall version.
PythonGuidance = PyCall.import_module("guidance")
Kernel.const_set :PythonGuidance, PythonGuidance
PythonJson = PyCall.import_module("json")
Kernel.const_set :PythonJson, PythonJson

# Remember to load these AFTER defining PythonGuidance
require_relative "guidance/program"
require_relative "guidance/llms"
require_relative "guidance/serializer"

module Guidance
include Guidance::Serializer
autoload :Program, "guidance/program"
autoload :Version, "guidance/version"
autoload :LLMs, "guidance/llms"
autoload :Serializer, "guidance/serializer"

def self.llm=(llm)
write_class_store :llm, llm
Thread.current[:guidance_llm] = llm
PythonGuidance.llm = llm
end

def self.llm = read_class_store :llm
def self.llm
Thread.current[:guidance_llm]
end

def self.llms = @llms ||= LLMs.new

Expand All @@ -39,12 +44,4 @@ def self.raise_if_python_exception(python_exception)
python_exception.__traceback__
)
end

private_class_method def read_class_store(key)
Thread.current[key]
end

private_class_method def write_class_store(key, value)
Thread.current[key] = value
end
end
36 changes: 34 additions & 2 deletions lib/guidance/program.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ def initialize(template, python_guidance_program: nil, **kwargs)
)
end

def [](*args) = @python_guidance_program[*args]
def [](key) = variables[key]

def []=(key, value)
@python_guidance_program.variables[key] = value
end

def call(wrap_result: true, **kwargs)
result = python_guidance_program.call(**kwargs).tap do |result|
Expand All @@ -33,9 +37,37 @@ def call!(**kwargs)
self
end

def serialize = Guidance.serialize(self)
def prompt = python_guidance_program.to_s

# This returns the variables as a hash. Note that they are frozen.
# You can set individual values directly on Progam via []= but not
# via permuting the returned results in the hash
def variables
# We go through JSON to ensure we get Ruby types
json, llm = variables_json_and_llm
variables = JSON.parse(json).merge(llm: llm)
deep_freeze variables
end

alias :run :call
alias :run! :call!

private
def deep_freeze(obj)
if obj.is_a?(Hash)
obj.each_value { |v| deep_freeze(v) }
elsif obj.is_a?(Enumerable) && !obj.is_a?(String)
obj.each { |v| deep_freeze(v) }
end
obj.freeze
end

# LLM does not serialize so we return it separately
def variables_json_and_llm
variables = @python_guidance_program.variables
llm = variables.pop("llm")
json = PythonJson.dumps variables
[json, llm]
end
end
end
40 changes: 40 additions & 0 deletions lib/guidance/serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "json"

# See https://github.com/microsoft/guidance/issues/63 on the state of serialization in Guidance
module Guidance
module Serializer
PROMPT_KEY = "prompt_text".freeze
ARGUMENTS_KEY = "arguments".freeze

SERIALIZERS = {
hash: proc { |hash_of_state| hash_of_state },
json: proc { |hash_of_state| JSON.generate(hash_of_state) }
}.freeze

DESERIALIZERS = {
hash: proc { |hash_of_state| hash_of_state },
json: proc { |json_string| JSON.parse json_string }
}.freeze

# Returns a serialized version of the program in the format requested
# IMPT: We don't capture the LLM itself as it does not serialize and is hard to introspect
def self.serialize(program, format: :json)
serializer = SERIALIZERS[format.to_sym]
raise ArgumentError, "Allowed formats are: #{SERIALIZERS.keys.join ", "}" unless serializer

hash_of_state = {
ARGUMENTS_KEY => program.variables.to_h,
PROMPT_KEY => program.prompt
}
serializer.call hash_of_state
end

def self.deserialize(serialized_form, format: :json, argument_overrides: {})
deserializer = DESERIALIZERS[format.to_sym]
raise ArgumentError, "Allowed formats are: #{DESERIALIZERS.keys.join ', '}" unless deserializer

hash_of_state = deserializer.call serialized_form
Program.new(hash_of_state[PROMPT_KEY], **hash_of_state[ARGUMENTS_KEY].merge(argument_overrides))
end
end
end

0 comments on commit 7efcf8f

Please sign in to comment.