autoflux 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfba17b715db371a3388379b0d894f1cd7e02e5ddec7dc9e30545b009037195e
4
- data.tar.gz: 768d96e08f58eac4516c03b6fb1ae13a6c4939ffa47702fa57de3c45cff8a831
3
+ metadata.gz: 85b6aa4923d77e37e9e4c0323b9d7b4ea211d1c826b832f439ffef41fc2838a4
4
+ data.tar.gz: 1f83f0a1a66cca3f03fdd0905625406e87fb5e43f9c8a8a467d4508d1fb2a450
5
5
  SHA512:
6
- metadata.gz: fe2ee26a057d023a378eee32fead552d45312724a1a59b4dcf1a6681cef889ffda78030fcafac5b98ee41ae2c4a4d3b24d859fa1682aac86595ed82e58759226
7
- data.tar.gz: dd3461bdffb1f07be326962f43b3ee661e901881eaea3a71c0225bb55047c559e13a5e62743e4426751a36af3447fdcf4084cddd238f155ce4529060f619802a
6
+ metadata.gz: 5014bfa1497302ff5a839f2ed914ca483af355faf5c33f6f5f543e2e3a4729a09515c58e473b6ecb2e6e03dd345a7354371728475f2fbee5998d1db514b7a465
7
+ data.tar.gz: 95a4935d4ca50f9817412184781ce486b5273ba349be6441f5842f746b8f61543f849d5c3d23fb39375e02f265078e7596809d7599a4a6135d76c2b16725950b
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 3.0
2
+ TargetRubyVersion: 3.2
3
3
  NewCops: enable
4
4
  SuggestExtensions: false
5
5
 
data/README.md CHANGED
@@ -28,11 +28,9 @@ Autoflux provides a default state machine for a chat workflow.
28
28
  stateDiagram-v2
29
29
  [*] --> Start
30
30
  Start --> Command
31
- Command --> Assistant
32
- Assistant --> Command
33
- Assistant --> Tool
34
- Tool --> Assistant
31
+ Command --> Agent
35
32
  Command --> Stop
33
+ Agent --> Command
36
34
  Stop --> [*]
37
35
  ```
38
36
 
@@ -47,116 +45,38 @@ workflow = Autoflux::Workflow.new(
47
45
  workflow.run
48
46
  ```
49
47
 
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.
48
+ When the `io` is `EOF`, the workflow will stop.
57
49
 
58
50
  ### Agent
59
51
 
60
- The agent is an adapter to the LLM model.
52
+ The agent is a interface implements `call` method to process the command.
61
53
 
62
54
  ```ruby
63
- # :nodoc:
64
- class OpenAIAgent
65
- attr_reader :client, :model
66
-
67
- def initialize(client:, model: 'gpt-4o-mini')
68
- super(tools: tools)
69
- @client = client
70
- @model = model
55
+ agent = ->(prompt, **context) {
56
+ case prompt
57
+ when 'hello'
58
+ 'Hello, how can I help you?'
59
+ when 'bye'
60
+ 'Goodbye'
61
+ else
62
+ 'I do not understand'
71
63
  end
72
-
73
- def call(memory:)
74
- msg = client.chat(
75
- parameters: {
76
- model: model,
77
- messages: memory.data.map { |event| convert_message(event) },
78
- }
79
- ).dig('choices', 0, 'message')
80
-
81
- convert_event(msg)
82
- end
83
-
84
- # If allow to use the tool, return tool object implements `Autoflux::_Tool` interface
85
- def tools?(name) = false
86
- def tool(name) = nil
87
-
88
- # Autoflux use a generic event object to represent the message, you have to convert it to the model's format
89
- def convert_message(event)
90
- {
91
- role: event[:role],
92
- content: event[:content]
93
- }.tap do |evt|
94
- evt[:tool_calls] = event[:invocations]&.map { |invocation| convert_tool_call(invocation) }
95
- evt[:tool_call_id] = event[:invocation_id] if event[:invocation_id]
96
- end
97
- end
98
-
99
- def convert_tool_call(invocation)
100
- {
101
- type: :function,
102
- id: invocation[:id],
103
- function: {
104
- name: invocation[:name],
105
- arguments: invocation[:args]
106
- }
107
- }
108
- end
109
-
110
- def convert_event(msg) # rubocop:disable Metrics/MethodLength
111
- {
112
- role: msg['role'],
113
- content: msg['content'],
114
- invocations: msg['tool_calls']&.map do |call|
115
- {
116
- id: call['id'],
117
- name: call.dig('function', 'name'),
118
- args: call.dig('function', 'arguments')
119
- }
120
- end
121
- }
122
- end
123
- end
64
+ }
124
65
  ```
