roseflow-openrouter 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87a20a3c7e3eabe923827fb046a4d418901e2719d845244b3cd6488301044a89
4
- data.tar.gz: b0b3cc0894bba52d00d95b75ec093a2c86cf1621d2bc9f7849de1a6c61b9b754
3
+ metadata.gz: ba6bf09e77bd3ffe1bfa32ba78f30a22bd8ee2d938da8c70f4a460440f0b636a
4
+ data.tar.gz: ec10261de4f4ca103b970a2c0b9852ca85902d473f4115feb825609a3ee521d1
5
5
  SHA512:
6
- metadata.gz: e393acceab954ba51cec59a2dd518928c9e9e3fcca4ec2f77475e1c4dc9b9caab8cd318b23f7d9026fbe8343e8cdf104d5f9f5d80a78e9505fe686444b6f6604
7
- data.tar.gz: d026c4386e835b3841e47e8c14ad913d25413bf82d3b1c6506c5c596017001936bc13926e44fa905cb0b36c476053f5ce6c5070d47ef7adf8009cc6ac305d78b
6
+ metadata.gz: b848cd7df7ba6ad3ca2db234ddbf189d3f9df181775156751dfe729e291df792d5d9330ad3a5ed02b9a51143764fdb082892ae503a15a3038c53bed07c24a530
7
+ data.tar.gz: 1009537eb7d61cfce5f9e3935fd018547f939be964ce7d57a6ee80ce0546fa223ec8edc566c73366620eee6b28437aca9afdda920d665e738b34ec16cc8e6bcd
data/CHANGELOG.md CHANGED
@@ -1,4 +1,6 @@
1
- ## [Unreleased]
1
+ ## [0.2.0] - 2023-07-24
2
+
3
+ - Model API changes to match Roseflow::AI::Model API spec.
2
4
 
3
5
  ## [0.1.0] - 2023-07-18
4
6
 
data/Gemfile CHANGED
@@ -8,3 +8,5 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
  gem "rspec", "~> 3.0"
10
10
  gem "standard", "~> 1.3"
11
+
12
+ eval_gemfile "Gemfile.local" if File.exist?("Gemfile.local")
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/chat/message"
4
+
5
+ module Types
6
+ module OpenAI
7
+ FunctionCallObject = Types::Hash
8
+ StringOrObject = Types::String | FunctionCallObject
9
+ StringOrArray = Types::String | Types::Array
10
+ end
11
+ end
12
+
13
+ module Roseflow
14
+ module OpenRouter
15
+ class ChatMessage < Roseflow::Chat::Message
16
+ end
17
+ end
18
+ end
@@ -4,17 +4,18 @@ require "faraday"
4
4
  require "faraday/retry"
5
5
  require "roseflow/open_router/config"
6
6
  require "roseflow/open_router/model"
7
-
8
- FARADAY_RETRY_OPTIONS = {
9
- max: 3,
10
- interval: 0.05,
11
- interval_randomness: 0.5,
12
- backoff_factor: 2,
13
- }
7
+ require "fast_jsonparser"
14
8
 
15
9
  module Roseflow
16
10
  module OpenRouter
17
11
  class Client
12
+ FARADAY_RETRY_OPTIONS = {
13
+ max: 3,
14
+ interval: 0.05,
15
+ interval_randomness: 0.5,
16
+ backoff_factor: 2,
17
+ }
18
+
18
19
  def initialize(config = Config.new, provider = nil)
19
20
  @config = config
20
21
  @provider = provider
@@ -22,12 +23,24 @@ module Roseflow
22
23
 
23
24
  def models
24
25
  response = connection.get("/api/v1/models")
25
- body = JSON.parse(response.body)
26
+ body = FastJsonparser.parse(response.body)
26
27
  body.fetch("data", []).map do |model|
27
28
  OpenRouter::Model.new(model, self)
28
29
  end
29
30
  end
30
31
 
32
+ def post(operation, &block)
33
+ connection.post(operation.path) do |request|
34
+ request.body = operation.body
35
+
36
+ if operation.stream
37
+ request.options.on_data = Proc.new do |chunk|
38
+ yield chunk
39
+ end
40
+ end
41
+ end
42
+ end
43
+
31
44
  def completion(model:, prompt:, **options)
32
45
  connection.post("/api/v1/chat/completions") do |request|
