autoflux 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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