roast-ai 0.4.0 → 0.4.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/.github/workflows/ci.yaml +2 -2
- data/CHANGELOG.md +65 -0
- data/CLAUDE.md +55 -9
- data/Gemfile +1 -0
- data/Gemfile.lock +8 -1
- data/README.md +69 -3
- data/bin/console +1 -0
- data/docs/AGENT_STEPS.md +33 -9
- data/docs/VALIDATION.md +178 -0
- data/examples/agent_continue/add_documentation/prompt.md +5 -0
- data/examples/agent_continue/add_error_handling/prompt.md +5 -0
- data/examples/agent_continue/analyze_codebase/prompt.md +7 -0
- data/examples/agent_continue/combined_workflow.yml +24 -0
- data/examples/agent_continue/continue_adding_features/prompt.md +4 -0
- data/examples/agent_continue/create_integration_tests/prompt.md +3 -0
- data/examples/agent_continue/document_with_context/prompt.md +5 -0
- data/examples/agent_continue/explore_api/prompt.md +6 -0
- data/examples/agent_continue/implement_client/prompt.md +6 -0
- data/examples/agent_continue/inline_workflow.yml +20 -0
- data/examples/agent_continue/refactor_code/prompt.md +2 -0
- data/examples/agent_continue/verify_changes/prompt.md +6 -0
- data/examples/agent_continue/workflow.yml +27 -0
- data/examples/agent_workflow/workflow.png +0 -0
- data/examples/api_workflow/workflow.png +0 -0
- data/examples/apply_diff_demo/README.md +58 -0
- data/examples/apply_diff_demo/apply_simple_change/prompt.md +13 -0
- data/examples/apply_diff_demo/create_sample_file/prompt.md +11 -0
- data/examples/apply_diff_demo/workflow.yml +24 -0
- data/examples/available_tools_demo/workflow.png +0 -0
- data/examples/bash_prototyping/api_testing.png +0 -0
- data/examples/bash_prototyping/system_analysis.png +0 -0
- data/examples/case_when/workflow.png +0 -0
- data/examples/cmd/basic_workflow.png +0 -0
- data/examples/cmd/dev_workflow.png +0 -0
- data/examples/cmd/explorer_workflow.png +0 -0
- data/examples/conditional/simple_workflow.png +0 -0
- data/examples/conditional/workflow.png +0 -0
- data/examples/context_management_demo/README.md +43 -0
- data/examples/context_management_demo/workflow.yml +42 -0
- data/examples/direct_coerce_syntax/workflow.png +0 -0
- data/examples/dot_notation/workflow.png +0 -0
- data/examples/exit_on_error/workflow.png +0 -0
- data/examples/grading/workflow.png +0 -0
- data/examples/interpolation/workflow.png +0 -0
- data/examples/interpolation/workflow.yml +1 -1
- data/examples/iteration/workflow.png +0 -0
- data/examples/json_handling/workflow.png +0 -0
- data/examples/mcp/database_workflow.png +0 -0
- data/examples/mcp/env_demo/workflow.png +0 -0
- data/examples/mcp/filesystem_demo/workflow.png +0 -0
- data/examples/mcp/github_workflow.png +0 -0
- data/examples/mcp/multi_mcp_workflow.png +0 -0
- data/examples/mcp/workflow.png +0 -0
- data/examples/no_model_fallback/README.md +17 -0
- data/examples/no_model_fallback/analyze_file/prompt.md +1 -0
- data/examples/no_model_fallback/analyze_patterns/prompt.md +27 -0
- data/examples/no_model_fallback/generate_report_for_md/prompt.md +10 -0
- data/examples/no_model_fallback/generate_report_for_rb/prompt.md +3 -0
- data/examples/no_model_fallback/sample.rb +42 -0
- data/examples/no_model_fallback/workflow.yml +19 -0
- data/examples/openrouter_example/workflow.png +0 -0
- data/examples/pre_post_processing/workflow.png +0 -0
- data/examples/rspec_to_minitest/workflow.png +0 -0
- data/examples/shared_config/example_with_shared_config/workflow.png +0 -0
- data/examples/shared_config/shared.png +0 -0
- data/examples/single_target_prepost/workflow.png +0 -0
- data/examples/smart_coercion_defaults/workflow.png +0 -0
- data/examples/step_configuration/workflow.png +0 -0
- data/examples/swarm_example.yml +25 -0
- data/examples/tool_config_example/workflow.png +0 -0
- data/examples/user_input/funny_name/workflow.png +0 -0
- data/examples/user_input/simple_input_demo/workflow.png +0 -0
- data/examples/user_input/survey_workflow.png +0 -0
- data/examples/user_input/workflow.png +0 -0
- data/examples/workflow_generator/workflow.png +0 -0
- data/lib/roast/helpers/timeout_handler.rb +91 -0
- data/lib/roast/services/context_threshold_checker.rb +42 -0
- data/lib/roast/services/token_counting_service.rb +44 -0
- data/lib/roast/tools/apply_diff.rb +128 -0
- data/lib/roast/tools/bash.rb +15 -9
- data/lib/roast/tools/cmd.rb +32 -12
- data/lib/roast/tools/coding_agent.rb +64 -9
- data/lib/roast/tools/context_summarizer.rb +108 -0
- data/lib/roast/tools/swarm.rb +124 -0
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/agent_step.rb +9 -2
- data/lib/roast/workflow/base_iteration_step.rb +3 -2
- data/lib/roast/workflow/base_workflow.rb +41 -2
- data/lib/roast/workflow/configuration.rb +2 -1
- data/lib/roast/workflow/configuration_loader.rb +63 -1
- data/lib/roast/workflow/context_manager.rb +89 -0
- data/lib/roast/workflow/each_step.rb +1 -1
- data/lib/roast/workflow/output_handler.rb +1 -1
- data/lib/roast/workflow/repeat_step.rb +1 -1
- data/lib/roast/workflow/replay_handler.rb +1 -1
- data/lib/roast/workflow/sqlite_state_repository.rb +342 -0
- data/lib/roast/workflow/state_manager.rb +2 -2
- data/lib/roast/workflow/state_repository_factory.rb +36 -0
- data/lib/roast/workflow/step_completion_reporter.rb +27 -0
- data/lib/roast/workflow/step_executor_coordinator.rb +19 -18
- data/lib/roast/workflow/step_executor_with_reporting.rb +68 -0
- data/lib/roast/workflow/step_loader.rb +1 -1
- data/lib/roast/workflow/step_name_extractor.rb +84 -0
- data/lib/roast/workflow/validation_command.rb +197 -0
- data/lib/roast/workflow/validators/base_validator.rb +44 -0
- data/lib/roast/workflow/validators/dependency_validator.rb +223 -0
- data/lib/roast/workflow/validators/linting_validator.rb +113 -0
- data/lib/roast/workflow/validators/schema_validator.rb +90 -0
- data/lib/roast/workflow/validators/step_collector.rb +57 -0
- data/lib/roast/workflow/validators/validation_orchestrator.rb +52 -0
- data/lib/roast/workflow/workflow_executor.rb +11 -4
- data/lib/roast/workflow/workflow_runner.rb +6 -0
- data/lib/roast/workflow_diagram_generator.rb +298 -0
- data/lib/roast.rb +157 -0
- data/roast.gemspec +2 -1
- data/schema/workflow.json +77 -1
- metadata +101 -1
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
module Roast
|
6
|
+
module Tools
|
7
|
+
module Swarm
|
8
|
+
extend self
|
9
|
+
|
10
|
+
DEFAULT_CONFIG_PATHS = [
|
11
|
+
".swarm.yml",
|
12
|
+
"swarm.yml",
|
13
|
+
".swarm/config.yml",
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
CONFIG_PATH_KEY = "path"
|
17
|
+
private_constant :DEFAULT_CONFIG_PATHS, :CONFIG_PATH_KEY
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def included(base)
|
21
|
+
base.class_eval do
|
22
|
+
function(
|
23
|
+
:swarm,
|
24
|
+
"Execute Claude Swarm to orchestrate multiple Claude Code instances",
|
25
|
+
prompt: {
|
26
|
+
type: "string",
|
27
|
+
description: "The prompt to send to the swarm agents",
|
28
|
+
required: true,
|
29
|
+
},
|
30
|
+
path: {
|
31
|
+
type: "string",
|
32
|
+
description: "Path to the swarm configuration file (optional)",
|
33
|
+
required: false,
|
34
|
+
},
|
35
|
+
) do |params|
|
36
|
+
Roast::Tools::Swarm.call(params[:prompt], params[:path])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def post_configuration_setup(base, config = {})
|
42
|
+
@tool_config = config
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :tool_config
|
46
|
+
end
|
47
|
+
|
48
|
+
def call(prompt, step_path = nil)
|
49
|
+
config_path = determine_config_path(step_path)
|
50
|
+
|
51
|
+
if config_path.nil?
|
52
|
+
return "Error: No swarm configuration file found. Please create a .swarm.yml file or specify a path."
|
53
|
+
end
|
54
|
+
|
55
|
+
unless File.exist?(config_path)
|
56
|
+
return "Error: Swarm configuration file not found at: #{config_path}"
|
57
|
+
end
|
58
|
+
|
59
|
+
Roast::Helpers::Logger.info("🐝 Running Claude Swarm with config: #{config_path}\n")
|
60
|
+
|
61
|
+
execute_swarm(prompt, config_path)
|
62
|
+
rescue StandardError => e
|
63
|
+
handle_error(e)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def determine_config_path(step_path)
|
69
|
+
# Priority: step-level path > tool-level path > default locations
|
70
|
+
|
71
|
+
# 1. Check step-level path
|
72
|
+
return step_path if step_path
|
73
|
+
|
74
|
+
# 2. Check tool-level path from configuration
|
75
|
+
if tool_config && tool_config[CONFIG_PATH_KEY]
|
76
|
+
return tool_config[CONFIG_PATH_KEY]
|
77
|
+
end
|
78
|
+
|
79
|
+
# 3. Check default locations
|
80
|
+
DEFAULT_CONFIG_PATHS.find { |path| File.exist?(path) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def execute_swarm(prompt, config_path)
|
84
|
+
# Build the swarm command with proper escaping
|
85
|
+
command = build_swarm_command(prompt, config_path)
|
86
|
+
|
87
|
+
result = ""
|
88
|
+
|
89
|
+
# Execute the command directly with the prompt included
|
90
|
+
IO.popen(command, err: [:child, :out]) do |io|
|
91
|
+
result = io.read
|
92
|
+
end
|
93
|
+
|
94
|
+
exit_status = $CHILD_STATUS.exitstatus
|
95
|
+
|
96
|
+
format_output(command, result, exit_status)
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_swarm_command(prompt, config_path)
|
100
|
+
# Build the claude-swarm command with properly escaped arguments
|
101
|
+
[
|
102
|
+
"claude-swarm",
|
103
|
+
"--config",
|
104
|
+
config_path,
|
105
|
+
"--prompt",
|
106
|
+
prompt,
|
107
|
+
].shelljoin
|
108
|
+
end
|
109
|
+
|
110
|
+
def format_output(command, result, exit_status)
|
111
|
+
"Command: #{command}\n" \
|
112
|
+
"Exit status: #{exit_status}\n" \
|
113
|
+
"Output:\n#{result}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def handle_error(error)
|
117
|
+
error_message = "Error running swarm: #{error.message}"
|
118
|
+
Roast::Helpers::Logger.error("#{error_message}\n")
|
119
|
+
Roast::Helpers::Logger.debug("#{error.backtrace.join("\n")}\n") if ENV["DEBUG"]
|
120
|
+
error_message
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/roast/version.rb
CHANGED
@@ -12,8 +12,15 @@ module Roast
|
|
12
12
|
read_sidecar_prompt
|
13
13
|
end
|
14
14
|
|
15
|
-
#
|
16
|
-
|
15
|
+
# Extract agent-specific configuration from workflow config
|
16
|
+
step_config = workflow.config[name.to_s] || {}
|
17
|
+
agent_options = {
|
18
|
+
include_context_summary: step_config.fetch("include_context_summary", false),
|
19
|
+
continue: step_config.fetch("continue", false),
|
20
|
+
}
|
21
|
+
|
22
|
+
# Call CodingAgent directly with the prompt content and options
|
23
|
+
result = Roast::Tools::CodingAgent.call(prompt_content, **agent_options)
|
17
24
|
|
18
25
|
# Process output if print_response is enabled
|
19
26
|
process_output(result, print_response:)
|
@@ -65,10 +65,11 @@ module Roast
|
|
65
65
|
executor ||= WorkflowExecutor.new(context, config_hash, context_path)
|
66
66
|
results = []
|
67
67
|
|
68
|
-
steps.
|
68
|
+
steps.each_with_index do |step, index|
|
69
|
+
is_last_step = (index == steps.length - 1)
|
69
70
|
result = case step
|
70
71
|
when String
|
71
|
-
executor.execute_step(step)
|
72
|
+
executor.execute_step(step, is_last_step:)
|
72
73
|
when Hash, Array
|
73
74
|
executor.execute_steps([step])
|
74
75
|
end
|
@@ -16,9 +16,11 @@ module Roast
|
|
16
16
|
:session_name,
|
17
17
|
:session_timestamp,
|
18
18
|
:model,
|
19
|
-
:workflow_configuration
|
19
|
+
:workflow_configuration,
|
20
|
+
:storage_type,
|
21
|
+
:context_management_config
|
20
22
|
|
21
|
-
attr_reader :pre_processing_data
|
23
|
+
attr_reader :pre_processing_data, :context_manager
|
22
24
|
|
23
25
|
delegate :api_provider, :openai?, to: :workflow_configuration, allow_nil: true
|
24
26
|
delegate :output, :output=, :append_to_final_output, :final_output, to: :output_manager
|
@@ -36,6 +38,8 @@ module Roast
|
|
36
38
|
|
37
39
|
# Initialize managers
|
38
40
|
@output_manager = OutputManager.new
|
41
|
+
@context_manager = ContextManager.new
|
42
|
+
@context_management_config = {}
|
39
43
|
|
40
44
|
# Setup prompt and handlers
|
41
45
|
read_sidecar_prompt.then do |prompt|
|
@@ -53,22 +57,48 @@ module Roast
|
|
53
57
|
step_model = kwargs[:model]
|
54
58
|
|
55
59
|
with_model(step_model) do
|
60
|
+
# Configure context manager if needed
|
61
|
+
if @context_management_config.any?
|
62
|
+
@context_manager.configure(@context_management_config)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Track token usage before API call
|
66
|
+
messages = kwargs[:messages] || transcript.flatten.compact
|
67
|
+
if @context_management_config[:enabled]
|
68
|
+
@context_manager.track_usage(messages)
|
69
|
+
@context_manager.check_warnings
|
70
|
+
end
|
71
|
+
|
56
72
|
ActiveSupport::Notifications.instrument("roast.chat_completion.start", {
|
57
73
|
model: model,
|
58
74
|
parameters: kwargs.except(:openai, :model),
|
59
75
|
})
|
60
76
|
|
77
|
+
# Clear any previous response
|
78
|
+
Thread.current[:chat_completion_response] = nil
|
79
|
+
|
61
80
|
# Call the parent module's chat_completion
|
62
81
|
# skip model because it is read directly from the model method
|
63
82
|
result = super(**kwargs.except(:model))
|
64
83
|
execution_time = Time.now - start_time
|
65
84
|
|
85
|
+
# Extract token usage from the raw response stored by Raix
|
86
|
+
raw_response = Thread.current[:chat_completion_response]
|
87
|
+
token_usage = extract_token_usage(raw_response) if raw_response
|
88
|
+
|
89
|
+
# Update context manager with actual token usage if available
|
90
|
+
if token_usage && @context_management_config[:enabled]
|
91
|
+
actual_total = token_usage.dig("total_tokens") || token_usage.dig(:total_tokens)
|
92
|
+
@context_manager.update_with_actual_usage(actual_total) if actual_total
|
93
|
+
end
|
94
|
+
|
66
95
|
ActiveSupport::Notifications.instrument("roast.chat_completion.complete", {
|
67
96
|
success: true,
|
68
97
|
model: model,
|
69
98
|
parameters: kwargs.except(:openai, :model),
|
70
99
|
execution_time: execution_time,
|
71
100
|
response_size: result.to_s.length,
|
101
|
+
token_usage: token_usage,
|
72
102
|
})
|
73
103
|
result
|
74
104
|
end
|
@@ -115,6 +145,15 @@ module Roast
|
|
115
145
|
def read_sidecar_prompt
|
116
146
|
Roast::Helpers::PromptLoader.load_prompt(self, file)
|
117
147
|
end
|
148
|
+
|
149
|
+
def extract_token_usage(result)
|
150
|
+
# Token usage is typically in the response metadata
|
151
|
+
# This depends on the API provider's response format
|
152
|
+
return unless result.is_a?(Hash) || result.respond_to?(:to_h)
|
153
|
+
|
154
|
+
result_hash = result.is_a?(Hash) ? result : result.to_h
|
155
|
+
result_hash.dig("usage") || result_hash.dig(:usage)
|
156
|
+
end
|
118
157
|
end
|
119
158
|
end
|
120
159
|
end
|
@@ -7,7 +7,7 @@ module Roast
|
|
7
7
|
class Configuration
|
8
8
|
MCPTool = Struct.new(:name, :config, :only, :except, keyword_init: true)
|
9
9
|
|
10
|
-
attr_reader :config_hash, :workflow_path, :name, :steps, :pre_processing, :post_processing, :tools, :tool_configs, :mcp_tools, :function_configs, :model, :resource
|
10
|
+
attr_reader :config_hash, :workflow_path, :name, :steps, :pre_processing, :post_processing, :tools, :tool_configs, :mcp_tools, :function_configs, :model, :resource, :context_management
|
11
11
|
attr_accessor :target
|
12
12
|
|
13
13
|
delegate :api_provider, :openrouter?, :openai?, :uri_base, to: :api_configuration
|
@@ -32,6 +32,7 @@ module Roast
|
|
32
32
|
@mcp_tools = ConfigurationLoader.extract_mcp_tools(@config_hash)
|
33
33
|
@function_configs = ConfigurationLoader.extract_functions(@config_hash)
|
34
34
|
@model = ConfigurationLoader.extract_model(@config_hash)
|
35
|
+
@context_management = ConfigurationLoader.extract_context_management(@config_hash)
|
35
36
|
|
36
37
|
# Initialize components
|
37
38
|
@api_configuration = ApiConfiguration.new(@config_hash)
|
@@ -8,7 +8,7 @@ module Roast
|
|
8
8
|
# Load configuration from a YAML file
|
9
9
|
# @param workflow_path [String] Path to the workflow YAML file
|
10
10
|
# @return [Hash] The parsed configuration hash
|
11
|
-
def load(workflow_path)
|
11
|
+
def load(workflow_path, options = {})
|
12
12
|
validate_path!(workflow_path)
|
13
13
|
|
14
14
|
# Load shared.yml if it exists one level above
|
@@ -23,6 +23,18 @@ module Roast
|
|
23
23
|
end
|
24
24
|
|
25
25
|
yaml_content += File.read(workflow_path)
|
26
|
+
|
27
|
+
# Use comprehensive validation if requested
|
28
|
+
if options[:comprehensive_validation]
|
29
|
+
validator = Validators::ValidationOrchestrator.new(yaml_content, workflow_path)
|
30
|
+
unless validator.valid?
|
31
|
+
raise_validation_errors(validator)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Show warnings if any
|
35
|
+
display_warnings(validator.warnings) if validator.warnings.any?
|
36
|
+
end
|
37
|
+
|
26
38
|
config_hash = YAML.load(yaml_content, aliases: true)
|
27
39
|
|
28
40
|
validate_config!(config_hash)
|
@@ -132,6 +144,30 @@ module Roast
|
|
132
144
|
options[:target] || config_hash["target"]
|
133
145
|
end
|
134
146
|
|
147
|
+
# Extract context management configuration
|
148
|
+
# @param config_hash [Hash] The configuration hash
|
149
|
+
# @return [Hash] The context management configuration with defaults
|
150
|
+
def extract_context_management(config_hash)
|
151
|
+
default_config = {
|
152
|
+
enabled: true,
|
153
|
+
strategy: "auto",
|
154
|
+
threshold: 0.8,
|
155
|
+
max_tokens: nil,
|
156
|
+
retain_steps: [],
|
157
|
+
}
|
158
|
+
|
159
|
+
return default_config unless config_hash["context_management"].is_a?(Hash)
|
160
|
+
|
161
|
+
config = config_hash["context_management"]
|
162
|
+
{
|
163
|
+
enabled: config.fetch("enabled", default_config[:enabled]),
|
164
|
+
strategy: config.fetch("strategy", default_config[:strategy]),
|
165
|
+
threshold: config.fetch("threshold", default_config[:threshold]),
|
166
|
+
max_tokens: config["max_tokens"],
|
167
|
+
retain_steps: config.fetch("retain_steps", default_config[:retain_steps]),
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
135
171
|
private
|
136
172
|
|
137
173
|
def validate_path!(workflow_path)
|
@@ -143,6 +179,32 @@ module Roast
|
|
143
179
|
def validate_config!(config_hash)
|
144
180
|
raise ArgumentError, "Invalid workflow configuration" unless config_hash.is_a?(Hash)
|
145
181
|
end
|
182
|
+
|
183
|
+
def raise_validation_errors(validator)
|
184
|
+
error_messages = validator.errors.map do |error|
|
185
|
+
message = "• #{error[:message]}"
|
186
|
+
message += " (#{error[:suggestion]})" if error[:suggestion]
|
187
|
+
message
|
188
|
+
end.join("\n")
|
189
|
+
|
190
|
+
raise CLI::Kit::Abort, <<~ERROR
|
191
|
+
Workflow validation failed with #{validator.errors.size} error(s):
|
192
|
+
|
193
|
+
#{error_messages}
|
194
|
+
ERROR
|
195
|
+
end
|
196
|
+
|
197
|
+
def display_warnings(warnings)
|
198
|
+
return if warnings.empty?
|
199
|
+
|
200
|
+
::CLI::UI::Frame.open("Validation Warnings", color: :yellow) do
|
201
|
+
warnings.each do |warning|
|
202
|
+
puts ::CLI::UI.fmt("{{yellow:#{warning[:message]}}}")
|
203
|
+
puts ::CLI::UI.fmt(" {{gray:→ #{warning[:suggestion]}}}") if warning[:suggestion]
|
204
|
+
puts
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
146
208
|
end
|
147
209
|
end
|
148
210
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
module Workflow
|
5
|
+
class ContextManager
|
6
|
+
attr_reader :total_tokens
|
7
|
+
|
8
|
+
def initialize(token_counter: nil, threshold_checker: nil)
|
9
|
+
@token_counter = token_counter || Services::TokenCountingService.new
|
10
|
+
@threshold_checker = threshold_checker || Services::ContextThresholdChecker.new
|
11
|
+
@total_tokens = 0
|
12
|
+
@message_count = 0
|
13
|
+
@config = default_config
|
14
|
+
@last_actual_update = nil
|
15
|
+
@estimated_tokens_since_update = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def configure(config)
|
19
|
+
@config = default_config.merge(config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def track_usage(messages)
|
23
|
+
current_tokens = @token_counter.count_messages(messages)
|
24
|
+
@total_tokens += current_tokens
|
25
|
+
@message_count += messages.size
|
26
|
+
|
27
|
+
{
|
28
|
+
current_tokens: current_tokens,
|
29
|
+
total_tokens: @total_tokens,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def should_compact?(token_count = @total_tokens)
|
34
|
+
return false unless @config[:enabled]
|
35
|
+
|
36
|
+
@threshold_checker.should_compact?(
|
37
|
+
token_count,
|
38
|
+
@config[:threshold],
|
39
|
+
@config[:max_tokens],
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_warnings(token_count = @total_tokens)
|
44
|
+
return unless @config[:enabled]
|
45
|
+
|
46
|
+
warning = @threshold_checker.check_warning_threshold(
|
47
|
+
token_count,
|
48
|
+
@config[:threshold],
|
49
|
+
@config[:max_tokens],
|
50
|
+
)
|
51
|
+
|
52
|
+
if warning
|
53
|
+
ActiveSupport::Notifications.instrument("roast.context_warning", warning)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def reset
|
58
|
+
@total_tokens = 0
|
59
|
+
@message_count = 0
|
60
|
+
end
|
61
|
+
|
62
|
+
def statistics
|
63
|
+
{
|
64
|
+
total_tokens: @total_tokens,
|
65
|
+
message_count: @message_count,
|
66
|
+
average_tokens_per_message: @message_count > 0 ? @total_tokens / @message_count : 0,
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def update_with_actual_usage(actual_total)
|
71
|
+
return unless actual_total && actual_total > 0
|
72
|
+
|
73
|
+
@total_tokens = actual_total
|
74
|
+
@last_actual_update = Time.now
|
75
|
+
@estimated_tokens_since_update = 0
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def default_config
|
81
|
+
{
|
82
|
+
enabled: true,
|
83
|
+
threshold: 0.8,
|
84
|
+
max_tokens: nil, # Will use default from threshold checker
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -11,7 +11,7 @@ module Roast
|
|
11
11
|
final_output = workflow.final_output.to_s
|
12
12
|
return if final_output.empty?
|
13
13
|
|
14
|
-
state_repository =
|
14
|
+
state_repository = StateRepositoryFactory.create(workflow.storage_type)
|
15
15
|
output_file = state_repository.save_final_output(workflow, final_output)
|
16
16
|
$stderr.puts "Final output saved to: #{output_file}" if output_file
|
17
17
|
rescue => e
|
@@ -9,7 +9,7 @@ module Roast
|
|
9
9
|
|
10
10
|
def initialize(workflow, state_repository: nil)
|
11
11
|
@workflow = workflow
|
12
|
-
@state_repository = state_repository ||
|
12
|
+
@state_repository = state_repository || StateRepositoryFactory.create(workflow.storage_type)
|
13
13
|
@processed = false
|
14
14
|
end
|
15
15
|
|