rasti-ai 3.1.0 → 3.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/.features/api-normalization/api-design.md +184 -0
- data/.features/api-normalization/overview.md +230 -0
- data/AGENTS.md +125 -15
- data/README.md +143 -19
- data/lib/rasti/ai/anthropic/assistant.rb +1 -11
- data/lib/rasti/ai/anthropic/client.rb +6 -28
- data/lib/rasti/ai/anthropic/provider.rb +87 -0
- data/lib/rasti/ai/assistant.rb +10 -11
- data/lib/rasti/ai/client.rb +13 -10
- data/lib/rasti/ai/errors.rb +8 -0
- data/lib/rasti/ai/gemini/assistant.rb +1 -11
- data/lib/rasti/ai/gemini/client.rb +2 -24
- data/lib/rasti/ai/gemini/provider.rb +90 -0
- data/lib/rasti/ai/huawei_maas/assistant.rb +0 -7
- data/lib/rasti/ai/huawei_maas/client.rb +0 -19
- data/lib/rasti/ai/huawei_maas/provider.rb +25 -0
- data/lib/rasti/ai/huawei_maas/roles.rb +1 -8
- data/lib/rasti/ai/open_ai/assistant.rb +0 -4
- data/lib/rasti/ai/open_ai/client.rb +3 -33
- data/lib/rasti/ai/open_ai/provider.rb +74 -0
- data/lib/rasti/ai/open_router/assistant.rb +0 -7
- data/lib/rasti/ai/open_router/client.rb +0 -19
- data/lib/rasti/ai/open_router/provider.rb +25 -0
- data/lib/rasti/ai/open_router/roles.rb +1 -8
- data/lib/rasti/ai/provider.rb +197 -0
- data/lib/rasti/ai/provider_aware.rb +17 -0
- data/lib/rasti/ai/result.rb +11 -0
- data/lib/rasti/ai/roles.rb +11 -0
- data/lib/rasti/ai/version.rb +1 -1
- data/lib/rasti/ai.rb +93 -0
- data/spec/anthropic/provider_spec.rb +106 -0
- data/spec/gemini/provider_spec.rb +90 -0
- data/spec/open_ai/provider_spec.rb +101 -0
- data/spec/rasti_ai_spec.rb +322 -0
- data/spec/resources/open_ai/conversation_request.json +1 -0
- data/spec/resources/open_ai/system_request.json +1 -0
- metadata +25 -2
|
@@ -2,25 +2,6 @@ module Rasti
|
|
|
2
2
|
module AI
|
|
3
3
|
module HuaweiMaaS
|
|
4
4
|
class Client < Rasti::AI::OpenAI::Client
|
|
5
|
-
|
|
6
|
-
private
|
|
7
|
-
|
|
8
|
-
def default_api_key
|
|
9
|
-
Rasti::AI.huawei_maas_api_key
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def default_model
|
|
13
|
-
Rasti::AI.huawei_maas_default_model
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def provider_name
|
|
17
|
-
'huawei_maas'
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def base_url
|
|
21
|
-
'https://api-ap-southeast-1.modelarts-maas.com/v2'
|
|
22
|
-
end
|
|
23
|
-
|
|
24
5
|
end
|
|
25
6
|
end
|
|
26
7
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Rasti
|
|
2
|
+
module AI
|
|
3
|
+
module HuaweiMaaS
|
|
4
|
+
class Provider < Rasti::AI::OpenAI::Provider
|
|
5
|
+
|
|
6
|
+
def name
|
|
7
|
+
:huawei_maas
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def default_model
|
|
11
|
+
Rasti::AI.huawei_maas_default_model
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def default_api_key
|
|
15
|
+
Rasti::AI.huawei_maas_api_key
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def base_url
|
|
19
|
+
'https://api-ap-southeast-1.modelarts-maas.com/v2'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -11,44 +11,14 @@ module Rasti
|
|
|
11
11
|
tool_choice: tools.empty? ? 'none' : 'auto'
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
body[:response_format]
|
|
15
|
-
body[:reasoning_effort]
|
|
14
|
+
body[:response_format] = response_format if response_format
|
|
15
|
+
body[:reasoning_effort] = reasoning_effort if reasoning_effort
|
|
16
16
|
|
|
17
17
|
post '/chat/completions', body
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
def parse_usage(response)
|
|
23
|
-
usage = response['usage']
|
|
24
|
-
return unless usage
|
|
25
|
-
Usage.new(
|
|
26
|
-
provider: provider_name,
|
|
27
|
-
model: response['model'],
|
|
28
|
-
input_tokens: usage['prompt_tokens'],
|
|
29
|
-
output_tokens: usage['completion_tokens'],
|
|
30
|
-
cached_tokens: usage.dig('prompt_tokens_details', 'cached_tokens') || 0,
|
|
31
|
-
reasoning_tokens: usage.dig('completion_tokens_details', 'reasoning_tokens') || 0,
|
|
32
|
-
raw: usage
|
|
33
|
-
)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def default_api_key
|
|
37
|
-
Rasti::AI.openai_api_key
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def default_model
|
|
41
|
-
Rasti::AI.openai_default_model
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def provider_name
|
|
45
|
-
'open_ai'
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def base_url
|
|
49
|
-
'https://api.openai.com/v1'
|
|
50
|
-
end
|
|
51
|
-
|
|
52
22
|
def build_request(uri)
|
|
53
23
|
request = super
|
|
54
24
|
request['Authorization'] = "Bearer #{api_key}"
|
|
@@ -58,4 +28,4 @@ module Rasti
|
|
|
58
28
|
end
|
|
59
29
|
end
|
|
60
30
|
end
|
|
61
|
-
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Rasti
|
|
2
|
+
module AI
|
|
3
|
+
module OpenAI
|
|
4
|
+
class Provider < Rasti::AI::Provider
|
|
5
|
+
|
|
6
|
+
THINKING_LEVELS = {
|
|
7
|
+
'low' => 'low'.freeze,
|
|
8
|
+
'medium' => 'medium'.freeze,
|
|
9
|
+
'high' => 'high'.freeze
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
:open_ai
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def default_model
|
|
17
|
+
Rasti::AI.openai_default_model
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def default_api_key
|
|
21
|
+
Rasti::AI.openai_api_key
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def base_url
|
|
25
|
+
'https://api.openai.com/v1'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_usage(response)
|
|
29
|
+
usage = response['usage']
|
|
30
|
+
return nil unless usage
|
|
31
|
+
|
|
32
|
+
Usage.new(
|
|
33
|
+
provider: name.to_s,
|
|
34
|
+
model: response['model'],
|
|
35
|
+
input_tokens: usage['prompt_tokens'],
|
|
36
|
+
output_tokens: usage['completion_tokens'],
|
|
37
|
+
cached_tokens: usage.dig('prompt_tokens_details', 'cached_tokens') || 0,
|
|
38
|
+
reasoning_tokens: usage.dig('completion_tokens_details', 'reasoning_tokens') || 0,
|
|
39
|
+
raw: usage
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def request(client:, messages:, system:, model:, json_schema:, thinking:)
|
|
46
|
+
all_messages = system ? [{role: Roles::SYSTEM, content: system}] + messages : messages
|
|
47
|
+
|
|
48
|
+
client.chat_completions messages: all_messages,
|
|
49
|
+
model: model,
|
|
50
|
+
response_format: response_format(json_schema),
|
|
51
|
+
reasoning_effort: thinking
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def encode_message(message)
|
|
55
|
+
{role: message[:role], content: message[:content]}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def parse_content(response)
|
|
59
|
+
response.dig 'choices', 0, 'message', 'content'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def response_format(json_schema)
|
|
63
|
+
return nil if json_schema.nil?
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
type: 'json_schema',
|
|
67
|
+
json_schema: json_schema
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -2,25 +2,6 @@ module Rasti
|
|
|
2
2
|
module AI
|
|
3
3
|
module OpenRouter
|
|
4
4
|
class Client < Rasti::AI::OpenAI::Client
|
|
5
|
-
|
|
6
|
-
private
|
|
7
|
-
|
|
8
|
-
def default_api_key
|
|
9
|
-
Rasti::AI.openrouter_api_key
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def default_model
|
|
13
|
-
Rasti::AI.openrouter_default_model
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def provider_name
|
|
17
|
-
'open_router'
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def base_url
|
|
21
|
-
'https://openrouter.ai/api/v1'
|
|
22
|
-
end
|
|
23
|
-
|
|
24
5
|
end
|
|
25
6
|
end
|
|
26
7
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Rasti
|
|
2
|
+
module AI
|
|
3
|
+
module OpenRouter
|
|
4
|
+
class Provider < Rasti::AI::OpenAI::Provider
|
|
5
|
+
|
|
6
|
+
def name
|
|
7
|
+
:open_router
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def default_model
|
|
11
|
+
Rasti::AI.openrouter_default_model
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def default_api_key
|
|
15
|
+
Rasti::AI.openrouter_api_key
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def base_url
|
|
19
|
+
'https://openrouter.ai/api/v1'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
module Rasti
|
|
2
|
+
module AI
|
|
3
|
+
class Provider
|
|
4
|
+
|
|
5
|
+
MODULES = {
|
|
6
|
+
open_ai: 'OpenAI',
|
|
7
|
+
gemini: 'Gemini',
|
|
8
|
+
anthropic: 'Anthropic',
|
|
9
|
+
open_router: 'OpenRouter',
|
|
10
|
+
huawei_maas: 'HuaweiMaaS'
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
ALIASES = {
|
|
14
|
+
openai: :open_ai,
|
|
15
|
+
openrouter: :open_router,
|
|
16
|
+
huawei: :huawei_maas
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
|
|
21
|
+
def build(name, **options)
|
|
22
|
+
return name if name.is_a? Provider
|
|
23
|
+
provider_class(name).new(**options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def names
|
|
27
|
+
MODULES.keys
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def provider_class(name)
|
|
33
|
+
key = normalize name
|
|
34
|
+
key = ALIASES.fetch key, key
|
|
35
|
+
|
|
36
|
+
raise Errors::UnknownProvider.new(name, names) unless MODULES.key? key
|
|
37
|
+
|
|
38
|
+
AI.const_get(MODULES[key])::Provider
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def normalize(name)
|
|
42
|
+
name.to_s.downcase.to_sym
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :model
|
|
48
|
+
|
|
49
|
+
def initialize(model:nil, api_key:nil, usage_tracker:nil, logger:nil,
|
|
50
|
+
http_connect_timeout:nil, http_read_timeout:nil, http_max_retries:nil)
|
|
51
|
+
|
|
52
|
+
@model = model || default_model
|
|
53
|
+
@api_key = api_key || default_api_key
|
|
54
|
+
@usage_tracker = usage_tracker
|
|
55
|
+
@logger = logger
|
|
56
|
+
@http_connect_timeout = http_connect_timeout
|
|
57
|
+
@http_read_timeout = http_read_timeout
|
|
58
|
+
@http_max_retries = http_max_retries
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def name
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def default_model
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def default_api_key
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def base_url
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse_usage(response)
|
|
78
|
+
raise NotImplementedError
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def build_client
|
|
82
|
+
client_class.new(
|
|
83
|
+
provider: self,
|
|
84
|
+
api_key: api_key,
|
|
85
|
+
logger: logger,
|
|
86
|
+
usage_tracker: usage_tracker,
|
|
87
|
+
http_connect_timeout: http_connect_timeout,
|
|
88
|
+
http_read_timeout: http_read_timeout,
|
|
89
|
+
http_max_retries: http_max_retries
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def create_assistant(state:nil, system:nil, model:nil, tools:[], mcp_servers:{},
|
|
94
|
+
json_schema:nil, thinking:nil, client:nil)
|
|
95
|
+
|
|
96
|
+
raise ArgumentError, 'Use state or system, not both' if state && system
|
|
97
|
+
|
|
98
|
+
state = AssistantState.new(context: system) if state.nil? && system
|
|
99
|
+
|
|
100
|
+
assistant_class.new(
|
|
101
|
+
provider: self,
|
|
102
|
+
client: client,
|
|
103
|
+
model: model || self.model,
|
|
104
|
+
state: state,
|
|
105
|
+
tools: tools,
|
|
106
|
+
mcp_servers: mcp_servers,
|
|
107
|
+
json_schema: json_schema,
|
|
108
|
+
thinking: thinking,
|
|
109
|
+
logger: logger
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def generate_text(prompt:nil, messages:nil, system:nil, model:nil,
|
|
114
|
+
json_schema:nil, thinking:nil, client:nil)
|
|
115
|
+
|
|
116
|
+
raise ArgumentError, 'Use prompt or messages, not both' if prompt && messages
|
|
117
|
+
raise ArgumentError, 'Undefined prompt or messages' if prompt.nil? && messages.nil?
|
|
118
|
+
|
|
119
|
+
system_prompt, conversation = split_system(messages || [{role: Roles::USER, content: prompt}])
|
|
120
|
+
|
|
121
|
+
client ||= build_client
|
|
122
|
+
|
|
123
|
+
response = request(
|
|
124
|
+
client: client,
|
|
125
|
+
messages: conversation.map { |message| encode_message message },
|
|
126
|
+
system: system || system_prompt,
|
|
127
|
+
model: model || self.model,
|
|
128
|
+
json_schema: json_schema,
|
|
129
|
+
thinking: thinking_config(thinking)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
Result.new content: parse_content(response),
|
|
133
|
+
usage: parse_usage(response),
|
|
134
|
+
raw: response
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
attr_reader :api_key, :usage_tracker, :logger,
|
|
140
|
+
:http_connect_timeout, :http_read_timeout, :http_max_retries
|
|
141
|
+
|
|
142
|
+
def provider_module
|
|
143
|
+
AI.const_get MODULES.fetch(name)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def client_class
|
|
147
|
+
provider_module::Client
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def assistant_class
|
|
151
|
+
provider_module::Assistant
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def split_system(messages)
|
|
155
|
+
normalized = messages.map { |message| normalize_message message }
|
|
156
|
+
|
|
157
|
+
system_messages, conversation = normalized.partition { |message| message[:role] == Roles::SYSTEM }
|
|
158
|
+
|
|
159
|
+
system_prompt = system_messages.map { |message| message[:content] }.join("\n") unless system_messages.empty?
|
|
160
|
+
|
|
161
|
+
[system_prompt, conversation]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def normalize_message(message)
|
|
165
|
+
{
|
|
166
|
+
role: (message[:role] || message['role'] || Roles::USER).to_s,
|
|
167
|
+
content: message[:content] || message['content']
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def thinking_config(level)
|
|
172
|
+
return nil if level.nil?
|
|
173
|
+
|
|
174
|
+
levels = self.class::THINKING_LEVELS
|
|
175
|
+
|
|
176
|
+
raise ArgumentError, "Invalid thinking level '#{level}'. Valid: #{levels.keys.join(', ')}" unless levels.key? level
|
|
177
|
+
|
|
178
|
+
levels[level]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# --- Template methods ---
|
|
182
|
+
|
|
183
|
+
def request(client:, messages:, system:, model:, json_schema:, thinking:)
|
|
184
|
+
raise NotImplementedError
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def encode_message(message)
|
|
188
|
+
raise NotImplementedError
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def parse_content(response)
|
|
192
|
+
raise NotImplementedError
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Rasti
|
|
2
|
+
module AI
|
|
3
|
+
module ProviderAware
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def provider
|
|
8
|
+
@provider ||= provider_module::Provider.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def provider_module
|
|
12
|
+
Object.const_get self.class.name.split('::')[0..-2].join('::')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/rasti/ai/version.rb
CHANGED
data/lib/rasti/ai.rb
CHANGED
|
@@ -15,12 +15,16 @@ module Rasti
|
|
|
15
15
|
extend ClassConfig
|
|
16
16
|
|
|
17
17
|
require_relative 'ai/errors'
|
|
18
|
+
require_relative 'ai/roles'
|
|
18
19
|
require_relative 'ai/usage'
|
|
20
|
+
require_relative 'ai/result'
|
|
19
21
|
require_relative 'ai/assistant_state'
|
|
20
22
|
require_relative 'ai/tool'
|
|
21
23
|
require_relative 'ai/tool_serializer'
|
|
24
|
+
require_relative 'ai/provider_aware'
|
|
22
25
|
require_relative 'ai/client'
|
|
23
26
|
require_relative 'ai/assistant'
|
|
27
|
+
require_relative 'ai/provider'
|
|
24
28
|
require_relative_pattern 'ai/open_ai/*'
|
|
25
29
|
require_relative_pattern 'ai/**/*'
|
|
26
30
|
|
|
@@ -30,6 +34,8 @@ module Rasti
|
|
|
30
34
|
attr_config :http_read_timeout, 60
|
|
31
35
|
attr_config :http_max_retries, 3
|
|
32
36
|
|
|
37
|
+
attr_config :default_provider, ENV['AI_DEFAULT_PROVIDER']
|
|
38
|
+
|
|
33
39
|
attr_config :openai_api_key, ENV['OPENAI_API_KEY']
|
|
34
40
|
attr_config :openai_default_model, ENV['OPENAI_DEFAULT_MODEL']
|
|
35
41
|
|
|
@@ -47,5 +53,92 @@ module Rasti
|
|
|
47
53
|
|
|
48
54
|
attr_config :usage_tracker, nil
|
|
49
55
|
|
|
56
|
+
class << self
|
|
57
|
+
|
|
58
|
+
def provider(name=nil, model:nil, api_key:nil, usage_tracker:nil, logger:nil,
|
|
59
|
+
http_connect_timeout:nil, http_read_timeout:nil, http_max_retries:nil)
|
|
60
|
+
|
|
61
|
+
build_provider(
|
|
62
|
+
name, model, api_key, usage_tracker, logger,
|
|
63
|
+
http_connect_timeout, http_read_timeout, http_max_retries
|
|
64
|
+
).first
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def create_assistant(provider:nil, model:nil, api_key:nil, usage_tracker:nil, logger:nil,
|
|
68
|
+
http_connect_timeout:nil, http_read_timeout:nil, http_max_retries:nil,
|
|
69
|
+
state:nil, system:nil, tools:[], mcp_servers:{}, json_schema:nil,
|
|
70
|
+
thinking:nil, client:nil)
|
|
71
|
+
|
|
72
|
+
resolved_provider, resolved_model = build_provider(
|
|
73
|
+
provider, model, api_key, usage_tracker, logger,
|
|
74
|
+
http_connect_timeout, http_read_timeout, http_max_retries
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
resolved_provider.create_assistant(
|
|
78
|
+
model: resolved_model,
|
|
79
|
+
state: state,
|
|
80
|
+
system: system,
|
|
81
|
+
tools: tools,
|
|
82
|
+
mcp_servers: mcp_servers,
|
|
83
|
+
json_schema: json_schema,
|
|
84
|
+
thinking: thinking,
|
|
85
|
+
client: client
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def generate_text(provider:nil, model:nil, api_key:nil, usage_tracker:nil, logger:nil,
|
|
90
|
+
http_connect_timeout:nil, http_read_timeout:nil, http_max_retries:nil,
|
|
91
|
+
prompt:nil, messages:nil, system:nil, json_schema:nil,
|
|
92
|
+
thinking:nil, client:nil)
|
|
93
|
+
|
|
94
|
+
resolved_provider, resolved_model = build_provider(
|
|
95
|
+
provider, model, api_key, usage_tracker, logger,
|
|
96
|
+
http_connect_timeout, http_read_timeout, http_max_retries
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
resolved_provider.generate_text(
|
|
100
|
+
model: resolved_model,
|
|
101
|
+
prompt: prompt,
|
|
102
|
+
messages: messages,
|
|
103
|
+
system: system,
|
|
104
|
+
json_schema: json_schema,
|
|
105
|
+
thinking: thinking,
|
|
106
|
+
client: client
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def build_provider(name, model, api_key, usage_tracker, logger,
|
|
113
|
+
http_connect_timeout, http_read_timeout, http_max_retries)
|
|
114
|
+
|
|
115
|
+
name, model = split_provider_and_model name, model
|
|
116
|
+
|
|
117
|
+
resolved_provider = Provider.build name,
|
|
118
|
+
model: model,
|
|
119
|
+
api_key: api_key,
|
|
120
|
+
usage_tracker: usage_tracker,
|
|
121
|
+
logger: logger,
|
|
122
|
+
http_connect_timeout: http_connect_timeout,
|
|
123
|
+
http_read_timeout: http_read_timeout,
|
|
124
|
+
http_max_retries: http_max_retries
|
|
125
|
+
|
|
126
|
+
[resolved_provider, model]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def split_provider_and_model(name, model)
|
|
130
|
+
if name.nil? && model.to_s.include?(':')
|
|
131
|
+
name, model = model.split ':', 2
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
name ||= default_provider
|
|
135
|
+
|
|
136
|
+
raise ArgumentError, "Undefined provider. Set it with provider: or Rasti::AI.default_provider. Valid providers: #{Provider.names.join(', ')}" if name.nil?
|
|
137
|
+
|
|
138
|
+
[name, model]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
50
143
|
end
|
|
51
144
|
end
|