roseflow-openrouter 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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