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 +7 -0
- data/CHANGELOG.md +9 -0
- data/README.md +83 -0
- data/lib/orchestra_ai/instrument.rb +22 -0
- data/lib/orchestra_ai/message.rb +23 -0
- data/lib/orchestra_ai/orchestra.rb +38 -0
- data/lib/orchestra_ai/prompt/create_task.rb +61 -0
- data/lib/orchestra_ai/prompt/summarize.rb +33 -0
- data/lib/orchestra_ai/result.rb +16 -0
- data/lib/orchestra_ai/step/create_task.rb +54 -0
- data/lib/orchestra_ai/step/summarize.rb +31 -0
- data/lib/orchestra_ai/task.rb +18 -0
- data/lib/orchestra_ai/version.rb +5 -0
- data/lib/orchestra_ai.rb +20 -0
- metadata +54 -0
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
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
|
data/lib/orchestra_ai.rb
ADDED
@@ -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: []
|