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 +4 -4
- data/CHANGELOG.md +3 -1
- data/Gemfile +2 -0
- data/lib/roseflow/open_router/chat_message.rb +18 -0
- data/lib/roseflow/open_router/client.rb +22 -9
- data/lib/roseflow/open_router/model.rb +34 -3
- data/lib/roseflow/open_router/operation_handler.rb +30 -0
- data/lib/roseflow/open_router/operations/base.rb +15 -0
- data/lib/roseflow/open_router/operations/completion.rb +37 -0
- data/lib/roseflow/open_router/provider.rb +5 -7
- data/lib/roseflow/open_router/response.rb +114 -0
- data/lib/roseflow/open_router/version.rb +1 -1
- data/lib/roseflow/open_router.rb +2 -0
- data/roseflow-openrouter.gemspec +1 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba6bf09e77bd3ffe1bfa32ba78f30a22bd8ee2d938da8c70f4a460440f0b636a
|
4
|
+
data.tar.gz: ec10261de4f4ca103b970a2c0b9852ca85902d473f4115feb825609a3ee521d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b848cd7df7ba6ad3ca2db234ddbf189d3f9df181775156751dfe729e291df792d5d9330ad3a5ed02b9a51143764fdb082892ae503a15a3038c53bed07c24a530
|
7
|
+
data.tar.gz: 1009537eb7d61cfce5f9e3935fd018547f939be964ce7d57a6ee80ce0546fa223ec8edc566c73366620eee6b28437aca9afdda920d665e738b34ec16cc8e6bcd
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -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 =
|
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
|
-
|
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(
|
29
|
-
@id = @model_.fetch(
|
30
|
-
@context_length = @model_.fetch(
|
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,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
|
-
|
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
|
data/lib/roseflow/open_router.rb
CHANGED
@@ -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
|
data/roseflow-openrouter.gemspec
CHANGED
@@ -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.
|
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-
|
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
|