hellm 0.0.3 → 0.0.5
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 +4 -4
- data/ARCHITECTURE.md +30 -14
- data/README.md +9 -3
- data/VERSION +1 -1
- data/lib/brave/search_api/client.rb +34 -0
- data/lib/brave/search_api.rb +10 -0
- data/lib/brave.rb +7 -0
- data/lib/hellm/agent.rb +43 -21
- data/lib/hellm/{builder.rb → project.rb} +38 -31
- data/lib/hellm/session.rb +17 -0
- data/lib/hellm/tools/agent_tool_factory.rb +17 -18
- data/lib/hellm/tools/brave_search_tool.rb +20 -0
- data/lib/hellm/tools/registry.rb +17 -4
- data/lib/hellm/tools/skill_tool.rb +7 -0
- data/lib/hellm/tools/{web_tool.rb → web_fetch_tool.rb} +1 -1
- data/lib/hellm.rb +3 -5
- metadata +11 -7
- data/lib/hellm/stream_adapter.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e145f6843dcffe58f1bcd576ca4c945d2e9727caf765500cf228cbb44d77c9e
|
|
4
|
+
data.tar.gz: 6ce567f6e7d01083a5a9b21b2ef82476b17e1ccbac249c201291b436f15b6055
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cfd84c50054e316c6e402f9360599bca016e3f1b8f36db599bd97f1ffe8a4bc951b110c6c462b7e9846a7485fe6394c74c72fac41391795c24e1bdbcf807322
|
|
7
|
+
data.tar.gz: 268ad8d66f1a59e8583a46d09b06efa1114d68c6c9e182b998bb818da6e2a1b24b6519494da34695c5aba6c4d71b95bfd9563b41b9280a27935ee62240480786
|
data/ARCHITECTURE.md
CHANGED
|
@@ -23,8 +23,8 @@ Main source folders:
|
|
|
23
23
|
|
|
24
24
|
```mermaid
|
|
25
25
|
flowchart TD
|
|
26
|
-
A[CLI: bin/hellm] --> B[Hellm.
|
|
27
|
-
B --> C[Hellm::
|
|
26
|
+
A[CLI: bin/hellm] --> B[Hellm.open_project]
|
|
27
|
+
B --> C[Hellm::Project]
|
|
28
28
|
C --> D[Load agents from .hellm/agents]
|
|
29
29
|
C --> E[Load skills from .hellm/skills/**/SKILL.md]
|
|
30
30
|
C --> F[Register builtin tools in Registry]
|
|
@@ -45,14 +45,14 @@ Location: lib/hellm.rb
|
|
|
45
45
|
|
|
46
46
|
Responsibilities:
|
|
47
47
|
- Stores global API key fallback from OPENAI_API_KEY
|
|
48
|
-
- Builds an agent through Hellm::
|
|
48
|
+
- Builds an agent through Hellm::Project
|
|
49
49
|
- Exposes logger and log-level integration with Langchain
|
|
50
50
|
|
|
51
51
|
Design notes:
|
|
52
52
|
- Global mutable state is limited to logger and API key.
|
|
53
53
|
- Agent construction delegates to Builder to keep orchestration out of the top-level API.
|
|
54
54
|
|
|
55
|
-
### Hellm::
|
|
55
|
+
### Hellm::Project
|
|
56
56
|
|
|
57
57
|
Location: lib/hellm/builder.rb
|
|
58
58
|
|
|
@@ -76,7 +76,6 @@ Responsibilities:
|
|
|
76
76
|
- Wraps Langchain::Assistant lifecycle and message exchange
|
|
77
77
|
- Converts configured tool names to tool instances via Registry
|
|
78
78
|
- Adds text and image input messages
|
|
79
|
-
- Streams callbacks through StreamAdapter
|
|
80
79
|
- Executes with retry logic for transient rate-limit errors
|
|
81
80
|
|
|
82
81
|
Behavior details:
|
|
@@ -100,13 +99,13 @@ Location: lib/hellm/skill.rb
|
|
|
100
99
|
Responsibilities:
|
|
101
100
|
- Value object representing skill metadata and markdown content
|
|
102
101
|
|
|
103
|
-
### Hellm::
|
|
102
|
+
### Hellm::Sesstion
|
|
104
103
|
|
|
105
|
-
Location: lib/hellm/
|
|
104
|
+
Location: lib/hellm/session.rb
|
|
106
105
|
|
|
107
106
|
Responsibilities:
|
|
108
|
-
- Callback interface for streamed assistant messages
|
|
109
|
-
- Base implementation is
|
|
107
|
+
- Callback interface for streamed assistant messages
|
|
108
|
+
- Base implementation is intended for customization
|
|
110
109
|
|
|
111
110
|
## Tooling Architecture
|
|
112
111
|
|
|
@@ -121,7 +120,6 @@ Responsibilities:
|
|
|
121
120
|
- Creates and registers sub-agent tools
|
|
122
121
|
|
|
123
122
|
Key implications:
|
|
124
|
-
- Tool instances are shared after first creation.
|
|
125
123
|
- Custom tools should avoid mutable request-specific state.
|
|
126
124
|
|
|
127
125
|
### Hellm::Tools::SkillTool
|
|
@@ -140,6 +138,24 @@ Responsibilities:
|
|
|
140
138
|
- Builds dynamic Langchain tools that proxy calls into sub-agents
|
|
141
139
|
- Starts a fresh sub-agent session per invocation
|
|
142
140
|
|
|
141
|
+
### Hellm::Tools::WebFetchTool
|
|
142
|
+
|
|
143
|
+
Location: lib/hellm/tools/web_fetch_tool.rb
|
|
144
|
+
|
|
145
|
+
Responsibilities:
|
|
146
|
+
- Reads raw content from HTTP(S) URLs
|
|
147
|
+
- Extracts plain text from HTML pages using Nokogiri
|
|
148
|
+
- Returns resilient error strings for network/parsing failures
|
|
149
|
+
|
|
150
|
+
### Hellm::Tools::BraveSearchTool
|
|
151
|
+
|
|
152
|
+
Location: lib/hellm/tools/brave_search_tool.rb
|
|
153
|
+
|
|
154
|
+
Responsibilities:
|
|
155
|
+
- Calls Brave::SearchApi for web search requests
|
|
156
|
+
- Exposes query/count/offset/country parameters as a tool function
|
|
157
|
+
- Serializes search responses to JSON for agent consumption
|
|
158
|
+
|
|
143
159
|
## Configuration Model
|
|
144
160
|
|
|
145
161
|
Project configuration is stored in a `.hellm` directory with the following structure:
|
|
@@ -160,10 +176,10 @@ Project configuration is stored in a `.hellm` directory with the following struc
|
|
|
160
176
|
## Extension Points
|
|
161
177
|
|
|
162
178
|
Supported extension seams:
|
|
163
|
-
- Register additional tools via Hellm::Tools::Registry
|
|
179
|
+
- Register additional tools via Hellm::Tools::Registry#add
|
|
164
180
|
- Add new agents by placing markdown files in .hellm/agents
|
|
165
181
|
- Add new skills by placing SKILL.md files in .hellm/skills
|
|
166
|
-
- Provide a custom
|
|
182
|
+
- Provide a custom Session implementation for telemetry/UI streaming/restorability/resumeability
|
|
167
183
|
|
|
168
184
|
## Known Risks And Gaps
|
|
169
185
|
|
|
@@ -172,8 +188,8 @@ Current technical risks:
|
|
|
172
188
|
- Minimal validation of markdown frontmatter means malformed config errors may surface late.
|
|
173
189
|
|
|
174
190
|
Testing status:
|
|
175
|
-
- RSpec covers core behavior for Builder, Registry,
|
|
176
|
-
- SimpleCov runs with the suite and writes coverage output to log/coverage.
|
|
191
|
+
- RSpec covers core behavior for Builder, Registry, tool classes (including WebFetchTool and BraveSearchTool), and LLM-free Agent logic.
|
|
192
|
+
- SimpleCov runs with the suite and writes coverage output to log/reports/coverage.
|
|
177
193
|
|
|
178
194
|
## Future Hardening Priorities
|
|
179
195
|
|
data/README.md
CHANGED
|
@@ -47,8 +47,12 @@ hellm Alfred "Tell me your favorite color and which month fits it."
|
|
|
47
47
|
|
|
48
48
|
The framework provides the following tools for agents to use:
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
| Tool Name | Aliases | Description |
|
|
51
|
+
| ------------- | ----------------------- | ------------------------------------------------------- |
|
|
52
|
+
| `calculator` | | a simple calculator for basic arithmetic operations. |
|
|
53
|
+
| `webfetch` | `web` | fetches web pages or other content from URLs. |
|
|
54
|
+
| `websearch` | `web` | performs web searches and returns search results. utilizes [Brave Search API](https://brave.com/search/api/), which needs environment variable BRAVE_API_KEY to be set to a valid API key. |
|
|
55
|
+
| `web` | | combines `webfetch` and `websearch` capabilities. |
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
### Custom Tools
|
|
@@ -56,7 +60,9 @@ The framework provides the following tools for agents to use:
|
|
|
56
60
|
Custom tools can be defined using `Langchain::ToolDefinition`, like it is described in the [Langchain documentation](https://github.com/patterns-ai-core/langchainrb#creating-custom-tools). Such tools need to be registered in `Hellm` before they can be used by agents:
|
|
57
61
|
|
|
58
62
|
```ruby
|
|
59
|
-
Hellm
|
|
63
|
+
project = Hellm.open_project("/path/to/project")
|
|
64
|
+
|
|
65
|
+
project.registry.add(:calculator) do
|
|
60
66
|
Langchain::Tool::Calculator.new
|
|
61
67
|
end
|
|
62
68
|
```
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.5
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Brave
|
|
2
|
+
module SearchApi
|
|
3
|
+
class Client
|
|
4
|
+
BASE_URL = "https://api.search.brave.com/res/v1/web/search"
|
|
5
|
+
|
|
6
|
+
def initialize(api_key)
|
|
7
|
+
@api_key = api_key
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def search(query, country: nil, count: 10, offset: 0)
|
|
11
|
+
uri = URI(BASE_URL)
|
|
12
|
+
params = {
|
|
13
|
+
q: query,
|
|
14
|
+
count: count,
|
|
15
|
+
offset: offset,
|
|
16
|
+
country: country
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
headers = {
|
|
20
|
+
"X-Subscription-Token" => @api_key,
|
|
21
|
+
"Content-Type" => "application/json"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
response = Net::HTTP.post(uri, params.to_json, headers)
|
|
25
|
+
|
|
26
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
27
|
+
JSON.parse(response.body)
|
|
28
|
+
else
|
|
29
|
+
raise "Brave Search API request failed with status #{response.code}: #{response.message}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/brave.rb
ADDED
data/lib/hellm/agent.rb
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
require_relative "
|
|
1
|
+
require_relative "session"
|
|
2
2
|
require_relative "tools/registry"
|
|
3
3
|
require "base64"
|
|
4
4
|
require "langchain"
|
|
5
5
|
|
|
6
6
|
class Hellm::Agent
|
|
7
|
-
attr_accessor :name, :llm, :description, :agents
|
|
8
|
-
|
|
9
|
-
# @param stream_adapter [Hellm::StreamAdapter] an object that responds to `message` and `tool_call` for streaming messages and tool calls
|
|
10
|
-
attr_accessor :stream_adapter
|
|
7
|
+
attr_accessor :name, :llm, :description, :agents, :registry
|
|
11
8
|
|
|
12
9
|
# instructions (string) for the agent.
|
|
13
10
|
attr_accessor :instructions
|
|
@@ -28,7 +25,7 @@ class Hellm::Agent
|
|
|
28
25
|
#
|
|
29
26
|
attr_accessor :tools
|
|
30
27
|
|
|
31
|
-
def initialize(model: nil, name: nil, description: nil,
|
|
28
|
+
def initialize(model: nil, name: nil, description: nil, session: nil, openai_api_key: nil, agents: nil, tools: nil, instructions: nil, registry: nil)
|
|
32
29
|
model ||= "gpt-5-nano"
|
|
33
30
|
Hellm.logger.debug("Initializing agent #{name} (#{model})")
|
|
34
31
|
@llm = Langchain::LLM::OpenAI.new(
|
|
@@ -38,11 +35,11 @@ class Hellm::Agent
|
|
|
38
35
|
self.name = name || self.class.name
|
|
39
36
|
self.description = description || ""
|
|
40
37
|
self.instructions = instructions || ""
|
|
41
|
-
self.
|
|
38
|
+
self.session = session || Hellm::Session.new
|
|
42
39
|
self.agents = agents || []
|
|
43
40
|
self.tools = tools || []
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
self.registry = registry || Hellm::Tools::Registry.new
|
|
42
|
+
@replaying_session = false
|
|
46
43
|
end
|
|
47
44
|
|
|
48
45
|
|
|
@@ -66,15 +63,40 @@ class Hellm::Agent
|
|
|
66
63
|
end
|
|
67
64
|
|
|
68
65
|
def new_session
|
|
66
|
+
@session = nil
|
|
69
67
|
@assistant = nil
|
|
70
68
|
end
|
|
71
69
|
|
|
70
|
+
def session=(value)
|
|
71
|
+
@session = value
|
|
72
|
+
@replaying_session = true
|
|
73
|
+
@session.messages.each do |message|
|
|
74
|
+
attributes = {
|
|
75
|
+
role: fetch_indifferent(message, :role),
|
|
76
|
+
content: fetch_indifferent(message, :content),
|
|
77
|
+
image_url: fetch_indifferent(message, :image_url),
|
|
78
|
+
tool_calls: fetch_indifferent(message, :tool_calls),
|
|
79
|
+
tool_call_id: fetch_indifferent(message, :tool_call_id)
|
|
80
|
+
}
|
|
81
|
+
assistant.add_message(**attributes)
|
|
82
|
+
end
|
|
83
|
+
@replaying_session = false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def session
|
|
87
|
+
@session ||= Hellm::Session.new
|
|
88
|
+
end
|
|
89
|
+
|
|
72
90
|
protected
|
|
91
|
+
def fetch_indifferent(hash, key)
|
|
92
|
+
hash[key.to_sym] || hash[key.to_s]
|
|
93
|
+
end
|
|
94
|
+
|
|
73
95
|
def assistant
|
|
74
96
|
if @assistant.nil?
|
|
75
97
|
tool_instances = [
|
|
76
|
-
*
|
|
77
|
-
*
|
|
98
|
+
*self.registry.get(*tools),
|
|
99
|
+
*self.registry.get_agent_tools(*agents)
|
|
78
100
|
]
|
|
79
101
|
@assistant = Langchain::Assistant.new(
|
|
80
102
|
llm: llm,
|
|
@@ -83,24 +105,24 @@ class Hellm::Agent
|
|
|
83
105
|
)
|
|
84
106
|
@assistant.add_message_callback = ->(message) {
|
|
85
107
|
Hellm.logger.debug("[Agent #{name} message] #{message.inspect}")
|
|
86
|
-
|
|
108
|
+
if @replaying_session != true
|
|
109
|
+
self.session.add_message({
|
|
110
|
+
agent: name,
|
|
111
|
+
role: message.role,
|
|
112
|
+
content: message.content,
|
|
113
|
+
image_url: message.image_url,
|
|
114
|
+
tool_calls: message.tool_calls,
|
|
115
|
+
tool_call_id: message.tool_call_id
|
|
116
|
+
})
|
|
117
|
+
end
|
|
87
118
|
}
|
|
88
119
|
@assistant.tool_execution_callback = ->(call_id, name, method, args) {
|
|
89
120
|
Hellm.logger.debug("[Agent #{self.name} tool_call] call_id: #{call_id}, name: #{name}, method: #{method}, args: #{args}")
|
|
90
|
-
self.stream_adapter.tool_call(call_id, name, method, args)
|
|
91
121
|
}
|
|
92
122
|
end
|
|
93
123
|
@assistant
|
|
94
124
|
end
|
|
95
125
|
|
|
96
|
-
def build_instruction_preface(agent_tool)
|
|
97
|
-
preamble = "# Hints\n\n"
|
|
98
|
-
preamble = "#{preamble}You are an AI agent named #{name}. You have access to the following skills and sub-agents:\n\n"
|
|
99
|
-
preamble << agent_tool.instructions_for_agents(*agents)
|
|
100
|
-
preamble << "\n\n"
|
|
101
|
-
preamble
|
|
102
|
-
end
|
|
103
|
-
|
|
104
126
|
def normalize_image_input(image_input)
|
|
105
127
|
return image_input if image_input.start_with?("http://", "https://", "data:image/")
|
|
106
128
|
|
|
@@ -1,54 +1,61 @@
|
|
|
1
1
|
require_relative "markdown_file"
|
|
2
|
-
require_relative "
|
|
2
|
+
require_relative "session"
|
|
3
3
|
require_relative "agent"
|
|
4
4
|
require_relative "skill"
|
|
5
5
|
require_relative "tools/skill_tool"
|
|
6
|
-
require_relative "tools/
|
|
6
|
+
require_relative "tools/web_fetch_tool"
|
|
7
|
+
require_relative "tools/brave_search_tool"
|
|
8
|
+
require_relative "tools/registry"
|
|
7
9
|
|
|
8
|
-
class Hellm::
|
|
9
|
-
attr_accessor :project_dir, :hellm_dir, :
|
|
10
|
+
class Hellm::Project
|
|
11
|
+
attr_accessor :project_dir, :hellm_dir, :agents, :registry
|
|
10
12
|
|
|
11
13
|
SUPPORTED_SKILL_ATTRIBUTEES = ["name", "description", "argument_hint"]
|
|
12
14
|
SUPPORTED_AGENT_ATTRIBUTEES = ["model", "name", "description", "tools", "agents"]
|
|
13
15
|
|
|
14
|
-
def initialize(project_dir: nil,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
def initialize(project_dir: nil, openai_api_key: nil)
|
|
17
|
+
self.registry = Hellm::Tools::Registry.new
|
|
18
|
+
self.project_dir = project_dir
|
|
19
|
+
self.hellm_dir = File.join(project_dir, ".hellm")
|
|
20
|
+
self.hellm_dir = File.join(project_dir, ".github") if !Dir.exist?(self.hellm_dir)
|
|
21
|
+
raise ArgumentError, "not found: #{project_dir}/.hellm" unless Dir.exist?(self.hellm_dir)
|
|
19
22
|
|
|
20
|
-
@stream_adapter = stream_adapter
|
|
21
23
|
@openai_api_key = openai_api_key
|
|
22
|
-
|
|
23
24
|
add_builtin_tools_to_registry
|
|
25
|
+
self.agents = build_project
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
def
|
|
27
|
-
agents
|
|
28
|
-
skills = get_skills_from_project_dir
|
|
29
|
-
skill_tool = Hellm::Tools::SkillTool.new
|
|
30
|
-
skill_tool.add(*skills)
|
|
31
|
-
Hellm::Tools::Registry.instance.add(:skill, skill_tool)
|
|
32
|
-
Hellm::Tools::Registry.instance.add_agents(*agents)
|
|
33
|
-
|
|
34
|
-
if agents.empty?
|
|
28
|
+
def find_agent(name)
|
|
29
|
+
if self.agents.empty?
|
|
35
30
|
agent = Hellm::Agent.new
|
|
36
31
|
else
|
|
37
|
-
agent = agents.find{ |agent| agent.name ==
|
|
38
|
-
raise ArgumentError, "Agent not found: #{
|
|
32
|
+
agent = self.agents.find{ |agent| agent.name == name }
|
|
33
|
+
raise ArgumentError, "Agent not found: #{name}" if agent.nil?
|
|
39
34
|
end
|
|
40
35
|
agent
|
|
41
36
|
end
|
|
42
37
|
|
|
43
38
|
private
|
|
39
|
+
def build_project
|
|
40
|
+
skills = build_skills_from_project_dir
|
|
41
|
+
skill_tool = Hellm::Tools::SkillTool.new
|
|
42
|
+
skill_tool.add(*skills)
|
|
43
|
+
self.registry.add(:skill, skill_tool)
|
|
44
|
+
|
|
45
|
+
agents = build_agents_from_project_dir(skill_tool)
|
|
46
|
+
self.registry.add_agents(*agents)
|
|
47
|
+
agents
|
|
48
|
+
end
|
|
49
|
+
|
|
44
50
|
def add_builtin_tools_to_registry
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
self.registry.add(:calculator) { Langchain::Tool::Calculator.new }
|
|
52
|
+
self.registry.add(:bravesearch) { Hellm::Tools::BraveSearchTool.new }
|
|
53
|
+
self.registry.add(:webfetch) { Hellm::Tools::WebFetchTool.new }
|
|
47
54
|
end
|
|
48
55
|
|
|
49
|
-
def
|
|
56
|
+
def build_skills_from_project_dir
|
|
50
57
|
skills_dir = File.join(hellm_dir, "skills")
|
|
51
|
-
return unless Dir.exist?(skills_dir)
|
|
58
|
+
return [] unless Dir.exist?(skills_dir)
|
|
52
59
|
|
|
53
60
|
Dir.glob(File.join(skills_dir, "**/SKILL.md")).map do |file|
|
|
54
61
|
markdown_dir = File.dirname(file)
|
|
@@ -63,15 +70,15 @@ class Hellm::Builder
|
|
|
63
70
|
end
|
|
64
71
|
end
|
|
65
72
|
|
|
66
|
-
def
|
|
73
|
+
def build_agents_from_project_dir(skill_tool)
|
|
67
74
|
agents_dir = File.join(hellm_dir, "agents")
|
|
68
|
-
return unless Dir.exist?(agents_dir)
|
|
75
|
+
return [] unless Dir.exist?(agents_dir)
|
|
69
76
|
|
|
70
77
|
Dir.glob(File.join(agents_dir, "*.md")).map do |file|
|
|
71
78
|
markdown_file = Hellm::MarkdownFile.read(file)
|
|
72
79
|
|
|
73
80
|
tools = markdown_file.fetch("tools", [])
|
|
74
|
-
tools << "
|
|
81
|
+
tools << "default"
|
|
75
82
|
|
|
76
83
|
Hellm::Agent.new(
|
|
77
84
|
model: markdown_file.fetch("model"),
|
|
@@ -80,8 +87,8 @@ class Hellm::Builder
|
|
|
80
87
|
tools: tools,
|
|
81
88
|
agents: markdown_file.fetch("agents", []),
|
|
82
89
|
instructions: markdown_file.content.strip,
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
openai_api_key: @openai_api_key,
|
|
91
|
+
registry: self.registry
|
|
85
92
|
)
|
|
86
93
|
end
|
|
87
94
|
end
|
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
module Hellm::Tools
|
|
2
2
|
require "langchain"
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
class AgentToolFactory
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@@agent_name = agent_name
|
|
6
|
+
class AgentBase
|
|
7
|
+
extend Langchain::ToolDefinition
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
def initialize(agent)
|
|
10
|
+
@agent = agent
|
|
11
|
+
end
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
def run_agent(content: nil, image: nil)
|
|
14
|
+
@agent.new_session
|
|
15
|
+
@agent.add(content: content, image: image)
|
|
16
|
+
@agent.run
|
|
17
|
+
@agent.response
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.create_sub_agent_tool(agent_name, agent)
|
|
22
|
+
klass = Class.new(AgentBase) do
|
|
23
|
+
define_singleton_method(:tool_name) { agent_name }
|
|
14
24
|
|
|
15
25
|
define_function "run_agent", description: "Run the agent #{agent.name} and return its response.\n#{agent.description}" do
|
|
16
26
|
property :content, type: "string", description: "Textual content to pass to the agent.", required: false
|
|
17
27
|
property :image, type: "string", description: "Image URL or path to pass to the agent.", required: false
|
|
18
28
|
end
|
|
19
|
-
|
|
20
|
-
def initialize(agent)
|
|
21
|
-
@agent = agent
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def run_agent(content: nil, image: nil)
|
|
25
|
-
@agent.new_session
|
|
26
|
-
@agent.add(content: content, image: image)
|
|
27
|
-
@agent.run
|
|
28
|
-
@agent.response
|
|
29
|
-
end
|
|
30
29
|
end
|
|
31
30
|
klass.new(agent)
|
|
32
31
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "brave"
|
|
2
|
+
|
|
3
|
+
module Hellm::Tools
|
|
4
|
+
class BraveSearchTool
|
|
5
|
+
extend Langchain::ToolDefinition
|
|
6
|
+
|
|
7
|
+
define_function :search, description: "Performs a web search using 'Brave Search' search engine." do
|
|
8
|
+
property :query, type: "string", description: "The search query.", required: true
|
|
9
|
+
property :count, type: "integer", description: "The number of search results to return.", required: false
|
|
10
|
+
property :offset, type: "integer", description: "The offset for the search results.", required: false
|
|
11
|
+
property :country, type: "string", description: "The 2-letter country code for the search results.", required: false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def search(query:, count: 10, offset: 0, country: nil)
|
|
15
|
+
results = Brave::SearchApi.search(query, count: count, offset: offset, country: country)
|
|
16
|
+
results.to_json
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/hellm/tools/registry.rb
CHANGED
|
@@ -2,13 +2,19 @@ require_relative "agent_tool_factory"
|
|
|
2
2
|
|
|
3
3
|
module Hellm::Tools
|
|
4
4
|
class Registry
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
ALIASES = {
|
|
6
|
+
"default" => ["skill", "calculator"],
|
|
7
|
+
"web" => ["webfetch", "bravesearch"],
|
|
8
|
+
"websearch" => ["bravesearch"],
|
|
9
|
+
"agent" => [] # agent tools are dynamically generated from registered agents
|
|
10
|
+
}
|
|
8
11
|
|
|
9
12
|
def initialize
|
|
10
13
|
@tools = {}
|
|
11
|
-
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def available_tools
|
|
17
|
+
@tools.keys
|
|
12
18
|
end
|
|
13
19
|
|
|
14
20
|
def add(tool_name, instance=nil, &factory)
|
|
@@ -19,6 +25,7 @@ module Hellm::Tools
|
|
|
19
25
|
end
|
|
20
26
|
|
|
21
27
|
def get(*tool_names)
|
|
28
|
+
tool_names = resolve_aliases(*tool_names)
|
|
22
29
|
tools = tool_names.map do |tool_name|
|
|
23
30
|
entry = @tools[tool_name.to_s]
|
|
24
31
|
if entry.nil?
|
|
@@ -49,5 +56,11 @@ module Hellm::Tools
|
|
|
49
56
|
def agent_tool_name(agent_name)
|
|
50
57
|
"agent_tool_" + agent_name.gsub(/[\s-]+/, "_").downcase
|
|
51
58
|
end
|
|
59
|
+
|
|
60
|
+
def resolve_aliases(*tool_names)
|
|
61
|
+
tool_names.map do |tool_name|
|
|
62
|
+
ALIASES.fetch(tool_name.to_s, [tool_name.to_s])
|
|
63
|
+
end.flatten
|
|
64
|
+
end
|
|
52
65
|
end
|
|
53
66
|
end
|
|
@@ -4,6 +4,12 @@ module Hellm::Tools
|
|
|
4
4
|
class SkillTool
|
|
5
5
|
extend Langchain::ToolDefinition
|
|
6
6
|
|
|
7
|
+
attr_reader :skills
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@skills = []
|
|
11
|
+
end
|
|
12
|
+
|
|
7
13
|
def add(*skills)
|
|
8
14
|
skills.each do |skill|
|
|
9
15
|
add_skill(skill)
|
|
@@ -12,6 +18,7 @@ module Hellm::Tools
|
|
|
12
18
|
|
|
13
19
|
private
|
|
14
20
|
def add_skill(skill)
|
|
21
|
+
@skills << skill
|
|
15
22
|
skill_name = skill.name.gsub(/[\s-]+/, "_").downcase
|
|
16
23
|
method_name = "get_skill_#{skill_name}"
|
|
17
24
|
self.class.class_eval(
|
|
@@ -2,7 +2,7 @@ require "open-uri"
|
|
|
2
2
|
require "nokogiri"
|
|
3
3
|
|
|
4
4
|
module Hellm::Tools
|
|
5
|
-
class
|
|
5
|
+
class WebFetchTool
|
|
6
6
|
extend Langchain::ToolDefinition
|
|
7
7
|
|
|
8
8
|
define_function :read, description: "Reads content from a URL. Use this function when you want to get text/binary raw data from the response, e.g. to check the HTML-code of a page." do
|
data/lib/hellm.rb
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "hellm/version"
|
|
4
|
-
require_relative "hellm/
|
|
4
|
+
require_relative "hellm/project"
|
|
5
5
|
|
|
6
6
|
module Hellm
|
|
7
7
|
class Error < StandardError; end
|
|
8
|
-
# Your code goes here...
|
|
9
8
|
|
|
10
9
|
def self.openai_api_key=(key)
|
|
11
10
|
@api_key = key if !key.nil? && !key.empty?
|
|
@@ -15,10 +14,9 @@ module Hellm
|
|
|
15
14
|
@api_key ||= ENV["OPENAI_API_KEY"]
|
|
16
15
|
end
|
|
17
16
|
|
|
18
|
-
def self.
|
|
17
|
+
def self.open_project(project_dir, openai_api_key: nil)
|
|
19
18
|
self.openai_api_key = openai_api_key
|
|
20
|
-
|
|
21
|
-
builder.build(talking_to: agent_name)
|
|
19
|
+
Hellm::Project.new(project_dir: project_dir, openai_api_key: openai_api_key)
|
|
22
20
|
end
|
|
23
21
|
|
|
24
22
|
def self.logger=(logger)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hellm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ubity UG
|
|
@@ -81,19 +81,19 @@ dependencies:
|
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: 1.19.4
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: gsearch-parser
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 2.2
|
|
89
|
+
version: 0.2.2
|
|
90
90
|
type: :runtime
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 2.2
|
|
96
|
+
version: 0.2.2
|
|
97
97
|
description: |
|
|
98
98
|
Declarative agentic framework for LLMs, allowing you to build autonomous agents that can perform complex tasks and interact with the world, without writing code.
|
|
99
99
|
Agents and skills are defined in markdown files, quite similar to how Github Copilot works.
|
|
@@ -118,16 +118,20 @@ files:
|
|
|
118
118
|
- example/.hellm/skills/farben/SKILL.md
|
|
119
119
|
- example/example1.sh
|
|
120
120
|
- example/example2.sh
|
|
121
|
+
- lib/brave.rb
|
|
122
|
+
- lib/brave/search_api.rb
|
|
123
|
+
- lib/brave/search_api/client.rb
|
|
121
124
|
- lib/hellm.rb
|
|
122
125
|
- lib/hellm/agent.rb
|
|
123
|
-
- lib/hellm/builder.rb
|
|
124
126
|
- lib/hellm/markdown_file.rb
|
|
127
|
+
- lib/hellm/project.rb
|
|
128
|
+
- lib/hellm/session.rb
|
|
125
129
|
- lib/hellm/skill.rb
|
|
126
|
-
- lib/hellm/stream_adapter.rb
|
|
127
130
|
- lib/hellm/tools/agent_tool_factory.rb
|
|
131
|
+
- lib/hellm/tools/brave_search_tool.rb
|
|
128
132
|
- lib/hellm/tools/registry.rb
|
|
129
133
|
- lib/hellm/tools/skill_tool.rb
|
|
130
|
-
- lib/hellm/tools/
|
|
134
|
+
- lib/hellm/tools/web_fetch_tool.rb
|
|
131
135
|
- lib/hellm/version.rb
|
|
132
136
|
- sig/hellm.rbs
|
|
133
137
|
homepage: https://gitlab.com/ubity/gems/hellm
|