ruby_llm-agents 1.3.4 → 2.1.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/README.md +112 -336
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +52 -12
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +89 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +526 -1037
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +13 -17
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +33 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +77 -259
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +54 -23
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +97 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +8 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +14 -83
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
# Database-backed API configuration for LLM providers
|
|
6
|
-
#
|
|
7
|
-
# Stores API keys (encrypted at rest) and configuration options that can be
|
|
8
|
-
# managed via the dashboard UI. Supports both global settings and per-tenant
|
|
9
|
-
# overrides.
|
|
10
|
-
#
|
|
11
|
-
# Resolution priority: per-tenant DB > global DB > config file (RubyLLM.configure)
|
|
12
|
-
#
|
|
13
|
-
# @!attribute [rw] scope_type
|
|
14
|
-
# @return [String] Either 'global' or 'tenant'
|
|
15
|
-
# @!attribute [rw] scope_id
|
|
16
|
-
# @return [String, nil] Tenant ID when scope_type='tenant'
|
|
17
|
-
#
|
|
18
|
-
# @example Setting global API keys
|
|
19
|
-
# config = ApiConfiguration.global
|
|
20
|
-
# config.update!(
|
|
21
|
-
# openai_api_key: "sk-...",
|
|
22
|
-
# anthropic_api_key: "sk-ant-..."
|
|
23
|
-
# )
|
|
24
|
-
#
|
|
25
|
-
# @example Setting tenant-specific configuration
|
|
26
|
-
# tenant_config = ApiConfiguration.for_tenant!("acme_corp")
|
|
27
|
-
# tenant_config.update!(
|
|
28
|
-
# anthropic_api_key: "sk-ant-tenant-specific...",
|
|
29
|
-
# default_model: "claude-sonnet-4-20250514"
|
|
30
|
-
# )
|
|
31
|
-
#
|
|
32
|
-
# @example Resolving configuration for a tenant
|
|
33
|
-
# resolved = ApiConfiguration.resolve(tenant_id: "acme_corp")
|
|
34
|
-
# resolved.apply_to_ruby_llm! # Apply to RubyLLM.configuration
|
|
35
|
-
#
|
|
36
|
-
# @see ResolvedConfig
|
|
37
|
-
# @api public
|
|
38
|
-
class ApiConfiguration < ::ActiveRecord::Base
|
|
39
|
-
self.table_name = "ruby_llm_agents_api_configurations"
|
|
40
|
-
|
|
41
|
-
# Valid scope types
|
|
42
|
-
SCOPE_TYPES = %w[global tenant].freeze
|
|
43
|
-
|
|
44
|
-
# All API key attributes that should be encrypted
|
|
45
|
-
API_KEY_ATTRIBUTES = %i[
|
|
46
|
-
openai_api_key
|
|
47
|
-
anthropic_api_key
|
|
48
|
-
gemini_api_key
|
|
49
|
-
deepseek_api_key
|
|
50
|
-
mistral_api_key
|
|
51
|
-
perplexity_api_key
|
|
52
|
-
openrouter_api_key
|
|
53
|
-
gpustack_api_key
|
|
54
|
-
xai_api_key
|
|
55
|
-
ollama_api_key
|
|
56
|
-
bedrock_api_key
|
|
57
|
-
bedrock_secret_key
|
|
58
|
-
bedrock_session_token
|
|
59
|
-
vertexai_credentials
|
|
60
|
-
].freeze
|
|
61
|
-
|
|
62
|
-
# All endpoint attributes
|
|
63
|
-
ENDPOINT_ATTRIBUTES = %i[
|
|
64
|
-
openai_api_base
|
|
65
|
-
gemini_api_base
|
|
66
|
-
ollama_api_base
|
|
67
|
-
gpustack_api_base
|
|
68
|
-
xai_api_base
|
|
69
|
-
].freeze
|
|
70
|
-
|
|
71
|
-
# All default model attributes
|
|
72
|
-
MODEL_ATTRIBUTES = %i[
|
|
73
|
-
default_model
|
|
74
|
-
default_embedding_model
|
|
75
|
-
default_image_model
|
|
76
|
-
default_moderation_model
|
|
77
|
-
].freeze
|
|
78
|
-
|
|
79
|
-
# Connection settings attributes
|
|
80
|
-
CONNECTION_ATTRIBUTES = %i[
|
|
81
|
-
request_timeout
|
|
82
|
-
max_retries
|
|
83
|
-
retry_interval
|
|
84
|
-
retry_backoff_factor
|
|
85
|
-
retry_interval_randomness
|
|
86
|
-
http_proxy
|
|
87
|
-
].freeze
|
|
88
|
-
|
|
89
|
-
# All configurable attributes (excluding API keys)
|
|
90
|
-
NON_KEY_ATTRIBUTES = (
|
|
91
|
-
ENDPOINT_ATTRIBUTES +
|
|
92
|
-
MODEL_ATTRIBUTES +
|
|
93
|
-
CONNECTION_ATTRIBUTES +
|
|
94
|
-
%i[
|
|
95
|
-
openai_organization_id
|
|
96
|
-
openai_project_id
|
|
97
|
-
bedrock_region
|
|
98
|
-
vertexai_project_id
|
|
99
|
-
vertexai_location
|
|
100
|
-
]
|
|
101
|
-
).freeze
|
|
102
|
-
|
|
103
|
-
# Encrypt all API keys using Rails encrypted attributes
|
|
104
|
-
# Requires Rails encryption to be configured (rails credentials:edit)
|
|
105
|
-
API_KEY_ATTRIBUTES.each do |key_attr|
|
|
106
|
-
encrypts key_attr, deterministic: false
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Validations
|
|
110
|
-
validates :scope_type, presence: true, inclusion: { in: SCOPE_TYPES }
|
|
111
|
-
validates :scope_id, uniqueness: { scope: :scope_type }, allow_nil: true
|
|
112
|
-
validate :validate_scope_consistency
|
|
113
|
-
|
|
114
|
-
# Scopes
|
|
115
|
-
scope :global_config, -> { where(scope_type: "global", scope_id: nil) }
|
|
116
|
-
scope :for_scope, ->(type, id) { where(scope_type: type, scope_id: id) }
|
|
117
|
-
scope :tenant_configs, -> { where(scope_type: "tenant") }
|
|
118
|
-
|
|
119
|
-
# Provider configuration mappings for display
|
|
120
|
-
PROVIDERS = {
|
|
121
|
-
openai: {
|
|
122
|
-
name: "OpenAI",
|
|
123
|
-
key_attr: :openai_api_key,
|
|
124
|
-
base_attr: :openai_api_base,
|
|
125
|
-
extra_attrs: [:openai_organization_id, :openai_project_id],
|
|
126
|
-
capabilities: ["Chat", "Embeddings", "Images", "Moderation"]
|
|
127
|
-
},
|
|
128
|
-
anthropic: {
|
|
129
|
-
name: "Anthropic",
|
|
130
|
-
key_attr: :anthropic_api_key,
|
|
131
|
-
capabilities: ["Chat"]
|
|
132
|
-
},
|
|
133
|
-
gemini: {
|
|
134
|
-
name: "Google Gemini",
|
|
135
|
-
key_attr: :gemini_api_key,
|
|
136
|
-
base_attr: :gemini_api_base,
|
|
137
|
-
capabilities: ["Chat", "Embeddings", "Images"]
|
|
138
|
-
},
|
|
139
|
-
deepseek: {
|
|
140
|
-
name: "DeepSeek",
|
|
141
|
-
key_attr: :deepseek_api_key,
|
|
142
|
-
capabilities: ["Chat"]
|
|
143
|
-
},
|
|
144
|
-
mistral: {
|
|
145
|
-
name: "Mistral",
|
|
146
|
-
key_attr: :mistral_api_key,
|
|
147
|
-
capabilities: ["Chat", "Embeddings"]
|
|
148
|
-
},
|
|
149
|
-
perplexity: {
|
|
150
|
-
name: "Perplexity",
|
|
151
|
-
key_attr: :perplexity_api_key,
|
|
152
|
-
capabilities: ["Chat"]
|
|
153
|
-
},
|
|
154
|
-
openrouter: {
|
|
155
|
-
name: "OpenRouter",
|
|
156
|
-
key_attr: :openrouter_api_key,
|
|
157
|
-
capabilities: ["Chat"]
|
|
158
|
-
},
|
|
159
|
-
gpustack: {
|
|
160
|
-
name: "GPUStack",
|
|
161
|
-
key_attr: :gpustack_api_key,
|
|
162
|
-
base_attr: :gpustack_api_base,
|
|
163
|
-
capabilities: ["Chat"]
|
|
164
|
-
},
|
|
165
|
-
xai: {
|
|
166
|
-
name: "xAI",
|
|
167
|
-
key_attr: :xai_api_key,
|
|
168
|
-
base_attr: :xai_api_base,
|
|
169
|
-
capabilities: ["Chat"]
|
|
170
|
-
},
|
|
171
|
-
ollama: {
|
|
172
|
-
name: "Ollama",
|
|
173
|
-
key_attr: :ollama_api_key,
|
|
174
|
-
base_attr: :ollama_api_base,
|
|
175
|
-
capabilities: ["Chat", "Embeddings"]
|
|
176
|
-
},
|
|
177
|
-
bedrock: {
|
|
178
|
-
name: "AWS Bedrock",
|
|
179
|
-
key_attr: :bedrock_api_key,
|
|
180
|
-
extra_attrs: [:bedrock_secret_key, :bedrock_session_token, :bedrock_region],
|
|
181
|
-
capabilities: ["Chat", "Embeddings"]
|
|
182
|
-
},
|
|
183
|
-
vertexai: {
|
|
184
|
-
name: "Google Vertex AI",
|
|
185
|
-
key_attr: :vertexai_credentials,
|
|
186
|
-
extra_attrs: [:vertexai_project_id, :vertexai_location],
|
|
187
|
-
capabilities: ["Chat", "Embeddings"]
|
|
188
|
-
}
|
|
189
|
-
}.freeze
|
|
190
|
-
|
|
191
|
-
class << self
|
|
192
|
-
# Finds or creates the global configuration
|
|
193
|
-
#
|
|
194
|
-
# @return [ApiConfiguration] The global configuration record
|
|
195
|
-
def global
|
|
196
|
-
global_config.first_or_create!
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Finds a tenant-specific configuration
|
|
200
|
-
#
|
|
201
|
-
# @param tenant_id [String] The tenant identifier
|
|
202
|
-
# @return [ApiConfiguration, nil] The tenant configuration or nil
|
|
203
|
-
def for_tenant(tenant_id)
|
|
204
|
-
return nil if tenant_id.blank?
|
|
205
|
-
|
|
206
|
-
for_scope("tenant", tenant_id).first
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Finds or creates a tenant-specific configuration
|
|
210
|
-
#
|
|
211
|
-
# @param tenant_id [String] The tenant identifier
|
|
212
|
-
# @return [ApiConfiguration] The tenant configuration record
|
|
213
|
-
def for_tenant!(tenant_id)
|
|
214
|
-
raise ArgumentError, "tenant_id cannot be blank" if tenant_id.blank?
|
|
215
|
-
|
|
216
|
-
for_scope("tenant", tenant_id).first_or_create!(
|
|
217
|
-
scope_type: "tenant",
|
|
218
|
-
scope_id: tenant_id
|
|
219
|
-
)
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Resolves the effective configuration for a given tenant
|
|
223
|
-
#
|
|
224
|
-
# Creates a ResolvedConfig that combines tenant config > global DB > RubyLLM config
|
|
225
|
-
#
|
|
226
|
-
# @param tenant_id [String, nil] Optional tenant identifier
|
|
227
|
-
# @return [ResolvedConfig] The resolved configuration
|
|
228
|
-
def resolve(tenant_id: nil)
|
|
229
|
-
tenant_config = tenant_id.present? ? for_tenant(tenant_id) : nil
|
|
230
|
-
global = global_config.first
|
|
231
|
-
|
|
232
|
-
RubyLLM::Agents::ResolvedConfig.new(
|
|
233
|
-
tenant_config: tenant_config,
|
|
234
|
-
global_config: global,
|
|
235
|
-
ruby_llm_config: ruby_llm_current_config
|
|
236
|
-
)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Attempts to get the current RubyLLM configuration object
|
|
240
|
-
# Gets the current RubyLLM configuration object
|
|
241
|
-
#
|
|
242
|
-
# @return [Object, nil] The RubyLLM config object or nil
|
|
243
|
-
def ruby_llm_current_config
|
|
244
|
-
return nil unless defined?(::RubyLLM)
|
|
245
|
-
return nil unless RubyLLM.respond_to?(:config)
|
|
246
|
-
|
|
247
|
-
RubyLLM.config
|
|
248
|
-
rescue StandardError
|
|
249
|
-
nil
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
# Checks if the table exists (for graceful degradation)
|
|
253
|
-
#
|
|
254
|
-
# @return [Boolean]
|
|
255
|
-
def table_exists?
|
|
256
|
-
connection.table_exists?(table_name)
|
|
257
|
-
rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
|
|
258
|
-
false
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
# Checks if a specific attribute has a value set
|
|
263
|
-
#
|
|
264
|
-
# @param attr_name [Symbol, String] The attribute name
|
|
265
|
-
# @return [Boolean]
|
|
266
|
-
def has_value?(attr_name)
|
|
267
|
-
value = send(attr_name)
|
|
268
|
-
value.present?
|
|
269
|
-
rescue NoMethodError
|
|
270
|
-
false
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
# Returns a masked version of an API key for display
|
|
274
|
-
#
|
|
275
|
-
# @param attr_name [Symbol, String] The API key attribute name
|
|
276
|
-
# @return [String, nil] Masked key like "sk-ab****wxyz" or nil
|
|
277
|
-
def masked_key(attr_name)
|
|
278
|
-
value = send(attr_name)
|
|
279
|
-
return nil if value.blank?
|
|
280
|
-
|
|
281
|
-
mask_string(value)
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
# Returns the source of this configuration
|
|
285
|
-
#
|
|
286
|
-
# @return [String] "global" or "tenant:ID"
|
|
287
|
-
def source_label
|
|
288
|
-
scope_type == "global" ? "Global" : "Tenant: #{scope_id}"
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# Converts this configuration to a hash suitable for RubyLLM
|
|
292
|
-
#
|
|
293
|
-
# @return [Hash] Configuration hash with non-nil values
|
|
294
|
-
def to_ruby_llm_config
|
|
295
|
-
{}.tap do |config|
|
|
296
|
-
# API keys
|
|
297
|
-
config[:openai_api_key] = openai_api_key if openai_api_key.present?
|
|
298
|
-
config[:anthropic_api_key] = anthropic_api_key if anthropic_api_key.present?
|
|
299
|
-
config[:gemini_api_key] = gemini_api_key if gemini_api_key.present?
|
|
300
|
-
config[:deepseek_api_key] = deepseek_api_key if deepseek_api_key.present?
|
|
301
|
-
config[:mistral_api_key] = mistral_api_key if mistral_api_key.present?
|
|
302
|
-
config[:perplexity_api_key] = perplexity_api_key if perplexity_api_key.present?
|
|
303
|
-
config[:openrouter_api_key] = openrouter_api_key if openrouter_api_key.present?
|
|
304
|
-
config[:gpustack_api_key] = gpustack_api_key if gpustack_api_key.present?
|
|
305
|
-
config[:xai_api_key] = xai_api_key if xai_api_key.present?
|
|
306
|
-
config[:ollama_api_key] = ollama_api_key if ollama_api_key.present?
|
|
307
|
-
|
|
308
|
-
# Bedrock
|
|
309
|
-
config[:bedrock_api_key] = bedrock_api_key if bedrock_api_key.present?
|
|
310
|
-
config[:bedrock_secret_key] = bedrock_secret_key if bedrock_secret_key.present?
|
|
311
|
-
config[:bedrock_session_token] = bedrock_session_token if bedrock_session_token.present?
|
|
312
|
-
config[:bedrock_region] = bedrock_region if bedrock_region.present?
|
|
313
|
-
|
|
314
|
-
# Vertex AI
|
|
315
|
-
config[:vertexai_credentials] = vertexai_credentials if vertexai_credentials.present?
|
|
316
|
-
config[:vertexai_project_id] = vertexai_project_id if vertexai_project_id.present?
|
|
317
|
-
config[:vertexai_location] = vertexai_location if vertexai_location.present?
|
|
318
|
-
|
|
319
|
-
# Endpoints
|
|
320
|
-
config[:openai_api_base] = openai_api_base if openai_api_base.present?
|
|
321
|
-
config[:gemini_api_base] = gemini_api_base if gemini_api_base.present?
|
|
322
|
-
config[:ollama_api_base] = ollama_api_base if ollama_api_base.present?
|
|
323
|
-
config[:gpustack_api_base] = gpustack_api_base if gpustack_api_base.present?
|
|
324
|
-
config[:xai_api_base] = xai_api_base if xai_api_base.present?
|
|
325
|
-
|
|
326
|
-
# OpenAI options
|
|
327
|
-
config[:openai_organization_id] = openai_organization_id if openai_organization_id.present?
|
|
328
|
-
config[:openai_project_id] = openai_project_id if openai_project_id.present?
|
|
329
|
-
|
|
330
|
-
# Default models
|
|
331
|
-
config[:default_model] = default_model if default_model.present?
|
|
332
|
-
config[:default_embedding_model] = default_embedding_model if default_embedding_model.present?
|
|
333
|
-
config[:default_image_model] = default_image_model if default_image_model.present?
|
|
334
|
-
config[:default_moderation_model] = default_moderation_model if default_moderation_model.present?
|
|
335
|
-
|
|
336
|
-
# Connection settings
|
|
337
|
-
config[:request_timeout] = request_timeout if request_timeout.present?
|
|
338
|
-
config[:max_retries] = max_retries if max_retries.present?
|
|
339
|
-
config[:retry_interval] = retry_interval if retry_interval.present?
|
|
340
|
-
config[:retry_backoff_factor] = retry_backoff_factor if retry_backoff_factor.present?
|
|
341
|
-
config[:retry_interval_randomness] = retry_interval_randomness if retry_interval_randomness.present?
|
|
342
|
-
config[:http_proxy] = http_proxy if http_proxy.present?
|
|
343
|
-
end
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
# Returns provider status information for display
|
|
347
|
-
#
|
|
348
|
-
# @return [Array<Hash>] Array of provider status hashes
|
|
349
|
-
def provider_statuses
|
|
350
|
-
PROVIDERS.map do |key, info|
|
|
351
|
-
key_value = send(info[:key_attr])
|
|
352
|
-
{
|
|
353
|
-
key: key,
|
|
354
|
-
name: info[:name],
|
|
355
|
-
configured: key_value.present?,
|
|
356
|
-
masked_key: key_value.present? ? mask_string(key_value) : nil,
|
|
357
|
-
capabilities: info[:capabilities],
|
|
358
|
-
has_base_url: info[:base_attr].present? && send(info[:base_attr]).present?
|
|
359
|
-
}
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
private
|
|
364
|
-
|
|
365
|
-
# Validates scope consistency
|
|
366
|
-
def validate_scope_consistency
|
|
367
|
-
if scope_type == "global" && scope_id.present?
|
|
368
|
-
errors.add(:scope_id, "must be nil for global scope")
|
|
369
|
-
elsif scope_type == "tenant" && scope_id.blank?
|
|
370
|
-
errors.add(:scope_id, "must be present for tenant scope")
|
|
371
|
-
end
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
# Masks a string for display (shows first 2 and last 4 chars)
|
|
375
|
-
#
|
|
376
|
-
# @param value [String] The string to mask
|
|
377
|
-
# @return [String] Masked string
|
|
378
|
-
def mask_string(value)
|
|
379
|
-
return nil if value.blank?
|
|
380
|
-
return "****" if value.length <= 8
|
|
381
|
-
|
|
382
|
-
"#{value[0..1]}****#{value[-4..]}"
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
end
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
class Execution
|
|
6
|
-
# Workflow concern for workflow-related methods and aggregate calculations
|
|
7
|
-
#
|
|
8
|
-
# Provides instance methods for determining workflow type, calculating
|
|
9
|
-
# aggregate statistics across child executions, and retrieving workflow
|
|
10
|
-
# step/branch information.
|
|
11
|
-
#
|
|
12
|
-
# @see RubyLLM::Agents::Execution
|
|
13
|
-
# @api public
|
|
14
|
-
module Workflow
|
|
15
|
-
extend ActiveSupport::Concern
|
|
16
|
-
|
|
17
|
-
# Returns whether this is a workflow execution (has workflow_type)
|
|
18
|
-
#
|
|
19
|
-
# @return [Boolean] true if this is a workflow execution
|
|
20
|
-
def workflow?
|
|
21
|
-
workflow_type.present?
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Returns whether this is a root workflow execution (top-level)
|
|
25
|
-
#
|
|
26
|
-
# @return [Boolean] true if this is a workflow with no parent
|
|
27
|
-
def root_workflow?
|
|
28
|
-
workflow? && root?
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Returns all workflow steps/branches ordered by creation time
|
|
32
|
-
#
|
|
33
|
-
# @return [ActiveRecord::Relation] Child executions for this workflow
|
|
34
|
-
def workflow_steps
|
|
35
|
-
child_executions.order(:created_at)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Returns the count of child workflow steps
|
|
39
|
-
#
|
|
40
|
-
# @return [Integer] Number of child executions
|
|
41
|
-
def workflow_steps_count
|
|
42
|
-
child_executions.count
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# @!group Aggregate Statistics
|
|
46
|
-
|
|
47
|
-
# Returns aggregate stats for all child executions
|
|
48
|
-
#
|
|
49
|
-
# @return [Hash] Aggregated metrics including cost, tokens, duration
|
|
50
|
-
def workflow_aggregate_stats
|
|
51
|
-
return @workflow_aggregate_stats if defined?(@workflow_aggregate_stats)
|
|
52
|
-
|
|
53
|
-
children = child_executions.to_a
|
|
54
|
-
return empty_aggregate_stats if children.empty?
|
|
55
|
-
|
|
56
|
-
@workflow_aggregate_stats = {
|
|
57
|
-
total_cost: children.sum { |c| c.total_cost || 0 },
|
|
58
|
-
total_tokens: children.sum { |c| c.total_tokens || 0 },
|
|
59
|
-
input_tokens: children.sum { |c| c.input_tokens || 0 },
|
|
60
|
-
output_tokens: children.sum { |c| c.output_tokens || 0 },
|
|
61
|
-
total_duration_ms: children.sum { |c| c.duration_ms || 0 },
|
|
62
|
-
wall_clock_ms: calculate_wall_clock_duration(children),
|
|
63
|
-
steps_count: children.size,
|
|
64
|
-
successful_count: children.count(&:status_success?),
|
|
65
|
-
failed_count: children.count(&:status_error?),
|
|
66
|
-
timeout_count: children.count(&:status_timeout?),
|
|
67
|
-
running_count: children.count(&:status_running?),
|
|
68
|
-
success_rate: calculate_success_rate(children),
|
|
69
|
-
models_used: children.map(&:model_id).uniq.compact
|
|
70
|
-
}
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Returns aggregate total cost across all child executions
|
|
74
|
-
#
|
|
75
|
-
# @return [Float] Total cost in USD
|
|
76
|
-
def workflow_total_cost
|
|
77
|
-
workflow_aggregate_stats[:total_cost]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Returns aggregate total tokens across all child executions
|
|
81
|
-
#
|
|
82
|
-
# @return [Integer] Total tokens used
|
|
83
|
-
def workflow_total_tokens
|
|
84
|
-
workflow_aggregate_stats[:total_tokens]
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Returns the wall-clock duration (from first start to last completion)
|
|
88
|
-
#
|
|
89
|
-
# @return [Integer, nil] Duration in milliseconds
|
|
90
|
-
def workflow_wall_clock_ms
|
|
91
|
-
workflow_aggregate_stats[:wall_clock_ms]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Returns the sum of all step durations (may exceed wall-clock for parallel)
|
|
95
|
-
#
|
|
96
|
-
# @return [Integer] Sum of all durations in milliseconds
|
|
97
|
-
def workflow_sum_duration_ms
|
|
98
|
-
workflow_aggregate_stats[:total_duration_ms]
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Returns the overall workflow status based on child executions
|
|
102
|
-
#
|
|
103
|
-
# @return [Symbol] :success, :error, :timeout, :running, or :pending
|
|
104
|
-
def workflow_overall_status
|
|
105
|
-
stats = workflow_aggregate_stats
|
|
106
|
-
return :pending if stats[:steps_count].zero?
|
|
107
|
-
return :running if stats[:running_count] > 0
|
|
108
|
-
return :error if stats[:failed_count] > 0
|
|
109
|
-
return :timeout if stats[:timeout_count] > 0
|
|
110
|
-
|
|
111
|
-
:success
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# @!endgroup
|
|
115
|
-
|
|
116
|
-
private
|
|
117
|
-
|
|
118
|
-
# Returns empty aggregate stats hash
|
|
119
|
-
#
|
|
120
|
-
# @return [Hash] Empty stats with zero values
|
|
121
|
-
def empty_aggregate_stats
|
|
122
|
-
{
|
|
123
|
-
total_cost: 0,
|
|
124
|
-
total_tokens: 0,
|
|
125
|
-
input_tokens: 0,
|
|
126
|
-
output_tokens: 0,
|
|
127
|
-
total_duration_ms: 0,
|
|
128
|
-
wall_clock_ms: nil,
|
|
129
|
-
steps_count: 0,
|
|
130
|
-
successful_count: 0,
|
|
131
|
-
failed_count: 0,
|
|
132
|
-
timeout_count: 0,
|
|
133
|
-
running_count: 0,
|
|
134
|
-
success_rate: 0.0,
|
|
135
|
-
models_used: []
|
|
136
|
-
}
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Calculates wall-clock duration from child executions
|
|
140
|
-
#
|
|
141
|
-
# @param children [Array<Execution>] Child executions
|
|
142
|
-
# @return [Integer, nil] Duration in milliseconds
|
|
143
|
-
def calculate_wall_clock_duration(children)
|
|
144
|
-
started_times = children.map(&:started_at).compact
|
|
145
|
-
completed_times = children.map(&:completed_at).compact
|
|
146
|
-
|
|
147
|
-
return nil if started_times.empty? || completed_times.empty?
|
|
148
|
-
|
|
149
|
-
first_start = started_times.min
|
|
150
|
-
last_complete = completed_times.max
|
|
151
|
-
|
|
152
|
-
((last_complete - first_start) * 1000).round
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Calculates success rate from children
|
|
156
|
-
#
|
|
157
|
-
# @param children [Array<Execution>] Child executions
|
|
158
|
-
# @return [Float] Success rate as percentage
|
|
159
|
-
def calculate_success_rate(children)
|
|
160
|
-
return 0.0 if children.empty?
|
|
161
|
-
|
|
162
|
-
completed = children.reject(&:status_running?)
|
|
163
|
-
return 0.0 if completed.empty?
|
|
164
|
-
|
|
165
|
-
(completed.count(&:status_success?).to_f / completed.size * 100).round(1)
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_support/concern"
|
|
4
|
-
|
|
5
|
-
module RubyLLM
|
|
6
|
-
module Agents
|
|
7
|
-
class Tenant
|
|
8
|
-
# Manages API configuration for a tenant.
|
|
9
|
-
#
|
|
10
|
-
# Links to the ApiConfiguration model to provide per-tenant API keys
|
|
11
|
-
# and settings. Supports inheritance from global configuration when
|
|
12
|
-
# tenant-specific settings are not defined.
|
|
13
|
-
#
|
|
14
|
-
# @example Accessing tenant API keys
|
|
15
|
-
# tenant = Tenant.for("acme_corp")
|
|
16
|
-
# tenant.api_key_for(:openai) # => "sk-..."
|
|
17
|
-
# tenant.has_custom_api_keys? # => true
|
|
18
|
-
#
|
|
19
|
-
# @example Getting effective configuration
|
|
20
|
-
# config = tenant.effective_api_configuration
|
|
21
|
-
# config.apply_to_ruby_llm!
|
|
22
|
-
#
|
|
23
|
-
# @see ApiConfiguration
|
|
24
|
-
# @api public
|
|
25
|
-
module Configurable
|
|
26
|
-
extend ActiveSupport::Concern
|
|
27
|
-
|
|
28
|
-
included do
|
|
29
|
-
# Link to tenant-specific API configuration
|
|
30
|
-
has_one :api_configuration,
|
|
31
|
-
-> { where(scope_type: "tenant") },
|
|
32
|
-
class_name: "RubyLLM::Agents::ApiConfiguration",
|
|
33
|
-
foreign_key: :scope_id,
|
|
34
|
-
primary_key: :tenant_id,
|
|
35
|
-
dependent: :destroy
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Get API key for a specific provider
|
|
39
|
-
#
|
|
40
|
-
# @param provider [Symbol] Provider name (:openai, :anthropic, :gemini, etc.)
|
|
41
|
-
# @return [String, nil] The API key or nil if not configured
|
|
42
|
-
#
|
|
43
|
-
# @example
|
|
44
|
-
# tenant.api_key_for(:openai) # => "sk-abc123..."
|
|
45
|
-
# tenant.api_key_for(:anthropic) # => "sk-ant-xyz..."
|
|
46
|
-
def api_key_for(provider)
|
|
47
|
-
attr_name = "#{provider}_api_key"
|
|
48
|
-
api_configuration&.send(attr_name) if api_configuration&.respond_to?(attr_name)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Check if tenant has custom API keys configured
|
|
52
|
-
#
|
|
53
|
-
# @return [Boolean] true if tenant has an ApiConfiguration record
|
|
54
|
-
def has_custom_api_keys?
|
|
55
|
-
api_configuration.present?
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Get effective API configuration for this tenant
|
|
59
|
-
#
|
|
60
|
-
# Returns the resolved configuration that combines tenant-specific
|
|
61
|
-
# settings with global defaults.
|
|
62
|
-
#
|
|
63
|
-
# @return [ResolvedConfig] The resolved configuration
|
|
64
|
-
#
|
|
65
|
-
# @example
|
|
66
|
-
# config = tenant.effective_api_configuration
|
|
67
|
-
# config.openai_api_key # Tenant's key or global fallback
|
|
68
|
-
def effective_api_configuration
|
|
69
|
-
ApiConfiguration.resolve(tenant_id: tenant_id)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Get or create the API configuration for this tenant
|
|
73
|
-
#
|
|
74
|
-
# @return [ApiConfiguration] The tenant's API configuration record
|
|
75
|
-
def api_configuration!
|
|
76
|
-
api_configuration || create_api_configuration!(
|
|
77
|
-
scope_type: "tenant",
|
|
78
|
-
scope_id: tenant_id
|
|
79
|
-
)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Configure API settings for this tenant
|
|
83
|
-
#
|
|
84
|
-
# @yield [config] Block to configure the API settings
|
|
85
|
-
# @yieldparam config [ApiConfiguration] The configuration to modify
|
|
86
|
-
# @return [ApiConfiguration] The saved configuration
|
|
87
|
-
#
|
|
88
|
-
# @example
|
|
89
|
-
# tenant.configure_api do |config|
|
|
90
|
-
# config.openai_api_key = "sk-..."
|
|
91
|
-
# config.default_model = "gpt-4o"
|
|
92
|
-
# end
|
|
93
|
-
def configure_api(&block)
|
|
94
|
-
config = api_configuration!
|
|
95
|
-
yield(config) if block_given?
|
|
96
|
-
config.save!
|
|
97
|
-
config
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Check if a specific provider is configured for this tenant
|
|
101
|
-
#
|
|
102
|
-
# @param provider [Symbol] Provider name
|
|
103
|
-
# @return [Boolean] true if the provider has an API key set
|
|
104
|
-
def provider_configured?(provider)
|
|
105
|
-
api_key_for(provider).present?
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Get all configured providers for this tenant
|
|
109
|
-
#
|
|
110
|
-
# @return [Array<Symbol>] List of configured provider symbols
|
|
111
|
-
def configured_providers
|
|
112
|
-
return [] unless api_configuration
|
|
113
|
-
|
|
114
|
-
ApiConfiguration::PROVIDERS.keys.select do |provider|
|
|
115
|
-
provider_configured?(provider)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Get the default model for this tenant
|
|
120
|
-
#
|
|
121
|
-
# @return [String, nil] The default model or nil
|
|
122
|
-
def default_model
|
|
123
|
-
api_configuration&.default_model
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Get the default embedding model for this tenant
|
|
127
|
-
#
|
|
128
|
-
# @return [String, nil] The default embedding model or nil
|
|
129
|
-
def default_embedding_model
|
|
130
|
-
api_configuration&.default_embedding_model
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|