sublayer 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8254634ae1c2de3daa2f3a1fddf102df81ee82e79e345c1ef4a9256ea35553e2
4
- data.tar.gz: 7a2eb512a983ae642858af5c395d3651b69449359204975be9468b30736c6c40
3
+ metadata.gz: 9c0626295be488d2a39cfbe94652d3e1238b5885c24c5d4d130e0659939387fd
4
+ data.tar.gz: e21ee3e2177caff3a1125de4ba07672db13df661c5888e031d84148f4bfa0a01
5
5
  SHA512:
6
- metadata.gz: ea8183fce2be4f378a581d079a124df8c82b03165186575b6152576e76ca26fd93a925df6a108d1e348bdd393a71206d3e79ce832b000c9607224c9ea5fc46df
7
- data.tar.gz: a52f4942386fabe647fe97ee060747fa02e6f93c1261a33336bb14012383907419ad92e385c7fe4973df313dad8cc818f772063c8637ca81e318ba0b273430bb
6
+ metadata.gz: 8b2a17ca1598e68b7da6a0caf07d61d53f99c6de561f03bbd4f4a74a4bf05ad30ebb1c9261f34320297a1af88c4b82e35163a7711a3d260d97732d2b34b87f1a
7
+ data.tar.gz: adcb0094652bdd477f5cbbf3346037bbfa1b2b62a7c54a148ddd0d4cb440bef8d7c994487a8ef47aee30c0039d3bad7ddfafee587c3fa6c688c6e51f0e89d26e
data/README.md CHANGED
@@ -14,12 +14,18 @@ for new features and bug fixes.
14
14
 
15
15
  To maintain stability in your application, we recommend pinning the version of
16
16
  Sublayer in your Gemfile to a specific minor version. For example, to pin to
17
- version 0.1.x, you would add the following line to your Gemfile:
17
+ version 0.2.x, you would add the following line to your Gemfile:
18
18
 
19
19
  ```ruby
20
- gem 'sublayer', '~> 0.1'
20
+ gem 'sublayer', '~> 0.2'
21
21
  ```
22
22
 
23
+ ## Notes on 0.2
24
+
25
+ New default model update: gpt 4 turbo -> gpt 4o
26
+
27
+ Gemini: Updates include the use of beta API function calling features. Experimental and unstable.
28
+
23
29
  ## Installation
24
30
 
25
31
  Install the gem by running the following commands:
@@ -29,12 +35,12 @@ Install the gem by running the following commands:
29
35
  Or add this line to your application's Gemfile:
30
36
 
31
37
  ```ruby
32
- gem 'sublayer', '~> 0.1'
38
+ gem 'sublayer', '~> 0.2'
33
39
  ```
34
40
 
35
41
  ## Choose your AI Model
36
42
 
