hellm 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 75af48a7436c71030b39ada0b3acd8a74919c187816d3685715801f70bce314b
4
+ data.tar.gz: 11110f4770b2b25fb4ac92a46aff9d20b1c41a26abdf0d5e0790e7e0a698be9d
5
+ SHA512:
6
+ metadata.gz: 958c4ccb58b11db9e67168e7cd4e1a667be90ddcc921a7585a3f40a50c0a83d36c3def47b79fa4988e61f0ec39749edf9efc7d8421f2e70b9909f97d79d9d972
7
+ data.tar.gz: ad4474e9e0a64c4c9ff2c4687e3bf0c58ed40eaaf3078e42e8443ec8f62f8b6654625e4004218d0856e3c100a90e9d6ceafb791d3ba9de50255f850ca149bffb
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: developer
3
+ description: "Development agent for Hellm. Use for ruby and rspec changes."
4
+ tools: [read, search, edit, execute, todo]
5
+ user-invocable: true
6
+ ---
7
+
8
+ You are a Ruby development agent.
9
+
10
+ ## First Context To Load
11
+
12
+ Before proposing or editing code, read:
13
+
14
+ 1. `ARCHITECTURE.md`
15
+
16
+ ## Project-Specific Constraints
17
+
18
+ - Language throughout code and documentation is English.
19
+ - Keep domain logic inside existing namespaces (`Hellm`, `Hellm::Tools`) unless a new boundary is clearly justified.
20
+
21
+ ## Preferred Approach
22
+
23
+ 1. Identify impacted module and classes.
24
+ 2. Implement smallest coherent change, preserving existing APIs unless change request demands otherwise.
25
+ 3. Add or update tests to cover new behavior and prevent regressions.
26
+ 4. Summarize architecture impact in the final response, including code coverage report and any risks or missing tests.
27
+ 5. Update `ARCHITECTURE.md` if the change introduces new patterns or behaviors.
28
+
29
+ ## Skill Selection
30
+
31
+ - Use the `ruby` skill for language-level style, refactors, and Ruby object patterns.
32
+ - Use the `test` skill for writing and debugging specs with RSpec.
33
+
34
+ ## Output Requirements
35
+
36
+ - Explain what changed and why it matches existing project patterns.
37
+ - Call out risks, regressions, or missing tests when relevant.
38
+ - Keep recommendations grounded in `ARCHITECTURE.md` and current code behavior.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: ruby
3
+ description: "Ruby implementation skill. Use for idiomatic Ruby code changes."
4
+ argument-hint: "Describe the Ruby behavior to implement or refactor, including affected modules or classes."
5
+ user-invocable: true
6
+ ---
7
+
8
+ # Ruby Skill For Arealsans
9
+
10
+ Use this skill when the task is mostly Ruby-level logic.
11
+
12
+ ## Load Context First
13
+
14
+ 1. Read `ARCHITECTURE.md`.
15
+ 2. Read the concrete module or class files touched by the request.
16
+
17
+ ## Repository Ruby Conventions
18
+
19
+ - Prefer namespace-scoped classes/modules (`Hellm::...`, `Hellm::Tools::...`).
20
+ - Avoid introducing unrelated abstractions when a focused method-level change is sufficient.
21
+ - Private methods are only refactored if it improves clarity or reduces duplication.
22
+
23
+ ## Procedure
24
+
25
+ 1. Implement the minimum Ruby change that satisfies the request.
26
+ 2. Keep method names and signatures consistent unless the change requires API updates.
27
+
28
+ ## Quality Checklist
29
+
30
+ - Public method output remains compatible with current callers.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: test
3
+ description: "Testing skill. Use for writing and debugging specs with RSpec."
4
+ argument-hint: "Describe what behavior should be tested, including context and expected outcomes."
5
+ user-invocable: true
6
+ ---
7
+
8
+ # Testing Skill For Arealsans
9
+
10
+ Use this skill when implementing or fixing automated tests in this project.
11
+
12
+ ## Test Stack
13
+
14
+ - RSpec
15
+
16
+ ## Mandatory Context
17
+
18
+ 1. Read `ARCHITECTURE.md` first.
19
+ 2. Read existing nearby specs to mirror conventions before adding new ones.
20
+
21
+ ## When To Use
22
+
23
+ - Add coverage for new behavior
24
+ - Reproduce and prevent regressions
25
+
26
+ ## Procedure
27
+
28
+ 1. Identify the thinnest test that proves behavior.
29
+ 2. Run focused specs first, then a wider relevant subset.
30
+
31
+ ## Repository Guardrails
32
+
33
+ - Prefer clear setup helpers (before/after) over deeply nested let chains.
34
+ - Validate both success and failure paths.
35
+
36
+
37
+ ## Suggested Commands
38
+
39
+ - `bundle exec rspec spec/..._spec.rb`
40
+ - `bundle exec rspec --profile 10`
41
+
42
+ ## Code Coverage (SimpleCov)
43
+
44
+ - Use SimpleCov for all RSpec runs and keep project-wide minimum coverage at 80%.
45
+ - If coverage drops below 80%, tests should fail and coverage must be restored before merge.
46
+ - Prefer meaningful behavioral tests over coverage-only assertions.
47
+ - Output of SimpleCov should be reviewed to identify untested code paths and guide new test additions.
48
+ - SimpleCov should be configured to exclude irrelevant files and focus on application code.
49
+ - Output of SimpleCov should be written to directory `log/coverage` and include HTML reports for local review.
50
+
51
+
52
+ Example baseline configuration (typically in `spec/spec_helper.rb`):
53
+
54
+ ```ruby
55
+ require "simplecov"
56
+
57
+ SimpleCov.start "hellm" do
58
+ minimum_coverage 80
59
+ end
60
+ ```
61
+
62
+ ## Done Criteria
63
+
64
+ - New behavior is covered at the right layer.
65
+ - Regression path has a failing-to-passing spec story.
66
+ - SimpleCov project coverage is at least 80%.
67
+ - Specs are readable, deterministic, and avoid timing flakiness.
data/ARCHITECTURE.md ADDED
@@ -0,0 +1,183 @@
1
+ # Hellm Architecture Overview
2
+
3
+ ## Purpose
4
+
5
+ Hellm is a Ruby gem that builds and runs Langchain-based AI agents from project-local markdown configuration.
6
+
7
+ The framework focuses on:
8
+ - Declarative agent setup in markdown files
9
+ - Runtime composition of tools and sub-agent calls
10
+ - Skill injection from markdown documents
11
+ - A thin CLI wrapper for local execution
12
+
13
+ ## High-Level Structure
14
+
15
+ Main source folders:
16
+ - lib/hellm.rb: Public entrypoints and global configuration
17
+ - lib/hellm/: Core runtime classes
18
+ - lib/hellm/tools/: Tool registry and dynamic tool builders
19
+ - bin/hellm: Command-line runner
20
+ - example/: Sample .hellm project with agents and skills
21
+
22
+ ## Runtime Flow
23
+
24
+ ```mermaid
25
+ flowchart TD
26
+ A[CLI: bin/hellm] --> B[Hellm.build_agent]
27
+ B --> C[Hellm::Builder]
28
+ C --> D[Load agents from .hellm/agents]
29
+ C --> E[Load skills from .hellm/skills/**/SKILL.md]
30
+ C --> F[Register builtin tools in Registry]
31
+ C --> G[Register skill tool in Registry]
32
+ C --> H[Register sub-agent tools in Registry]
33
+ H --> I[Hellm::Agent selected by name]
34
+ I --> J[agent.add content/image]
35
+ J --> K[Langchain::Assistant.run!]
36
+ K --> L[Tool calls via Registry instances]
37
+ K --> M[Response returned by agent.response]
38
+ ```
39
+
40
+ ## Core Components
41
+
42
+ ### Hellm module
43
+
44
+ Location: lib/hellm.rb
45
+
46
+ Responsibilities:
47
+ - Stores global API key fallback from OPENAI_API_KEY
48
+ - Builds an agent through Hellm::Builder
49
+ - Exposes logger and log-level integration with Langchain
50
+
51
+ Design notes:
52
+ - Global mutable state is limited to logger and API key.
53
+ - Agent construction delegates to Builder to keep orchestration out of the top-level API.
54
+
55
+ ### Hellm::Builder
56
+
57
+ Location: lib/hellm/builder.rb
58
+
59
+ Responsibilities:
60
+ - Resolves configuration root (.hellm preferred, .github fallback)
61
+ - Loads agent definitions from markdown files under agents/
62
+ - Loads skill definitions from skills/**/SKILL.md
63
+ - Registers tools in Hellm::Tools::Registry
64
+ - Returns the selected runnable Hellm::Agent
65
+
66
+ Behavior details:
67
+ - Raises ArgumentError if no .hellm or .github directory exists.
68
+ - Always appends the skill tool to each configured agent.
69
+ - Registers built-in calculator tool by default.
70
+
71
+ ### Hellm::Agent
72
+
73
+ Location: lib/hellm/agent.rb
74
+
75
+ Responsibilities:
76
+ - Wraps Langchain::Assistant lifecycle and message exchange
77
+ - Converts configured tool names to tool instances via Registry
78
+ - Adds text and image input messages
79
+ - Streams callbacks through StreamAdapter
80
+ - Executes with retry logic for transient rate-limit errors
81
+
82
+ Behavior details:
83
+ - Default model is gpt-5-nano.
84
+ - Removes literal agent tool token from tools list and relies on explicit sub-agent wiring.
85
+ - Supports image input as URL, data URL, or local file path converted to base64 data URL.
86
+
87
+ ### Hellm::MarkdownFile
88
+
89
+ Location: lib/hellm/markdown_file.rb
90
+
91
+ Responsibilities:
92
+ - Reads markdown files with YAML frontmatter
93
+ - Splits frontmatter and content
94
+ - Exposes typed fetch access for attributes
95
+
96
+ ### Hellm::Skill
97
+
98
+ Location: lib/hellm/skill.rb
99
+
100
+ Responsibilities:
101
+ - Value object representing skill metadata and markdown content
102
+
103
+ ### Hellm::StreamAdapter
104
+
105
+ Location: lib/hellm/stream_adapter.rb
106
+
107
+ Responsibilities:
108
+ - Callback interface for streamed assistant messages and tool calls
109
+ - Base implementation is intentionally empty and intended for customization
110
+
111
+ ## Tooling Architecture
112
+
113
+ ### Hellm::Tools::Registry
114
+
115
+ Location: lib/hellm/tools/registry.rb
116
+
117
+ Responsibilities:
118
+ - Singleton registry for named tools
119
+ - Supports factory or fixed-instance registration
120
+ - Lazy-instantiates tools from factories and reuses instances
121
+ - Creates and registers sub-agent tools
122
+
123
+ Key implications:
124
+ - Tool instances are shared after first creation.
125
+ - Custom tools should avoid mutable request-specific state.
126
+
127
+ ### Hellm::Tools::SkillTool
128
+
129
+ Location: lib/hellm/tools/skill_tool.rb
130
+
131
+ Responsibilities:
132
+ - Dynamically defines tool functions for each loaded skill
133
+ - Returns raw skill content when called by the LLM
134
+
135
+ ### Hellm::Tools::AgentToolFactory
136
+
137
+ Location: lib/hellm/tools/agent_tool_factory.rb
138
+
139
+ Responsibilities:
140
+ - Builds dynamic Langchain tools that proxy calls into sub-agents
141
+ - Starts a fresh sub-agent session per invocation
142
+
143
+ ## Configuration Model
144
+
145
+ Project configuration is stored in a `.hellm` directory with the following structure:
146
+
147
+ ```text
148
+ .hellm/
149
+ |-- agents/
150
+ | `-- *.md
151
+ | |-- frontmatter: model, name, description, tools, agents
152
+ | `-- body: agent instructions
153
+ `-- skills/
154
+ `-- **/
155
+ `-- SKILL.md
156
+ |-- frontmatter: name, description, argument-hint
157
+ `-- body: skill text returned by the generated skill tool
158
+ ```
159
+
160
+ ## Extension Points
161
+
162
+ Supported extension seams:
163
+ - Register additional tools via Hellm::Tools::Registry.instance.add
164
+ - Add new agents by placing markdown files in .hellm/agents
165
+ - Add new skills by placing SKILL.md files in .hellm/skills
166
+ - Provide a custom StreamAdapter implementation for telemetry or UI streaming
167
+
168
+ ## Known Risks And Gaps
169
+
170
+ Current technical risks:
171
+ - Registry is process-global singleton with shared tool instances; stateful tools can leak behavior across sessions.
172
+ - Minimal validation of markdown frontmatter means malformed config errors may surface late.
173
+
174
+ Testing status:
175
+ - RSpec covers core behavior for Builder, Registry, tools, and LLM-free Agent logic.
176
+ - SimpleCov runs with the suite and writes coverage output to log/coverage.
177
+
178
+ ## Future Hardening Priorities
179
+
180
+ Recommended next steps:
181
+ - Add integration specs for builder loading, tool registration, and agent execution wiring.
182
+ - Add contract tests for markdown schema validation and clearer error messages.
183
+ - Add concurrency and isolation tests around Registry behavior.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 couchbelag
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,88 @@
1
+ # Hellm
2
+
3
+ ## Installation
4
+
5
+ Install the gem and add to the application's Gemfile by executing:
6
+
7
+ ```bash
8
+ bundle add hellm
9
+ ```
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ ```bash
14
+ gem install hellm
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ Create a project-local `.hellm` directory with agent and skill definitions.
20
+
21
+ Minimal structure (as used in `example/`):
22
+
23
+ ```text
24
+ .hellm/
25
+ agents/
26
+ alfred.agent.md
27
+ mathematician.agent.md
28
+ skills/
29
+ farben/
30
+ SKILL.md
31
+ ```
32
+
33
+ Set your OpenAI API key in an environment variable named `OPENAI_API_KEY` or put this variable in a `.env` file in the working directory:
34
+
35
+ ```bash
36
+ OPENAI_API_KEY=your_api_key_here
37
+ ```
38
+
39
+ Run an agent by passing the agent name and prompt:
40
+
41
+ ```bash
42
+ hellm Alfred "Tell me your favorite color and which month fits it."
43
+ ```
44
+
45
+
46
+ ### Builtin Tools
47
+
48
+ The framework provides the following tools for agents to use:
49
+
50
+ * `calculator` - a simple calculator for basic arithmetic operations.
51
+
52
+
53
+ ### Custom Tools
54
+
55
+ 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:
56
+
57
+ ```ruby
58
+ Hellm::Tools::Registry.instance.add(:calculator) do
59
+ Langchain::Tool::Calculator.new
60
+ end
61
+ ```
62
+
63
+ The code shows how to get access to the registry and add a custom tool with a unique name. The block is used as factory method to create a new instance of the tool when needed. Instances are created once only and reused for all agents, so do not store any state in the tool instance.
64
+
65
+
66
+ ### Example
67
+
68
+ The example in this repository provides a convenience script which runs the example with one call, showcasing the flow of actions taken by the agents:
69
+
70
+ ```bash
71
+ cd example
72
+ ./example.sh
73
+ ```
74
+
75
+
76
+ ## Development
77
+
78
+ 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.
79
+
80
+ 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).
81
+
82
+ ## Contributing
83
+
84
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hellm.
85
+
86
+ ## License
87
+
88
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+
10
+ desc "returns current version number as defined in lib/hellm/version.rb"
11
+ task :version do
12
+ puts Hellm::VERSION
13
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: Alfred
3
+ description: "General purpose agent."
4
+ tools: [agent]
5
+ agents: [Mathematician]
6
+ ---
7
+
8
+ You are Alfred, a helpful general purpose agent that ends every response's last sentence with ", Sir.". If you have a mathematical task, try to solve the problem by asking the Mathematician agent for help.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: Mathematician
3
+ description: "Agent for calculating mathematical expressions."
4
+ argument-hint: "Provide a mathematical expression to calculate."
5
+ tools: [calculator]
6
+ ---
7
+
8
+ You are Mathematician, a helpful agent that calculates mathematical expressions accurately with the calculator tool.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: farben
3
+ description: "A skill for mapping colors to month"
4
+ argument-hint: "Describe your color and why you like it."
5
+ ---
6
+
7
+ Map colors to months:
8
+
9
+ * red: january
10
+ * orange: february
11
+ * yellow: march
12
+ * green: april
13
+ * blue: may
14
+ * purple: june
15
+ * pink: july
16
+ * white: august
17
+ * black: september
18
+ * brown: october
19
+ * gray: november
20
+ * rainbow: december
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/bash
2
+
3
+ ../bin/hellm Alfred sag mir deine lieblingsfarbe und welcher monat dazu passt und berechne die anzahl monate von einschließlich diesem monat bis einschließlich dezember. Errechne mir die Fläche eines Kreises mit dieser Zahl als Radius.
@@ -0,0 +1,142 @@
1
+ require_relative "stream_adapter"
2
+ require_relative "tools/registry"
3
+ require "base64"
4
+ require "langchain"
5
+
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
11
+
12
+ # instructions (string) for the agent.
13
+ attr_accessor :instructions
14
+
15
+ # array of tools (Langchain::Tool) for the agent to use
16
+ #
17
+ # langchain.rb Built-in Tools:
18
+ # - Langchain::Tool::Calculator: Useful for evaluating math expressions. Requires gem "eqn".
19
+ # - Langchain::Tool::Database: Connect your SQL database. Requires gem "sequel".
20
+ # - Langchain::Tool::FileSystem: Interact with the file system (read & write).
21
+ # - Langchain::Tool::GoogleSearch: Wrapper around SerpApi's Google Search API. Requires gem "google_search_results".
22
+ # - Langchain::Tool::NewsRetriever: A wrapper around NewsApi.org to fetch news articles.
23
+ # - Langchain::Tool::RubyCodeInterpreter: Useful for evaluating generated Ruby code. Requires gem "safe_ruby" (In need of a better solution).
24
+ # - Langchain::Tool::Tavily: A wrapper around Tavily AI.
25
+ # - Langchain::Tool::Vectorsearch: A wrapper for vector search classes.
26
+ # - Langchain::Tool::Weather: Calls Open Weather API to retrieve the current weather.
27
+ # - Langchain::Tool::Wikipedia: Calls Wikipedia API. Requires gem "wikipedia-client".
28
+ #
29
+ attr_accessor :tools
30
+
31
+ def initialize(model: nil, name: nil, description: nil, stream_adapter: nil, openai_api_key: nil, agents: nil, tools: nil, instructions: nil)
32
+ model ||= "gpt-5-nano"
33
+ Hellm.logger.debug("Initializing agent #{name} (#{model})")
34
+ @llm = Langchain::LLM::OpenAI.new(
35
+ api_key: openai_api_key || ::Hellm.openai_api_key,
36
+ default_options: {chat_model: model}
37
+ )
38
+ self.name = name || self.class.name
39
+ self.description = description || ""
40
+ self.instructions = instructions || ""
41
+ self.stream_adapter = stream_adapter || Hellm::StreamAdapter.new
42
+ self.agents = agents || []
43
+ self.tools = tools || []
44
+ # remove "agent" from tools, this ability is controlled by the presence of agents
45
+ self.tools.reject!{|t| t == "agent"}
46
+ end
47
+
48
+
49
+ def add(content: nil, image: nil)
50
+ return if content.nil? && image.nil?
51
+ options = {}
52
+ options[:content] = content if !content.nil? && !content.empty?
53
+ options[:image_url] = normalize_image_input(image) if !image.nil? && !image.empty?
54
+ assistant.add_message(**options)
55
+ end
56
+
57
+ def run
58
+ with_rate_limit do
59
+ assistant.run!
60
+ end
61
+ end
62
+
63
+ def response
64
+ assistant_message = assistant.messages.reverse.find(&:llm?)
65
+ assistant_message&.content.to_s.strip
66
+ end
67
+
68
+ def new_session
69
+ @assistant = nil
70
+ end
71
+
72
+ protected
73
+ def assistant
74
+ if @assistant.nil?
75
+ tool_instances = [
76
+ *Hellm::Tools::Registry.instance.get(*tools),
77
+ *Hellm::Tools::Registry.instance.get_agent_tools(*agents)
78
+ ]
79
+ @assistant = Langchain::Assistant.new(
80
+ llm: llm,
81
+ tools: tool_instances,
82
+ instructions: instructions
83
+ )
84
+ @assistant.add_message_callback = ->(message) {
85
+ Hellm.logger.debug("[Agent #{name} message] #{message.inspect}")
86
+ self.stream_adapter.message(message)
87
+ }
88
+ @assistant.tool_execution_callback = ->(call_id, name, method, args) {
89
+ 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
+ }
92
+ end
93
+ @assistant
94
+ end
95
+
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
+ def normalize_image_input(image_input)
105
+ return image_input if image_input.start_with?("http://", "https://", "data:image/")
106
+
107
+ path = File.expand_path(image_input)
108
+ raise ArgumentError, "Image file not found: #{image_input}" unless File.exist?(path)
109
+
110
+ ext = File.extname(path).downcase
111
+ mime_type = case ext
112
+ when ".jpg", ".jpeg" then "image/jpeg"
113
+ when ".png" then "image/png"
114
+ when ".webp" then "image/webp"
115
+ when ".gif" then "image/gif"
116
+ else "application/octet-stream"
117
+ end
118
+
119
+ encoded = Base64.strict_encode64(File.binread(path))
120
+ "data:#{mime_type};base64,#{encoded}"
121
+ end
122
+
123
+ def rate_limited?(error)
124
+ message = error.message.downcase
125
+ message.include?("429") ||
126
+ message.include?("rate limit") ||
127
+ message.include?("rate_limit_exceeded")
128
+ end
129
+
130
+ def with_rate_limit
131
+ attempts = 0
132
+ begin
133
+ attempts += 1
134
+ yield
135
+ rescue StandardError => e
136
+ raise unless rate_limited?(e) && attempts < MAX_RETRIES
137
+ # Small exponential backoff for transient 429 errors.
138
+ sleep(0.25 * (2**(attempts - 1)))
139
+ retry
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,86 @@
1
+ require_relative "agent"
2
+ require_relative "skill"
3
+ require_relative "stream_adapter"
4
+ require_relative "tools/skill_tool"
5
+ require_relative "markdown_file"
6
+
7
+ class Hellm::Builder
8
+ attr_accessor :project_dir, :hellm_dir, :stream_adapter
9
+
10
+ SUPPORTED_SKILL_ATTRIBUTEES = ["name", "description", "argument_hint"]
11
+ SUPPORTED_AGENT_ATTRIBUTEES = ["model", "name", "description", "tools", "agents"]
12
+
13
+ def initialize(project_dir: nil, stream_adapter: nil, openai_api_key: nil)
14
+ @project_dir = project_dir
15
+ @hellm_dir = File.join(project_dir, ".hellm")
16
+ @hellm_dir = File.join(project_dir, ".github") if !Dir.exist?(@hellm_dir)
17
+ raise ArgumentError, "not found: #{project_dir}/.hellm" unless Dir.exist?(@hellm_dir)
18
+
19
+ @stream_adapter = stream_adapter
20
+ @openai_api_key = openai_api_key
21
+
22
+ add_builtin_tools_to_registry
23
+ end
24
+
25
+ def build(talking_to: nil)
26
+ agents = get_agents_from_project_dir
27
+ skills = get_skills_from_project_dir
28
+ skill_tool = Hellm::Tools::SkillTool.new
29
+ skill_tool.add(*skills)
30
+ Hellm::Tools::Registry.instance.add(:skill, skill_tool)
31
+ Hellm::Tools::Registry.instance.add_agents(*agents)
32
+
33
+ if agents.empty?
34
+ agent = Hellm::Agent.new
35
+ else
36
+ agent = agents.find{ |agent| agent.name == talking_to }
37
+ raise ArgumentError, "Agent not found: #{talking_to}" if agent.nil?
38
+ end
39
+ agent
40
+ end
41
+
42
+ private
43
+ def add_builtin_tools_to_registry
44
+ Hellm::Tools::Registry.instance.add(:calculator) { Langchain::Tool::Calculator.new }
45
+ end
46
+
47
+ def get_skills_from_project_dir
48
+ skills_dir = File.join(hellm_dir, "skills")
49
+ return unless Dir.exist?(skills_dir)
50
+
51
+ Dir.glob(File.join(skills_dir, "**/SKILL.md")).map do |file|
52
+ markdown_dir = File.dirname(file)
53
+ markdown_file = Hellm::MarkdownFile.read(file)
54
+ Hellm::Skill.new(
55
+ dir: markdown_dir,
56
+ name: markdown_file.fetch("name"),
57
+ description: markdown_file.fetch("description"),
58
+ argument_hint: markdown_file.fetch("argument-hint"),
59
+ content: markdown_file.content,
60
+ )
61
+ end
62
+ end
63
+
64
+ def get_agents_from_project_dir
65
+ agents_dir = File.join(hellm_dir, "agents")
66
+ return unless Dir.exist?(agents_dir)
67
+
68
+ Dir.glob(File.join(agents_dir, "*.md")).map do |file|
69
+ markdown_file = Hellm::MarkdownFile.read(file)
70
+
71
+ tools = markdown_file.fetch("tools", [])
72
+ tools << "skill"
73
+
74
+ Hellm::Agent.new(
75
+ model: markdown_file.fetch("model"),
76
+ name: markdown_file.fetch("name"),
77
+ description: markdown_file.fetch("description"),
78
+ tools: tools,
79
+ agents: markdown_file.fetch("agents", []),
80
+ instructions: markdown_file.content.strip,
81
+ stream_adapter: stream_adapter,
82
+ openai_api_key: @openai_api_key
83
+ )
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ require "yaml"
2
+
3
+ class Hellm::MarkdownFile
4
+ attr_accessor :path, :attributes, :content
5
+
6
+ def self.read(path)
7
+ file = new(path)
8
+ file.read
9
+ file
10
+ end
11
+
12
+ def fetch(key, default=nil)
13
+ attributes.fetch(key.to_s, default)
14
+ end
15
+
16
+ def read
17
+ file = File.read(path)
18
+ frontmatter, content = separate_frontmatter_and_content(file)
19
+ self.attributes = YAML.load(frontmatter) if frontmatter != ""
20
+ self.attributes ||= {}
21
+ self.content = content
22
+ self
23
+ end
24
+
25
+
26
+ protected
27
+ def initialize(path)
28
+ @path = path
29
+ @attributes = {}
30
+ @content = ""
31
+ end
32
+
33
+ def separate_frontmatter_and_content(file)
34
+ within_frontmatter = false
35
+ content = ""
36
+ frontmatter = ""
37
+
38
+ file.lines.each do |line|
39
+ if line.strip == "---"
40
+ within_frontmatter = !within_frontmatter
41
+ next
42
+ end
43
+
44
+ if within_frontmatter
45
+ frontmatter << line
46
+ else
47
+ content << line
48
+ end
49
+ end
50
+
51
+ [frontmatter, content]
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ class Hellm::Skill
2
+ attr_accessor :dir, :name, :description, :argument_hint, :content
3
+
4
+ def initialize(dir:, name:, description:, argument_hint: nil, content: nil)
5
+ @dir = dir
6
+ @name = name
7
+ @description = description
8
+ @argument_hint = argument_hint
9
+ @content = content
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ class Hellm::StreamAdapter
2
+ def message(message)
3
+ end
4
+
5
+ def tool_call(call_id, name, method, args)
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ module Hellm::Tools
2
+ require "langchain"
3
+
4
+ class AgentToolFactory
5
+ def self.create_sub_agent_tool(agent_name, agent)
6
+ klass = Class.new do
7
+ @@agent_name = agent_name
8
+
9
+ extend Langchain::ToolDefinition
10
+
11
+ def self.tool_name
12
+ @@agent_name
13
+ end
14
+
15
+ define_function "run_agent", description: "Run the agent #{agent.name} and return its response.\n#{agent.description}" do
16
+ property :content, type: "string", description: "Textual content to pass to the agent.", required: false
17
+ property :image, type: "string", description: "Image URL or path to pass to the agent.", required: false
18
+ 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
+ end
31
+ klass.new(agent)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "agent_tool_factory"
2
+
3
+ module Hellm::Tools
4
+ class Registry
5
+ def self.instance
6
+ @instance ||= new
7
+ end
8
+
9
+ def initialize
10
+ @tools = {}
11
+ @agent_tools = {}
12
+ end
13
+
14
+ def add(tool_name, instance=nil, &factory)
15
+ @tools[tool_name.to_s] = {
16
+ factory: factory,
17
+ instance: instance
18
+ }
19
+ end
20
+
21
+ def get(*tool_names)
22
+ tools = tool_names.map do |tool_name|
23
+ entry = @tools[tool_name.to_s]
24
+ if entry.nil?
25
+ raise ArgumentError, "Tool not found: #{tool_name}"
26
+ end
27
+ entry[:instance] ||= entry[:factory].call
28
+ entry[:instance]
29
+ end
30
+ tools.compact
31
+ end
32
+
33
+ def add_agents(*agents)
34
+ agents.each do |agent|
35
+ tool_name = agent_tool_name(agent.name)
36
+ tool = Hellm::Tools::AgentToolFactory.create_sub_agent_tool(tool_name, agent)
37
+ add(tool_name, tool)
38
+ end
39
+ end
40
+
41
+ def get_agent_tools(*agent_names)
42
+ tool_names = agent_names.map do |name|
43
+ agent_tool_name(name)
44
+ end
45
+ get(*tool_names)
46
+ end
47
+
48
+ private
49
+ def agent_tool_name(agent_name)
50
+ "agent_tool_" + agent_name.gsub(/[\s-]+/, "_").downcase
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ module Hellm::Tools
2
+ require "langchain"
3
+
4
+ class SkillTool
5
+ extend Langchain::ToolDefinition
6
+
7
+ def add(*skills)
8
+ skills.each do |skill|
9
+ add_skill(skill)
10
+ end
11
+ end
12
+
13
+ private
14
+ def add_skill(skill)
15
+ skill_name = skill.name.gsub(/[\s-]+/, "_").downcase
16
+ method_name = "get_skill_#{skill_name}"
17
+ self.class.class_eval(
18
+ <<~EOSKILL
19
+ define_function "#{method_name}", description: "SkillTool: Read skill #{skill.name}.\n#{skill.description}\n#{skill.argument_hint}\n\n"
20
+ def #{method_name}
21
+ <<~EOCONTENT
22
+ #{skill.content}
23
+ EOCONTENT
24
+ end
25
+ EOSKILL
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hellm
4
+ VERSION = File.read(File.join(__dir__, "..", "..", "VERSION")).strip
5
+ end
data/lib/hellm.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hellm/version"
4
+ require_relative "hellm/builder"
5
+
6
+ module Hellm
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+
10
+ def self.openai_api_key=(key)
11
+ @api_key = key if !key.nil? && !key.empty?
12
+ end
13
+
14
+ def self.openai_api_key
15
+ @api_key ||= ENV["OPENAI_API_KEY"]
16
+ end
17
+
18
+ def self.build_agent(agent_name, project_dir, stream_adapter: nil, openai_api_key: nil)
19
+ self.openai_api_key = openai_api_key
20
+ builder = Hellm::Builder.new(project_dir: project_dir, stream_adapter: stream_adapter, openai_api_key: openai_api_key)
21
+ builder.build(talking_to: agent_name)
22
+ end
23
+
24
+ def self.logger=(logger)
25
+ @logger = logger
26
+ end
27
+
28
+ def self.logger
29
+ @logger ||= Logger.new($stdout)
30
+ end
31
+
32
+ def self.set_log_level(level, langchain_level: Logger::ERROR)
33
+ self.logger.level = level
34
+ Langchain.logger.level = langchain_level
35
+ end
36
+ end
37
+
38
+ # default log level
39
+ Hellm.set_log_level(Logger::DEBUG)
data/sig/hellm.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Hellm
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hellm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ubity UG
8
+ - Tobi Schmid
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: langchainrb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.19.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.19.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-openai
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 8.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 8.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: base64
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: eqn
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.6.5
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.6.5
69
+ - !ruby/object:Gem::Dependency
70
+ name: google_search_results
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 2.2.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.2.0
83
+ description: |
84
+ Agentic framework for LLMs, allowing you to build autonomous agents that can perform complex tasks and interact with the world.
85
+ Name is inspired by the English word "helm", which refers to the steering mechanism of a ship, symbolizing guidance and direction.
86
+ The name also evokes the idea of taking control and navigating through challenges, much like how an agentic framework empowers LLMs to make decisions and take actions autonomously.
87
+ email:
88
+ - info@ubity.io
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".github/agents/developer.md"
94
+ - ".github/skills/ruby/SKILL.md"
95
+ - ".github/skills/test/SKILL.md"
96
+ - ARCHITECTURE.md
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - VERSION
101
+ - example/.hellm/agents/alfred.agent.md
102
+ - example/.hellm/agents/mathematician.agent.md
103
+ - example/.hellm/skills/farben/SKILL.md
104
+ - example/example.sh
105
+ - lib/hellm.rb
106
+ - lib/hellm/agent.rb
107
+ - lib/hellm/builder.rb
108
+ - lib/hellm/markdown_file.rb
109
+ - lib/hellm/skill.rb
110
+ - lib/hellm/stream_adapter.rb
111
+ - lib/hellm/tools/agent_tool_factory.rb
112
+ - lib/hellm/tools/registry.rb
113
+ - lib/hellm/tools/skill_tool.rb
114
+ - lib/hellm/version.rb
115
+ - sig/hellm.rbs
116
+ homepage: https://gitlab.com/ubity/gems/hellm
117
+ licenses:
118
+ - MIT
119
+ metadata:
120
+ homepage_uri: https://gitlab.com/ubity/gems/hellm
121
+ source_code_uri: https://gitlab.com/ubity/gems/hellm
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 3.4.1
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.6.9
137
+ specification_version: 4
138
+ summary: Ruby agentic framework for LLMs
139
+ test_files: []