deepagents 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.
@@ -0,0 +1,139 @@
1
+ require_relative 'errors'
2
+
3
+ module DeepAgents
4
+ # Todo class for managing todos
5
+ class Todo
6
+ attr_accessor :content, :status
7
+
8
+ def initialize(content, status = "pending")
9
+ raise ArgumentError, "Todo content cannot be nil or empty" if content.nil? || content.empty?
10
+ @content = content
11
+ @status = validate_status(status)
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ content: @content,
17
+ status: @status
18
+ }
19
+ end
20
+
21
+ def self.from_h(hash)
22
+ content = hash[:content] || hash["content"]
23
+ status = hash[:status] || hash["status"] || "pending"
24
+
25
+ raise ArgumentError, "Todo hash must contain content" if content.nil?
26
+ new(content, status)
27
+ end
28
+
29
+ private
30
+
31
+ def validate_status(status)
32
+ valid_statuses = ["pending", "in_progress", "completed"]
33
+ status = status.to_s.downcase
34
+
35
+ unless valid_statuses.include?(status)
36
+ raise ArgumentError, "Invalid todo status: #{status}. Must be one of: #{valid_statuses.join(', ')}"
37
+ end
38
+
39
+ status
40
+ end
41
+ end
42
+
43
+ # Deep agent state class
44
+ class DeepAgentState
45
+ attr_reader :todos, :files, :messages
46
+
47
+ def initialize
48
+ @todos = []
49
+ @files = {}
50
+ @messages = []
51
+ end
52
+
53
+ def todos=(value)
54
+ @todos = validate_todos(value)
55
+ end
56
+
57
+ def files=(value)
58
+ raise ArgumentError, "Files must be a hash" unless value.is_a?(Hash)
59
+ @files = value
60
+ end
61
+
62
+ def messages=(value)
63
+ raise ArgumentError, "Messages must be an array" unless value.is_a?(Array)
64
+ @messages = value
65
+ end
66
+
67
+ def get(key, default = nil)
68
+ case key.to_s
69
+ when "todos"
70
+ @todos
71
+ when "files"
72
+ @files
73
+ when "messages"
74
+ @messages
75
+ else
76
+ default
77
+ end
78
+ end
79
+
80
+ def update(todos: nil, files: nil, messages: nil)
81
+ begin
82
+ if todos
83
+ @todos = validate_todos(todos)
84
+ end
85
+
86
+ if files
87
+ raise ArgumentError, "Files must be a hash" unless files.is_a?(Hash)
88
+ @files.merge!(files)
89
+ end
90
+
91
+ if messages
92
+ raise ArgumentError, "Messages must be an array" unless messages.is_a?(Array)
93
+ @messages += messages
94
+ end
95
+ rescue => e
96
+ raise StateError, "Failed to update state: #{e.message}"
97
+ end
98
+ end
99
+
100
+ def [](key)
101
+ get(key)
102
+ end
103
+
104
+ def []=(key, value)
105
+ case key.to_s
106
+ when "todos"
107
+ self.todos = value
108
+ when "files"
109
+ self.files = value
110
+ when "messages"
111
+ self.messages = value
112
+ else
113
+ raise ArgumentError, "Unknown state key: #{key}"
114
+ end
115
+ end
116
+
117
+ def to_h
118
+ {
119
+ todos: @todos.map(&:to_h),
120
+ files: @files,
121
+ messages: @messages
122
+ }
123
+ end
124
+
125
+ private
126
+
127
+ def validate_todos(todos)
128
+ raise ArgumentError, "Todos must be an array" unless todos.is_a?(Array)
129
+
130
+ todos.map do |todo|
131
+ if todo.is_a?(Todo)
132
+ todo
133
+ else
134
+ Todo.from_h(todo)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,130 @@
1
+ require_relative 'errors'
2
+ require_relative 'state'
3
+
4
+ module DeepAgents
5
+ # SubAgent class for creating and managing sub-agents
6
+ class SubAgent
7
+ attr_reader :name, :instructions, :tools, :model, :state
8
+
9
+ def initialize(name, instructions, tools = [], model = nil)
10
+ raise ArgumentError, "Sub-agent name cannot be nil or empty" if name.nil? || name.empty?
11
+ raise ArgumentError, "Sub-agent instructions cannot be nil or empty" if instructions.nil? || instructions.empty?
12
+
13
+ @name = name
14
+ @instructions = instructions
15
+ @tools = tools
16
+ @model = model
17
+ @state = DeepAgentState.new
18
+ end
19
+
20
+ def run(input, max_iterations = 10)
21
+ raise ArgumentError, "Model must be provided to run the sub-agent" unless @model
22
+
23
+ prompt = build_prompt(input)
24
+ iterations = 0
25
+ messages = []
26
+
27
+ while iterations < max_iterations
28
+ iterations += 1
29
+
30
+ response = @model.generate(prompt, @tools)
31
+
32
+ if response[:tool_calls] && !response[:tool_calls].empty?
33
+ tool_call = response[:tool_calls].first
34
+ tool_name = tool_call[:name]
35
+ tool_args = tool_call[:arguments]
36
+
37
+ tool = find_tool(tool_name)
38
+ result = execute_tool(tool, tool_args)
39
+
40
+ messages << { role: "assistant", content: "I'll use the #{tool_name} tool." }
41
+ messages << { role: "system", content: "Tool result: #{result}" }
42
+
43
+ prompt = build_prompt(input, messages)
44
+ else
45
+ messages << { role: "assistant", content: response[:content] }
46
+ break
47
+ end
48
+ end
49
+
50
+ if iterations >= max_iterations
51
+ raise MaxIterationsError.new(max_iterations)
52
+ end
53
+
54
+ # Return the final response
55
+ messages.last[:content]
56
+ end
57
+
58
+ private
59
+
60
+ def build_prompt(input, messages = [])
61
+ prompt = "You are a sub-agent named #{@name} with the following instructions:\n\n"
62
+ prompt += "#{@instructions}\n\n"
63
+
64
+ if @tools && !@tools.empty?
65
+ prompt += "You have access to the following tools:\n\n"
66
+
67
+ @tools.each do |tool|
68
+ prompt += "#{tool.name}: #{tool.description}\n"
69
+ prompt += "Parameters: #{tool.parameters.join(', ')}\n\n"
70
+ end
71
+ end
72
+
73
+ prompt += "Input: #{input}\n\n"
74
+
75
+ if messages && !messages.empty?
76
+ prompt += "Previous messages:\n\n"
77
+
78
+ messages.each do |message|
79
+ prompt += "#{message[:role]}: #{message[:content]}\n\n"
80
+ end
81
+ end
82
+
83
+ prompt
84
+ end
85
+
86
+ def find_tool(name)
87
+ tool = @tools.find { |t| t.name == name }
88
+ raise ToolNotFoundError.new(name) unless tool
89
+ tool
90
+ end
91
+
92
+ def execute_tool(tool, args)
93
+ begin
94
+ if args.is_a?(Hash)
95
+ tool.call(**args)
96
+ else
97
+ tool.call(args)
98
+ end
99
+ rescue => e
100
+ "Error executing tool: #{e.message}"
101
+ end
102
+ end
103
+ end
104
+
105
+ # SubAgentRegistry class for managing sub-agents
106
+ class SubAgentRegistry
107
+ def initialize
108
+ @agents = {}
109
+ end
110
+
111
+ def register(agent)
112
+ raise ArgumentError, "Agent must be a SubAgent instance" unless agent.is_a?(SubAgent)
113
+ @agents[agent.name] = agent
114
+ end
115
+
116
+ def get(name)
117
+ agent = @agents[name]
118
+ raise SubAgentNotFoundError.new(name) unless agent
119
+ agent
120
+ end
121
+
122
+ def list
123
+ @agents.values
124
+ end
125
+
126
+ def names
127
+ @agents.keys
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,152 @@
1
+ require_relative 'errors'
2
+
3
+ module DeepAgents
4
+ # Tool class for defining tools that can be used by the agent
5
+ class Tool
6
+ attr_reader :name, :description, :parameters, :function
7
+
8
+ def initialize(name, description, parameters = [], &block)
9
+ raise ArgumentError, "Tool name cannot be nil or empty" if name.nil? || name.empty?
10
+ raise ArgumentError, "Tool description cannot be nil or empty" if description.nil? || description.empty?
11
+ raise ArgumentError, "Tool function block must be provided" unless block_given?
12
+
13
+ @name = name
14
+ @description = description
15
+ @parameters = parameters
16
+ @function = block
17
+ end
18
+
19
+ def call(*args, **kwargs)
20
+ begin
21
+ @function.call(*args, **kwargs)
22
+ rescue => e
23
+ raise ToolError.new(@name, e.message)
24
+ end
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ name: @name,
30
+ description: @description,
31
+ parameters: @parameters
32
+ }
33
+ end
34
+ end
35
+
36
+ # ToolRegistry class for managing tools
37
+ class ToolRegistry
38
+ def initialize
39
+ @tools = {}
40
+ end
41
+
42
+ def register(tool)
43
+ raise ArgumentError, "Tool must be a Tool instance" unless tool.is_a?(Tool)
44
+ @tools[tool.name] = tool
45
+ end
46
+
47
+ def get(name)
48
+ tool = @tools[name]
49
+ raise ToolNotFoundError.new(name) unless tool
50
+ tool
51
+ end
52
+
53
+ def list
54
+ @tools.values
55
+ end
56
+
57
+ def names
58
+ @tools.keys
59
+ end
60
+
61
+ def to_h
62
+ @tools.transform_values(&:to_h)
63
+ end
64
+ end
65
+
66
+ # FileSystem class for managing virtual files
67
+ class FileSystem
68
+ def initialize(initial_files = {})
69
+ @files = initial_files
70
+ end
71
+
72
+ def read(path)
73
+ file = @files[path]
74
+ raise FileNotFoundError.new(path) unless file
75
+ file
76
+ end
77
+
78
+ def write(path, content)
79
+ @files[path] = content
80
+ end
81
+
82
+ def delete(path)
83
+ raise FileNotFoundError.new(path) unless @files.key?(path)
84
+ @files.delete(path)
85
+ end
86
+
87
+ def list
88
+ @files.keys
89
+ end
90
+
91
+ def exists?(path)
92
+ @files.key?(path)
93
+ end
94
+
95
+ def to_h
96
+ @files
97
+ end
98
+ end
99
+
100
+ # Standard tools that can be used by the agent
101
+ module StandardTools
102
+ def self.file_system_tools(file_system)
103
+ [
104
+ Tool.new("read_file", "Read a file from the file system", ["path"]) do |path|
105
+ file_system.read(path)
106
+ end,
107
+ Tool.new("write_file", "Write content to a file in the file system", ["path", "content"]) do |path, content|
108
+ file_system.write(path, content)
109
+ "File written to #{path}"
110
+ end,
111
+ Tool.new("list_files", "List all files in the file system") do
112
+ file_system.list
113
+ end,
114
+ Tool.new("delete_file", "Delete a file from the file system", ["path"]) do |path|
115
+ file_system.delete(path)
116
+ "File deleted: #{path}"
117
+ end
118
+ ]
119
+ end
120
+
121
+ def self.todo_tools(state)
122
+ [
123
+ Tool.new("add_todo", "Add a new todo item", ["content"]) do |content|
124
+ todo = Todo.new(content)
125
+ state.todos << todo
126
+ "Todo added: #{content}"
127
+ end,
128
+ Tool.new("list_todos", "List all todo items") do
129
+ state.todos.map.with_index do |todo, index|
130
+ "#{index + 1}. [#{todo.status}] #{todo.content}"
131
+ end
132
+ end,
133
+ Tool.new("update_todo", "Update a todo item status", ["index", "status"]) do |index, status|
134
+ index = index.to_i - 1
135
+ if index < 0 || index >= state.todos.length
136
+ raise ArgumentError, "Invalid todo index: #{index + 1}"
137
+ end
138
+
139
+ todo = state.todos[index]
140
+ todo.status = status
141
+ "Todo updated: #{todo.content} - #{status}"
142
+ end
143
+ ]
144
+ end
145
+
146
+ def self.planning_tool
147
+ Tool.new("plan", "Create a plan for solving a task", ["task"]) do |task|
148
+ "Plan for #{task}:\n1. Analyze the requirements\n2. Break down into subtasks\n3. Implement each subtask\n4. Test the solution\n5. Refine as needed"
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,3 @@
1
+ module DeepAgents
2
+ VERSION = "0.1.0"
3
+ end
data/lib/deepagents.rb ADDED
@@ -0,0 +1,61 @@
1
+ require_relative "deepagents/version"
2
+ require_relative "deepagents/errors"
3
+ require_relative "deepagents/state"
4
+ require_relative "deepagents/tools"
5
+ require_relative "deepagents/sub_agent"
6
+ require_relative "deepagents/models"
7
+ require_relative "deepagents/graph"
8
+
9
+ # External dependencies
10
+ require "langchainrb"
11
+ require "langgraph_rb"
12
+
13
+ module DeepAgents
14
+ # Main entry point to create a deep agent
15
+ def self.create_deep_agent(tools, instructions, model: nil, subagents: nil, state_schema: nil)
16
+ begin
17
+ # Validate inputs
18
+ raise ArgumentError, "Tools must be an array" unless tools.is_a?(Array)
19
+ raise ArgumentError, "Instructions cannot be nil or empty" if instructions.nil? || instructions.empty?
20
+
21
+ # If model is a string, try to create an appropriate model adapter
22
+ if model.is_a?(String)
23
+ if model.downcase.include?("claude")
24
+ model = Models::Claude.new(model: model)
25
+ elsif model.downcase.include?("gpt") || model.downcase.include?("openai")
26
+ model = Models::OpenAI.new(model: model)
27
+ else
28
+ # Default to Claude if we can't determine the model type
29
+ model = Models::Claude.new(model: model)
30
+ end
31
+ end
32
+
33
+ Graph.create_deep_agent(tools, instructions, model: model, subagents: subagents, state_schema: state_schema)
34
+ rescue => e
35
+ # Wrap any errors in our own Error class
36
+ raise Error, "Failed to create deep agent: #{e.message}"
37
+ end
38
+ end
39
+
40
+ # Convenience method to create a Claude model
41
+ def self.claude_model(api_key: nil, model: "claude-3-sonnet-20240229")
42
+ begin
43
+ Models::Claude.new(api_key: api_key, model: model)
44
+ rescue LoadError => e
45
+ raise Error, "Failed to create Claude model: The 'anthropic' gem is not installed. Please run 'gem install anthropic' or add it to your Gemfile."
46
+ rescue => e
47
+ raise Error, "Failed to create Claude model: #{e.message}"
48
+ end
49
+ end
50
+
51
+ # Convenience method to create an OpenAI model
52
+ def self.openai_model(api_key: nil, model: "gpt-4o")
53
+ begin
54
+ Models::OpenAI.new(api_key: api_key, model: model)
55
+ rescue LoadError => e
56
+ raise Error, "Failed to create OpenAI model: The 'openai' gem is not installed. Please run 'gem install openai' or add it to your Gemfile."
57
+ rescue => e
58
+ raise Error, "Failed to create OpenAI model: #{e.message}"
59
+ end
60
+ end
61
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deepagents
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Davis
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-08-19 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: langchainrb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.6'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.6'
26
+ - !ruby/object:Gem::Dependency
27
+ name: langgraph_rb
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: anthropic
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.21'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.21'
96
+ description: Ruby implementation of deepagents - a library for creating deep agents
97
+ with planning capabilities, sub-agent spawning, and a mock file system
98
+ email:
99
+ - chrisdavis179@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".rspec"
105
+ - LICENSE
106
+ - README.md
107
+ - Rakefile
108
+ - examples/langchain_integration.rb
109
+ - examples/research_agent.rb
110
+ - lib/deepagents.rb
111
+ - lib/deepagents/deepagentsrb/errors.rb
112
+ - lib/deepagents/deepagentsrb/graph.rb
113
+ - lib/deepagents/deepagentsrb/models.rb
114
+ - lib/deepagents/deepagentsrb/state.rb
115
+ - lib/deepagents/deepagentsrb/sub_agent.rb
116
+ - lib/deepagents/deepagentsrb/tools.rb
117
+ - lib/deepagents/deepagentsrb/version.rb
118
+ - lib/deepagents/errors.rb
119
+ - lib/deepagents/graph.rb
120
+ - lib/deepagents/models.rb
121
+ - lib/deepagents/state.rb
122
+ - lib/deepagents/sub_agent.rb
123
+ - lib/deepagents/tools.rb
124
+ - lib/deepagents/version.rb
125
+ homepage: https://github.com/cdaviis/deepagents
126
+ licenses:
127
+ - MIT
128
+ metadata:
129
+ homepage_uri: https://github.com/cdaviis/deepagents
130
+ source_code_uri: https://github.com/cdaviis/deepagents
131
+ changelog_uri: https://github.com/cdaviis/deepagents/blob/main/CHANGELOG.md
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 2.7.0
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.6.2
147
+ specification_version: 4
148
+ summary: General purpose 'deep agent' with sub-agent spawning, todo list capabilities,
149
+ and mock file system for Ruby
150
+ test_files: []