From 4f2f7f0958b9a2ff21ab56903dfa6caaf6c33313 Mon Sep 17 00:00:00 2001 From: Lauri Jutila Date: Wed, 2 Aug 2023 11:49:45 +0300 Subject: [PATCH] Adapters, reasoning. --- lib/roseflow.rb | 5 +- lib/roseflow/actions/ai/chat.rb | 21 ++++ lib/roseflow/actions/ai/resolve_model.rb | 4 +- lib/roseflow/actions/ai/resolve_provider.rb | 6 +- lib/roseflow/ai/model.rb | 55 ++++++++- lib/roseflow/ai/model_interface.rb | 23 ++++ lib/roseflow/ai/models/base_adapter.rb | 13 +++ lib/roseflow/ai/models/instance_factory.rb | 22 ++++ lib/roseflow/ai/models/openai_adapter.rb | 22 ++++ lib/roseflow/ai/models/openrouter_adapter.rb | 22 ++++ lib/roseflow/ai/provider.rb | 24 ++-- lib/roseflow/ai/provider_interface.rb | 23 ++++ lib/roseflow/ai/providers/base_adapter.rb | 17 +++ lib/roseflow/ai/providers/instance_factory.rb | 22 ++++ lib/roseflow/ai/providers/openai_adapter.rb | 15 +++ .../ai/providers/openrouter_adapter.rb | 29 +++++ lib/roseflow/interaction/with_cli.rb | 35 ++++++ .../interaction/with_documentation.rb | 37 ++++++ lib/roseflow/model_repository.rb | 43 +++++++ lib/roseflow/provider_repository.rb | 71 ++++++++++++ .../actions/add_points_to_context.rb | 22 ++++ .../actions/build_skeleton_prompt.rb | 27 +++++ .../actions/expand_single_skeleton_point.rb | 42 +++++++ .../actions/generate_final_answer.rb | 35 ++++++ .../reasoning/actions/generate_skeleton.rb | 17 +++ .../parallel_expand_skeleton_points.rb | 34 ++++++ .../interactions/expand_skeleton_points.rb | 38 ++++++ ...and_single_skeleton_point_system_prompt.rb | 45 ++++++++ ...xpand_single_skeleton_point_user_prompt.rb | 65 +++++++++++ .../prompts/final_answer_system_prompt.rb | 55 +++++++++ .../prompts/final_answer_user_prompt.rb | 17 +++ .../prompts/skeleton_system_prompt.rb | 108 ++++++++++++++++++ lib/roseflow/reasoning/skeleton_of_thought.rb | 28 +++++ lib/roseflow/reasoning/skeleton_point.rb | 16 +++ lib/roseflow/reasoning/thought_generator.rb | 35 ++++++ .../prompts/thinking_prompt.rb | 86 ++++++++++++++ lib/roseflow/registry.rb | 46 ++++++++ lib/roseflow/types.rb | 1 + lib/roseflow/version.rb | 4 +- roseflow.gemspec | 4 + spec/actions/ai/resolve_model_spec.rb | 36 +++--- spec/chat/dialogue_spec.rb | 5 - spec/fixtures/vcr/ai/resolve_model.yml | 59 ++++++++++ spec/spec_helper.rb | 11 ++ 44 files changed, 1296 insertions(+), 49 deletions(-) create mode 100644 lib/roseflow/actions/ai/chat.rb create mode 100644 lib/roseflow/ai/model_interface.rb create mode 100644 lib/roseflow/ai/models/base_adapter.rb create mode 100644 lib/roseflow/ai/models/instance_factory.rb create mode 100644 lib/roseflow/ai/models/openai_adapter.rb create mode 100644 lib/roseflow/ai/models/openrouter_adapter.rb create mode 100644 lib/roseflow/ai/provider_interface.rb create mode 100644 lib/roseflow/ai/providers/base_adapter.rb create mode 100644 lib/roseflow/ai/providers/instance_factory.rb create mode 100644 lib/roseflow/ai/providers/openai_adapter.rb create mode 100644 lib/roseflow/ai/providers/openrouter_adapter.rb create mode 100644 lib/roseflow/interaction/with_cli.rb create mode 100644 lib/roseflow/interaction/with_documentation.rb create mode 100644 lib/roseflow/model_repository.rb create mode 100644 lib/roseflow/provider_repository.rb create mode 100644 lib/roseflow/reasoning/actions/add_points_to_context.rb create mode 100644 lib/roseflow/reasoning/actions/build_skeleton_prompt.rb create mode 100644 lib/roseflow/reasoning/actions/expand_single_skeleton_point.rb create mode 100644 lib/roseflow/reasoning/actions/generate_final_answer.rb create mode 100644 lib/roseflow/reasoning/actions/generate_skeleton.rb create mode 100644 lib/roseflow/reasoning/actions/parallel_expand_skeleton_points.rb create mode 100644 lib/roseflow/reasoning/interactions/expand_skeleton_points.rb create mode 100644 lib/roseflow/reasoning/prompts/expand_single_skeleton_point_system_prompt.rb create mode 100644 lib/roseflow/reasoning/prompts/expand_single_skeleton_point_user_prompt.rb create mode 100644 lib/roseflow/reasoning/prompts/final_answer_system_prompt.rb create mode 100644 lib/roseflow/reasoning/prompts/final_answer_user_prompt.rb create mode 100644 lib/roseflow/reasoning/prompts/skeleton_system_prompt.rb create mode 100644 lib/roseflow/reasoning/skeleton_of_thought.rb create mode 100644 lib/roseflow/reasoning/skeleton_point.rb create mode 100644 lib/roseflow/reasoning/thought_generator.rb create mode 100644 lib/roseflow/reasoning/tree_of_thoughts/prompts/thinking_prompt.rb create mode 100644 lib/roseflow/registry.rb create mode 100644 spec/fixtures/vcr/ai/resolve_model.yml diff --git a/lib/roseflow.rb b/lib/roseflow.rb index 07aa226..172f737 100644 --- a/lib/roseflow.rb +++ b/lib/roseflow.rb @@ -5,12 +5,15 @@ require "roseflow/action" require "roseflow/ai/model" require "roseflow/ai/provider" -require "roseflow/embeddings/base" +require "roseflow/chat/dialogue" require "roseflow/finite_machine" require "roseflow/interaction" +require "roseflow/interaction/with_cli" +require "roseflow/interaction/with_documentation" require "roseflow/interaction_context" require "roseflow/interactions/ai/initialize_llm" require "roseflow/vector_stores/base" +require "roseflow/registry" module Roseflow class Error < StandardError; end diff --git a/lib/roseflow/actions/ai/chat.rb b/lib/roseflow/actions/ai/chat.rb new file mode 100644 index 0000000..f3a8a63 --- /dev/null +++ b/lib/roseflow/actions/ai/chat.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Roseflow + module Actions + module AI + class Chat + extend Roseflow::Action + + expects :messages + expects :model + expects :options, default: {} + + promises :response + + executed do |context| + context[:response] = context.model.chat(context.options.merge(messages: context.messages)) + end + end + end + end +end diff --git a/lib/roseflow/actions/ai/resolve_model.rb b/lib/roseflow/actions/ai/resolve_model.rb index 0f3ad73..326dfb0 100644 --- a/lib/roseflow/actions/ai/resolve_model.rb +++ b/lib/roseflow/actions/ai/resolve_model.rb @@ -13,13 +13,13 @@ class ResolveModel promises :llm executed do |context| - model = context.provider.models.find(context[:model]) + model = Registry.get(:models).find(context[:model]) unless model context.fail_and_return!("Model #{context[:model]} not found") end - context[:llm] = Roseflow::AI::Model.new(name: model.name, provider: context.provider) + context[:llm] = model end end end diff --git a/lib/roseflow/actions/ai/resolve_provider.rb b/lib/roseflow/actions/ai/resolve_provider.rb index c590b20..428e4eb 100644 --- a/lib/roseflow/actions/ai/resolve_provider.rb +++ b/lib/roseflow/actions/ai/resolve_provider.rb @@ -2,7 +2,6 @@ require "roseflow/action" require "roseflow/ai/provider" -require "roseflow/openai/config" module Roseflow module Actions @@ -20,10 +19,7 @@ class ResolveProvider private_class_method def self.resolve_provider(provider) - case provider - when :openai - Roseflow::AI::Provider.new(name: :openai, credentials: Roseflow::OpenAI::Config.new) - end + Registry.get(:providers).find(provider) end end end diff --git a/lib/roseflow/ai/model.rb b/lib/roseflow/ai/model.rb index f8a1d91..abe4e41 100644 --- a/lib/roseflow/ai/model.rb +++ b/lib/roseflow/ai/model.rb @@ -1,18 +1,61 @@ # frozen_string_literal: true +require "roseflow/model_repository" +require "roseflow/provider_repository" +require "roseflow/ai/models/instance_factory" +require "roseflow/ai/models/openai_adapter" +require "roseflow/ai/models/openrouter_adapter" + module Roseflow module AI + class ModelInstanceNotFoundError < StandardError; end + class Model - attr_reader :name, :provider + attr_reader :name - def initialize(name:, provider:) + delegate :chat, to: :instance + + def initialize(name:, provider: nil) + raise ArgumentError, "Name must be provided" if name.nil? + provider = resolve_provider(name, provider) + instance = create_adapted_instance(name, provider) @name = name - @provider = provider - @model_ = provider.models.find(name) + @instance = instance + @_provider = provider + end + + def provider + _provider.name + end + + def call(operation, options, &block) + instance.call(operation, options, &block) + end + + def operations + instance.operations + end + + def self.load(name) + Registry.get(:models).find(name) + end + + private + + attr_reader :instance, :_provider + + def resolve_provider(name, provider) + if provider.nil? + provider_name = Registry.get(:models).find(name).provider + Registry.get(:providers).find(provider_name) + else + return provider if provider.instance_of?(Provider) + Registry.get(:providers).find(provider) + end end - def call(operation, input) - @model_.call(operation, input) + def create_adapted_instance(name, provider) + Models::InstanceFactory.create(name, provider) end end # Model end # AI diff --git a/lib/roseflow/ai/model_interface.rb b/lib/roseflow/ai/model_interface.rb new file mode 100644 index 0000000..c1a9fc5 --- /dev/null +++ b/lib/roseflow/ai/model_interface.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Roseflow + module AI + module ModelInterface + def call(**options) + raise NotImplementedError, "Model must implement #call" + end + + def chat(**options) + raise NotImplementedError, "Model must implement #chat" + end + + def completion(**options) + raise NotImplementedError, "Model must implement #completion" + end + + def operations + raise NotImplementedError, "Model must implement #operations" + end + end + end +end diff --git a/lib/roseflow/ai/models/base_adapter.rb b/lib/roseflow/ai/models/base_adapter.rb new file mode 100644 index 0000000..9910ae0 --- /dev/null +++ b/lib/roseflow/ai/models/base_adapter.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Roseflow + module AI + module Models + class BaseAdapter + def initialize(model) + @model = model + end + end + end + end +end diff --git a/lib/roseflow/ai/models/instance_factory.rb b/lib/roseflow/ai/models/instance_factory.rb new file mode 100644 index 0000000..37f0600 --- /dev/null +++ b/lib/roseflow/ai/models/instance_factory.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Roseflow + module AI + module Models + class InstanceFactory + def self.create(name, provider) + provider_model = provider.models.find(name) + raise ModelInstanceNotFoundError, "Instance for #{name} not found" if provider_model.nil? + + begin + provider_config = PROVIDER_GEMS.find { |gem, settings| settings[:name] == provider.name.to_sym }.last + adapter_class = Models.const_get(provider_config.fetch(:adapter_class)) + adapter_class.new(provider_model) + rescue => exception + raise NotImplementedError, "Model adapter for provider '#{provider.name}' not implemented" + end + end + end + end + end +end diff --git a/lib/roseflow/ai/models/openai_adapter.rb b/lib/roseflow/ai/models/openai_adapter.rb new file mode 100644 index 0000000..a1c822f --- /dev/null +++ b/lib/roseflow/ai/models/openai_adapter.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "roseflow/ai/model_interface" +require "roseflow/ai/models/base_adapter" + +module Roseflow + module AI + module Models + class OpenAIAdapter < BaseAdapter + include ModelInterface + + def call(operation, options, &block) + @model.call(operation, options, &block) + end + + def chat(options, &block) + @model.chat(options.delete(:messages), options, &block) + end + end + end + end +end diff --git a/lib/roseflow/ai/models/openrouter_adapter.rb b/lib/roseflow/ai/models/openrouter_adapter.rb new file mode 100644 index 0000000..772bf20 --- /dev/null +++ b/lib/roseflow/ai/models/openrouter_adapter.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "roseflow/ai/model_interface" +require "roseflow/ai/models/base_adapter" + +module Roseflow + module AI + module Models + class OpenRouterAdapter < OpenAIAdapter + include ModelInterface + + def call(operation, options, &block) + @model.call(operation, options, &block) + end + + def chat(options, &block) + @model.chat(options.delete(:messages), options, &block) + end + end + end + end +end diff --git a/lib/roseflow/ai/provider.rb b/lib/roseflow/ai/provider.rb index 4d5d519..5fb223a 100644 --- a/lib/roseflow/ai/provider.rb +++ b/lib/roseflow/ai/provider.rb @@ -1,30 +1,32 @@ # frozen_string_literal: true -require "roseflow/openai/provider" +require "roseflow/provider_repository" +require "roseflow/ai/providers/instance_factory" module Roseflow module AI class Provider - def initialize(name:, credentials:) + attr_reader :name + + def initialize(name:, config:) + instance = create_adapted_instance(config) @name = name - @credentials = credentials - initialize_provider + @instance = instance end def models - @models ||= provider.models + @models ||= instance.models end private - attr_reader :name, :credentials, :provider + attr_reader :instance - def initialize_provider - case name - when :openai - @provider = Roseflow::OpenAI::Provider.new(credentials: credentials) - end + def create_adapted_instance(config) + Providers::InstanceFactory.create(config) end end # Provider + + ProviderNotFoundError = ProviderRepository::ProviderNotFoundError end # AI end # Roseflow diff --git a/lib/roseflow/ai/provider_interface.rb b/lib/roseflow/ai/provider_interface.rb new file mode 100644 index 0000000..1fd6e6d --- /dev/null +++ b/lib/roseflow/ai/provider_interface.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Roseflow + module AI + module ProviderInterface + def call + raise NotImplementedError, "Provider must implement #call" + end + + def chat + raise NotImplementedError, "Provider must implement #chat" + end + + def completion + raise NotImplementedError, "Provider must implement #completion" + end + + def models + raise NotImplementedError, "Provider must implement #models" + end + end + end +end diff --git a/lib/roseflow/ai/providers/base_adapter.rb b/lib/roseflow/ai/providers/base_adapter.rb new file mode 100644 index 0000000..94ccfba --- /dev/null +++ b/lib/roseflow/ai/providers/base_adapter.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "roseflow/ai/provider_interface" + +module Roseflow + module AI + module Providers + class BaseAdapter + include ProviderInterface + + def initialize(provider) + @provider = provider + end + end + end + end +end diff --git a/lib/roseflow/ai/providers/instance_factory.rb b/lib/roseflow/ai/providers/instance_factory.rb new file mode 100644 index 0000000..5646710 --- /dev/null +++ b/lib/roseflow/ai/providers/instance_factory.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "./openai_adapter" +require_relative "./openrouter_adapter" + +module Roseflow + module AI + module Providers + class InstanceFactory + def self.create(config) + begin + adapter = Providers.const_get(config.fetch(:adapter_class)) + klass = Object.const_get("#{config.fetch(:namespace)}::Provider") + adapter.new(klass.new) + rescue => exception + raise NotImplementedError, "Adapter for provider #{config.fetch(:name)} not implemented" + end + end + end + end + end +end diff --git a/lib/roseflow/ai/providers/openai_adapter.rb b/lib/roseflow/ai/providers/openai_adapter.rb new file mode 100644 index 0000000..ae09049 --- /dev/null +++ b/lib/roseflow/ai/providers/openai_adapter.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "roseflow/ai/providers/base_adapter" + +module Roseflow + module AI + module Providers + class OpenAIAdapter < BaseAdapter + def models + @provider.models + end + end + end + end +end diff --git a/lib/roseflow/ai/providers/openrouter_adapter.rb b/lib/roseflow/ai/providers/openrouter_adapter.rb new file mode 100644 index 0000000..149d3eb --- /dev/null +++ b/lib/roseflow/ai/providers/openrouter_adapter.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Roseflow + module AI + module Providers + class OpenRouterAdapter < BaseAdapter + def models + @provider.models + end + + def call(**options) + raise NotImplementedError, "Model must implement #call" + end + + def chat(**options) + raise NotImplementedError, "Model must implement #chat" + end + + def completion(**options) + raise NotImplementedError, "Model must implement #completion" + end + + def operations + raise NotImplementedError, "Model must implement #operations" + end + end + end + end +end diff --git a/lib/roseflow/interaction/with_cli.rb b/lib/roseflow/interaction/with_cli.rb new file mode 100644 index 0000000..55bfc66 --- /dev/null +++ b/lib/roseflow/interaction/with_cli.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Roseflow + module Interaction + # This module is used to define the CLI interface for an interaction + module WithCli + def self.extended(base_class) + base_class.extend ClassMethods + end + + module ClassMethods + def cli(&block) + @cli_proxy = CliProxy.new + @cli_proxy.instance_eval(&block) + end + + def command + return self.name if @cli_proxy.nil? + @cli_proxy.command + end + end + + class CliProxy + # Defines command name for interaction + # @param [String] command_name + def command(command = nil) + return @command = command if command + return @command if @command + + raise ArgumentError, "No command name provided" + end + end + end + end +end diff --git a/lib/roseflow/interaction/with_documentation.rb b/lib/roseflow/interaction/with_documentation.rb new file mode 100644 index 0000000..35b8985 --- /dev/null +++ b/lib/roseflow/interaction/with_documentation.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Roseflow + module Interaction + # This module is used to define the documentation for an interaction + module WithDocumentation + def self.extended(base_class) + base_class.extend ClassMethods + end + + module ClassMethods + def documentation(&block) + documentation_proxy = DocumentationProxy.new + documentation_proxy.instance_eval(&block) + end + end + + class DocumentationProxy + # Defines a description for the interaction + def description(description) + end + + # Defines a list of input parameters for the interaction + # @param key [Symbol] the name of the parameter + # @param description [String] the description of the parameter + def expects(key, description = "") + end + + # Defines a list of output parameters for the interaction + # @param key [Symbol] the name of the parameter + # @param description [String] the description of the parameter + def promises(key, description = "") + end + end + end + end +end diff --git a/lib/roseflow/model_repository.rb b/lib/roseflow/model_repository.rb new file mode 100644 index 0000000..4581b5e --- /dev/null +++ b/lib/roseflow/model_repository.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "hashie/mash" +require "active_support/core_ext/module/delegation" + +module Roseflow + class ModelRepository + class ModelNotFoundError < StandardError; end + + attr_reader :models + + def initialize + @models = load_models + end + + def all + models + end + + def find(name) + result = models.find { |model| model.name == name } + raise ModelNotFoundError, "Model #{name} not found" if result.nil? + result + end + + def list + models.map(&:name) + end + + private + + def load_models + models = [] + providers = Registry.get(:providers).all + providers.each do |provider| + provider.models.each do |model| + models << AI::Model.new(name: model.name, provider: provider) + end + end + models + end + end +end diff --git a/lib/roseflow/provider_repository.rb b/lib/roseflow/provider_repository.rb new file mode 100644 index 0000000..b13fc3a --- /dev/null +++ b/lib/roseflow/provider_repository.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "hashie/mash" + +PROVIDER_GEMS = { + "roseflow-openai": { + name: :openai, + require_name: "roseflow/openai", + namespace: "Roseflow::OpenAI", + adapter_class: "OpenAIAdapter", + }, + "roseflow-openrouter": { + name: :openrouter, + require_name: "roseflow/open_router", + namespace: "Roseflow::OpenRouter", + adapter_class: "OpenRouterAdapter", + }, +} + +PROVIDER_GEMS.each do |gem, settings| + if Gem.loaded_specs.key?(gem.to_s) + require "#{settings[:require_name]}/provider" + end +end + +module Roseflow + class ProviderRepository + class ProviderNotFoundError < StandardError; end + + def initialize + @providers = load_providers + end + + def all + providers + end + + def find(name) + result = providers.find { |provider| provider.name == name } + raise ProviderNotFoundError, "Provider #{name} not found" if result.nil? + result + end + + def list + providers.map(&:name) + end + + def self.find(name) + Registry.get(:providers).find(name) + end + + def self.providers + Registry.get(:providers).all + end + + private + + attr_reader :providers + + def load_providers + providers = [] + PROVIDER_GEMS.each do |gem, config| + if Gem.loaded_specs.key?(gem.to_s) + # providers << AI::Providers::InstanceFactory.create(config) + providers << AI::Provider.new(name: config.fetch(:name), config: config) + end + end + providers + end + end +end diff --git a/lib/roseflow/reasoning/actions/add_points_to_context.rb b/lib/roseflow/reasoning/actions/add_points_to_context.rb new file mode 100644 index 0000000..1b8614b --- /dev/null +++ b/lib/roseflow/reasoning/actions/add_points_to_context.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "roseflow/reasoning/skeleton_point" + +module Roseflow + module Reasoning + module Actions + class AddPointsToContext + extend Roseflow::Action + + expects :skeleton + + executed do |ctx| + json = JSON.parse(ctx.skeleton) + ctx[:points] = json["skeleton"].map { |point| SkeletonPoint.new(point) } + rescue JSON::ParserError + ctx.fail! + end + end + end + end +end diff --git a/lib/roseflow/reasoning/actions/build_skeleton_prompt.rb b/lib/roseflow/reasoning/actions/build_skeleton_prompt.rb new file mode 100644 index 0000000..9260aac --- /dev/null +++ b/lib/roseflow/reasoning/actions/build_skeleton_prompt.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "roseflow/reasoning/prompts/skeleton_system_prompt" + +module Roseflow + module Reasoning + module Actions + class BuildSkeletonPrompt + extend Roseflow::Action + + expects :prompt, :model + + executed do |ctx| + ctx[:messages] = [build_system_message, build_user_message(ctx.prompt)] + end + + def self.build_system_message + Roseflow::Chat::SystemMessage.new(role: "system", content: Prompts::SkeletonSystemPrompt.new.call) + end + + def self.build_user_message(prompt) + Roseflow::Chat::UserMessage.new(role: "user", content: prompt) + end + end + end + end +end diff --git a/lib/roseflow/reasoning/actions/expand_single_skeleton_point.rb b/lib/roseflow/reasoning/actions/expand_single_skeleton_point.rb new file mode 100644 index 0000000..a056532 --- /dev/null +++ b/lib/roseflow/reasoning/actions/expand_single_skeleton_point.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "roseflow/reasoning/prompts/expand_single_skeleton_point_system_prompt" +require "roseflow/reasoning/prompts/expand_single_skeleton_point_user_prompt" +require "roseflow/reasoning/skeleton_point" + +module Roseflow + module Reasoning + module Actions + class ExpandSingleSkeletonPoint + extend Roseflow::Action + + expects :point + + executed do |ctx| + ctx[:expanded_points] = {} unless ctx[:expanded_points].is_a?(Hash) + point = SkeletonPoint.new(ctx[:point]) + messages = [ + build_system_message(ctx[:skeleton], ctx[:prompt]), + build_user_message(point), + ] + response = ctx[:model].chat(messages: messages, max_tokens: 200, temperature: 1.0).response.to_s + ctx[:expanded_points][point.order] = SkeletonPoint.new(JSON.parse(response)) + end + + def self.build_system_message(skeleton, prompt) + { + role: "system", + content: Prompts::ExpandSingleSkeletonPointSystemPrompt.new(skeleton, prompt).call, + } + end + + def self.build_user_message(point) + { + role: "user", + content: Prompts::ExpandSingleSkeletonPointUserPrompt.new(point).call, + } + end + end + end + end +end diff --git a/lib/roseflow/reasoning/actions/generate_final_answer.rb b/lib/roseflow/reasoning/actions/generate_final_answer.rb new file mode 100644 index 0000000..e499265 --- /dev/null +++ b/lib/roseflow/reasoning/actions/generate_final_answer.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "roseflow/chat/message" +require "roseflow/reasoning/prompts/final_answer_system_prompt" +require "roseflow/reasoning/prompts/final_answer_user_prompt" + +module Roseflow + module Reasoning + module Actions + class GenerateFinalAnswer + extend Roseflow::Action + + expects :expanded_points + + executed do |ctx| + messages = [ + Roseflow::Chat::SystemMessage.new(role: "system", content: Prompts::FinalAnswerSystemPrompt.new(ctx[:prompt], resolve_model_provider(ctx[:model].name)).call), + Roseflow::Chat::UserMessage.new(role: "user", content: Prompts::FinalAnswerUserPrompt.new(ctx[:expanded_points]).call), + ] + + ctx[:answer] = ctx[:model].chat(messages: messages, max_tokens: 2048, temperature: 1.0).response + end + + def self.resolve_model_provider(model_name) + case model_name + when /anthropic/ + :anthropic + when /gpt/ + :openai + end + end + end + end + end +end diff --git a/lib/roseflow/reasoning/actions/generate_skeleton.rb b/lib/roseflow/reasoning/actions/generate_skeleton.rb new file mode 100644 index 0000000..904320e --- /dev/null +++ b/lib/roseflow/reasoning/actions/generate_skeleton.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + module Actions + class GenerateSkeleton + extend Roseflow::Action + + expects :model, :messages + + executed do |ctx| + ctx[:skeleton] = ctx.model.chat(messages: ctx.messages, max_tokens: 2048, temperature: 1.0).response.to_s + end + end + end + end +end diff --git a/lib/roseflow/reasoning/actions/parallel_expand_skeleton_points.rb b/lib/roseflow/reasoning/actions/parallel_expand_skeleton_points.rb new file mode 100644 index 0000000..4a9b75e --- /dev/null +++ b/lib/roseflow/reasoning/actions/parallel_expand_skeleton_points.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "async" +require "async/barrier" + +require "roseflow/reasoning/actions/expand_single_skeleton_point" + +module Roseflow + module Reasoning + module Actions + class ParallelExpandSkeletonPoints + extend Roseflow::Action + + expects :points + + executed do |ctx| + ctx[:expanded_points] = {} unless ctx[:expanded_points].is_a?(Hash) + barrier = Async::Barrier.new + + Sync do + ctx[:points].map do |point| + barrier.async do + action = Actions::ExpandSingleSkeletonPoint.execute(ctx.merge(point: point)) + ctx[:expanded_points][point.order] = action[:expanded_points][point.order] + end + end.map(&:wait) + ensure + barrier.stop + end + end + end + end + end +end diff --git a/lib/roseflow/reasoning/interactions/expand_skeleton_points.rb b/lib/roseflow/reasoning/interactions/expand_skeleton_points.rb new file mode 100644 index 0000000..55deb16 --- /dev/null +++ b/lib/roseflow/reasoning/interactions/expand_skeleton_points.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "roseflow/interaction" +require "roseflow/reasoning/actions/add_points_to_context" +require "roseflow/reasoning/actions/expand_single_skeleton_point" +require "roseflow/reasoning/actions/parallel_expand_skeleton_points" + +module Roseflow + module Reasoning + module Interactions + class ExpandSkeletonPoints + extend Roseflow::Interaction + + def self.call(context) + with(context).reduce(self.actions) + end + + def self.actions + [ + Actions::AddPointsToContext, + reduce_case( + value: :method, + when: { + sequential: [ + iterate(:points, [Actions::ExpandSingleSkeletonPoint]), + ], + parallel: [ + Actions::ParallelExpandSkeletonPoints, + ], + }, + else: [Actions::ParallelExpandSkeletonPoints], + ), + ] + end + end + end + end +end diff --git a/lib/roseflow/reasoning/prompts/expand_single_skeleton_point_system_prompt.rb b/lib/roseflow/reasoning/prompts/expand_single_skeleton_point_system_prompt.rb new file mode 100644 index 0000000..efa5a84 --- /dev/null +++ b/lib/roseflow/reasoning/prompts/expand_single_skeleton_point_system_prompt.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + module Prompts + class ExpandSingleSkeletonPointSystemPrompt < Roseflow::Prompt + def initialize(skeleton, prompt) + @skeleton = skeleton + @prompt = prompt + end + + def template + plain_raw predirective + plain_raw prompt_string(@prompt) + plain_raw skeleton_string(@skeleton) + end + + def predirective + <<-PROMPT + You are responsible for continuing the writing of one and only one point in the overall answer + to the following question. + PROMPT + end + + def prompt_string(prompt) + <<-PROMPT + The question is: + + #{prompt} + + PROMPT + end + + def skeleton_string(skeleton) + <<-PROMPT + The skeleton of the answer is: + + #{skeleton} + + PROMPT + end + end + end + end +end diff --git a/lib/roseflow/reasoning/prompts/expand_single_skeleton_point_user_prompt.rb b/lib/roseflow/reasoning/prompts/expand_single_skeleton_point_user_prompt.rb new file mode 100644 index 0000000..5fdf8a5 --- /dev/null +++ b/lib/roseflow/reasoning/prompts/expand_single_skeleton_point_user_prompt.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + module Prompts + class ExpandSingleSkeletonPointUserPrompt < Roseflow::Prompt + def initialize(point) + @point = point + end + + def template + plain_raw predirective + plain_raw point_string(@point) + plain_raw json_schema + end + + def predirective + # <<-PROMPT + # Continue and only continue the writing of point #{@point.order}. Write it **very shortly** + # in a few sentences and do not continue with other points. + # PROMPT + <<-PROMPT + Continue and only continue the writing of point #{@point.order}. Write it succinctly + in a few sentences and do not continue with other points. + PROMPT + end + + def point_string(point) + <<-PROMPT + + #{point.to_s} + + PROMPT + end + + def json_schema + <<-SCHEMA + Return your answer using the following JSON schema. + + #{schema.to_json} + + SCHEMA + end + + def schema + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "order": { + "type": "integer", + "minimum": 1, + "maximum": 10, + }, + "text": { + "type": "string", + }, + }, + "required": ["order", "text"], + } + end + end + end + end +end diff --git a/lib/roseflow/reasoning/prompts/final_answer_system_prompt.rb b/lib/roseflow/reasoning/prompts/final_answer_system_prompt.rb new file mode 100644 index 0000000..c3f742b --- /dev/null +++ b/lib/roseflow/reasoning/prompts/final_answer_system_prompt.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + module Prompts + class FinalAnswerSystemPrompt < Roseflow::Prompt + def initialize(question, provider = :openai) + @question = question + @provider = provider + end + + def template + plain_raw directive + end + + def directive + case @provider + when :openai + openai_directive + when :anthropic + anthropic_directive + end + end + + def openai_directive + <<-PROMPT + You are an expert writer, responsible for writing the final, comprehensive answer to the following question. + + + #{@question} + + + The user will provide a list of points that should guide your writing. Carefully consider all points + provided when formulating your answer. + PROMPT + end + + def anthropic_directive + <<-PROMPT + Human: You are an expert writer, responsible for writing the final, comprehensive answer to the following question. + + + #{@question} + + + The user will provide a list of points that should guide your writing. Carefully consider all points + provided when formulating your answer. + + When you reply, write your answer inside XML tags. + PROMPT + end + end + end + end +end diff --git a/lib/roseflow/reasoning/prompts/final_answer_user_prompt.rb b/lib/roseflow/reasoning/prompts/final_answer_user_prompt.rb new file mode 100644 index 0000000..6683304 --- /dev/null +++ b/lib/roseflow/reasoning/prompts/final_answer_user_prompt.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + module Prompts + class FinalAnswerUserPrompt < Roseflow::Prompt + def initialize(points) + @points = points + end + + def template + plain_raw @points.map(&:to_s).join("\n") + end + end + end + end +end diff --git a/lib/roseflow/reasoning/prompts/skeleton_system_prompt.rb b/lib/roseflow/reasoning/prompts/skeleton_system_prompt.rb new file mode 100644 index 0000000..12f62d2 --- /dev/null +++ b/lib/roseflow/reasoning/prompts/skeleton_system_prompt.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + module Prompts + class SkeletonSystemPrompt < Roseflow::Prompt + def template + plain_raw predirective + plain_raw json_schema + plain_raw directive + end + + def predirective + <<-PROMPT + You are an organizer responsible for determining the skeleton of thought for a superintelligent AI called Deep Thought. + Provide a skeleton in a list of points, numbered 1., 2., 3., etc. to assist Deep Thought in generating comprehensive + high quality answers. Instead of writing a full sentence, each skeleton point should be very short with only 3~6 words. + Generally, a skeleton should have 3~10 points. The skeleton should be as comprehensive as possible, covering all key + points of the answer. The skeleton should be as high quality as possible, with each point being clear and concise. + + Question: + How should an organization approach creating a winning strategy? + + Skeleton: + 1. Understand organizational mission + 2. Analyze market environment + 3. Identify core competencies + 4. SWOT analysis + 5. Define clear objectives + 6. Devise strategic initiatives + 7. Implementation plan + 8. Regular performance evaluation + 9. Adapt to changes + 10. Continual improvement + + Question: What's the best way to replace a carburetor in an engine? + + 1. Acquire correct replacement part + 2. Disconnect battery + 3. Remove air cleaner assembly + 4. Disconnect fuel lines + 5. Unbolt and remove old carburetor + 6. Install new carburetor + 7. Connect fuel lines + 8. Reattach air cleaner assembly + 9. Reconnect battery + 10. Test engine performance + + Question: How do you make a cup of tea? + Skeleton: + 1. Select preferred tea type + 2. Boil water + 3. Pre-warm teapot/cup + 4. Add tea to teapot + 5. Pour hot water + 6. Steep for optimal time + 7. Remove tea leaves/bag + 8. Optional: Add sweeteners/milk + + PROMPT + end + + def directive + <<-PROMPT + Now, please provide a skeleton for the following question. Use provided JSON schema to format your skeleton. Return only JSON. + PROMPT + end + + def json_schema + <<-PROMPT + Produce a JSON output according to the following schema without any formatting or markdown. + + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "question": { + "type": "string" + }, + "skeleton": { + "type": "array", + "items": { + "type": "object", + "properties": { + "order": { + "type": "integer", + "minimum": 1, + "maximum": 10 + }, + "text": { + "type": "string" + } + }, + "required": ["order", "text"] + }, + "minItems": 3, + "maxItems": 10 + } + }, + "required": ["question", "skeleton"] + } + + PROMPT + end + end + end + end +end diff --git a/lib/roseflow/reasoning/skeleton_of_thought.rb b/lib/roseflow/reasoning/skeleton_of_thought.rb new file mode 100644 index 0000000..1e09f53 --- /dev/null +++ b/lib/roseflow/reasoning/skeleton_of_thought.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "roseflow/interaction" +require "roseflow/reasoning/actions/build_skeleton_prompt" +require "roseflow/reasoning/actions/generate_skeleton" +require "roseflow/reasoning/interactions/expand_skeleton_points" +require "roseflow/reasoning/actions/generate_final_answer" + +module Roseflow + module Reasoning + class SkeletonOfThought + extend Roseflow::Interaction + + def self.call(**options) + with(options).reduce(self.actions) + end + + def self.actions + [ + Actions::BuildSkeletonPrompt, + Actions::GenerateSkeleton, + Interactions::ExpandSkeletonPoints, + Actions::GenerateFinalAnswer, + ] + end + end + end +end diff --git a/lib/roseflow/reasoning/skeleton_point.rb b/lib/roseflow/reasoning/skeleton_point.rb new file mode 100644 index 0000000..0cf0535 --- /dev/null +++ b/lib/roseflow/reasoning/skeleton_point.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + class SkeletonPoint < Dry::Struct + transform_keys(&:to_sym) + + attribute :order, Types::Integer + attribute :text, Types::String + + def to_s + "#{order}. #{text}" + end + end + end +end diff --git a/lib/roseflow/reasoning/thought_generator.rb b/lib/roseflow/reasoning/thought_generator.rb new file mode 100644 index 0000000..f7f9802 --- /dev/null +++ b/lib/roseflow/reasoning/thought_generator.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "async" + +module Roseflow + module Reasoning + class ThoughtGenerator + def initialize(model) + @model = model + end + + def max_tokens + 2048 + end + + def temperature + 1.0 + end + + def think(prompt, k) + Sync do |parent| + Array.new(k) do + parent.async do + model.chat(messages: [prompt], max_tokens: max_tokens, temperature: temperature).response + end + end.map(&:wait) + end + end + + private + + attr_reader :model + end + end +end diff --git a/lib/roseflow/reasoning/tree_of_thoughts/prompts/thinking_prompt.rb b/lib/roseflow/reasoning/tree_of_thoughts/prompts/thinking_prompt.rb new file mode 100644 index 0000000..5d1a5f6 --- /dev/null +++ b/lib/roseflow/reasoning/tree_of_thoughts/prompts/thinking_prompt.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module Roseflow + module Reasoning + module Prompts + class ThinkingPrompt < Roseflow::Prompt + def initialize(initial:, state:, rejected: [], react: false) + @initial_prompt = initial + @state_as_text = state + @rejected_solutions = rejected + @react = react + end + + def react_prompting? + !!@react + end + + def template + plain_raw(predirective) + plain_raw(reasoning) if @state_as_text.present? + plain_raw(directive) + plain_raw(rejected_solutions) if @rejected_solutions.any? + plain_raw(no_rejected_solutions) unless @rejected_solutions.any? + plain_raw(post_directive) + plain_raw(react_prompt_string) if react_prompting? + end + + def predirective + <<-PROMPT + You're a superintelligent AI called Deep Thought, devoted to helping Humans by any means necessary. + Your purpose is to generate a series of solutions to comply with the user's instructions. You must + generate solutions on the basis of determining the most reliable solution in the shortest amount of time, + while taking rejected solutions into account and learning from them. + PROMPT + end + + def reasoning + <<-PROMPT + Considering the reasoning provided: + + #{@state_as_text} + + PROMPT + end + + def directive + <<-PROMPT + Devise the best possible solution for the task: + + #{@initial_prompt} + + PROMPT + end + + def rejected_solutions + <<-PROMPT + Here are evaluated solutions that were rejected: + + #{@rejected_solutions} + + PROMPT + end + + def no_rejected_solutions + <<-PROMPT + No solutions have yet been generated and rejected. + PROMPT + end + + def post_directive + <<-PROMPT + Complete the "#{@initial_prompt}" without making the same mistakes you did with the evaluated rejected + solutions. Be simple. Be direct. Provide intuitive solutions as soon as you think of them. + PROMPT + end + + def react_prompt_string + <<-PROMPT + Write down your observations in format '[Observation]:xxxx', + then write down your thoughts in format '[Thoughts]:xxxx'. + PROMPT + end + end + end + end +end diff --git a/lib/roseflow/registry.rb b/lib/roseflow/registry.rb new file mode 100644 index 0000000..0b9a435 --- /dev/null +++ b/lib/roseflow/registry.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Roseflow + class UnknownRegistryKeyError < StandardError; end + + class Registry + include Singleton + + def initialize + @store = {} + end + + def keys + @store.keys + end + + def register(key, value) + @store.store(key, value) + end + + def get(key) + @store.fetch(key) { initialize_instance(key) || raise(UnknownRegistryKeyError, "Unknown registry key #{key}") } + end + + def self.get(key) + instance.get(key) + end + + def self.register(name, value) + instance.register(name, value) + end + + private + + def initialize_instance(key) + case key + when :providers + register(:providers, ProviderRepository.new) + when :models + register(:models, ModelRepository.new) + when :default_model + register(:default_model, AI::Model.load("gpt-3.5-turbo")) + end + end + end +end diff --git a/lib/roseflow/types.rb b/lib/roseflow/types.rb index 893f4c0..ef51390 100644 --- a/lib/roseflow/types.rb +++ b/lib/roseflow/types.rb @@ -6,4 +6,5 @@ module Types include Dry.Types() Number = Types::Float | Types::Integer + StringOrNil = Types::String | Types::Nil end diff --git a/lib/roseflow/version.rb b/lib/roseflow/version.rb index a4cfc64..7508609 100644 --- a/lib/roseflow/version.rb +++ b/lib/roseflow/version.rb @@ -7,8 +7,8 @@ def self.gem_version module VERSION MAJOR = 0 - MINOR = 0 - PATCH = 1 + MINOR = 2 + PATCH = 0 PRE = nil STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".") diff --git a/roseflow.gemspec b/roseflow.gemspec index aa4c9d3..b9e325a 100644 --- a/roseflow.gemspec +++ b/roseflow.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |spec| # Uncomment to register a new dependency of your gem spec.add_dependency "activesupport", "~> 7.0" spec.add_dependency "activemodel", "~> 7.0" + spec.add_dependency "async", "~> 2.6.0" spec.add_dependency "anyway_config", "~> 2.0" spec.add_dependency "dry-struct", "~> 1.6" spec.add_dependency "dry-validation", "~> 1.10" @@ -47,7 +48,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency "pragmatic_segmenter", "~> 0.3" spec.add_development_dependency "roseflow-openai", "~> 0.1.0" + spec.add_development_dependency "roseflow-openrouter", "~> 0.1.0" spec.add_development_dependency "roseflow-pinecone", "~> 0.1.0" spec.add_development_dependency "webmock" spec.add_development_dependency "vcr" + spec.add_development_dependency "coveralls_reborn" + spec.add_development_dependency "simplecov" end diff --git a/spec/actions/ai/resolve_model_spec.rb b/spec/actions/ai/resolve_model_spec.rb index 985ffd7..62f284f 100644 --- a/spec/actions/ai/resolve_model_spec.rb +++ b/spec/actions/ai/resolve_model_spec.rb @@ -12,17 +12,19 @@ module AI describe "#call" do context "can be called" do - let(:provider) { instance_double("Roseflow::Provider") } + let(:provider) { Registry.get(:providers).find(:openai) } let(:ctx) { Roseflow::InteractionContext.make(provider: provider, model: model) } it "calls the action" do - expect(action).to receive(:execute) - action.execute(ctx) + VCR.use_cassette("ai/resolve_model") do + expect(action).to receive(:execute) + action.execute(ctx) + end end end context "when the model is found" do - let(:provider) { instance_double("Roseflow::Provider") } + let(:provider) { Registry.get(:providers).find(:openai) } let(:ctx) { Roseflow::InteractionContext.make(provider: provider, model: model) } before(:each) do @@ -32,28 +34,28 @@ module AI end it "returns a result" do - result = described_class.execute(ctx) - expect(result).to be_a Roseflow::InteractionContext + VCR.use_cassette("ai/resolve_model") do + result = described_class.execute(ctx) + expect(result).to be_a Roseflow::InteractionContext + end end it "adds a new LLM to the context" do - result = action.execute(ctx) - expect(result.llm).to be_a Roseflow::AI::Model + VCR.use_cassette("ai/resolve_model") do + result = action.execute(ctx) + expect(result.llm).to be_a Roseflow::AI::Model + end end end context "when the model is not found" do - let(:provider) { instance_double("Roseflow::Provider") } - let(:ctx) { Roseflow::InteractionContext.make(provider: provider, model: model) } - - before(:each) do - models = [double(name: "gpt-10.5-pro")] - allow(provider).to receive(:models).and_return(models) - allow(models).to receive(:find).with(model).and_return(nil) - end + let(:provider) { Registry.get(:providers).find(:openai) } + let(:ctx) { Roseflow::InteractionContext.make(provider: provider, model: "gpt-10") } it "the context fails" do - expect(action.execute(ctx)).to be_failure + VCR.use_cassette("ai/resolve_model") do + expect { action.execute(ctx) }.to raise_error Roseflow::ModelRepository::ModelNotFoundError + end end end end diff --git a/spec/chat/dialogue_spec.rb b/spec/chat/dialogue_spec.rb index 7b598bd..7d01b24 100644 --- a/spec/chat/dialogue_spec.rb +++ b/spec/chat/dialogue_spec.rb @@ -4,11 +4,6 @@ require "roseflow/chat/personality" require "roseflow/chat/message" -VCR.configure do |config| - config.filter_sensitive_data("") { Roseflow::OpenAI::Config.new.api_key } - config.filter_sensitive_data("") { Roseflow::OpenAI::Config.new.organization_id } -end - module Roseflow module Chat RSpec.describe Dialogue do diff --git a/spec/fixtures/vcr/ai/resolve_model.yml b/spec/fixtures/vcr/ai/resolve_model.yml new file mode 100644 index 0000000..1e0892c --- /dev/null +++ b/spec/fixtures/vcr/ai/resolve_model.yml @@ -0,0 +1,59 @@ +--- +http_interactions: +- request: + method: get + uri: https://openrouter.ai/api/v1/models + body: + encoding: US-ASCII + string: '' + headers: + Http-Referer: + - https://roseflow.ai + User-Agent: + - Faraday v2.7.10 + Authorization: + - Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Headers: + - Authorization, X-Api-Key, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, + Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, HTTP-Referer, + X-Windowai-Title, X-Openrouter-Title, X-Title + Access-Control-Allow-Methods: + - GET,OPTIONS,PATCH,DELETE,POST,PUT + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - public, max-age=0, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 02 Aug 2023 08:04:04 GMT + Server: + - Vercel + Strict-Transport-Security: + - max-age=63072000 + X-Matched-Path: + - "/api/v1/models" + X-Vercel-Cache: + - MISS + X-Vercel-Id: + - arn1::g9nv9-1690963443122-b75e2c397511 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"data":[{"id":"openai/gpt-3.5-turbo","pricing":{"prompt":"0.0000015","completion":"0.000002"},"context_length":4095,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"openai/gpt-3.5-turbo-0301","pricing":{"prompt":"0.0000015","completion":"0.000002"},"context_length":4095,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"openai/gpt-3.5-turbo-16k","pricing":{"prompt":"0.000003","completion":"0.000004"},"context_length":16383,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"openai/gpt-4","pricing":{"prompt":"0.00003","completion":"0.00006"},"context_length":8191,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"openai/gpt-4-0314","pricing":{"prompt":"0.00003","completion":"0.00006"},"context_length":8191,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"openai/gpt-4-32k","pricing":{"prompt":"0.00006","completion":"0.00012"},"context_length":32767,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"openai/text-davinci-002","pricing":{"prompt":"0.00002","completion":"0.00002"},"context_length":4095,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"anthropic/claude-2","pricing":{"prompt":"0.00001102","completion":"0.00003268"},"context_length":100000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"anthropic/claude-instant-v1","pricing":{"prompt":"0.00000163","completion":"0.00000551"},"context_length":100000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"anthropic/claude-v1","pricing":{"prompt":"0.00001102","completion":"0.00003268"},"context_length":9000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"anthropic/claude-1.2","pricing":{"prompt":"0.00001102","completion":"0.00003268"},"context_length":9000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"anthropic/claude-instant-v1-100k","pricing":{"prompt":"0.00000163","completion":"0.00000551"},"context_length":100000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"anthropic/claude-v1-100k","pricing":{"prompt":"0.00001102","completion":"0.00003268"},"context_length":100000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"anthropic/claude-instant-1.0","pricing":{"prompt":"0.00000163","completion":"0.00000551"},"context_length":9000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"google/palm-2-chat-bison","pricing":{"prompt":"0","completion":"0"},"context_length":8000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"google/palm-2-codechat-bison","pricing":{"prompt":"0","completion":"0"},"context_length":8000,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"meta-llama/llama-2-13b-chat","pricing":{"prompt":"0.000004","completion":"0.000004"},"context_length":4096,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}},{"id":"meta-llama/llama-2-70b-chat","pricing":{"prompt":"0.000016","completion":"0.000016"},"context_length":4096,"input_limits":{"prompt_tokens":1000000,"max_tokens":1000000}}]}' + recorded_at: Wed, 02 Aug 2023 08:04:04 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 18e9ebb..8c4720f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,17 @@ require "roseflow" require "webmock/rspec" require "vcr" +require "coveralls" +require "simplecov" + +# Coveralls.wear! +SimpleCov.start + +VCR.configure do |config| + config.filter_sensitive_data("") { Roseflow::OpenAI::Config.new.api_key } + config.filter_sensitive_data("") { Roseflow::OpenAI::Config.new.organization_id } + config.filter_sensitive_data("") { Roseflow::OpenRouter::Config.new.api_key } +end Dir[File.join(File.dirname(__FILE__), "support", "**", "*.rb")].sort.each { |f| require f }