ruby_llm-agents 0.4.0 → 1.0.0.beta.1
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 +225 -34
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
- data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
- data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +13 -1
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +189 -35
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -283
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -209
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
- data/lib/ruby_llm/agents/configuration.rb +0 -357
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../results/embedding_result"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
# Base class for creating embedding generators
|
|
8
|
+
#
|
|
9
|
+
# Embedder inherits from BaseAgent and uses the middleware pipeline
|
|
10
|
+
# for caching, reliability, instrumentation, and budget controls.
|
|
11
|
+
# Only the core embedding logic is implemented here.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# class DocumentEmbedder < RubyLLM::Agents::Embedder
|
|
15
|
+
# model 'text-embedding-3-small'
|
|
16
|
+
# dimensions 512
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# result = DocumentEmbedder.call(text: "Hello world")
|
|
20
|
+
# result.vector # => [0.123, -0.456, ...]
|
|
21
|
+
#
|
|
22
|
+
# @example Batch processing
|
|
23
|
+
# result = DocumentEmbedder.call(texts: ["Hello", "World"])
|
|
24
|
+
# result.vectors # => [[...], [...]]
|
|
25
|
+
#
|
|
26
|
+
# @example With preprocessing
|
|
27
|
+
# class CleanEmbedder < RubyLLM::Agents::Embedder
|
|
28
|
+
# model 'text-embedding-3-small'
|
|
29
|
+
#
|
|
30
|
+
# def preprocess(text)
|
|
31
|
+
# text.strip.downcase.gsub(/\s+/, ' ')
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# @api public
|
|
36
|
+
class Embedder < BaseAgent
|
|
37
|
+
class << self
|
|
38
|
+
# Returns the agent type for embedders
|
|
39
|
+
#
|
|
40
|
+
# @return [Symbol] :embedding
|
|
41
|
+
def agent_type
|
|
42
|
+
:embedding
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @!group Embedding-specific DSL
|
|
46
|
+
|
|
47
|
+
# Sets or returns the embedding model
|
|
48
|
+
#
|
|
49
|
+
# Defaults to the embedding model from configuration, not the
|
|
50
|
+
# conversation model that BaseAgent uses.
|
|
51
|
+
#
|
|
52
|
+
# @param value [String, nil] The model identifier to set
|
|
53
|
+
# @return [String] The current model setting
|
|
54
|
+
# @example
|
|
55
|
+
# model "text-embedding-3-large"
|
|
56
|
+
def model(value = nil)
|
|
57
|
+
@model = value if value
|
|
58
|
+
return @model if defined?(@model) && @model
|
|
59
|
+
|
|
60
|
+
# For inheritance: check if parent is also an Embedder
|
|
61
|
+
if superclass.respond_to?(:agent_type) && superclass.agent_type == :embedding
|
|
62
|
+
superclass.model
|
|
63
|
+
else
|
|
64
|
+
default_embedding_model
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Sets or returns the vector dimensions
|
|
69
|
+
#
|
|
70
|
+
# Some models (like OpenAI text-embedding-3) support reducing
|
|
71
|
+
# dimensions for more efficient storage.
|
|
72
|
+
#
|
|
73
|
+
# @param value [Integer, nil] The dimensions to set
|
|
74
|
+
# @return [Integer, nil] The current dimensions setting
|
|
75
|
+
# @example
|
|
76
|
+
# dimensions 512
|
|
77
|
+
def dimensions(value = nil)
|
|
78
|
+
@dimensions = value if value
|
|
79
|
+
@dimensions || inherited_or_default(:dimensions, default_embedding_dimensions)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Sets or returns the batch size
|
|
83
|
+
#
|
|
84
|
+
# When embedding multiple texts, they are split into batches
|
|
85
|
+
# of this size for API calls.
|
|
86
|
+
#
|
|
87
|
+
# @param value [Integer, nil] Maximum texts per API call
|
|
88
|
+
# @return [Integer] The current batch size
|
|
89
|
+
# @example
|
|
90
|
+
# batch_size 50
|
|
91
|
+
def batch_size(value = nil)
|
|
92
|
+
@batch_size = value if value
|
|
93
|
+
@batch_size || inherited_or_default(:batch_size, default_embedding_batch_size)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @!endgroup
|
|
97
|
+
|
|
98
|
+
# Executes the embedder with the given parameters
|
|
99
|
+
#
|
|
100
|
+
# @param text [String, nil] Single text to embed
|
|
101
|
+
# @param texts [Array<String>, nil] Multiple texts to embed
|
|
102
|
+
# @param options [Hash] Additional options
|
|
103
|
+
# @yield [batch_result, index] Called after each batch completes
|
|
104
|
+
# @yieldparam batch_result [EmbeddingResult] Result for the batch
|
|
105
|
+
# @yieldparam index [Integer] Batch index (0-based)
|
|
106
|
+
# @return [EmbeddingResult] The embedding result
|
|
107
|
+
# @raise [ArgumentError] If both text: and texts: are provided
|
|
108
|
+
def call(text: nil, texts: nil, **options, &block)
|
|
109
|
+
new(text: text, texts: texts, **options).call(&block)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def inherited_or_default(method, default)
|
|
115
|
+
superclass.respond_to?(method) ? superclass.send(method) : default
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def default_embedding_model
|
|
119
|
+
RubyLLM::Agents.configuration.default_embedding_model
|
|
120
|
+
rescue StandardError
|
|
121
|
+
"text-embedding-3-small"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def default_embedding_dimensions
|
|
125
|
+
RubyLLM::Agents.configuration.default_embedding_dimensions
|
|
126
|
+
rescue StandardError
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def default_embedding_batch_size
|
|
131
|
+
RubyLLM::Agents.configuration.default_embedding_batch_size
|
|
132
|
+
rescue StandardError
|
|
133
|
+
100
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @!attribute [r] text
|
|
138
|
+
# @return [String, nil] Single text to embed
|
|
139
|
+
# @!attribute [r] texts
|
|
140
|
+
# @return [Array<String>, nil] Multiple texts to embed
|
|
141
|
+
attr_reader :text, :texts
|
|
142
|
+
|
|
143
|
+
# Creates a new Embedder instance
|
|
144
|
+
#
|
|
145
|
+
# @param text [String, nil] Single text to embed
|
|
146
|
+
# @param texts [Array<String>, nil] Multiple texts to embed
|
|
147
|
+
# @param options [Hash] Additional options
|
|
148
|
+
def initialize(text: nil, texts: nil, **options)
|
|
149
|
+
@text = text
|
|
150
|
+
@texts = texts
|
|
151
|
+
@batch_block = nil
|
|
152
|
+
|
|
153
|
+
# Set model to embedding model if not specified
|
|
154
|
+
options[:model] ||= self.class.model || self.class.class_eval { default_embedding_model }
|
|
155
|
+
|
|
156
|
+
super(**options)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Executes the embedding through the middleware pipeline
|
|
160
|
+
#
|
|
161
|
+
# @yield [batch_result, index] Called after each batch completes
|
|
162
|
+
# @return [EmbeddingResult] The embedding result
|
|
163
|
+
def call(&block)
|
|
164
|
+
@batch_block = block
|
|
165
|
+
context = build_context
|
|
166
|
+
result_context = Pipeline::Executor.execute(context)
|
|
167
|
+
result_context.output
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# The input for this embedding operation
|
|
171
|
+
#
|
|
172
|
+
# Used by the pipeline to generate cache keys and for instrumentation.
|
|
173
|
+
#
|
|
174
|
+
# @return [String, Array<String>] The input text(s)
|
|
175
|
+
def user_prompt
|
|
176
|
+
input_texts.join("\n---\n")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Preprocesses text before embedding
|
|
180
|
+
#
|
|
181
|
+
# Override this method in subclasses to apply custom preprocessing
|
|
182
|
+
# like normalization, cleaning, or truncation.
|
|
183
|
+
#
|
|
184
|
+
# @param text [String] The text to preprocess
|
|
185
|
+
# @return [String] The preprocessed text
|
|
186
|
+
# @example Custom preprocessing
|
|
187
|
+
# def preprocess(text)
|
|
188
|
+
# text.strip.downcase.gsub(/\s+/, ' ').truncate(8000)
|
|
189
|
+
# end
|
|
190
|
+
def preprocess(text)
|
|
191
|
+
text
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Core embedding execution
|
|
195
|
+
#
|
|
196
|
+
# This is called by the Pipeline::Executor after middleware
|
|
197
|
+
# has been applied. Only contains the embedding API logic.
|
|
198
|
+
#
|
|
199
|
+
# @param context [Pipeline::Context] The execution context
|
|
200
|
+
# @return [void] Sets context.output with the EmbeddingResult
|
|
201
|
+
def execute(context)
|
|
202
|
+
# Track timing internally since middleware sets completed_at after execute returns
|
|
203
|
+
execution_started_at = Time.current
|
|
204
|
+
|
|
205
|
+
input_list = input_texts
|
|
206
|
+
validate_input!(input_list)
|
|
207
|
+
|
|
208
|
+
all_vectors = []
|
|
209
|
+
total_input_tokens = 0
|
|
210
|
+
total_cost = 0.0
|
|
211
|
+
batch_count = resolved_batch_size
|
|
212
|
+
|
|
213
|
+
batches = input_list.each_slice(batch_count).to_a
|
|
214
|
+
|
|
215
|
+
batches.each_with_index do |batch, index|
|
|
216
|
+
batch_result = execute_batch(batch)
|
|
217
|
+
|
|
218
|
+
all_vectors.concat(batch_result[:vectors])
|
|
219
|
+
total_input_tokens += batch_result[:input_tokens] || 0
|
|
220
|
+
total_cost += batch_result[:cost] || 0.0
|
|
221
|
+
|
|
222
|
+
# Yield batch result for progress tracking
|
|
223
|
+
if @batch_block
|
|
224
|
+
batch_embedding_result = build_batch_result(batch_result, batch.size)
|
|
225
|
+
@batch_block.call(batch_embedding_result, index)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
execution_completed_at = Time.current
|
|
230
|
+
duration_ms = ((execution_completed_at - execution_started_at) * 1000).to_i
|
|
231
|
+
|
|
232
|
+
# Update context with token/cost info
|
|
233
|
+
context.input_tokens = total_input_tokens
|
|
234
|
+
context.output_tokens = 0
|
|
235
|
+
context.input_cost = total_cost
|
|
236
|
+
context.output_cost = 0.0
|
|
237
|
+
context.total_cost = total_cost.round(6)
|
|
238
|
+
|
|
239
|
+
# Build final result
|
|
240
|
+
context.output = build_result(
|
|
241
|
+
vectors: all_vectors,
|
|
242
|
+
input_tokens: total_input_tokens,
|
|
243
|
+
total_cost: total_cost,
|
|
244
|
+
count: input_list.size,
|
|
245
|
+
started_at: context.started_at || execution_started_at,
|
|
246
|
+
completed_at: execution_completed_at,
|
|
247
|
+
duration_ms: duration_ms,
|
|
248
|
+
tenant_id: context.tenant_id
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Generates the cache key for this embedding
|
|
253
|
+
#
|
|
254
|
+
# @return [String] Cache key in format "ruby_llm_agents/embedding/..."
|
|
255
|
+
def agent_cache_key
|
|
256
|
+
components = [
|
|
257
|
+
"ruby_llm_agents",
|
|
258
|
+
"embedding",
|
|
259
|
+
self.class.name,
|
|
260
|
+
self.class.version,
|
|
261
|
+
resolved_model,
|
|
262
|
+
resolved_dimensions,
|
|
263
|
+
Digest::SHA256.hexdigest(input_texts.map { |t| preprocess(t) }.join("\n"))
|
|
264
|
+
].compact
|
|
265
|
+
|
|
266
|
+
components.join("/")
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
protected
|
|
270
|
+
|
|
271
|
+
# Returns the normalized input texts
|
|
272
|
+
#
|
|
273
|
+
# @return [Array<String>] Array of texts to embed
|
|
274
|
+
def input_texts
|
|
275
|
+
@input_texts ||= normalize_input
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
# Builds context for pipeline execution
|
|
281
|
+
#
|
|
282
|
+
# @return [Pipeline::Context] The context object
|
|
283
|
+
def build_context
|
|
284
|
+
Pipeline::Context.new(
|
|
285
|
+
input: user_prompt,
|
|
286
|
+
agent_class: self.class,
|
|
287
|
+
agent_instance: self,
|
|
288
|
+
model: resolved_model,
|
|
289
|
+
tenant: @options[:tenant],
|
|
290
|
+
skip_cache: @options[:skip_cache]
|
|
291
|
+
)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Normalizes input to an array of texts
|
|
295
|
+
#
|
|
296
|
+
# @return [Array<String>] Array of texts
|
|
297
|
+
# @raise [ArgumentError] If both or neither text/texts provided
|
|
298
|
+
def normalize_input
|
|
299
|
+
if @text && @texts
|
|
300
|
+
raise ArgumentError, "Provide either text: or texts:, not both"
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
if @text.nil? && @texts.nil?
|
|
304
|
+
raise ArgumentError, "Provide either text: or texts:"
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
@texts || [@text]
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Validates the input texts
|
|
311
|
+
#
|
|
312
|
+
# @param texts [Array<String>] Texts to validate
|
|
313
|
+
# @raise [ArgumentError] If validation fails
|
|
314
|
+
def validate_input!(texts)
|
|
315
|
+
if texts.empty?
|
|
316
|
+
raise ArgumentError, "texts cannot be empty"
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
texts.each_with_index do |txt, idx|
|
|
320
|
+
unless txt.is_a?(String)
|
|
321
|
+
raise ArgumentError, "texts[#{idx}] must be a String, got #{txt.class}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
if txt.empty?
|
|
325
|
+
raise ArgumentError, "texts[#{idx}] cannot be empty"
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Executes a single batch of texts
|
|
331
|
+
#
|
|
332
|
+
# @param texts [Array<String>] Texts to embed
|
|
333
|
+
# @return [Hash] Batch result with vectors, tokens, cost
|
|
334
|
+
def execute_batch(texts)
|
|
335
|
+
preprocessed = texts.map { |t| preprocess(t) }
|
|
336
|
+
|
|
337
|
+
embed_options = { model: resolved_model }
|
|
338
|
+
embed_options[:dimensions] = resolved_dimensions if resolved_dimensions
|
|
339
|
+
|
|
340
|
+
response = RubyLLM.embed(preprocessed, **embed_options)
|
|
341
|
+
|
|
342
|
+
# ruby_llm returns vectors as an array (even for single text)
|
|
343
|
+
vectors = response.vectors
|
|
344
|
+
vectors = [vectors] unless vectors.first.is_a?(Array)
|
|
345
|
+
|
|
346
|
+
{
|
|
347
|
+
vectors: vectors,
|
|
348
|
+
input_tokens: response.input_tokens,
|
|
349
|
+
model: response.model,
|
|
350
|
+
cost: calculate_cost(response)
|
|
351
|
+
}
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Builds a batch result for progress callback
|
|
355
|
+
#
|
|
356
|
+
# @param batch_data [Hash] Raw batch data
|
|
357
|
+
# @param count [Integer] Number of texts in batch
|
|
358
|
+
# @return [EmbeddingResult] Result for the batch
|
|
359
|
+
def build_batch_result(batch_data, count)
|
|
360
|
+
EmbeddingResult.new(
|
|
361
|
+
vectors: batch_data[:vectors],
|
|
362
|
+
model_id: batch_data[:model],
|
|
363
|
+
dimensions: batch_data[:vectors].first&.size,
|
|
364
|
+
input_tokens: batch_data[:input_tokens],
|
|
365
|
+
total_cost: batch_data[:cost],
|
|
366
|
+
count: count
|
|
367
|
+
)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Builds the final result object
|
|
371
|
+
#
|
|
372
|
+
# @param vectors [Array<Array<Float>>] All vectors
|
|
373
|
+
# @param input_tokens [Integer] Total tokens
|
|
374
|
+
# @param total_cost [Float] Total cost
|
|
375
|
+
# @param count [Integer] Total texts
|
|
376
|
+
# @param started_at [Time] When execution started
|
|
377
|
+
# @param completed_at [Time] When execution completed
|
|
378
|
+
# @param duration_ms [Integer] Execution duration in ms
|
|
379
|
+
# @param tenant_id [String, nil] Tenant identifier
|
|
380
|
+
# @return [EmbeddingResult] The final result
|
|
381
|
+
def build_result(vectors:, input_tokens:, total_cost:, count:, started_at:, completed_at:, duration_ms:, tenant_id:)
|
|
382
|
+
EmbeddingResult.new(
|
|
383
|
+
vectors: vectors,
|
|
384
|
+
model_id: resolved_model,
|
|
385
|
+
dimensions: vectors.first&.size,
|
|
386
|
+
input_tokens: input_tokens,
|
|
387
|
+
total_cost: total_cost,
|
|
388
|
+
duration_ms: duration_ms,
|
|
389
|
+
count: count,
|
|
390
|
+
started_at: started_at,
|
|
391
|
+
completed_at: completed_at,
|
|
392
|
+
tenant_id: tenant_id
|
|
393
|
+
)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Calculates cost for an embedding response
|
|
397
|
+
#
|
|
398
|
+
# @param response [Object] The ruby_llm embedding response
|
|
399
|
+
# @return [Float] Cost in USD
|
|
400
|
+
def calculate_cost(response)
|
|
401
|
+
# ruby_llm may provide cost directly, otherwise estimate
|
|
402
|
+
return response.input_cost if response.respond_to?(:input_cost) && response.input_cost
|
|
403
|
+
|
|
404
|
+
# Fallback: estimate based on tokens and model
|
|
405
|
+
tokens = response.input_tokens || 0
|
|
406
|
+
model_name = response.model.to_s
|
|
407
|
+
|
|
408
|
+
price_per_million = case model_name
|
|
409
|
+
when /text-embedding-3-small/
|
|
410
|
+
0.02
|
|
411
|
+
when /text-embedding-3-large/
|
|
412
|
+
0.13
|
|
413
|
+
when /text-embedding-ada/
|
|
414
|
+
0.10
|
|
415
|
+
else
|
|
416
|
+
0.02 # Default to small pricing
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
(tokens / 1_000_000.0) * price_per_million
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Resolves the model to use
|
|
423
|
+
#
|
|
424
|
+
# @return [String] The model identifier
|
|
425
|
+
def resolved_model
|
|
426
|
+
@model || self.class.model
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Resolves the dimensions to use
|
|
430
|
+
#
|
|
431
|
+
# @return [Integer, nil] The dimensions or nil for model default
|
|
432
|
+
def resolved_dimensions
|
|
433
|
+
@options[:dimensions] || self.class.dimensions
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Resolves the batch size to use
|
|
437
|
+
#
|
|
438
|
+
# @return [Integer] The batch size
|
|
439
|
+
def resolved_batch_size
|
|
440
|
+
@options[:batch_size] || self.class.batch_size
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../results/moderation_result"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
# Standalone moderator for content moderation using the middleware pipeline
|
|
8
|
+
#
|
|
9
|
+
# Provides a class-based interface for moderating content with built-in
|
|
10
|
+
# support for caching, instrumentation, and multi-tenancy through the
|
|
11
|
+
# middleware pipeline.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# class ContentModerator < RubyLLM::Agents::Moderator
|
|
15
|
+
# model 'omni-moderation-latest'
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# result = ContentModerator.call(text: "content to check")
|
|
19
|
+
# result.flagged? # => true/false
|
|
20
|
+
#
|
|
21
|
+
# @example With configuration
|
|
22
|
+
# class StrictModerator < RubyLLM::Agents::Moderator
|
|
23
|
+
# model 'omni-moderation-latest'
|
|
24
|
+
# threshold 0.7
|
|
25
|
+
# categories :hate, :violence, :harassment
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# @example Runtime override
|
|
29
|
+
# result = ContentModerator.call(
|
|
30
|
+
# text: "content",
|
|
31
|
+
# threshold: 0.9,
|
|
32
|
+
# categories: [:hate]
|
|
33
|
+
# )
|
|
34
|
+
#
|
|
35
|
+
# @api public
|
|
36
|
+
class Moderator < BaseAgent
|
|
37
|
+
class << self
|
|
38
|
+
# Returns the agent type for moderators
|
|
39
|
+
#
|
|
40
|
+
# @return [Symbol] :moderation
|
|
41
|
+
def agent_type
|
|
42
|
+
:moderation
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @!group Moderation-specific DSL
|
|
46
|
+
|
|
47
|
+
# Sets or returns the moderation model
|
|
48
|
+
#
|
|
49
|
+
# Defaults to the moderation model from configuration, not the
|
|
50
|
+
# conversation model that BaseAgent uses.
|
|
51
|
+
#
|
|
52
|
+
# @param value [String, nil] Model identifier to set
|
|
53
|
+
# @return [String] Current model setting
|
|
54
|
+
def model(value = nil)
|
|
55
|
+
@model = value if value
|
|
56
|
+
return @model if defined?(@model) && @model
|
|
57
|
+
|
|
58
|
+
# For inheritance: check if parent is also a Moderator
|
|
59
|
+
if superclass.respond_to?(:agent_type) && superclass.agent_type == :moderation
|
|
60
|
+
superclass.model
|
|
61
|
+
else
|
|
62
|
+
default_moderation_model
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Sets or returns the score threshold
|
|
67
|
+
#
|
|
68
|
+
# @param value [Float, nil] Threshold value (0.0-1.0)
|
|
69
|
+
# @return [Float, nil] Current threshold
|
|
70
|
+
def threshold(value = nil)
|
|
71
|
+
@threshold = value if value
|
|
72
|
+
@threshold || inherited_or_default(:threshold, nil)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Sets or returns the categories to check
|
|
76
|
+
#
|
|
77
|
+
# @param cats [Array<Symbol>] Category symbols
|
|
78
|
+
# @return [Array<Symbol>, nil] Current categories
|
|
79
|
+
def categories(*cats)
|
|
80
|
+
@categories = cats.flatten.map(&:to_sym) if cats.any?
|
|
81
|
+
@categories || inherited_or_default(:categories, nil)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @!endgroup
|
|
85
|
+
|
|
86
|
+
# Factory method to instantiate and execute moderation
|
|
87
|
+
#
|
|
88
|
+
# @param text [String] Text to moderate
|
|
89
|
+
# @param options [Hash] Runtime options
|
|
90
|
+
# @option options [String] :model Override moderation model
|
|
91
|
+
# @option options [Float] :threshold Override threshold
|
|
92
|
+
# @option options [Array<Symbol>] :categories Override categories
|
|
93
|
+
# @option options [Object] :tenant Tenant for multi-tenancy
|
|
94
|
+
# @return [ModerationResult] The moderation result
|
|
95
|
+
def call(text:, **options)
|
|
96
|
+
new(text: text, **options).call
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def inherited_or_default(method, default)
|
|
102
|
+
superclass.respond_to?(method) ? superclass.send(method) : default
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def default_moderation_model
|
|
106
|
+
RubyLLM::Agents.configuration.default_moderation_model
|
|
107
|
+
rescue StandardError
|
|
108
|
+
"omni-moderation-latest"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @!attribute [r] text
|
|
113
|
+
# @return [String] Text to moderate
|
|
114
|
+
attr_reader :text
|
|
115
|
+
|
|
116
|
+
# Creates a new Moderator instance
|
|
117
|
+
#
|
|
118
|
+
# @param text [String] Text to moderate
|
|
119
|
+
# @param options [Hash] Runtime options
|
|
120
|
+
def initialize(text:, **options)
|
|
121
|
+
@text = text
|
|
122
|
+
@runtime_threshold = options.delete(:threshold)
|
|
123
|
+
@runtime_categories = options.delete(:categories)
|
|
124
|
+
|
|
125
|
+
# Set model to moderation model if not specified
|
|
126
|
+
options[:model] ||= self.class.model
|
|
127
|
+
|
|
128
|
+
super(**options)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Executes the moderation through the middleware pipeline
|
|
132
|
+
#
|
|
133
|
+
# @return [ModerationResult] The moderation result
|
|
134
|
+
def call
|
|
135
|
+
context = build_context
|
|
136
|
+
result_context = Pipeline::Executor.execute(context)
|
|
137
|
+
result_context.output
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The input for this moderation operation
|
|
141
|
+
#
|
|
142
|
+
# Used by the pipeline to generate cache keys and for instrumentation.
|
|
143
|
+
#
|
|
144
|
+
# @return [String] The text being moderated
|
|
145
|
+
def user_prompt
|
|
146
|
+
text
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Core moderation execution
|
|
150
|
+
#
|
|
151
|
+
# This is called by the Pipeline::Executor after middleware
|
|
152
|
+
# has been applied. Only contains the moderation API logic.
|
|
153
|
+
#
|
|
154
|
+
# @param context [Pipeline::Context] The execution context
|
|
155
|
+
# @return [void] Sets context.output with the ModerationResult
|
|
156
|
+
def execute(context)
|
|
157
|
+
# Track timing internally
|
|
158
|
+
execution_started_at = Time.current
|
|
159
|
+
|
|
160
|
+
moderation_opts = {}
|
|
161
|
+
moderation_opts[:model] = resolved_model if resolved_model
|
|
162
|
+
|
|
163
|
+
raw_result = RubyLLM.moderate(text, **moderation_opts)
|
|
164
|
+
|
|
165
|
+
execution_completed_at = Time.current
|
|
166
|
+
duration_ms = ((execution_completed_at - execution_started_at) * 1000).to_i
|
|
167
|
+
|
|
168
|
+
# Update context with basic info (no tokens for moderation)
|
|
169
|
+
context.input_tokens = 0
|
|
170
|
+
context.output_tokens = 0
|
|
171
|
+
context.total_cost = 0.0
|
|
172
|
+
|
|
173
|
+
# Build final result
|
|
174
|
+
context.output = ModerationResult.new(
|
|
175
|
+
result: raw_result,
|
|
176
|
+
threshold: resolved_threshold,
|
|
177
|
+
categories: resolved_categories
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Generates the cache key for this moderation
|
|
182
|
+
#
|
|
183
|
+
# @return [String] Cache key in format "ruby_llm_agents/moderation/..."
|
|
184
|
+
def agent_cache_key
|
|
185
|
+
components = [
|
|
186
|
+
"ruby_llm_agents",
|
|
187
|
+
"moderation",
|
|
188
|
+
self.class.name,
|
|
189
|
+
self.class.version,
|
|
190
|
+
resolved_model,
|
|
191
|
+
resolved_threshold,
|
|
192
|
+
resolved_categories&.sort&.join(","),
|
|
193
|
+
Digest::SHA256.hexdigest(text)
|
|
194
|
+
].compact
|
|
195
|
+
|
|
196
|
+
components.join("/")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
# Builds context for pipeline execution
|
|
202
|
+
#
|
|
203
|
+
# @return [Pipeline::Context] The context object
|
|
204
|
+
def build_context
|
|
205
|
+
Pipeline::Context.new(
|
|
206
|
+
input: user_prompt,
|
|
207
|
+
agent_class: self.class,
|
|
208
|
+
agent_instance: self,
|
|
209
|
+
model: resolved_model,
|
|
210
|
+
tenant: @options[:tenant],
|
|
211
|
+
skip_cache: @options[:skip_cache]
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Resolves the model to use
|
|
216
|
+
#
|
|
217
|
+
# @return [String] The model identifier
|
|
218
|
+
def resolved_model
|
|
219
|
+
@model || self.class.model
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Resolves the threshold to use
|
|
223
|
+
#
|
|
224
|
+
# @return [Float, nil] The threshold
|
|
225
|
+
def resolved_threshold
|
|
226
|
+
@runtime_threshold || self.class.threshold
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Resolves the categories to check
|
|
230
|
+
#
|
|
231
|
+
# @return [Array<Symbol>, nil] The categories
|
|
232
|
+
def resolved_categories
|
|
233
|
+
@runtime_categories || self.class.categories
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|