33
46
  request.body = options.merge({
@@ -84,7 +97,7 @@ module Roseflow
84
97
  return chunk unless chunk.match(/{.*}/)
85
98
 
86
99
  chunk.scan(/{.*}/).map do |json|
87
- JSON.parse(json).dig("choices", 0, "delta", "content")
100
+ FastJsonparser.parse(json).dig("choices", 0, "delta", "content")
88
101
  end.join("")
89
102
  end
90
103
  end
@@ -3,6 +3,9 @@
3
3
  require "dry/struct"
4
4
  require "active_support/core_ext/module/delegation"
5
5
 
6
+ require "roseflow/open_router/operation_handler"
7
+ require "roseflow/open_router/response"
8
+
6
9
  module Types
7
10
  include Dry.Types()
8
11
  end
@@ -22,12 +25,40 @@ module Roseflow
22
25
  @context_length
23
26
  end
24
27
 
28
+ # Convenience method for chat completions.
29
+ #
30
+ # @param messages [Array<String>] Messages to use
31
+ # @param options [Hash] Options to use
32
+ # @yield [chunk] Chunk of data if stream is enabled
33
+ # @return [OpenAI::ChatResponse] the chat response object if no block is given
34
+ def chat(messages, options = {}, &block)
35
+ response = call(:completion, options.merge({ messages: messages, model: name }), &block)
36
+ ChatResponse.new(response) unless block_given?
37
+ end
38
+
39
+ # Calls the model.
40
+ #
41
+ # @param operation [Symbol] Operation to perform
42
+ # @param options [Hash] Options to use
43
+ # @yield [chunk] Chunk of data if stream is enabled
44
+ # @return [Faraday::Response] raw API response if no block is given
45
+ def call(operation, options, &block)
46
+ operation = OperationHandler.new(operation, options.merge({ model: name })).call
47
+ client.post(operation, &block)
48
+ end
49
+
25
50
  private
26
51
 
52
+ attr_reader :provider_
53
+
27
54
  def assign_attributes
28
- @name = @model_.fetch("id")
29
- @id = @model_.fetch("id")
30
- @context_length = @model_.fetch("context_length")
55
+ @name = @model_.fetch(:id)
56
+ @id = @model_.fetch(:id)
57
+ @context_length = @model_.fetch(:context_length)
58
+ end
59
+
60
+ def client
61
+ provider_.client
31
62
  end
32
63
  end
33
64
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operations/completion"
4
+
5
+ module Roseflow
6
+ module OpenRouter
7
+ class OperationHandler
8
+ OPERATION_CLASSES = {
9
+ completion: Operations::Completion,
10
+ }
11
+
12
+ def initialize(operation, options = {})
13
+ @operation = operation
14
+ @options = options
15
+ end
16
+
17
+ def call
18
+ operation_class.new(@options)
19
+ end
20
+
21
+ private
22
+
23
+ def operation_class
24
+ OPERATION_CLASSES.fetch(@operation) do
25
+ raise ArgumentError, "Invalid operation: #{@operation}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module OpenRouter
5
+ module Operations
6
+ class Base < Dry::Struct
7
+ transform_keys(&:to_sym)
8
+
9
+ def body
10
+ to_h.except(:path)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "roseflow/open_router/chat_message"
5
+
6
+ module Roseflow
7
+ module OpenRouter
8
+ module Operations
9
+ # Chat operation.
10
+ #
11
+ # Given a list of messages comprising a conversation, the model will
12
+ # return a response.
13
+ #
14
+ # See https://openrouter.ai/docs for more information.
15
+ #
16
+ # The OpenRouter API is compatible with OpenAI Chat API. See
17
+ # https://platform.openai.com/docs/api-reference/chat/create for more.
18
+ class Completion < Base
19
+ attribute? :model, Types::String
20
+ attribute :messages, Types::Array.of(ChatMessage)
21
+ attribute? :functions, Types::Array.of(Types::Hash)
22
+ attribute? :function_call, Types::OpenAI::StringOrObject
23
+ attribute :temperature, Types::Float.default(1.0)
24
+ attribute :top_p, Types::Float.default(1.0)
25
+ attribute :n, Types::Integer.default(1)
26
+ attribute :stream, Types::Bool.default(false)
27
+ attribute? :stop, Types::OpenAI::StringOrArray
28
+ attribute? :max_tokens, Types::Integer
29
+ attribute :presence_penalty, Types::Number.default(0)
30
+ attribute :frequency_penalty, Types::Number.default(0)
31
+ attribute? :user, Types::String
32
+
33
+ attribute :path, Types::String.default("/api/v1/chat/completions")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "roseflow/open_router/client"
4
+ require "roseflow/open_router/config"
5
+ require "roseflow/open_router/model_repository"
6
+
3
7
  module Roseflow
4
8
  module OpenRouter
5
9
  class Provider
@@ -16,13 +20,7 @@ module Roseflow
16
20
  end
17
21
 
18
22
  def completion(model:, prompt:, **options)
19
- streaming = options.fetch(:streaming, false)
20
-
21
- if streaming
22
- client.streaming_completion(model: model, prompt: prompt, **options)
23
- else
24
- client.completion(model: model, prompt: prompt, **options)
25
- end
23
+ raise DeprecationError, "This method is deprecated. Please use Model operations instead."
26
24
  end
27
25
 
28
26
  private
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/struct"
4
+ require "active_support/core_ext/module/delegation"
5
+
6
+ require "roseflow/types"
7
+ require "fast_jsonparser"
8
+
9
+ module Roseflow
10
+ module OpenRouter
11
+ class ApiResponse
12
+ delegate :success?, :failure?, :body, to: :@response
13
+
14
+ def initialize(response)
15
+ @response = response
16
+ end
17
+ end
18
+
19
+ class TextApiResponse < ApiResponse
20
+ def body
21
+ @body ||= FastJsonparser.parse(@response.body)
22
+ end
23
+
24
+ def choices
25
+ body[:choices].map { |choice| Choice.new(choice) }
26
+ end
27
+ end
28
+
29
+ class ChatResponse < TextApiResponse
30
+ def response
31
+ choices.first
32
+ end
33
+
34
+ alias reply response
35
+ end
36
+
37
+ class Choice < Dry::Struct
38
+ transform_keys(&:to_sym)
39
+
40
+ attribute? :text, Types::String
41
+ attribute? :message do
42
+ attribute :role, Types::String
43
+ attribute :content, Types::String
44
+ end
45
+
46
+ attribute? :finish_reason, Types::String
47
+ attribute :index, Types::Integer
48
+
49
+ def to_s
50
+ return message.content if message
51
+ return text if text
52
+ end
53
+ end # Choice
54
+
55
+ class DataOnlyEvent < Dry::Struct
56
+ transform_keys(&:to_sym)
57
+ end
58
+
59
+ class Delta < Dry::Struct
60
+ transform_keys(&:to_sym)
61
+
62
+ attribute :content, Types::String
63
+ end
64
+
65
+ class StreamingChoice < Dry::Struct
66
+ transform_keys(&:to_sym)
67
+
68
+ attribute :index, Types::Integer
69
+ attribute :delta, Delta
70
+ attribute :finish_reason, Types::StringOrNil
71
+ end
72
+
73
+ class StreamingDone < Dry::Struct
74
+ end
75
+
76
+ class StreamingData < Dry::Struct
77
+ transform_keys(&:to_sym)
78
+
79
+ attribute :choices, Types::Array.of(StreamingChoice)
80
+ attribute :model, Types::String
81
+
82
+ def self.from_chunk(data)
83
+ return StreamingDone.new if data == "[DONE]"
84
+ new(FastJsonparser.parse(data.strip))
85
+ rescue StandardError => e
86
+ puts "FAILED: #{data}"
87
+ puts "EXCEPTION: #{e.message}"
88
+ end
89
+ end
90
+
91
+ class ApiUsage < Dry::Struct
92
+ transform_keys(&:to_sym)
93
+
94
+ attribute :prompt_tokens, Types::Integer
95
+ attribute? :completion_tokens, Types::Integer
96
+ attribute :total_tokens, Types::Integer
97
+ end # ApiUsage
98
+
99
+ class ApiResponseBody < Dry::Struct
100
+ transform_keys(&:to_sym)
101
+
102
+ attribute? :id, Types::String
103
+ attribute :object, Types::String
104
+ attribute :created, Types::Integer
105
+ attribute? :model, Types::String
106
+ attribute :usage, ApiUsage
107
+ attribute :choices, Types::Array
108
+
109
+ def success?
110
+ true
111
+ end
112
+ end # ApiResponseBody
113
+ end
114
+ end
@@ -8,7 +8,7 @@ module Roseflow
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 0
11
- MINOR = 1
11
+ MINOR = 2
12
12
  PATCH = 0
13
13
  PRE = nil
14
14
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "roseflow/types"
3
4
  require_relative "open_router/version"
4
5
  require_relative "open_router/client"
5
6
  require_relative "open_router/config"
@@ -10,5 +11,6 @@ require_relative "open_router/provider"
10
11
  module Roseflow
11
12
  module OpenRouter
12
13
  class Error < StandardError; end
14
+ class DeprecationError < StandardError; end
13
15
  end
14
16
  end
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "dry-struct"
35
35
  spec.add_dependency "faraday"
36
36
  spec.add_dependency "faraday-retry"
37
+ spec.add_dependency "fast_jsonparser", "~> 0.6.0"
37
38
 
38
39
  spec.add_development_dependency "awesome_print"
39
40
  spec.add_development_dependency "pry"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roseflow-openrouter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lauri Jutila
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-19 00:00:00.000000000 Z
11
+ date: 2023-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: fast_jsonparser
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.6.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.6.0
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: awesome_print
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -167,11 +181,16 @@ files:
167
181
  - Rakefile
168
182
  - config/openrouter.yml
169
183
  - lib/roseflow/open_router.rb
184
+ - lib/roseflow/open_router/chat_message.rb
170
185
  - lib/roseflow/open_router/client.rb
171
186
  - lib/roseflow/open_router/config.rb
172
187
  - lib/roseflow/open_router/model.rb
173
188
  - lib/roseflow/open_router/model_repository.rb
189
+ - lib/roseflow/open_router/operation_handler.rb
190
+ - lib/roseflow/open_router/operations/base.rb
191
+ - lib/roseflow/open_router/operations/completion.rb
174
192
  - lib/roseflow/open_router/provider.rb
193
+ - lib/roseflow/open_router/response.rb
175
194
  - lib/roseflow/open_router/version.rb
176
195
  - roseflow-openrouter.gemspec
177
196
  - sig/roseflow/openrouter.rbs