agentic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e110789346f794e5ca8b259bfec4fef822829854f6fa079ffa5ebb1f32c5547b
4
+ data.tar.gz: 0c4f3458d21a47e68b51326663abce0a1662ecc70c36adb615b5bab3b1a11357
5
+ SHA512:
6
+ metadata.gz: cfa927848cac32b8482e253f3e038be7bb85d69cc27ec25a5a0e883fd88beab91f0a44d16744e8eb9c198941d78ad34b1e058c92a8a7353e5b60b8fb31e49846
7
+ data.tar.gz: 0ab4180f1e7583bd7760a48c9b03bae74938219c6706b71f7cf7c8cf63e6059637030a4953076dd2e646ec1919f30d7ea1070b88a6640e5ce6af852d0587bcfc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ require:
2
+ - standard
3
+ - rubocop-rspec
4
+
5
+ inherit_gem:
6
+ standard: config/base.yml
7
+
8
+ AllCops:
9
+ NewCops: enable
10
+ Exclude:
11
+ - vendor/**/*
12
+
13
+ RSpec:
14
+ Enabled: true # enable rubocop-rspec cops
15
+ RSpec/DescribeClass:
16
+ Enabled: false # ignore missing comments on classes
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-06-27
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Valentino Stoll
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Agentic
2
+
3
+ A simple Ruby command-line tool to build and run AI Agents in a [plan-and-execute](https://blog.langchain.dev/planning-agents/#plan-and-execute) fashion.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add agentic
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install agentic
14
+
15
+ # Agentic
16
+
17
+ A simple Ruby command-line tool to build and run AI Agents in a [plan-and-execute](https://blog.langchain.dev/planning-agents/#plan-and-execute) fashion.
18
+
19
+ ## Installation
20
+
21
+ Install the gem and add to the application's Gemfile by executing:
22
+
23
+ $ bundle add agentic
24
+
25
+ If bundler is not being used to manage dependencies, install the gem by executing:
26
+
27
+ $ gem install agentic
28
+
29
+ ## Usage
30
+
31
+ To use the TaskPlanner:
32
+
33
+ 1. Require the gem:
34
+ ```ruby
35
+ require 'agentic'
36
+ ```
37
+
38
+ 2. Configure your OpenAI API key:
39
+ ```ruby
40
+ Agentic.configure do |config|
41
+ config.access_token = 'your_openai_api_key'
42
+ end
43
+ ```
44
+
45
+ 3. Create a TaskPlanner instance with a goal:
46
+ ```ruby
47
+ planner = Agentic::TaskPlanner.new("Write a blog post about Ruby on Rails")
48
+ ```
49
+
50
+ 4. Generate and display the plan:
51
+ ```ruby
52
+ plan = planner.plan
53
+ puts plan
54
+ ```
55
+
56
+ This will output a structured plan for accomplishing your goal, including tasks and expected output format.
57
+
58
+ ## Development
59
+
60
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
61
+
62
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
63
+
64
+ ## Contributing
65
+
66
+ Bug reports and pull requests are welcome on GitHub at https://github.com/codenamev/agentic. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/codenamev/agentic/blob/main/CODE_OF_CONDUCT.md).
67
+
68
+ ## License
69
+
70
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
71
+
72
+ ## Code of Conduct
73
+
74
+ Everyone interacting in the Agentic project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/codenamev/agentic/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
data/exe/agentic ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "agentic"
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentic
4
+ class Agent
5
+ include FactoryMethods
6
+
7
+ configurable :role, :purpose, :backstory, :tools
8
+
9
+ assembly do |agent|
10
+ agent.tools ||= Set.new
11
+ end
12
+
13
+ def execute(task)
14
+ task.perform(self)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentic
4
+ module FactoryMethods
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.instance_variable_set(:@configurable_attributes, Set.new)
8
+ base.instance_variable_set(:@assembly_instructions, Set.new)
9
+ end
10
+
11
+ module ClassMethods
12
+ def build
13
+ agent = new
14
+ yield(agent) if block_given?
15
+ configure(agent)
16
+ run_assembly_instructions(agent)
17
+ agent
18
+ end
19
+
20
+ private
21
+
22
+ def configure(agent)
23
+ configurable_attributes.each do |attr|
24
+ agent.public_send(:"#{attr}=", nil) unless agent.public_send(attr)
25
+ end
26
+ end
27
+
28
+ def run_assembly_instructions(agent)
29
+ assembly_instructions.each do |instruction|
30
+ instruction.call(agent)
31
+ end
32
+ end
33
+
34
+ def configurable(*attrs)
35
+ @configurable_attributes.merge(attrs)
36
+ attr_accessor(*attrs)
37
+ end
38
+
39
+ def assembly(&block)
40
+ @assembly_instructions << block
41
+ end
42
+
43
+ def configurable_attributes
44
+ @configurable_attributes
45
+ end
46
+
47
+ def assembly_instructions
48
+ @assembly_instructions
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openai"
4
+
5
+ module Agentic
6
+ # Generic wrapper for LLM API clients
7
+ class LlmClient
8
+ # @return [OpenAI::Client] The underlying LLM client instance
9
+ attr_reader :client, :last_response
10
+
11
+ # Initializes a new LlmClient
12
+ # @param config [LlmConfig] The configuration for the LLM
13
+ def initialize(config)
14
+ @client = OpenAI::Client.new(access_token: Agentic.configuration.access_token)
15
+ @config = config
16
+ @last_response = nil
17
+ end
18
+
19
+ # Sends a completion request to the LLM
20
+ # @param messages [Array<Hash>] The messages to send
21
+ # @return [Hash] The response from the LLM
22
+ def complete(messages, output_schema: nil)
23
+ parameters = {model: @config.model, messages: messages}
24
+ if output_schema
25
+ parameters[:response_format] = {
26
+ type: "json_schema",
27
+ json_schema: output_schema.to_hash
28
+ }
29
+ end
30
+
31
+ @last_response = client.chat(parameters: parameters)
32
+
33
+ if output_schema
34
+ content = JSON.parse(@last_response.dig("choices", 0, "message", "content"))
35
+
36
+ if (refusal = @last_response.dig("choices", 0, "message", "refusal"))
37
+ {refusal: refusal, content: nil}
38
+ else
39
+ {content: content}
40
+ end
41
+ else
42
+ @last_response.dig("choices", 0, "message", "content")
43
+ end
44
+ end
45
+
46
+ # Fetches available models from the LLM provider
47
+ # @return [Array<Hash>] The list of available models
48
+ def models
49
+ client.models.list&.dig("data")
50
+ end
51
+
52
+ # Queries generation stats for a given generation ID
53
+ # @param generation_id [String] The ID of the generation
54
+ # @return [Hash] The generation stats
55
+ def query_generation_stats(generation_id)
56
+ client.query_generation_stats(generation_id)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentic
4
+ class LlmConfig
5
+ attr_accessor :model
6
+
7
+ def initialize(model: "gpt-4o-2024-08-06")
8
+ @model = model
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ # lib/agentic/logger.rb
2
+ # frozen_string_literal: true
3
+
4
+ require "logger"
5
+
6
+ module Agentic
7
+ class Logger < ::Logger
8
+ COLORS = {
9
+ "FATAL" => :red,
10
+ "ERROR" => :red,
11
+ "WARN" => :orange,
12
+ "INFO" => :yellow,
13
+ "DEBUG" => :white
14
+ }
15
+
16
+ # Simple formatter which only displays the message.
17
+ class SimpleFormatter < ::Logger::Formatter
18
+ # This method is invoked when a log event occurs
19
+ def call(severity, timestamp, progname, msg)
20
+ if $stdout.tty? && severity.respond_to?(:colorize)
21
+ "#{severity.colorize(COLORS[severity])}: #{(String === msg) ? msg : msg.inspect}\n"
22
+ else
23
+ "#{severity}: #{(String === msg) ? msg : msg.inspect}\n"
24
+ end
25
+ end
26
+ end
27
+
28
+ def initialize(*args)
29
+ super
30
+ @formatter = SimpleFormatter < ::Logger::Formatter
31
+ end
32
+
33
+ def self.info(message)
34
+ instance.info(message)
35
+ end
36
+
37
+ def self.error(message)
38
+ instance.error(message)
39
+ end
40
+
41
+ def self.debug(message)
42
+ instance.debug(message)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,124 @@
1
+ require "json"
2
+ require "dry/schema"
3
+ require "openai"
4
+ require "ostruct"
5
+
6
+ # Credit: https://github.com/alexrudall/ruby-openai/issues/508#issuecomment-2291916913
7
+ module Agentic
8
+ module StructuredOutputs
9
+ # Schema class for defining JSON schemas
10
+ class Schema
11
+ MAX_OBJECT_PROPERTIES = 100
12
+ MAX_NESTING_DEPTH = 5
13
+
14
+ def initialize(name = nil, &block)
15
+ # Use the provided name or derive from class name
16
+ @name = name || self.class.name.split("::").last.downcase
17
+ # Initialize the base schema structure
18
+ @schema = {
19
+ type: "object",
20
+ properties: {},
21
+ required: [],
22
+ additionalProperties: false,
23
+ strict: true
24
+ }
25
+ @definitions = {}
26
+ # Execute the provided block to define the schema
27
+ yield(self) if block
28
+ validate_schema
29
+ end
30
+
31
+ # Convert the schema to a hash format
32
+ def to_hash
33
+ {
34
+ name: @name,
35
+ description: "Schema for the structured response",
36
+ schema: @schema
37
+ }
38
+ end
39
+
40
+ # Define a string property
41
+ def string(name, enum: nil)
42
+ add_property(name, enum ? {type: "string", enum: enum} : {type: "string"})
43
+ end
44
+
45
+ # Define a number property
46
+ def number(name)
47
+ add_property(name, {type: "number"})
48
+ end
49
+
50
+ # Define a boolean property
51
+ def boolean(name)
52
+ add_property(name, {type: "boolean"})
53
+ end
54
+
55
+ # Define an object property
56
+ def object(name, &block)
57
+ properties = {}
58
+ required = []
59
+ Schema.new.tap do |s|
60
+ s.instance_eval(&block)
61
+ properties = s.instance_variable_get(:@schema)[:properties]
62
+ required = s.instance_variable_get(:@schema)[:required]
63
+ end
64
+ add_property(name, {type: "object", properties: properties, required: required, additionalProperties: false})
65
+ end
66
+
67
+ # Define an array property
68
+ def array(name, items:)
69
+ add_property(name, {type: "array", items: items})
70
+ end
71
+
72
+ # Define an anyOf property
73
+ def any_of(name, schemas)
74
+ add_property(name, {anyOf: schemas})
75
+ end
76
+
77
+ # Define a reusable schema component
78
+ def define(name, &block)
79
+ @definitions[name] = Schema.new(&block).instance_variable_get(:@schema)
80
+ end
81
+
82
+ # Reference a defined schema component
83
+ def ref(name)
84
+ {"$ref" => "#/$defs/#{name}"}
85
+ end
86
+
87
+ private
88
+
89
+ # Add a property to the schema
90
+ def add_property(name, definition)
91
+ @schema[:properties][name] = definition
92
+ @schema[:required] << name
93
+ end
94
+
95
+ # Validate the schema against defined limits
96
+ def validate_schema
97
+ properties_count = count_properties(@schema)
98
+ raise "Exceeded maximum number of object properties" if properties_count > MAX_OBJECT_PROPERTIES
99
+
100
+ max_depth = calculate_max_depth(@schema)
101
+ raise "Exceeded maximum nesting depth" if max_depth > MAX_NESTING_DEPTH
102
+ end
103
+
104
+ # Count the total number of properties in the schema
105
+ def count_properties(schema)
106
+ return 0 unless schema.is_a?(Hash) && schema[:properties]
107
+ count = schema[:properties].size
108
+ schema[:properties].each_value do |prop|
109
+ count += count_properties(prop)
110
+ end
111
+ count
112
+ end
113
+
114
+ # Calculate the maximum nesting depth of the schema
115
+ def calculate_max_depth(schema, current_depth = 1)
116
+ return current_depth unless schema.is_a?(Hash) && schema[:properties]
117
+ max_child_depth = schema[:properties].values.map do |prop|
118
+ calculate_max_depth(prop, current_depth + 1)
119
+ end.max
120
+ [current_depth, max_child_depth].max
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentic
4
+ # Handles the task planning process for Agentic using LLM
5
+ class TaskPlanner
6
+ # @return [String] The goal to be accomplished
7
+ attr_reader :goal
8
+
9
+ # @return [Array<Hash>] The list of tasks to accomplish the goal
10
+ attr_reader :tasks
11
+
12
+ # @return [Hash] The expected answer format
13
+ attr_reader :expected_answer
14
+
15
+ # @return [LlmConfig] The configuration for the LLM
16
+ attr_reader :llm_config
17
+
18
+ # Initializes a new TaskPlanner
19
+ # @param goal [String] The goal to be accomplished
20
+ # @param llm_config [LlmConfig] The configuration for the LLM
21
+ def initialize(goal, llm_config = LlmConfig.new)
22
+ @goal = goal
23
+ @tasks = []
24
+ @expected_answer = {}
25
+ @llm_config = llm_config
26
+ end
27
+
28
+ # Analyzes the goal and breaks it down into tasks using LLM
29
+ # @return [void]
30
+ def analyze_goal
31
+ system_message = "You are an expert project planner. Your task is to break down complex goals into actionable tasks."
32
+ user_message = "Goal: #{@goal}\n\nBreak this goal down into a series of tasks. For each task:\n1. Specify the type of agent best suited to complete it.\n2. Include a brief description of the agent\n3. Include a set of instructions that the agent can follow to perform this task."
33
+
34
+ schema = StructuredOutputs::Schema.new("tasks") do |s|
35
+ s.array :tasks, items: {
36
+ type: "object",
37
+ properties: {
38
+ description: {type: "string"},
39
+ agent: {
40
+ type: "object",
41
+ properties: {
42
+ name: {type: "string"},
43
+ description: {type: "string"},
44
+ instructions: {type: "string"}
45
+ },
46
+ required: %w[name description instructions]
47
+ }
48
+ },
49
+ required: %w[description agent]
50
+ }
51
+ end
52
+
53
+ response = llm_request(system_message, user_message, schema)
54
+ @tasks = response[:content]["tasks"]
55
+ end
56
+
57
+ # Determines the expected answer format using LLM
58
+ # @return [void]
59
+ def determine_expected_answer
60
+ system_message = "You are an expert in report structuring and formatting. Your task is to determine the best format for a given report goal."
61
+ user_message = "Goal: #{@goal}\n\nDetermine the optimal format, sections, and length for a report addressing this goal."
62
+
63
+ schema = StructuredOutputs::Schema.new("answer_format") do |s|
64
+ s.string :format
65
+ s.array :sections, items: {type: "string"}
66
+ s.string :length
67
+ end
68
+
69
+ response = llm_request(system_message, user_message, schema)
70
+ @expected_answer = response[:content]
71
+ end
72
+
73
+ # Displays the execution plan
74
+ # @return [String] The formatted execution plan
75
+ def display_plan
76
+ plan = "Execution Plan:\n\n"
77
+ @tasks.each_with_index do |task, index|
78
+ plan += "#{index + 1}. #{task["description"]} (Agent: #{task["agent"].inspect})\n"
79
+ end
80
+ plan += "\nExpected Answer:\n"
81
+ plan += "Format: #{@expected_answer["format"]}\n"
82
+ plan += "Sections: #{@expected_answer["sections"].join(", ")}\n"
83
+ plan += "Length: #{@expected_answer["length"]}\n"
84
+ plan
85
+ end
86
+
87
+ # Executes the entire planning process
88
+ # @return [String] The formatted execution plan
89
+ def plan
90
+ analyze_goal
91
+ determine_expected_answer
92
+ display_plan
93
+ end
94
+
95
+ private
96
+
97
+ # Makes a request to the LLM
98
+ # @param system_message [String] The system message for the LLM
99
+ # @param user_message [String] The user message for the LLM
100
+ # @param schema [StructuredOutputs::Schema] The schema for structured output
101
+ # @return [Hash] The LLM's response
102
+ def llm_request(system_message, user_message, schema)
103
+ messages = [
104
+ {role: "system", content: system_message},
105
+ {role: "user", content: user_message}
106
+ ]
107
+ llm_client.complete(messages, output_schema: schema)
108
+ end
109
+
110
+ def llm_client
111
+ @llm_client ||= Agentic.client(@llm_config)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentic
4
+ VERSION = "0.1.0"
5
+ end
data/lib/agentic.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ loader = Zeitwerk::Loader.for_gem
5
+ loader.setup
6
+
7
+ module Agentic
8
+ class Error < StandardError; end
9
+
10
+ class << self
11
+ attr_accessor :logger
12
+ end
13
+
14
+ self.logger ||= Logger.new($stdout, level: :debug)
15
+
16
+ class Configuration
17
+ attr_accessor :access_token
18
+
19
+ def initialize
20
+ @access_token = ENV["OPENAI_ACCESS_TOKEN"]
21
+ end
22
+ end
23
+
24
+ class << self
25
+ attr_writer :configuration
26
+ end
27
+
28
+ def self.configuration
29
+ @configuration ||= Configuration.new
30
+ end
31
+
32
+ def self.configure
33
+ yield(configuration)
34
+ end
35
+
36
+ def self.client(config)
37
+ LlmClient.new(config)
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentic
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agentic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Valentino Stoll
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-schema
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-openai
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: zeitwerk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Easily build, manage, deploy, and run self-contained purpose-driven AI
56
+ Agents.
57
+ email:
58
+ - v@codenamev.com
59
+ executables:
60
+ - agentic
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - ".standard.yml"
67
+ - CHANGELOG.md
68
+ - CODE_OF_CONDUCT.md
69
+ - LICENSE.txt
70
+ - README.md
71
+ - Rakefile
72
+ - exe/agentic
73
+ - lib/agentic.rb
74
+ - lib/agentic/agent.rb
75
+ - lib/agentic/factory_methods.rb
76
+ - lib/agentic/llm_client.rb
77
+ - lib/agentic/llm_config.rb
78
+ - lib/agentic/logger.rb
79
+ - lib/agentic/structured_outputs.rb
80
+ - lib/agentic/task_planner.rb
81
+ - lib/agentic/version.rb
82
+ - sig/agentic/builder.rbs
83
+ homepage: https://github.com/codenamev/agentic
84
+ licenses:
85
+ - MIT
86
+ metadata:
87
+ homepage_uri: https://github.com/codenamev/agentic
88
+ source_code_uri: https://github.com/codenamev/agentic
89
+ changelog_uri: https://github.com/codenamev/agentic/tree/main/CHANGELOG.md
90
+ allowed_push_host: https://rubygems.org
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 3.0.0
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.5.11
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: An AI Agent builder and orchestrator
110
+ test_files: []