diff --git a/Gemfile.lock b/Gemfile.lock index 3e69689..7a02495 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - sublayer (0.2.6) + sublayer (0.2.7) activesupport httparty listen diff --git a/lib/sublayer/cli/commands/action.rb b/lib/sublayer/cli/commands/action.rb index 7c33778..df00e1b 100644 --- a/lib/sublayer/cli/commands/action.rb +++ b/lib/sublayer/cli/commands/action.rb @@ -9,6 +9,10 @@ class Action < Thor::Group class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m + def self.banner + "sublayer generate:action" + end + def confirm_usage_of_ai_api puts "You are about to generate a new agent that uses an AI API to generate content." puts "Please ensure you have the necessary API keys and that you are aware of the costs associated with using the API." diff --git a/lib/sublayer/cli/commands/agent.rb b/lib/sublayer/cli/commands/agent.rb index ff9c828..22dbf34 100644 --- a/lib/sublayer/cli/commands/agent.rb +++ b/lib/sublayer/cli/commands/agent.rb @@ -9,6 +9,10 @@ class Agent < Thor::Group class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m + def self.banner + "sublayer generate:agent" + end + def confirm_usage_of_ai_api puts "You are about to generate a new agent that uses an AI API to generate content." puts "Please ensure you have the necessary API keys and that you are aware of the costs associated with using the API." diff --git a/lib/sublayer/cli/commands/generator.rb b/lib/sublayer/cli/commands/generator.rb index 7da20b8..329d2cf 100644 --- a/lib/sublayer/cli/commands/generator.rb +++ b/lib/sublayer/cli/commands/generator.rb @@ -1,4 +1,5 @@ require_relative "generators/sublayer_generator_generator" +require_relative "generators/sublayer_command_generator" module Sublayer module Commands @@ -8,6 +9,15 @@ class Generator < Thor::Group class_option :description, type: :string, desc: "Description of the generator you want to generate", aliases: :d class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m + class_option :generate_command, type: :boolean, desc: "Generate a command for the new generator", aliases: :c + + def self.banner + "sublayer generate:generator" + end + + def self.source_root + File.expand_path("../../templates", __FILE__) + end def confirm_usage_of_ai_api puts "You are about to generate a new generator that uses an AI API to generate content." @@ -27,6 +37,10 @@ def ask_for_generator_details @description = options[:description] || ask("Enter a description for the Sublayer Generator you'd like to create:") @ai_provider = options[:provider] || ask("Select an AI provider:", default: "OpenAI", limited_to: @available_providers) @ai_model = options[:model] || select_ai_model + + if is_cli_project? && options[:generate_command].nil? + @generate_command = yes?("Would you like to create a corresponding CLI command for this generator?") + end end def generate_generator @@ -52,7 +66,46 @@ def save_generator_to_destination_folder create_file File.join(@destination_folder, @results.filename), @results.code end + def generate_command_if_requested + return unless @generate_command + + say "Generating command..." + + generator_code = File.read(File.join(@destination_folder, @results.filename)) + command_results = SublayerCommandGenerator.new(generator_code: generator_code).generate + @command_class_name = command_results.class_name + @command_description = command_results.description + @command_execute_body = command_results.execute_body + @command_filename = command_results.filename + + commands_folder = File.join("lib", @project_name, "commands") + + destination_path = File.join(commands_folder, @command_filename) + template("utilities/cli/command.rb.tt", destination_path) + end + private + def is_cli_project? + config_path = find_config_file + return false unless config_path + + config = YAML.load_file(config_path) + @project_name = config[:project_name] + config[:project_template] == 'CLI' + rescue StandardError => e + say "Error reading project configuration: #{e.message}", :red + false + end + + def find_config_file + possible_paths = [ + File.join(Dir.pwd, 'config', 'sublayer.yml'), + File.join(Dir.pwd, 'lib', '*', 'config', 'sublayer.yml' ) + ] + + Dir.glob(possible_paths).first + end + def select_ai_model case @ai_provider when "OpenAI" diff --git a/lib/sublayer/cli/commands/generators/sublayer_command_generator.rb b/lib/sublayer/cli/commands/generators/sublayer_command_generator.rb new file mode 100644 index 0000000..edf0f95 --- /dev/null +++ b/lib/sublayer/cli/commands/generators/sublayer_command_generator.rb @@ -0,0 +1,55 @@ +class SublayerCommandGenerator < Sublayer::Generators::Base + llm_output_adapter type: :named_strings, + name: "sublayer_command", + description: "The new command code based on the generator", + attributes: [ + { name: "class_name", description: "The class name of the command" }, + { name: "description", description: "The description of the command" }, + { name: "execute_body", description: "The code inside the execute method" }, + { name: "filename", description: "The filename of the command, snake_cased with a .rb extension" } + ] + + def initialize(generator_code:) + @generator_code = generator_code + end + + def generate + super + end + + def prompt + <<-PROMPT + You are an expert Ruby developer. + + Given the following Sublayer generator code: + + #{@generator_code} + + Please generate a Thor command class that interacts with this generator. The command should: + + - Be a subclass of `BaseCommand`. + - Include a descriptive class name. + - Provide a description for the command. + - Implement an `execute` method that accepts appropriate arguments and invokes the generator. + + Provide the class name, description, execute method body, and filename for the command. + + These parameters will be used in a template to create the command file. The template is: + module <%= project_name.camelize %> + module Commands + class <%= command_class_name %> < BaseCommand + def self.description + "<%= command_description %>" + end + + def execute(*args) + <%= command_execute_body %> + end + end + end + end + + Take into account any parameters the generator requires and map them to command-line arguments. + PROMPT + end + end \ No newline at end of file diff --git a/lib/sublayer/cli/commands/new_project.rb b/lib/sublayer/cli/commands/new_project.rb index 84b1d70..567f5ff 100644 --- a/lib/sublayer/cli/commands/new_project.rb +++ b/lib/sublayer/cli/commands/new_project.rb @@ -17,6 +17,10 @@ def self.source_root File.dirname(__FILE__) end + def self.banner + "sublayer new PROJECT_NAME" + end + def ask_for_project_details puts options[:template] @project_template = options[:template] || ask("Select a project template:", default: "CLI", limited_to: %w[CLI QuickScript]) diff --git a/lib/sublayer/cli/templates/utilities/cli/command.rb.tt b/lib/sublayer/cli/templates/utilities/cli/command.rb.tt new file mode 100644 index 0000000..7c227eb --- /dev/null +++ b/lib/sublayer/cli/templates/utilities/cli/command.rb.tt @@ -0,0 +1,13 @@ +module <%= @project_name.camelize %> + module Commands + class <%= @command_class_name %> < BaseCommand + def self.description + "<%= @command_description %>" + end + + def execute(*args) + <%= @command_execute_body %> + end + end + end +end \ No newline at end of file diff --git a/lib/sublayer/version.rb b/lib/sublayer/version.rb index d9e5887..655c9b7 100644 --- a/lib/sublayer/version.rb +++ b/lib/sublayer/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Sublayer - VERSION = "0.2.6" + VERSION = "0.2.7" end diff --git a/spec/cli/generators/command_generator_spec.rb b/spec/cli/generators/command_generator_spec.rb new file mode 100644 index 0000000..41d9d58 --- /dev/null +++ b/spec/cli/generators/command_generator_spec.rb @@ -0,0 +1,75 @@ +require "spec_helper" + +require_relative "../../../lib/sublayer/cli/commands/generators/sublayer_command_generator" + +RSpec.describe SublayerCommandGenerator do + def generate + described_class.new(generator_code: File.read("spec/generators/examples/task_steps_generator.rb")).generate + end + + context "OpenAI" do + before do + Sublayer.configuration.ai_provider = Sublayer::Providers::OpenAI + Sublayer.configuration.ai_model = "gpt-4o-mini" + end + + it "generates the Sublayer Command code and filename" do + VCR.use_cassette("openai/cli/generators/sublayer_command_generator") do + results = generate + + expect(results.filename).to be_a(String) + expect(results.filename.length).to be > 0 + expect(results.class_name).to be_a(String) + expect(results.class_name.length).to be > 0 + expect(results.description).to be_a(String) + expect(results.description.length).to be > 0 + expect(results.execute_body).to be_a(String) + expect(results.execute_body.length).to be > 0 + end + end + end + + context "Gemini" do + before do + Sublayer.configuration.ai_provider = Sublayer::Providers::Gemini + Sublayer.configuration.ai_model = "gemini-1.5-flash" + end + + it "generates the Sublayer Command code and filename" do + VCR.use_cassette("gemini/cli/generators/sublayer_command_generator") do + results = generate + + expect(results.filename).to be_a(String) + expect(results.filename.length).to be > 0 + expect(results.class_name).to be_a(String) + expect(results.class_name.length).to be > 0 + expect(results.description).to be_a(String) + expect(results.description.length).to be > 0 + expect(results.execute_body).to be_a(String) + expect(results.execute_body.length).to be > 0 + end + end + end + + context "Claude" do + before do + Sublayer.configuration.ai_provider = Sublayer::Providers::Claude + Sublayer.configuration.ai_model = "claude-3-haiku-20240307" + end + + it "generates the Sublayer Command code and filename" do + VCR.use_cassette("claude/cli/generators/sublayer_command_generator") do + results = generate + + expect(results.filename).to be_a(String) + expect(results.filename.length).to be > 0 + expect(results.class_name).to be_a(String) + expect(results.class_name.length).to be > 0 + expect(results.description).to be_a(String) + expect(results.description.length).to be > 0 + expect(results.execute_body).to be_a(String) + expect(results.execute_body.length).to be > 0 + end + end + end +end \ No newline at end of file diff --git a/spec/vcr_cassettes/claude/cli/generators/sublayer_command_generator.yml b/spec/vcr_cassettes/claude/cli/generators/sublayer_command_generator.yml new file mode 100644 index 0000000..356d1cd --- /dev/null +++ b/spec/vcr_cassettes/claude/cli/generators/sublayer_command_generator.yml @@ -0,0 +1,104 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.anthropic.com/v1/messages + body: + encoding: UTF-8 + string: '{"model":"claude-3-haiku-20240307","max_tokens":4096,"tools":[{"name":"sublayer_command","description":"The + new command code based on the generator","input_schema":{"type":"object","properties":{"sublayer_command":{"type":"object","description":"The + new command code based on the generator","properties":{"class_name":{"type":"string","description":"The + class name of the command"},"description":{"type":"string","description":"The + description of the command"},"execute_body":{"type":"string","description":"The + code inside the execute method"},"filename":{"type":"string","description":"The + filename of the command, snake_cased with a .rb extension"}},"required":["class_name","description","execute_body","filename"]}},"required":["sublayer_command"]}}],"tool_choice":{"type":"tool","name":"sublayer_command"},"messages":[{"role":"user","content":" You + are an expert Ruby developer.\n\n Given the following Sublayer generator + code:\n\n class TaskStepsGenerator < Sublayer::Generators::Base\n llm_output_adapter + type: :list_of_named_strings,\n name: \"steps\",\n description: \"List + of steps to complete a task\",\n item_name: \"step\",\n attributes: + [ { name: \"description\", description: \"A brief description of the step\" + }, { name: \"command\", description: \"The command to execute for this step\" + } ]\n\n def initialize(task:)\n @task = task\n end\n\n def generate\n super\n end\n\n def + prompt\n <<-PROMPT\n You are an expert at breaking down tasks into step-by-step + instructions with associated commands.\n Please generate a list of steps + to complete the following task:\n\n #{@task}\n\n For each step, provide:\n - + description: A brief description of what the step accomplishes\n - command: + The exact command to execute for this step (if applicable; use \"N/A\" if + no command is needed)\n\n Provide your response as a list of objects, each + containing the above attributes.\n Ensure the steps are in the correct + order to complete the task successfully.\n PROMPT\n end\nend\n\n\n Please + generate a Thor command class that interacts with this generator. The command + should:\n\n - Be a subclass of `BaseCommand`.\n - Include a descriptive + class name.\n - Provide a description for the command.\n - Implement + an `execute` method that accepts appropriate arguments and invokes the generator.\n\n Provide + the class name, description, execute method body, and filename for the command.\n\n These + parameters will be used in a template to create the command file. The template + is:\n module <%= project_name.camelize %>\n module Commands\n class + <%= command_class_name %> < BaseCommand\n def self.description\n \"<%= + command_description %>\"\n end\n\n def execute(*args)\n <%= + command_execute_body %>\n end\n end\n end\n end\n\n Take + into account any parameters the generator requires and map them to command-line + arguments.\n"}]}' + headers: + X-Api-Key: + - "" + Anthropic-Version: + - '2023-06-01' + Content-Type: + - application/json + Anthropic-Beta: + - tools-2024-04-04 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 17 Sep 2024 18:27:05 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Requests-Limit: + - '1000' + Anthropic-Ratelimit-Requests-Remaining: + - '999' + Anthropic-Ratelimit-Requests-Reset: + - '2024-09-17T18:27:34Z' + Anthropic-Ratelimit-Tokens-Limit: + - '100000' + Anthropic-Ratelimit-Tokens-Remaining: + - '100000' + Anthropic-Ratelimit-Tokens-Reset: + - '2024-09-17T18:27:05Z' + Request-Id: + - req_01DRdnwpEjKT8vq1m5aZDsHm + X-Cloud-Trace-Context: + - 7a31ac7276a8382105264da41b0e183c + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - 8c4b25ed892c5e6b-EWR + body: + encoding: ASCII-8BIT + string: '{"id":"msg_014Ae1nqsVAKA4S3JiDU49sk","type":"message","role":"assistant","model":"claude-3-haiku-20240307","content":[{"type":"tool_use","id":"toolu_016ryjzz8w9iKh5DtdR1YKdu","name":"sublayer_command","input":{"sublayer_command":{"class_name":"TaskStepsCommand","description":"Generate + a list of steps to complete a task, including descriptions and commands.","execute_body":"\n task + = args.first\n generator = TaskStepsGenerator.new(task: task)\n steps + = generator.generate\n steps.each do |step|\n puts \"Step: #{step[''description'']}\"\n puts + \"Command: #{step[''command'']}\"\n puts\n end\n ","filename":"task_steps_command.rb"}}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":1065,"output_tokens":169}}' + recorded_at: Tue, 17 Sep 2024 18:27:05 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/vcr_cassettes/gemini/cli/generators/sublayer_command_generator.yml b/spec/vcr_cassettes/gemini/cli/generators/sublayer_command_generator.yml new file mode 100644 index 0000000..91deb22 --- /dev/null +++ b/spec/vcr_cassettes/gemini/cli/generators/sublayer_command_generator.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key= + body: + encoding: UTF-8 + string: '{"contents":{"role":"user","parts":{"text":" You are an expert + Ruby developer.\n\n Given the following Sublayer generator code:\n\n class + TaskStepsGenerator < Sublayer::Generators::Base\n llm_output_adapter type: + :list_of_named_strings,\n name: \"steps\",\n description: \"List of + steps to complete a task\",\n item_name: \"step\",\n attributes: [ { + name: \"description\", description: \"A brief description of the step\" }, + { name: \"command\", description: \"The command to execute for this step\" + } ]\n\n def initialize(task:)\n @task = task\n end\n\n def generate\n super\n end\n\n def + prompt\n <<-PROMPT\n You are an expert at breaking down tasks into step-by-step + instructions with associated commands.\n Please generate a list of steps + to complete the following task:\n\n #{@task}\n\n For each step, provide:\n - + description: A brief description of what the step accomplishes\n - command: + The exact command to execute for this step (if applicable; use \"N/A\" if + no command is needed)\n\n Provide your response as a list of objects, each + containing the above attributes.\n Ensure the steps are in the correct + order to complete the task successfully.\n PROMPT\n end\nend\n\n\n Please + generate a Thor command class that interacts with this generator. The command + should:\n\n - Be a subclass of `BaseCommand`.\n - Include a descriptive + class name.\n - Provide a description for the command.\n - Implement + an `execute` method that accepts appropriate arguments and invokes the generator.\n\n Provide + the class name, description, execute method body, and filename for the command.\n\n These + parameters will be used in a template to create the command file. The template + is:\n module <%= project_name.camelize %>\n module Commands\n class + <%= command_class_name %> < BaseCommand\n def self.description\n \"<%= + command_description %>\"\n end\n\n def execute(*args)\n <%= + command_execute_body %>\n end\n end\n end\n end\n\n Take + into account any parameters the generator requires and map them to command-line + arguments.\n"}},"generationConfig":{"responseMimeType":"application/json","responseSchema":{"type":"OBJECT","properties":{"sublayer_command":{"type":"object","description":"The + new command code based on the generator","properties":{"class_name":{"type":"string","description":"The + class name of the command"},"description":{"type":"string","description":"The + description of the command"},"execute_body":{"type":"string","description":"The + code inside the execute method"},"filename":{"type":"string","description":"The + filename of the command, snake_cased with a .rb extension"}},"required":["class_name","description","execute_body","filename"]}},"required":["sublayer_command"]}}}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Tue, 17 Sep 2024 18:27:06 GMT + Server: + - scaffolding on HTTPServer2 + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=738 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "{\"sublayer_command\": {\"class_name\": \"GenerateTaskStepsCommand\", \"description\": \"Generate a list of steps to complete a task, including descriptions and commands.\", \"execute_body\": \"generator = TaskStepsGenerator.new(task: args[0])\\nputs generator.generate\", \"filename\": \"generate_task_steps_command.rb\"}} " + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0, + "safetyRatings": [ + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE" + } + ] + } + ], + "usageMetadata": { + "promptTokenCount": 490, + "candidatesTokenCount": 77, + "totalTokenCount": 567 + } + } + recorded_at: Tue, 17 Sep 2024 18:27:06 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/vcr_cassettes/openai/cli/generators/sublayer_command_generator.yml b/spec/vcr_cassettes/openai/cli/generators/sublayer_command_generator.yml new file mode 100644 index 0000000..f621dea --- /dev/null +++ b/spec/vcr_cassettes/openai/cli/generators/sublayer_command_generator.yml @@ -0,0 +1,147 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"gpt-4o-mini","messages":[{"role":"user","content":" You + are an expert Ruby developer.\n\n Given the following Sublayer generator + code:\n\n class TaskStepsGenerator < Sublayer::Generators::Base\n llm_output_adapter + type: :list_of_named_strings,\n name: \"steps\",\n description: \"List + of steps to complete a task\",\n item_name: \"step\",\n attributes: + [ { name: \"description\", description: \"A brief description of the step\" + }, { name: \"command\", description: \"The command to execute for this step\" + } ]\n\n def initialize(task:)\n @task = task\n end\n\n def generate\n super\n end\n\n def + prompt\n <<-PROMPT\n You are an expert at breaking down tasks into step-by-step + instructions with associated commands.\n Please generate a list of steps + to complete the following task:\n\n #{@task}\n\n For each step, provide:\n - + description: A brief description of what the step accomplishes\n - command: + The exact command to execute for this step (if applicable; use \"N/A\" if + no command is needed)\n\n Provide your response as a list of objects, each + containing the above attributes.\n Ensure the steps are in the correct + order to complete the task successfully.\n PROMPT\n end\nend\n\n\n Please + generate a Thor command class that interacts with this generator. The command + should:\n\n - Be a subclass of `BaseCommand`.\n - Include a descriptive + class name.\n - Provide a description for the command.\n - Implement + an `execute` method that accepts appropriate arguments and invokes the generator.\n\n Provide + the class name, description, execute method body, and filename for the command.\n\n These + parameters will be used in a template to create the command file. The template + is:\n module <%= project_name.camelize %>\n module Commands\n class + <%= command_class_name %> < BaseCommand\n def self.description\n \"<%= + command_description %>\"\n end\n\n def execute(*args)\n <%= + command_execute_body %>\n end\n end\n end\n end\n\n Take + into account any parameters the generator requires and map them to command-line + arguments.\n"}],"tool_choice":{"type":"function","function":{"name":"sublayer_command"}},"tools":[{"type":"function","function":{"name":"sublayer_command","description":"The + new command code based on the generator","parameters":{"type":"object","properties":{"sublayer_command":{"type":"object","description":"The + new command code based on the generator","properties":{"class_name":{"type":"string","description":"The + class name of the command"},"description":{"type":"string","description":"The + description of the command"},"execute_body":{"type":"string","description":"The + code inside the execute method"},"filename":{"type":"string","description":"The + filename of the command, snake_cased with a .rb extension"}},"required":["class_name","description","execute_body","filename"]}}},"required":["sublayer_command"]}}]}' + headers: + Content-Type: + - application/json + Authorization: + - Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 17 Sep 2024 18:27:07 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - sublayer + Openai-Processing-Ms: + - '785' + Openai-Version: + - '2020-10-01' + Strict-Transport-Security: + - max-age=15552000; includeSubDomains; preload + X-Ratelimit-Limit-Requests: + - '10000' + X-Ratelimit-Limit-Tokens: + - '10000000' + X-Ratelimit-Remaining-Requests: + - '9999' + X-Ratelimit-Remaining-Tokens: + - '9999454' + X-Ratelimit-Reset-Requests: + - 6ms + X-Ratelimit-Reset-Tokens: + - 3ms + X-Request-Id: + - req_8670d7f5686bd2cb37ce5ca9c8dab21c + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=5FHl7.TO_A5rRZ10OxPy1vdY3uWEPNCeOHHvNMd38aY-1726597627-1.0.1.1-YwfvnxeL8.aqerbuuWFtFc3WNZSuNkD.UqUWjQ6X2st1bNDx1ayloEOGDXM8VxCA06yYgwqRlQ3ZpE4.9lIe2w; + path=/; expires=Tue, 17-Sep-24 18:57:07 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=s6DExVlKvJBmAVWsbFY2OzyDwD7MSF0oLtnjUC.p8Nk-1726597627548-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - 8c4b25fd692a423a-EWR + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-A8XCkjH0aTQ0oCIrSF3JOmSilf2tP", + "object": "chat.completion", + "created": 1726597626, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_5ucK7D4dt8zCNd9f7aUReyOz", + "type": "function", + "function": { + "name": "sublayer_command", + "arguments": "{\"sublayer_command\":{\"class_name\":\"TaskStepsCommand\",\"description\":\"Generates a list of steps to complete a task with associated commands\",\"execute_body\":\"task = args.first\\nsteps_generator = TaskStepsGenerator.new(task: task)\\nsteps = steps_generator.generate\\nputs steps.to_json\",\"filename\":\"task_steps_command.rb\"}}" + } + } + ], + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 572, + "completion_tokens": 67, + "total_tokens": 639, + "completion_tokens_details": { + "reasoning_tokens": 0 + } + }, + "system_fingerprint": "fp_483d39d857" + } + recorded_at: Tue, 17 Sep 2024 18:27:07 GMT +recorded_with: VCR 6.2.0