37
- Sublayer is model-agnostic and can be used with any AI model. Below are the
43
+ Sublayer is model-agnostic and can be used with any AI model. Below are the supported LLM Providers. Check out our [docs](https://docs.sublayer.com) to add your own custom Provider.
38
44
 
39
45
  ### OpenAI (Default)
40
46
 
@@ -45,10 +51,12 @@ Visit [OpenAI](https://openai.com/product) to get an API key.
45
51
  Usage:
46
52
  ```ruby
47
53
  Sublayer.configuration.ai_provider = Sublayer::Providers::OpenAI
48
- Sublayer.configuration.ai_model = "gpt-4-turbo-preview"
54
+ Sublayer.configuration.ai_model = "gpt-4o"
49
55
  ```
50
56
 
51
- ### Gemini
57
+ ### Gemini [UNSTABLE]
58
+
59
+ (Gemini's function calling API is in beta. Not recommended for production use.)
52
60
 
53
61
  Expects you to have a Gemini API key set in the `GEMINI_API_KEY` environment variable.
54
62
 
@@ -57,7 +65,7 @@ Visit [Google AI Studio](https://ai.google.dev/) to get an API key.
57
65
  Usage:
58
66
  ```ruby
59
67
  Sublayer.configuration.ai_provider = Sublayer::Providers::Gemini
60
- Sublayer.configuration.ai_model = "gemini-pro"
68
+ Sublayer.configuration.ai_model = "gemini-1.5-pro"
61
69
  ```
62
70
 
63
71
  ### Claude
@@ -70,7 +78,7 @@ Visit [Anthropic](https://anthropic.com/) to get an API key.
70
78
  Usage:
71
79
  ```ruby
72
80
  Sublayer.configuration.ai_provider = Sublayer::Providers::Claude
73
- Sublayer.configuration.ai_model ="claude-3-opus-20240229"
81
+ Sublayer.configuration.ai_model ="claude-3-5-sonnet-20240620"
74
82
  ```
75
83
 
76
84
  ## Concepts
@@ -81,49 +89,46 @@ Generators are responsible for generating specific outputs based on input data.
81
89
  They focus on a single generation task and do not perform any actions or complex
82
90
  decision-making. Generators are the building blocks of the Sublayer framework.
83
91
 
84
- Examples (in the `/lib/sublayer/generators/examples` directory):
85
- - `CodeFromDescriptionGenerator`: Generates code based on a description and the
86
- technologies used.
87
- - `DescriptionFromCodeGenerator`: Generates a description of the code passed in to
88
- it.
89
- - `CodeFromBlueprintGenerator`: Generates code based on a blueprint, a blueprint
90
- description, and a description of the desired code.
92
+ Examples (in the `/spec/generators/examples` directory):
93
+ - [CodeFromDescriptionGenerator](https://github.com/sublayerapp/sublayer/blob/main/spec/generators/examples/code_from_description_generator.rb):
94
+ Generates code based on a description and the technologies used.
95
+ - [DescriptionFromCodeGenerator](https://github.com/sublayerapp/sublayer/blob/main/spec/generators/description_from_code_generator_spec.rb):
96
+ Generates a description of the code passed in to it.
97
+ - [CodeFromBlueprintGenerator](https://github.com/sublayerapp/sublayer/blob/main/spec/generators/examples/code_from_blueprint_generator.rb):
98
+ Generates code based on a blueprint, a blueprint description, and a description of the desired code.
91
99
 
92
100
 
93
- ### Actions (Coming Soon)
101
+ ### Actions
94
102
 
95
- Actions are responsible for performing specific operations to get inputs for a
96
- Generator or based on the generated output from a Generator. They encapsulate a
97
- single action and do not involve complex decision-making. Actions are the
98
- executable units that bring the generated inputs to life.
103
+ Actions perform specific operations to either get inputs for a Generator or use
104
+ the generated output from a Generator. Actions do not involve complex decision making.
99
105
 
100
106
  Examples:
101
- - SaveToFileAction: Saves generated output to a file.
102
- - RunCommandLineCommandAction: Runs a generated command line command.
107
+ - [WriteFileAction](https://github.com/sublayerapp/tddbot/blob/43297c5da9445bd6c8882d5e3876cff5fc6b2650/lib/tddbot/sublayer/actions/write_file_action.rb):
108
+ Saves generated output to a file.
109
+ - [RunTestCommandAction](https://github.com/sublayerapp/tddbot/blob/43297c5da9445bd6c8882d5e3876cff5fc6b2650/lib/tddbot/sublayer/actions/run_test_command_action.rb):
110
+ Runs a generated command line command.
103
111
 
104
- ### Tasks (Coming Soon)
112
+ ### Agents
105
113
 
106
- Tasks combine Generators and Actions to accomplish a specific goal. They involve
107
- a sequence of generation and action steps that may include basic decision-making
108
- and flow control. Tasks are the high-level building blocks that define the
109
- desired outcome.
114
+ Sublayer Agents are autonomous entities designed to perform specific
115
+ tasks or monitor systems.
110
116
 
111
117
  Examples:
112
- - ModifyFileContentsTask: Generates new file contents based on the existing
113
- contents and a set of rules, and then saves the new contents to the file.
118
+ - [RSpecAgent](https://github.com/sublayerapp/sublayer/blob/main/spec/agents/examples/rspec_agent.rb):
119
+ Runs tests whenever a file is changed. If the tests fail the code is changed
120
+ by the agent to pass the tests.
114
121
 
115
- ### Agents (Coming Soon)
122
+ ### Triggers
116
123
 
117
- Agents are high-level entities that coordinate and orchestrate multiple Tasks to
118
- achieve a broader goal. They involve complex decision-making, monitoring, and
119
- adaptation based on the outcomes of the Tasks. Agents are the intelligent
120
- supervisors that manage the overall workflow.
124
+ Sublayer Triggers are used in agents. Triggers decide when an agent is activated
125
+ and performs its task
121
126
 
122
127
  Examples:
123
- - CustomerSupportAgent: Handles customer support inquiries by using various
124
- Tasks such as understanding the customer's issue, generating appropriate
125
- responses, and performing actions like sending emails or creating support
126
- tickets.
128
+ - [FileChange](https://github.com/sublayerapp/sublayer/blob/main/lib/sublayer/triggers/file_change.rb):
129
+ This built in sublayer trigger, listens for file changes
130
+ - [TimeInterval](https://docs.sublayer.com/docs/guides/build-a-custom-trigger)
131
+ This custom trigger tutorial shows how to create your own trigger, this one activates on a time interval
127
132
 
128
133
  ## Usage Examples
129
134
 
@@ -0,0 +1,29 @@
1
+ module Sublayer
2
+ module Components
3
+ module OutputAdapters
4
+ module Formattable
5
+ def format_properties
6
+ formatted_properties = {}
7
+ self.properties.each do |prop|
8
+ property = {
9
+ type: prop.type,
10
+ description: prop.description
11
+ }
12
+
13
+ property[:enum] = prop.enum if prop.respond_to?(:enum) && prop.enum
14
+ property[:default] = prop.default if prop.respond_to?(:default) && !prop.default.nil?
15
+ property[:minimum] = prop.minimum if prop.respond_to?(:minimum) && !prop.minimum.nil?
16
+ property[:maximum] = prop.maximum if prop.respond_to?(:maximum) && !prop.maximum.nil?
17
+ property[:items] = prop.items if prop.respond_to?(:items) && prop.items
18
+ formatted_properties[prop.name.to_sym] = property
19
+ end
20
+ formatted_properties
21
+ end
22
+
23
+ def format_required
24
+ self.properties.select(&:required).map(&:name)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ module Sublayer
2
+ module Components
3
+ module OutputAdapters
4
+ class ListOfStrings
5
+ attr_reader :name, :description
6
+
7
+ def initialize(options)
8
+ @name = options[:name]
9
+ @description = options[:description]
10
+ end
11
+
12
+ def properties
13
+ [
14
+ OpenStruct.new(
15
+ name: @name,
16
+ type: 'array',
17
+ description: @description,
18
+ required: true,
19
+ items: {
20
+ type: 'string'
21
+ }
22
+ )
23
+ ]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -18,6 +18,9 @@ module Sublayer
18
18
  else
19
19
  raise "Output adapter must be specified with :class or :type"
20
20
  end
21
+
22
+ options[:name] = options[:name].to_s if options[:name].is_a?(Symbol)
23
+
21
24
  klass.new(options)
22
25
  end
23
26
  end
@@ -4,7 +4,7 @@ module Sublayer
4
4
  attr_reader :results
5
5
 
6
6
  def self.llm_output_adapter(options)
7
- output_adapter = Sublayer::Components::OutputAdapters.create(options)
7
+ output_adapter = Sublayer::Components::OutputAdapters.create(options).extend(Sublayer::Components::OutputAdapters::Formattable)
8
8
  const_set(:OUTPUT_ADAPTER, output_adapter)
9
9
  end
10
10
 
@@ -1,5 +1,5 @@
1
1
  # Sublayer.configuration.ai_provider = Sublayer::Providers::Claude
2
- # Sublayer.configuration.ai_model ="claude-3-opus-20240229"
2
+ # Sublayer.configuration.ai_model ="claude-3-5-sonnet-20240620"
3
3
 
4
4
  module Sublayer
5
5
  module Providers
@@ -22,32 +22,23 @@ module Sublayer
22
22
  description: output_adapter.description,
23
23
  input_schema: {
24
24
  type: "object",
25
- properties: format_properties(output_adapter),
26
- required: output_adapter.properties.select(&:required).map(&:name)
25
+ properties: output_adapter.format_properties,
26
+ required: output_adapter.format_required
27
27
  }
28
28
  }
29
29
  ],
30
+ tool_choice: { type: "tool", name: output_adapter.name },
30
31
  messages: [{ "role": "user", "content": prompt }]
31
32
  }.to_json
32
33
  )
34
+
33
35
  raise "Error generating with Claude, error: #{response.body}" unless response.code == 200
34
36
 
35
- function_input = JSON.parse(response.body).dig("content").find {|content| content['type'] == 'tool_use'}.dig("input")
36
- function_input[output_adapter.name]
37
- end
37
+ tool_use = JSON.parse(response.body).dig("content").find { |content| content['type'] == 'tool_use' && content['name'] == output_adapter.name }
38
38
 
39
- private
40
- def self.format_properties(output_adapter)
41
- output_adapter.properties.each_with_object({}) do |property, hash|
42
- hash[property.name] = {
43
- type: property.type,
44
- description: property.description
45
- }
39
+ raise "Error generating with Claude, error: No function called. If the answer is in the response, try rewording your prompt or output adapter name to be from the perspective of the model. Response: #{response.body}" unless tool_use
46
40
 
47
- if property.enum
48
- hash[property.name][:enum] = property.enum
49
- end
50
- end
41
+ tool_use.dig("input")[output_adapter.name]
51
42
  end
52
43
  end
53
44
  end
@@ -1,44 +1,41 @@
1
+ # *UNSTABLE* Gemini function calling API is in beta.
2
+ # Provider is not recommended until API update.
3
+
1
4
  # Sublayer.configuration.ai_provider = Sublayer::Providers::Gemini
2
- # Sublayer.configuration.ai_model = "gemini-pro"
5
+ # Sublayer.configuration.ai_model = "gemini-1.5-pro"
3
6
 
4
7
  module Sublayer
5
8
  module Providers
6
9
  class Gemini
7
10
  def self.call(prompt:, output_adapter:)
8
- system_prompt = <<-PROMPT
9
- You have access to a set of tools to answer the prompt.
10
-
11
- You may call tools like this:
12
- <tool_calls>
13
- <tool_call>
14
- <tool_name>$TOOL_NAME</tool_name>
15
- <parameters>
16
- <$PARAMETER_NAME>$VALUE</$PARAMETER_NAME>
17
- ...
18
- </parameters>
19
- </tool_call>
20
- </tool_calls>
21
-
22
- Here are the tools available:
23
- <tools>
24
- <tool_description>
25
- <tool_name>#{output_adapter.name}</tool_name>
26
- <tool_description>#{output_adapter.description}</tool_description>
27
- <parameters>
28
- #{format_properties(output_adapter)}
29
- </parameters>
30
- </tool_description>
31
- </tools>
32
-
33
- Respond only with valid xml.
34
- The entire response should be wrapped in a <response> tag.
35
- Your response should call a tool inside a <tool_calls> tag.
36
- PROMPT
37
-
38
11
  response = HTTParty.post(
39
12
  "https://generativelanguage.googleapis.com/v1beta/models/#{Sublayer.configuration.ai_model}:generateContent?key=#{ENV['GEMINI_API_KEY']}",
40
13
  body: {
41
- contents: { role: "user", parts: { text: "#{system_prompt}\n#{prompt}" } }
14
+ contents: {
15
+ role: "user",
16
+ parts: {
17
+ text: "#{prompt}"
18
+ },
19
+ },
20
+ tools: {
21
+ functionDeclarations: [
22
+ {
23
+ name: output_adapter.name,
24
+ description: output_adapter.description,
25
+ parameters: {
26
+ type: "OBJECT",
27
+ properties: output_adapter.format_properties,
28
+ required: output_adapter.format_required
29
+ }
30
+ }
31
+ ]
32
+ },
33
+ tool_config: {
34
+ function_calling_config: {
35
+ mode: "ANY",
36
+ allowed_function_names: [output_adapter.name]
37
+ }
38
+ }
42
39
  }.to_json,
43
40
  headers: {
44
41
  "Content-Type" => "application/json"
@@ -47,22 +44,7 @@ module Sublayer
47
44
 
48
45
  raise "Error generating with Gemini, error: #{response.body}" unless response.success?
49
46
 
50
- text_containing_xml = response.dig('candidates', 0, 'content', 'parts', 0, 'text')
51
- tool_output = Nokogiri::HTML.parse(text_containing_xml.match(/\<#{output_adapter.name}\>(.*?)\<\/#{output_adapter.name}\>/m)[1]).text
52
-
53
- raise "Gemini did not format response, error: #{response.body}" unless tool_output
54
- return tool_output
55
- end
56
-
57
- private
58
- def self.format_properties(output_adapter)
59
- output_adapter.properties.each_with_object("") do |property, xml|
60
- xml << "<name>#{property.name}</name>"
61
- xml << "<type>#{property.type}</type>"
62
- xml << "<description>#{property.description}</description>"
63
- xml << "<required>#{property.required}</required>"
64
- xml << "<enum>#{property.enum}</enum>" if property.enum
65
- end
47
+ argument = response.dig("candidates", 0, "content", "parts", 0, "functionCall", "args", output_adapter.name)
66
48
  end
67
49
  end
68
50
  end
@@ -1,5 +1,5 @@
1
1
  # Sublayer.configuration.ai_provider = Sublayer::Providers::OpenAI
2
- # Sublayer.configuration.ai_model = "gpt-4-turbo-preview"
2
+ # Sublayer.configuration.ai_model = "gpt-4o"
3
3
 
4
4
  module Sublayer
5
5
  module Providers
@@ -25,9 +25,9 @@ module Sublayer
25
25
  description: output_adapter.description,
26
26
  parameters: {
27
27
  type: "object",
28
- properties: OpenAI.format_properties(output_adapter)
28
+ properties: output_adapter.format_properties
29
29
  },
30
- required: [output_adapter.properties.select(&:required).map(&:name)]
30
+ required: output_adapter.format_required
31
31
  }
32
32
  }
33
33
  ]
@@ -36,24 +36,13 @@ module Sublayer
36
36
 
37
37
  message = response.dig("choices", 0, "message")
38
38
 
39
- raise "No function called" unless message["tool_calls"].length > 0
39
+ raise "No function called" unless message["tool_calls"]
40
40
 
41
41
  function_body = message.dig("tool_calls", 0, "function", "arguments")
42
- JSON.parse(function_body)[output_adapter.name]
43
- end
44
42
 
45
- private
46
- def self.format_properties(output_adapter)
47
- output_adapter.properties.each_with_object({}) do |property, hash|
48
- hash[property.name] = {
49
- type: property.type,
50
- description: property.description
51
- }
52
-
53
- if property.enum
54
- hash[property.name][:enum] = property.enum
55
- end
56
- end
43
+ raise "Error generating with OpenAI. Empty response. Try rewording your output adapter params to be from the perspective of the model. Full Response: #{response}" if function_body == "{}"
44
+
45
+ results = JSON.parse(function_body)[output_adapter.name]
57
46
  end
58
47
  end
59
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sublayer
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/sublayer.rb CHANGED
@@ -22,7 +22,7 @@ module Sublayer
22
22
  def self.configuration
23
23
  @configuration ||= OpenStruct.new(
24
24
  ai_provider: Sublayer::Providers::OpenAI,
25
- ai_model: "gpt-4-turbo-preview"
25
+ ai_model: "gpt-4o"
26
26
  )
27
27
  end
28
28
 
data/sublayer.gemspec CHANGED
@@ -41,6 +41,6 @@ Gem::Specification.new do |spec|
41
41
  spec.add_development_dependency "rspec", "~> 3.12"
42
42
  spec.add_development_dependency "pry", "~> 0.14"
43
43
  spec.add_development_dependency "vcr", "~> 6.0"
44
- spec.add_development_dependency "webmock", "~> 3.0"
44
+ spec.add_development_dependency "webmock", "~> 3"
45
45
  spec.add_development_dependency "clag"
46
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sublayer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Werner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-11 00:00:00.000000000 Z
11
+ date: 2024-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-openai
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: '3.0'
159
+ version: '3'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: '3.0'
166
+ version: '3'
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: clag
169
169
  requirement: !ruby/object:Gem::Requirement
@@ -193,6 +193,8 @@ files:
193
193
  - lib/sublayer/actions/base.rb
194
194
  - lib/sublayer/agents/base.rb
195
195
  - lib/sublayer/components/output_adapters.rb
196
+ - lib/sublayer/components/output_adapters/formattable.rb
197
+ - lib/sublayer/components/output_adapters/list_of_strings.rb
196
198
  - lib/sublayer/components/output_adapters/single_string.rb
197
199
  - lib/sublayer/components/output_adapters/string_selection_from_list.rb
198
200
  - lib/sublayer/generators/base.rb