orchestra_ai 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bc16033f35561a6926f78cd55986bb3ac690fa6232a687b1de69c2595dc7141f
4
+ data.tar.gz: 80d83b10e78ddc0b67b15bf0a2efeaa9e4bd01d4c356cff29043979cc3ca7b8e
5
+ SHA512:
6
+ metadata.gz: bf2eaad561ed358d645304a01e8092d86e82012b001e656fd5008d19e75721f2bb6e53f1f408060cd8bcd8350663aa046aea0703f634da27159f40f39cd6ad66
7
+ data.tar.gz: 28b03b211b0030b386adcdffcb639c215c72a12c57f1af6dd1e87780dcccc3634bdde2e11fac5b086010bfc207b547888161efc3600a7d077edac90191335cc1
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.1] - 2025-04-11
4
+
5
+ - Added message history inteface to the agent
6
+
7
+ ## [0.1.0] - 2025-04-11
8
+
9
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # OrchestraAI
2
+
3
+ OrchestraAI is a Ruby library that allows you to create and manage workflows for LLMs (Large Language Models) using a simple and intuitive syntax. It provides a way to define instruments (actions) and orchestrate them in a structured manner, making it easier to build complex applications that leverage the power of LLMs.
4
+
5
+ ## Features
6
+
7
+ - Define instruments (actions) with arguments and perform them in a structured manner
8
+ - Orchestrate multiple instruments to create complex workflows
9
+ - Zero dependencies
10
+ - Can reuse your current LLM client
11
+
12
+ ## Installation
13
+
14
+ Install the gem and add to the application's Gemfile by executing:
15
+
16
+ ```bash
17
+ bundle add orchestra_ai
18
+ ```
19
+
20
+ If bundler is not being used to manage dependencies, install the gem by executing:
21
+
22
+ ```bash
23
+ gem install orchestra_ai
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ require 'orchestra_ai'
30
+
31
+ class CreateNoteInstrument < OrchestraAI::Instrument
32
+ argument :title, String
33
+ argument :content, String
34
+ argument :folder_id, Integer
35
+
36
+ def perform(title:, content:, folder_id:)
37
+ Post.create(title:, content:, folder_id:)
38
+ end
39
+ end
40
+
41
+ class CreateFolderInstrument < OrchestraAI::Instrument
42
+ argument :name, String
43
+ argument :path, String
44
+
45
+ def perform(name:, path:)
46
+ Folder.create(name:, path:)
47
+ end
48
+ end
49
+
50
+ class WikiOrchestra < OrchestraAI::Orchestra
51
+ instrument CreateNoteInstrument
52
+ instrument CreateFolderInstrument
53
+ end
54
+
55
+ chat = RubyLLM.chat
56
+
57
+ WikiOrchestra.play(
58
+ <<~TEXT
59
+ - Create a folder named "My Documents"
60
+ - Create a post titled "My First Post" with content "Hello, world!" in the folder "My Documents"
61
+ TEXT
62
+ ) do |prompt|
63
+ chat.ask(prompt) do |chunk|
64
+ puts chunk.content
65
+ end
66
+
67
+ chat.messages.last.content
68
+ end
69
+ ```
70
+
71
+ ## Development
72
+
73
+ 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.
74
+
75
+ 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).
76
+
77
+ ## Contributing
78
+
79
+ Bug reports and pull requests are welcome on GitHub at https://github.com/moekiorg/orchestra_ai. 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/orchestra_ai/orchestra_ai/blob/main/CODE_OF_CONDUCT.md).
80
+
81
+ ## Code of Conduct
82
+
83
+ Everyone interacting in the He project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/orchestra_ai/orchestra_ai/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ class Instrument
5
+ class << self
6
+ attr_accessor :arguments
7
+
8
+ def argument(name, type)
9
+ @arguments ||= []
10
+ @arguments << {name:, type:}
11
+ end
12
+ end
13
+
14
+ def perform(**args)
15
+ raise NotImplementedError, "You must implement the perform method"
16
+ end
17
+
18
+ def key
19
+ self.class.name
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ class Message
5
+ attr_accessor :messages
6
+
7
+ def initialize
8
+ @messages = []
9
+ end
10
+
11
+ def <<(message)
12
+ @messages << message
13
+ end
14
+
15
+ def to_s
16
+ @messages.join("\n")
17
+ end
18
+
19
+ def clear
20
+ @messages.clear
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ class Orchestra
5
+ class << self
6
+ attr_reader :instruments
7
+
8
+ def instrument(klass)
9
+ @instruments ||= []
10
+ @instruments << klass
11
+ end
12
+
13
+ def play(message, &block)
14
+ results = []
15
+ loop do
16
+ creation = Step::CreateTask.new(
17
+ instruments: instruments.map(&:new),
18
+ message:,
19
+ block:,
20
+ results:
21
+ )
22
+ creation.perform
23
+ task = creation.task
24
+ result = task.result
25
+ results << result
26
+ if creation.metadata[:continuation].to_s == "false"
27
+ break
28
+ end
29
+ end
30
+ summarize = Step::Summarize.new(
31
+ results:,
32
+ block:
33
+ )
34
+ summarize.perform
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ module Prompt
5
+ class CreateTask
6
+ def initialize(instruments:, results:)
7
+ @instruments = instruments
8
+ @results = results
9
+ end
10
+
11
+ def to_s
12
+ <<~XML
13
+ <Instructions>
14
+ You are a workflow agent. Your task is to create a workflow based on the user's instructions.
15
+ `<Message>` tags contain your message to the user.
16
+ `<Metadata>` tags contain metadata about the workflow. If the workflow will continue, set the `continuation` key to `true`. If you think that the workflow is finished when the task is completed, set the `continuation` key to `false`.
17
+ `<Task>` tags contain the tasks to be performed. This tag must be present one time. Task must contain object not array.
18
+ </Instructions>
19
+
20
+ <Options>
21
+ #{
22
+ @instruments.map { |instrument|
23
+ {
24
+ key: instrument.key,
25
+ params: instrument.class.arguments.map { _1.transform_values { |v| v.name } }
26
+ }
27
+ }.to_json
28
+ }
29
+ </Options>
30
+
31
+ <Example type="success">
32
+ <Message>
33
+ I will create a folder named "My Folder".
34
+ </Message>
35
+
36
+ <Metadata>
37
+ { "continuation": false | true }
38
+ </Metadata>
39
+
40
+ <Task>
41
+ {
42
+ "id": 1,
43
+ "key": "create_folder",
44
+ "params": {
45
+ "name": "My Folder",
46
+ "path": "My Folder"
47
+ }
48
+ }
49
+ </Task>
50
+ </Example>
51
+
52
+ <Example type="error">
53
+ <Error>
54
+ <Message>There was an error creating the folder</Message>
55
+ </Error>
56
+ </Example>
57
+ XML
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ module Prompt
5
+ class Summarize
6
+ def initialize(results:)
7
+ @results = results
8
+ end
9
+
10
+ def to_s
11
+ <<~XML
12
+ <Instruction>
13
+ <Item>
14
+ The workflow has been executed. Here are the results:
15
+ #{
16
+ @results.map { |r|
17
+ "Task: #{r.action.key}, Params: #{r.action.class.arguments.to_json}, Result: #{r.result}"
18
+ }.join("\n")
19
+ }
20
+ Now, please summarize the results in a single JSON object
21
+ </Item>
22
+ </Instruction>
23
+
24
+ <Example type="success">
25
+ <Message>
26
+ I have created a folder named "My Folder" and a post titled "My Post" with content "This is my post" in the folder "My Folder"
27
+ </Message>
28
+ </Example>
29
+ XML
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ class Result
5
+ attr_reader :action, :result
6
+
7
+ def initialize(action:, result:)
8
+ @action = action
9
+ @result = result
10
+ end
11
+
12
+ def self.find(all, id)
13
+ all.find { |r| r.action.id == id.to_i }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ module Step
5
+ class CreateTask
6
+ def initialize(instruments:, message:, results:, block: nil)
7
+ @message = message
8
+ @results = results
9
+ @block = block
10
+ @instruments = instruments
11
+ @response = nil
12
+ end
13
+
14
+ def perform
15
+ @response = @block.call(
16
+ [
17
+ @message,
18
+ Prompt::CreateTask.new(instruments: @instruments, results: @results).to_s
19
+ ].join
20
+ )
21
+
22
+ if @response.match?(/<Error>/)
23
+ raise Error, "Error: #{@response.match(/<Message>(.+)<\/Message>/m)[1]}"
24
+ end
25
+
26
+ unless @response.match?(/<Message>(.+)<\/Message>/m)
27
+ raise NoMessageError, "Error: No message found in the response"
28
+ end
29
+
30
+ unless @response.match?(/<Task>(.+)<\/Task>/m)
31
+ raise NoTaskError, "Error: No task found in the response"
32
+ end
33
+
34
+ @response
35
+ end
36
+
37
+ def task
38
+ Task.new(**JSON.parse(@response.match(/<Task>(.+)<\/Task>/m)[1], symbolize_names: true))
39
+ end
40
+
41
+ def message
42
+ @response.match(/<Message>(.+)<\/Message>/m)[1]
43
+ end
44
+
45
+ def metadata
46
+ if /<Metadata>(.+)<\/Metadata>/m.match?(@response)
47
+ JSON.parse(@response.match(/<Metadata>(.+)<\/Metadata>/m)[1], symbolize_names: true)
48
+ else
49
+ {}
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ module Step
5
+ class Summarize
6
+ def initialize(results:, block: nil)
7
+ @block = block
8
+ @results = results
9
+ end
10
+
11
+ def perform
12
+ prompt = Prompt::Summarize.new(results: @results)
13
+ @response = @block.call(prompt.to_s)
14
+
15
+ if @response.match?(/<Error>/)
16
+ raise Error, "Error: #{@response.match(/<Message>(.+)<\/Message>/m)[1]}"
17
+ end
18
+
19
+ unless @response.match?(/<Message>(.+)<\/Message>/m)
20
+ raise NoMessageError, "Error: No message found in the response"
21
+ end
22
+
23
+ @response
24
+ end
25
+
26
+ def message
27
+ @response.match(/<Message>(.+)<\/Message>/m)[1]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ class Task
5
+ attr_accessor :id, :key, :params
6
+
7
+ def initialize(id:, key:, params: {})
8
+ @id = id
9
+ @key = key
10
+ @params = params
11
+ end
12
+
13
+ def result
14
+ action = Object.const_get(key).new
15
+ Result.new(action:, result: action.perform(**@params))
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrchestraAI
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "orchestra_ai/version"
4
+ require_relative "orchestra_ai/prompt/summarize"
5
+ require_relative "orchestra_ai/step/summarize"
6
+ require_relative "orchestra_ai/prompt/create_task"
7
+ require_relative "orchestra_ai/step/create_task"
8
+ require_relative "orchestra_ai/orchestra"
9
+ require_relative "orchestra_ai/task"
10
+ require_relative "orchestra_ai/message"
11
+ require_relative "orchestra_ai/result"
12
+ require_relative "orchestra_ai/instrument"
13
+
14
+ module OrchestraAI
15
+ class Error < StandardError; end
16
+
17
+ class NoMessageError < StandardError; end
18
+
19
+ class NoTaskError < StandardError; end
20
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: orchestra_ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - KAWAKAMI Moeki
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-04-12 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: OrchestraAI is a Ruby library that allows you to create and manage workflows
13
+ using LLMs (Large Language Models). It provides a simple and intuitive interface
14
+ for defining tasks, managing dependencies, and executing workflows.
15
+ email:
16
+ - hi@moeki.org
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - README.md
23
+ - lib/orchestra_ai.rb
24
+ - lib/orchestra_ai/instrument.rb
25
+ - lib/orchestra_ai/message.rb
26
+ - lib/orchestra_ai/orchestra.rb
27
+ - lib/orchestra_ai/prompt/create_task.rb
28
+ - lib/orchestra_ai/prompt/summarize.rb
29
+ - lib/orchestra_ai/result.rb
30
+ - lib/orchestra_ai/step/create_task.rb
31
+ - lib/orchestra_ai/step/summarize.rb
32
+ - lib/orchestra_ai/task.rb
33
+ - lib/orchestra_ai/version.rb
34
+ homepage: https://github.com/moekiorg/orchestra_ai
35
+ licenses: []
36
+ metadata: {}
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.6.6
52
+ specification_version: 4
53
+ summary: LLM Workflow Agent for Ruby
54
+ test_files: []