autoflux 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.husky/commit-msg +1 -0
- data/.husky/pre-commit +1 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +170 -0
- data/Rakefile +12 -0
- data/commitlint.config.js +1 -0
- data/lib/autoflux/agent.rb +26 -0
- data/lib/autoflux/assistant.rb +18 -0
- data/lib/autoflux/io.rb +15 -0
- data/lib/autoflux/memory.rb +28 -0
- data/lib/autoflux/start.rb +8 -0
- data/lib/autoflux/state.rb +11 -0
- data/lib/autoflux/stdio.rb +29 -0
- data/lib/autoflux/stop.rb +8 -0
- data/lib/autoflux/tool.rb +19 -0
- data/lib/autoflux/tools.rb +30 -0
- data/lib/autoflux/user.rb +17 -0
- data/lib/autoflux/version.rb +5 -0
- data/lib/autoflux/workflow.rb +24 -0
- data/lib/autoflux.rb +20 -0
- data/package-lock.json +1249 -0
- data/package.json +11 -0
- data/sig/autoflux.rbs +4 -0
- metadata +71 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 10be8379032b489e881578406d74b7f9f0c80b480fc61b5ccf6c7e8e041f59ab
|
4
|
+
data.tar.gz: d1567e48c28a48cd432c6bb4aa5c8c1856e8b920c3c8768f5c0ac4764bdc8dcc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: acccc975d159aaf9d9262b2fa1cdc0a1cd2d8fc5a2bf3181121a721efe65d6731b733f7f588b0cfac30efe68d0979b14c54aca5b0bbcc0cb6a10bc8b0b38bfff
|
7
|
+
data.tar.gz: 5c99079282673226f90293ec9e0ec03ac65c1a658441bdf91511aadcc01ce2aa729d6accee8b7f864aa29b69e58b8eed9f4bc63ea6bc404e0048a799e0e4e1a5
|
data/.husky/commit-msg
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
npx --no -- commitlint --verbose --edit $1
|
data/.husky/pre-commit
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
bundle exec rake
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
5
|
+
|
6
|
+
Style/StringLiterals:
|
7
|
+
EnforcedStyle: double_quotes
|
8
|
+
|
9
|
+
Style/StringLiteralsInInterpolation:
|
10
|
+
EnforcedStyle: double_quotes
|
11
|
+
|
12
|
+
Metrics/BlockLength:
|
13
|
+
Exclude:
|
14
|
+
- spec/**/*
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Aotokitsuruya
|
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,170 @@
|
|
1
|
+
Autoflux
|
2
|
+
===
|
3
|
+
|
4
|
+
A lightweight AI agent framework
|
5
|
+
|
6
|
+
> [!WARNING]
|
7
|
+
> To support common agentic AI workflow, the API will be changed at any time until the design is completed.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
bundle add autoflux
|
15
|
+
```
|
16
|
+
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
gem install autoflux
|
21
|
+
```
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Autoflux provides a default state machine for a chat workflow.
|
26
|
+
|
27
|
+
```mermaid
|
28
|
+
stateDiagram-v2
|
29
|
+
[*] --> Start
|
30
|
+
Start --> User
|
31
|
+
User --> Assistant
|
32
|
+
Assistant --> User
|
33
|
+
Assistant --> Tool
|
34
|
+
Tool --> Assistant
|
35
|
+
User --> Stop
|
36
|
+
Stop --> [*]
|
37
|
+
```
|
38
|
+
|
39
|
+
To start a new workflow use `Autoflux::Workflow`:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
workflow = Autoflux::Workflow.new(
|
43
|
+
agent: agent,
|
44
|
+
io: io,
|
45
|
+
)
|
46
|
+
|
47
|
+
workflow.run
|
48
|
+
```
|
49
|
+
|
50
|
+
You can give a system prompt when running the workflow:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
workflow.run(system_prompt: 'Help user to solve the problem')
|
54
|
+
```
|
55
|
+
|
56
|
+
When receive "exit" from the user, the workflow transition to the stop state.
|
57
|
+
|
58
|
+
### Agent
|
59
|
+
|
60
|
+
The agent is an adapter to the LLM model. You can ihnerit the `Autoflux::Agent` class to implement your own agent.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# :nodoc:
|
64
|
+
class OpenAIAgent < Autoflux::Agent
|
65
|
+
attr_reader :client, :model
|
66
|
+
|
67
|
+
def initialize(client:, tools: [], model: 'gpt-4o-mini')
|
68
|
+
super(tools: tools)
|
69
|
+
@client = client
|
70
|
+
@model = model
|
71
|
+
end
|
72
|
+
|
73
|
+
def call(memory:)
|
74
|
+
client.chat(
|
75
|
+
parameters: {
|
76
|
+
model: model,
|
77
|
+
messages: memory.data,
|
78
|
+
tools: tools
|
79
|
+
}
|
80
|
+
).dig('choices', 0, 'message')
|
81
|
+
end
|
82
|
+
|
83
|
+
def tools
|
84
|
+
@tools ||= @_tools.map do |tool|
|
85
|
+
{
|
86
|
+
type: :function,
|
87
|
+
function: {
|
88
|
+
name: tool.name,
|
89
|
+
description: tool.description,
|
90
|
+
parameters: tool.parameters
|
91
|
+
}
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
The memory is chat history which keep in the workflow. You can decide to use it or not.
|
99
|
+
|
100
|
+
### Tool
|
101
|
+
|
102
|
+
The tool is a function that can be used in the agent's response. You can ihnerit the `Autoflux::Tool` class to implement your own tool.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# :nodoc:
|
106
|
+
class AddToCartTool < Autoflux::Tool
|
107
|
+
def initialize # rubocop:disable Metrics/MethodLength
|
108
|
+
super(
|
109
|
+
name: 'add_to_cart',
|
110
|
+
description: 'Add the product to the cart',
|
111
|
+
parameters: {
|
112
|
+
type: 'object',
|
113
|
+
properties: {
|
114
|
+
name: { type: 'string', description: 'The name of the product' },
|
115
|
+
quantity: { type: 'number', description: 'The quantity of the product' }
|
116
|
+
}
|
117
|
+
}
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def call(name:, quantity:, **)
|
122
|
+
{ success: true, content: "Added #{quantity} #{name} to the cart" }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
The tool is require the name and description. The parameters is optional.
|
128
|
+
|
129
|
+
### IO
|
130
|
+
|
131
|
+
The IO is an adapter to the input and output. You can ihnerit the `Autoflux::IO` class to implement your own IO.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
# :nodoc:
|
135
|
+
class ConsoleIO < Autoflux::IO
|
136
|
+
def input
|
137
|
+
print 'User: '
|
138
|
+
gets.chomp
|
139
|
+
end
|
140
|
+
|
141
|
+
def output(message)
|
142
|
+
puts "Assistant: #{message}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
The default `Autoflux::Stdio` implement the minimal Standard I/O support.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
workflow = Autoflux::Workflow.new(
|
151
|
+
agent: agent,
|
152
|
+
io: Autoflux::Stdio.new(prompt: '> ')
|
153
|
+
)
|
154
|
+
|
155
|
+
workflow.run
|
156
|
+
```
|
157
|
+
|
158
|
+
## Development
|
159
|
+
|
160
|
+
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.
|
161
|
+
|
162
|
+
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).
|
163
|
+
|
164
|
+
## Contributing
|
165
|
+
|
166
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/elct9620/autoflux.
|
167
|
+
|
168
|
+
## License
|
169
|
+
|
170
|
+
The gem is available as open source under the terms of the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export default { extends: ['@commitlint/config-conventional'] };
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The Agent is a abstract class to represent the adapter of the Language Model.
|
5
|
+
class Agent
|
6
|
+
# @rbs tools: Array<Tool>
|
7
|
+
def initialize(tools: [])
|
8
|
+
@_tools = tools
|
9
|
+
end
|
10
|
+
|
11
|
+
# @rbs name: String
|
12
|
+
def tool?(name)
|
13
|
+
@_tools.any? { |tool| tool.name == name }
|
14
|
+
end
|
15
|
+
|
16
|
+
# @rbs name: String
|
17
|
+
def tool(name)
|
18
|
+
@_tools.find { |tool| tool.name == name }
|
19
|
+
end
|
20
|
+
|
21
|
+
# @rbs memory: Memory?
|
22
|
+
def call(**)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The Assistant state is used to call the agent.
|
5
|
+
class Assistant
|
6
|
+
OUTPUT_ROLE_NAME = "assistant"
|
7
|
+
|
8
|
+
def call(workflow:)
|
9
|
+
res = workflow.agent.call(memory: workflow.memory)
|
10
|
+
workflow.memory.push(res)
|
11
|
+
return Tools.new if res["tool_calls"]&.any?
|
12
|
+
|
13
|
+
workflow.io.write(res["content"]) if res["role"] == OUTPUT_ROLE_NAME
|
14
|
+
|
15
|
+
User.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/autoflux/io.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The IO is abstract class to represent the interface of the IO
|
5
|
+
class IO
|
6
|
+
def read
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
# @rbs data: String
|
11
|
+
def write(_)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The Memory is a class to store the memory of the workflow.
|
5
|
+
class Memory
|
6
|
+
attr_reader :data, :verbose
|
7
|
+
|
8
|
+
# @rbs data: Array[Hash]
|
9
|
+
def initialize(data: [], verbose: false)
|
10
|
+
@data = data
|
11
|
+
@verbose = verbose
|
12
|
+
freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
# Push the data to the memory.
|
16
|
+
#
|
17
|
+
# @rbs data: Hash
|
18
|
+
def push(data)
|
19
|
+
puts JSON.pretty_generate(data) if verbose
|
20
|
+
@data.push(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get the last data from the memory.
|
24
|
+
def last
|
25
|
+
@data.last
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The Stdio is a class to represent the standard input/output.
|
5
|
+
class Stdio < IO
|
6
|
+
attr_accessor :prompt
|
7
|
+
|
8
|
+
# @rbs input: IO
|
9
|
+
# @rbs output: IO
|
10
|
+
def initialize(input: $stdin, output: $stdout, prompt: nil)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@input = input
|
14
|
+
@output = output
|
15
|
+
@prompt = prompt
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
def read
|
20
|
+
print prompt if prompt
|
21
|
+
@input.gets.chomp
|
22
|
+
end
|
23
|
+
|
24
|
+
# @rbs data: String
|
25
|
+
def write(data)
|
26
|
+
@output.puts(data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The Tool is a abstract class to represent the adapter of tools which can be used by the Language Model.
|
5
|
+
class Tool
|
6
|
+
attr_reader :name, :description, :parameters
|
7
|
+
|
8
|
+
def initialize(name:, description:, parameters: nil)
|
9
|
+
@name = name
|
10
|
+
@description = description
|
11
|
+
@parameters = parameters
|
12
|
+
freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(**)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The Tools state is used to call the tools provided by the agent.
|
5
|
+
class Tools
|
6
|
+
def call(workflow:)
|
7
|
+
workflow.memory.last["tool_calls"]&.each do |tool|
|
8
|
+
workflow.memory.push(
|
9
|
+
role: :tool,
|
10
|
+
content: run(workflow: workflow, tool: tool).to_json,
|
11
|
+
tool_call_id: tool["id"]
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
Assistant.new
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def run(workflow:, tool:)
|
21
|
+
name = tool.dig("function", "name")
|
22
|
+
params = JSON.parse(tool.dig("function", "arguments"), symbolize_names: true)
|
23
|
+
return { status: "error", message: "Tool not found" } unless workflow.agent.tool?(name)
|
24
|
+
|
25
|
+
workflow.agent.tool(name).call(**params)
|
26
|
+
rescue JSON::ParserError
|
27
|
+
{ status: "error", message: "Invalid arguments" }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The User state is used to get the user input.
|
5
|
+
class User
|
6
|
+
EXIT_COMMAND = "exit"
|
7
|
+
|
8
|
+
def call(workflow:)
|
9
|
+
input = workflow.io.read
|
10
|
+
return Stop.new if input == EXIT_COMMAND
|
11
|
+
|
12
|
+
workflow.memory.push(role: :user, content: input)
|
13
|
+
|
14
|
+
Assistant.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Autoflux
|
4
|
+
# The workflow is a state machine to manage the flow of agentic AI.
|
5
|
+
class Workflow
|
6
|
+
attr_reader :agent, :state, :memory, :io
|
7
|
+
|
8
|
+
# @rbs state: State
|
9
|
+
def initialize(agent:, io:, state: Start.new, memory: Memory.new)
|
10
|
+
@agent = agent
|
11
|
+
@io = io
|
12
|
+
@state = state
|
13
|
+
@memory = memory
|
14
|
+
end
|
15
|
+
|
16
|
+
# Run the workflow.
|
17
|
+
#
|
18
|
+
# @rbs system_prompt: String?
|
19
|
+
def run(system_prompt: nil)
|
20
|
+
memory.push(role: :system, content: system_prompt) unless system_prompt.nil?
|
21
|
+
@state = @state.call(workflow: self) until @state.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/autoflux.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "autoflux/version"
|
4
|
+
|
5
|
+
# The Autoflux is a lightweight AI agent framework.
|
6
|
+
module Autoflux
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
require_relative "autoflux/tool"
|
10
|
+
require_relative "autoflux/agent"
|
11
|
+
require_relative "autoflux/io"
|
12
|
+
require_relative "autoflux/memory"
|
13
|
+
require_relative "autoflux/state"
|
14
|
+
require_relative "autoflux/start"
|
15
|
+
require_relative "autoflux/user"
|
16
|
+
require_relative "autoflux/assistant"
|
17
|
+
require_relative "autoflux/tools"
|
18
|
+
require_relative "autoflux/stop"
|
19
|
+
require_relative "autoflux/workflow"
|
20
|
+
end
|