125
66
 
126
- The memory is history which keep in the workflow. You can decide to use it or not.
127
-
128
- ### Tool
129
-
130
- The tool is a function that can be used in the agent's response.
67
+ The workflow will pass itself as context to the agent.
131
68
 
132
69
  ```ruby
133
- # :nodoc:
134
- class AddToCartTool
135
- attr_reader :name, :description, :parameters
136
-
137
- def initialize # rubocop:disable Metrics/MethodLength
138
- @name = 'add_to_cart'
139
- @description = 'Add the product to the cart'
140
- @parameters = {
141
- type: 'object',
142
- properties: {
143
- name: { type: 'string', description: 'The name of the product' },
144
- quantity: { type: 'number', description: 'The quantity of the product' }
145
- }
146
- }
147
- end
148
-
149
- def call(workflow:, params:)
150
- { success: true, content: "Added #{params[:quantity]} #{params[:name]} to the cart" }
151
- end
152
- end
70
+ agent = ->(prompt, workflow:, **) {
71
+ workflow.io.write("User: #{prompt}")
72
+ }
153
73
  ```
154
74
 
155
- > You can define how to tell the agent to use the tool by adding `name` and `description` to the tool.
75
+ Workflow never knows the how the agent works and which tool is used.
156
76
 
157
77
  ### IO
158
78
 
159
- The IO is an adapter to the input and output.
79
+ The IO is an adapter to let the workflow interact with the user.
160
80
 
161
81
  ```ruby
162
82
  # :nodoc:
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Autoflux
4
+ module Step
5
+ # The Agent is transfer control to the agent and wait for the next event
6
+ class Agent
7
+ attr_reader :prompt
8
+
9
+ def initialize(prompt: nil)
10
+ @prompt = prompt
11
+ end
12
+
13
+ def to_s = self.class.name || "Agent"
14
+
15
+ def call(workflow:)
16
+ res = workflow.agent.call(prompt, workflow: workflow)
17
+ workflow.io.write(res.to_s)
18
+ Command.new
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,19 +4,13 @@ module Autoflux
4
4
  module Step
5
5
  # The Command step is used to get the user input.
6
6
  class Command
7
- EXIT_COMMAND = "exit"
8
-
9
7
  def to_s = self.class.name || "Command"
10
8
 
11
9
  def call(workflow:)
12
10
  input = workflow.io.read
13
11
  return Stop.new if input.nil?
14
- return Stop.new if input == EXIT_COMMAND
15
12
 
16
- # @type var event: Autoflux::event
17
- event = { role: ROLE_USER, content: input }
18
- workflow.memory.push(event)
19
- Assistant.new
13
+ Agent.new(prompt: input)
20
14
  end
21
15
  end
22
16
  end
data/lib/autoflux/step.rb CHANGED
@@ -6,7 +6,6 @@ module Autoflux
6
6
  require "autoflux/step/start"
7
7
  require "autoflux/step/stop"
8
8
  require "autoflux/step/command"
9
- require "autoflux/step/assistant"
10
- require "autoflux/step/tool"
9
+ require "autoflux/step/agent"
11
10
  end
12
11
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Autoflux
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -18,37 +18,29 @@ module Autoflux
18
18
 
19
19
  include Enumerable
20
20
 
21
- attr_reader :id, :agent, :memory, :io
21
+ attr_reader :id, :agent, :io
22
22
 
23
23
  # @rbs state: State
24
- def initialize(agent:, io:, id: nil, step: Step::Start.new, memory: Memory.new)
24
+ def initialize(agent:, io:, id: nil, step: Step::Start.new)
25
25
  @id = id || Workflow.next_id
26
26
  @agent = agent
27
27
  @io = io
28
28
  @step = step
29
- @memory = memory
30
29
  end
31
30
 
32
31
  def each
33
32
  return to_enum(:each) unless block_given?
34
33
 
35
- yield self
36
- while @step
34
+ loop do
35
+ break unless @step
36
+
37
+ yield self
37
38
  @step = step.call(workflow: self)
38
- yield self if @step
39
39
  end
40
40
  end
41
41
 
42
42
  # Run the workflow.
43
- #
44
- # @rbs system_prompt: String?
45
- def run(system_prompt: nil, &block)
46
- if system_prompt
47
- # @type var event: Autoflux::event
48
- event = { role: ROLE_SYSTEM, content: system_prompt }
49
- memory.push(event)
50
- end
51
-
43
+ def run(&block)
52
44
  each(&block || ->(_workflow) {})
53
45
  end
54
46
 
data/lib/autoflux.rb CHANGED
@@ -6,12 +6,6 @@ require_relative "autoflux/version"
6
6
  module Autoflux
7
7
  class Error < StandardError; end
8
8
 
9
- ROLE_SYSTEM = "system"
10
- ROLE_ASSISTANT = "assistant"
11
- ROLE_TOOL = "tool"
12
- ROLE_USER = "user"
13
-
14
- require_relative "autoflux/memory"
15
9
  require_relative "autoflux/step"
16
10
  require_relative "autoflux/workflow"
17
11
  end
@@ -1,8 +1,6 @@
1
1
  module Autoflux
2
2
  # Agent implements the _Agent interface.
3
3
  interface _Agent
4
- def tool?: (String name) -> bool
5
- def tool: (String name) -> _Tool?
6
- def call: (memory: _Memory) -> event
4
+ def call: (String? prompt, workflow: Workflow) -> _ToS
7
5
  end
8
6
  end
@@ -5,32 +5,24 @@ module Autoflux
5
5
  end
6
6
 
7
7
  module Step
8
- # The Start step is used to start the workflow.
9
8
  class Start
10
9
  include Autoflux::_Step
11
10
  end
12
11
 
13
- # The Stop step is used to stop the workflow.
14
12
  class Stop
15
13
  include Autoflux::_Step
16
14
  end
17
15
 
18
- # The Assistant state is used to call the agent.
19
- class Assistant
16
+ class Agent
20
17
  include Autoflux::_Step
21
- OUTPUT_ROLE_NAME: "assistant"
22
- end
23
18
 
24
- # The Tool state is used to call the tools provided by the agent.
25
- class Tool
26
- include Autoflux::_Step
27
- def run: (workflow: Workflow, invocation: invocation) -> ::_ToJson
19
+ attr_reader prompt: String?
20
+
21
+ def initialize: (?prompt: String) -> void
28
22
  end
29
23
 
30
- # The Command step is used to get the user input.
31
24
  class Command
32
25
  include Autoflux::_Step
33
- EXIT_COMMAND: "exit"
34
26
  end
35
27
  end
36
28
  end
@@ -7,18 +7,16 @@ module Autoflux
7
7
  @agent: _Agent
8
8
  @io: _IO
9
9
  @step: _Step
10
- @memory: _Memory
11
10
 
12
11
  def self.next_id: () -> String
13
12
 
14
13
  attr_reader id: String
15
14
  attr_reader agent: _Agent
16
- attr_reader memory: _Memory
17
15
  attr_reader io: _IO
18
16
 
19
- def initialize: (agent: _Agent, io: _IO, ?id: String?, ?step: _Step, ?memory: _Memory) -> void
17
+ def initialize: (agent: _Agent, io: _IO, ?id: String?, ?step: _Step) -> void
20
18
  def each: { (Workflow) -> void } -> void
21
- def run: (?system_prompt: String?) ?{ (Workflow) -> void } -> void
19
+ def run: () ?{ (Workflow) -> void } -> void
22
20
  def stop: () -> void
23
21
  def step: () -> _Step
24
22
  def to_h: () -> { id: String, step: String }
data/sig/autoflux.rbs CHANGED
@@ -1,11 +1,6 @@
1
1
  module Autoflux
2
2
  VERSION: String
3
3
 
4
- ROLE_SYSTEM: eventRole & "system"
5
- ROLE_USER: eventRole & "user"
6
- ROLE_TOOL: eventRole & "tool"
7
- ROLE_ASSISTANT: eventRole & "assistant"
8
-
9
4
  class Error < StandardError
10
5
  end
11
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autoflux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-07 00:00:00.000000000 Z
11
+ date: 2025-01-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A lightweight AI agent framework for Ruby
14
14
  email:
@@ -27,26 +27,21 @@ files:
27
27
  - Steepfile
28
28
  - commitlint.config.js
29
29
  - lib/autoflux.rb
30
- - lib/autoflux/memory.rb
31
30
  - lib/autoflux/stdio.rb
32
31
  - lib/autoflux/step.rb
33
- - lib/autoflux/step/assistant.rb
32
+ - lib/autoflux/step/agent.rb
34
33
  - lib/autoflux/step/command.rb
35
34
  - lib/autoflux/step/start.rb
36
35
  - lib/autoflux/step/stop.rb
37
- - lib/autoflux/step/tool.rb
38
36
  - lib/autoflux/version.rb
39
37
  - lib/autoflux/workflow.rb
40
38
  - package-lock.json
41
39
  - package.json
42
40
  - sig/autoflux.rbs
43
41
  - sig/autoflux/agent.rbs
44
- - sig/autoflux/event.rbs
45
42
  - sig/autoflux/io.rbs
46
- - sig/autoflux/memory.rbs
47
43
  - sig/autoflux/stdio.rbs
48
44
  - sig/autoflux/step.rbs
49
- - sig/autoflux/tool.rbs
50
45
  - sig/autoflux/workflow.rbs
51
46
  homepage: https://github.com/elct9620/autoflux
52
47
  licenses:
@@ -63,7 +58,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
58
  requirements:
64
59
  - - ">="
65
60
  - !ruby/object:Gem::Version
66
- version: 3.0.0
61
+ version: 3.2.0
67
62
  required_rubygems_version: !ruby/object:Gem::Requirement
68
63
  requirements:
69
64
  - - ">="
@@ -1,28 +0,0 @@
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(event)
19
- puts JSON.pretty_generate(event) if verbose
20
- @data.push(event)
21
- end
22
-
23
- # Get the last data from the memory.
24
- def last
25
- @data.last
26
- end
27
- end
28
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Autoflux
4
- module Step
5
- # The Assistant state is used to call the agent.
6
- class Assistant
7
- def to_s = self.class.name || "Assistant"
8
-
9
- def call(workflow:)
10
- event = workflow.agent.call(memory: workflow.memory)
11
- workflow.memory.push(event)
12
-
13
- # @type var invocation_event: Autoflux::invocationEvent
14
- invocation_event = event
15
- return Tool.new if invocation_event[:invocations]&.any?
16
-
17
- # @type var event: Autoflux::textEvent
18
- workflow.io.write(event[:content]) if event[:role] == ROLE_ASSISTANT
19
-
20
- Command.new
21
- end
22
- end
23
- end
24
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Autoflux
4
- module Step
5
- # The Tool state is used to call the tools provided by the agent.
6
- class Tool
7
- def to_s = self.class.name || "Tool"
8
-
9
- def call(workflow:)
10
- # @type var event: Autoflux::invocationEvent
11
- event = workflow.memory.last
12
- event[:invocations]&.each do |invocation|
13
- # @type: var invocation: Autoflux::invocation
14
- # @type: var event: Autoflux::invocationResultEvent
15
- event = {
16
- role: ROLE_TOOL,
17
- content: run(workflow: workflow, invocation: invocation).to_json,
18
- invocation_id: invocation[:id]
19
- }
20
- workflow.memory.push(event)
21
- end
22
-
23
- Assistant.new
24
- end
25
-
26
- protected
27
-
28
- def run(workflow:, invocation:)
29
- name = invocation[:name]
30
- params = JSON.parse(invocation[:args], symbolize_names: true)
31
- return { status: "error", message: "Tool not found" } unless workflow.agent.tool?(name)
32
-
33
- workflow.agent.tool(name)&.call(workflow: workflow, params: params)
34
- rescue JSON::ParserError
35
- { status: "error", message: "Invalid arguments" }
36
- end
37
- end
38
- end
39
- end
@@ -1,27 +0,0 @@
1
- module Autoflux
2
- type jsonString = String
3
-
4
- type invocation = {
5
- id: String,
6
- name: String,
7
- args: jsonString
8
- }
9
-
10
- type eventRole = "assistant" | "system" | "user" | "tool" | String
11
- type baseEvent = {
12
- role: eventRole,
13
- agent_id?: String,
14
- raw?: Hash[untyped, untyped]
15
- }
16
- type textEvent = baseEvent & {
17
- content: String
18
- }
19
- type invocationEvent = baseEvent & {
20
- invocations: Array[invocation]
21
- }
22
- type invocationResultEvent = textEvent & {
23
- invocation_id: String
24
- }
25
-
26
- type event = textEvent | invocationEvent | invocationResultEvent
27
- end
@@ -1,20 +0,0 @@
1
- module Autoflux
2
- interface _Memory
3
- def data: () -> Array[event]
4
- def push: (event event) -> void
5
- def last: () -> event
6
- end
7
-
8
- # The Memory is a class to store the memory of the workflow.
9
- class Memory
10
- include _Memory
11
-
12
- @data: Array[event]
13
- @verbose: bool
14
-
15
- attr_reader verbose: bool
16
-
17
- # @rbs data: Array[Hash]
18
- def initialize: (?data: Array[event], ?verbose: bool) -> void
19
- end
20
- end
@@ -1,5 +0,0 @@
1
- module Autoflux
2
- interface _Tool
3
- def call: (workflow: Workflow, params: untyped) -> untyped
4
- end
5
- end