llmp 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.
- checksums.yaml +7 -0
- data/README.md +219 -0
- data/bin/llmp +23 -0
- data/lib/llmp/cache.rb +49 -0
- data/lib/llmp/cli.rb +84 -0
- data/lib/llmp/config.rb +26 -0
- data/lib/llmp/pipe.rb +83 -0
- data/lib/llmp/version.rb +5 -0
- data/lib/llmp.rb +10 -0
- data/llmp.gemspec +37 -0
- metadata +152 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8faca582b2fb9348ca7659a1e44a16c510ffb91d56381b077fd3b6887ec06ec1
|
|
4
|
+
data.tar.gz: 55dd40087e0e4a62f63a500d40a953ffe1c96c5e7dccc25b57afcc909f8c9dc2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cbb418e596938dbc053b6f6e36011ab6b091fe1791a039bb9c56427513bdde4dd3e2995822687835f6bd5c9ec2a7885280cc13cda5dad51ae9befbb2e6d9d389
|
|
7
|
+
data.tar.gz: dc3683e5a59f2dda7d380f2d3046bc70c12f626e0f24a52fc73b546e275d174c94a6f8edd3f0c50fd4f75fa2069099f01d396940676f003c00e02f40015c4731
|
data/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# llmpipe
|
|
2
|
+
|
|
3
|
+
A Ruby-based CLI for building composable LLM workflows using the Unix pipe philosophy.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`llmp` treats LLM prompts as Unix commands that can be chained together. Each pipe is defined by a YAML configuration file and communicates via standard input/output.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
llmp summarize < article.txt | llmp translate > output.md
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install llmp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
With Nix:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
nix run github:pcboy/llmpipe -- <pipe_name_or_path> [args...] [options]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Why llmp? You could just do `agent <<< 'my prompt'` and be done with it?
|
|
26
|
+
|
|
27
|
+
Yes, you could just do `qwen <<< 'my prompt'` and call it a day. That works fine for one-offs.
|
|
28
|
+
|
|
29
|
+
But you'll miss out on:
|
|
30
|
+
|
|
31
|
+
- **Caching** : llmp responses are automatically cached by content hash. Same input = instant result, no API call.
|
|
32
|
+
- **Single source of truth** : Prompts live in YAML files under one folder, can easily be version controlled
|
|
33
|
+
- **Composability** : Chain pipes together like Unix commands, with each step reusable and testable.
|
|
34
|
+
|
|
35
|
+
It's for sure nothing world-changing. It's just a nice way for me to work with quick LLMs operations from the command line.
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### 1. Configure RubyLLM
|
|
40
|
+
|
|
41
|
+
Create `~/.config/llmp/config.yml`:
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
# API key for your LLM provider
|
|
45
|
+
openai_api_key: your-api-key
|
|
46
|
+
|
|
47
|
+
# Custom endpoint (for Ollama, vLLM, LiteLLM, etc.)
|
|
48
|
+
openai_api_base: http://localhost:11434/v1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Any configuration key supported by RubyLLM can be used here. The complete list of available options is documented at [rubyllm.com/configuration](https://rubyllm.com/configuration/).
|
|
52
|
+
|
|
53
|
+
### 2. Create a Pipe
|
|
54
|
+
|
|
55
|
+
Create for instance a `translate.yml` in your project directory, or globally at `$XDG_CONFIG_HOME/llmp/pipes/translate.yml`:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
provider: openai
|
|
59
|
+
model: qwen3.5
|
|
60
|
+
temperature: 0.6
|
|
61
|
+
|
|
62
|
+
system_prompt: |
|
|
63
|
+
Detect the language of the input.
|
|
64
|
+
If it's English, translate to Japanese, otherwise translate to English.
|
|
65
|
+
Preserve the original formatting and tone.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Run the Pipeline
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
➜ echo "Hello, world" | llmp translate
|
|
72
|
+
こんにちは、世界
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## CLI Usage
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
llmp <pipe_name_or_path> [args...] [options]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Arguments
|
|
82
|
+
|
|
83
|
+
Pipe arguments are available in prompts as `$1`, `$2`, `$3`, etc., and `$*` for all arguments joined:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# $1 = "ruby", $2 = "performance"
|
|
87
|
+
llmp review ruby performance < code.rb
|
|
88
|
+
|
|
89
|
+
# $* = "security audit"
|
|
90
|
+
llmp analyze "security audit" < code.rb
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
# In your YAML:
|
|
95
|
+
system_prompt: |
|
|
96
|
+
Review this code for $1 issues.
|
|
97
|
+
Focus on: $2
|
|
98
|
+
All args: $*
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Options
|
|
102
|
+
|
|
103
|
+
| Option | Description |
|
|
104
|
+
| --------------- | ------------------------------- |
|
|
105
|
+
| `-f, --force` | Bypass cache and force LLM call |
|
|
106
|
+
| `-h, --help` | Show help message |
|
|
107
|
+
| `-v, --version` | Print version |
|
|
108
|
+
|
|
109
|
+
### Pipe Resolution
|
|
110
|
+
|
|
111
|
+
When you run `llmp summarize`, the framework resolves the YAML file using this priority:
|
|
112
|
+
|
|
113
|
+
1. **Direct path** — If argument contains `/` or ends with `.yml`/`.yaml`
|
|
114
|
+
2. **Current directory** — Looks for `summarize.yml` or `summarize.yaml`
|
|
115
|
+
3. **Environment variable** — Checks `$LLMP_PIPES_PATH/summarize.yml`
|
|
116
|
+
4. **Global fallback** — XDG config directory (`$XDG_CONFIG_HOME/llmp/pipes/summarize.yml`)
|
|
117
|
+
|
|
118
|
+
## Pipe Definition (YAML Schema)
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
# Required: Provider and model
|
|
122
|
+
provider: openai # or anthropic, google, etc.
|
|
123
|
+
model: qwen3.5
|
|
124
|
+
|
|
125
|
+
# Optional: Model parameters
|
|
126
|
+
temperature: 0.7
|
|
127
|
+
|
|
128
|
+
# Required: System prompt
|
|
129
|
+
system_prompt: |
|
|
130
|
+
Find Job Title in the given JD
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Configuration
|
|
134
|
+
|
|
135
|
+
### RubyLLM Configuration
|
|
136
|
+
|
|
137
|
+
The configuration file is stored following the XDG Base Directory specification:
|
|
138
|
+
|
|
139
|
+
- **Location**: `$XDG_CONFIG_HOME/llmp/config.yml` (defaults to `~/.config/llmp/config.yml`)
|
|
140
|
+
|
|
141
|
+
All keys in this file are passed directly to `RubyLLM.configure`:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
# Authentication
|
|
145
|
+
openai_api_key: your-api-key
|
|
146
|
+
|
|
147
|
+
# Custom endpoints
|
|
148
|
+
openai_api_base: http://localhost:11434/v1
|
|
149
|
+
|
|
150
|
+
# Optional settings
|
|
151
|
+
openai_organization: your-org-id
|
|
152
|
+
openai_timeout: 30
|
|
153
|
+
openai_extra_headers:
|
|
154
|
+
X-Custom-Header: value
|
|
155
|
+
|
|
156
|
+
# Logging
|
|
157
|
+
log_level: info
|
|
158
|
+
log_errors: true
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Any configuration key supported by RubyLLM can be used here. The complete list of available options is documented at [rubyllm.com/configuration](https://rubyllm.com/configuration/).
|
|
162
|
+
|
|
163
|
+
## Examples
|
|
164
|
+
|
|
165
|
+
### Basic Pipeline
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Do a code review on current branch, and apply the necessary changes
|
|
169
|
+
git diff master | llmp code-review | qwen -y && \
|
|
170
|
+
git commit -a -m `git diff | llmp commit-message`
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Everything is cached
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# First run - cache miss, calls LLM
|
|
177
|
+
llmp summarize < input.txt
|
|
178
|
+
|
|
179
|
+
# Second run - cache hit, instant result
|
|
180
|
+
llmp summarize < input.txt
|
|
181
|
+
|
|
182
|
+
# Force refresh - bypass cache
|
|
183
|
+
llmp summarize --force < input.txt
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Custom Pipe Path
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# Use pipe from current directory (my_pipe.yml)
|
|
190
|
+
llmp my_pipe
|
|
191
|
+
|
|
192
|
+
# Use pipe from specific path
|
|
193
|
+
llmp ./pipes/custom.yml
|
|
194
|
+
|
|
195
|
+
# Use pipe from global config directory
|
|
196
|
+
llmp translate # resolves to ~/.config/llmp/translate.yml
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Caching
|
|
200
|
+
|
|
201
|
+
`llmp` automatically caches LLM responses to save time and API costs.
|
|
202
|
+
|
|
203
|
+
- **Cache key**: SHA256 hash of (YAML content + input data)
|
|
204
|
+
- **Cache location**: `.llmp/cache/`
|
|
205
|
+
- **Cache invalidation**: Automatic when YAML or input changes
|
|
206
|
+
- **Bypass cache**: Use `--force` flag
|
|
207
|
+
|
|
208
|
+
Cache storage: `$XDG_CACHE_HOME/llmp/` (defaults to `~/.cache/llmp/`)
|
|
209
|
+
Config files: `$XDG_CONFIG_HOME/llmp/` (defaults to `~/.config/llmp/`)
|
|
210
|
+
|
|
211
|
+
## Environment Variables
|
|
212
|
+
|
|
213
|
+
| Variable | Description |
|
|
214
|
+
| ----------------- | ------------------------------------ |
|
|
215
|
+
| `LLMP_PIPES_PATH` | Custom directory for pipe YAML files |
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT
|
data/bin/llmp
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/llmp'
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
opts = Llmp::Cli.parse(ARGV)
|
|
8
|
+
|
|
9
|
+
unless opts[:pipe_path]
|
|
10
|
+
warn "Error: Pipe '#{opts[:pipe]}' not found"
|
|
11
|
+
exit 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
pipe = Llmp::Pipe.new(opts[:pipe_path], args: opts[:args])
|
|
15
|
+
input_data = $stdin.read
|
|
16
|
+
|
|
17
|
+
result = pipe.execute(input_data, force: opts[:force])
|
|
18
|
+
$stdout.write(result)
|
|
19
|
+
rescue StandardError => e
|
|
20
|
+
warn "Error: #{e.message}"
|
|
21
|
+
warn e.backtrace.first(5) if ENV['LLMP_DEBUG']
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
data/lib/llmp/cache.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'runcom'
|
|
7
|
+
|
|
8
|
+
module Llmp
|
|
9
|
+
class Cache
|
|
10
|
+
def initialize
|
|
11
|
+
@cache = Runcom::Cache.new('llmp')
|
|
12
|
+
@cache_dir = @cache.passive.to_s
|
|
13
|
+
FileUtils.mkdir_p(@cache_dir)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def key_for(yaml_content, input_data)
|
|
17
|
+
Digest::SHA256.hexdigest(yaml_content + input_data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cache_path(key)
|
|
21
|
+
File.join(@cache_dir, "#{key}.json")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def lock_path(key)
|
|
25
|
+
File.join(@cache_dir, "#{key}.lock")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def fetch(yaml_content, input_data, force: false)
|
|
29
|
+
key = key_for(yaml_content, input_data)
|
|
30
|
+
path = cache_path(key)
|
|
31
|
+
lock_file = lock_path(key)
|
|
32
|
+
|
|
33
|
+
File.open(lock_file, File::CREAT | File::RDWR) do |lf|
|
|
34
|
+
lf.flock(File::LOCK_EX)
|
|
35
|
+
|
|
36
|
+
# Check cache only if not forced
|
|
37
|
+
if !force && File.exist?(path)
|
|
38
|
+
cached = JSON.parse(File.read(path), symbolize_names: true)
|
|
39
|
+
return cached[:output]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Cache miss or forced - compute and store atomically
|
|
43
|
+
output = yield
|
|
44
|
+
File.write(path, JSON.pretty_generate({ output: }))
|
|
45
|
+
output
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/llmp/cli.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optimist'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'runcom'
|
|
6
|
+
|
|
7
|
+
module Llmp
|
|
8
|
+
class Cli
|
|
9
|
+
def self.parse(argv = ARGV)
|
|
10
|
+
opts = build_options(argv)
|
|
11
|
+
|
|
12
|
+
positional = argv.reject { |arg| arg.start_with?('-') }
|
|
13
|
+
if positional.empty? || opts[:help]
|
|
14
|
+
build_options(['--help'])
|
|
15
|
+
exit 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
opts[:pipe] = positional.first
|
|
19
|
+
opts[:pipe_path] = resolve_pipe_path(positional.first)
|
|
20
|
+
opts[:args] = positional.drop(1)
|
|
21
|
+
|
|
22
|
+
opts
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.build_options(argv)
|
|
26
|
+
Optimist.options(argv) do
|
|
27
|
+
version 'Llmp v0.1.0'
|
|
28
|
+
banner <<~BANNER
|
|
29
|
+
|
|
30
|
+
Usage: llmp <pipe_name_or_path> [args...] [options]
|
|
31
|
+
|
|
32
|
+
A Ruby-based CLI framework for composable LLM workflows using Unix pipe philosophy.
|
|
33
|
+
|
|
34
|
+
Pipe arguments are available in prompts as $1, $2, $3, etc. and $* for all arguments.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
llmp summarize < url.txt
|
|
38
|
+
llmp ./pipes/custom.yml < url.txt
|
|
39
|
+
llmp translate -f < data.txt
|
|
40
|
+
llmp review ruby -- -Wno-unused-variable < code.rb
|
|
41
|
+
BANNER
|
|
42
|
+
|
|
43
|
+
opt :force, 'Bypass cache and force LLM call', type: :boolean, default: nil, short: 'f'
|
|
44
|
+
opt :help, 'Show this message', type: :boolean, default: nil, short: 'h'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.resolve_pipe_path(pipe_arg)
|
|
49
|
+
return pipe_arg if pipe_arg.include?('/') || pipe_arg.end_with?('.yml', '.yaml')
|
|
50
|
+
|
|
51
|
+
paths_to_try = [
|
|
52
|
+
cwd_path(pipe_arg),
|
|
53
|
+
env_var_path(pipe_arg),
|
|
54
|
+
global_path(pipe_arg)
|
|
55
|
+
].compact
|
|
56
|
+
|
|
57
|
+
paths_to_try.each do |path|
|
|
58
|
+
return path if File.exist?(path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.cwd_path(pipe_arg)
|
|
65
|
+
base_name = pipe_arg.end_with?('.yml', '.yaml') ? pipe_arg : "#{pipe_arg}.yml"
|
|
66
|
+
File.expand_path(base_name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.env_var_path(pipe_arg)
|
|
70
|
+
env_path = ENV['LLMP_PIPES_PATH']
|
|
71
|
+
return nil if env_path.nil? || env_path.empty?
|
|
72
|
+
|
|
73
|
+
base_name = pipe_arg.end_with?('.yml', '.yaml') ? pipe_arg : "#{pipe_arg}.yml"
|
|
74
|
+
File.expand_path(base_name, env_path)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.global_path(pipe_arg)
|
|
78
|
+
config = Runcom::Config.new("llmp/pipes/#{pipe_arg}")
|
|
79
|
+
base_name = pipe_arg.end_with?('.yml', '.yaml') ? pipe_arg : "#{pipe_arg}.yml"
|
|
80
|
+
config_path = config.active || config.passive
|
|
81
|
+
File.join(File.dirname(config_path.to_s), base_name)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/llmp/config.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'runcom'
|
|
6
|
+
|
|
7
|
+
module Llmp
|
|
8
|
+
class Config
|
|
9
|
+
def self.load(path: nil)
|
|
10
|
+
if path
|
|
11
|
+
config_path = path
|
|
12
|
+
else
|
|
13
|
+
config = Runcom::Config.new('llmp/config.yml')
|
|
14
|
+
config_path = config.active || config.passive
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return {} unless config_path
|
|
18
|
+
return {} unless File.exist?(config_path)
|
|
19
|
+
|
|
20
|
+
YAML.safe_load(File.read(config_path.to_s)) || {}
|
|
21
|
+
rescue Psych::SyntaxError => e
|
|
22
|
+
warn "Warning: Invalid YAML in #{config_path}: #{e.message}"
|
|
23
|
+
{}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/llmp/pipe.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'ruby_llm'
|
|
6
|
+
require_relative 'cache'
|
|
7
|
+
require_relative 'config'
|
|
8
|
+
|
|
9
|
+
module Llmp
|
|
10
|
+
class Pipe
|
|
11
|
+
REQUIRED_FIELDS = %w[provider model system_prompt].freeze
|
|
12
|
+
attr_reader :config, :yaml_content
|
|
13
|
+
|
|
14
|
+
def initialize(yaml_path, args: [], cache: nil)
|
|
15
|
+
@yaml_path = yaml_path
|
|
16
|
+
@args = args
|
|
17
|
+
@yaml_content = File.read(yaml_path)
|
|
18
|
+
@config = YAML.safe_load(substitute_args(@yaml_content))
|
|
19
|
+
|
|
20
|
+
missing = REQUIRED_FIELDS.reject { |f| @config.key?(f) }
|
|
21
|
+
raise "Missing required fields: #{missing.join(', ')}" unless missing.empty?
|
|
22
|
+
|
|
23
|
+
@cache = cache || Cache.new
|
|
24
|
+
@input_data = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def execute(input_data, force: false)
|
|
28
|
+
@input_data = input_data
|
|
29
|
+
|
|
30
|
+
configure
|
|
31
|
+
|
|
32
|
+
@cache.fetch(@yaml_content, @input_data, force: force) do
|
|
33
|
+
call_llm
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def substitute_args(content)
|
|
40
|
+
return content if @args.empty?
|
|
41
|
+
|
|
42
|
+
result = content
|
|
43
|
+
|
|
44
|
+
# Replace $* with all arguments joined by space (YAML-escaped)
|
|
45
|
+
result = result.gsub('$*', escape_yaml_value(@args.join(' ')))
|
|
46
|
+
|
|
47
|
+
# Replace $1, $2, $3, etc. with corresponding arguments (YAML-escaped)
|
|
48
|
+
@args.each_with_index do |arg, index|
|
|
49
|
+
result = result.gsub("$#{index + 1}", escape_yaml_value(arg))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
result
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def escape_yaml_value(value)
|
|
56
|
+
YAML.dump(value).gsub(/^--- /, '').strip
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def configure
|
|
60
|
+
user_config = Config.load
|
|
61
|
+
|
|
62
|
+
RubyLLM.configure do |config|
|
|
63
|
+
user_config.each do |key, value|
|
|
64
|
+
config.send("#{key}=", value) if config.respond_to?("#{key}=")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def call_llm
|
|
70
|
+
provider = @config['provider']
|
|
71
|
+
model = @config['model']
|
|
72
|
+
temperature = @config['temperature'] || 0.6
|
|
73
|
+
system_prompt = @config['system_prompt']
|
|
74
|
+
|
|
75
|
+
chat = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true)
|
|
76
|
+
chat.with_temperature(temperature)
|
|
77
|
+
chat.with_instructions(system_prompt)
|
|
78
|
+
response = chat.ask(@input_data)
|
|
79
|
+
|
|
80
|
+
response.content
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/llmp/version.rb
ADDED
data/lib/llmp.rb
ADDED
data/llmp.gemspec
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/llmp/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'llmp'
|
|
7
|
+
spec.version = Llmp::VERSION
|
|
8
|
+
spec.authors = ['David Hagege']
|
|
9
|
+
spec.email = ['david@joynetiks.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'A Ruby-based CLI for composable LLM workflows'
|
|
12
|
+
spec.description = <<~EOL
|
|
13
|
+
Build composable LLM workflows using Unix pipe philosophy.
|
|
14
|
+
Each pipe is a YAML-configured LLM call that communicates via stdin/stdout.
|
|
15
|
+
EOL
|
|
16
|
+
spec.homepage = 'https://github.com/pcboy/llmpipe'
|
|
17
|
+
spec.license = 'MIT'
|
|
18
|
+
spec.required_ruby_version = '>= 3.4.0'
|
|
19
|
+
|
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
21
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
22
|
+
|
|
23
|
+
spec.files = Dir.glob('lib/**/*') + Dir.glob('bin/*') + %w[README.md llmp.gemspec]
|
|
24
|
+
|
|
25
|
+
spec.bindir = 'bin'
|
|
26
|
+
spec.executables = ['llmp']
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
|
|
29
|
+
spec.add_dependency 'json', '~> 2.0'
|
|
30
|
+
spec.add_dependency 'optimist', '~> 3.0'
|
|
31
|
+
spec.add_dependency 'ruby_llm', '~> 1.16'
|
|
32
|
+
spec.add_dependency 'runcom', '~> 12.3'
|
|
33
|
+
|
|
34
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
35
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
36
|
+
spec.add_development_dependency 'ruby_llm-test', '~> 0.2'
|
|
37
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: llmp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David Hagege
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: optimist
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ruby_llm
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.16'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.16'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: runcom
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '12.3'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '12.3'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: ruby_llm-test
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0.2'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.2'
|
|
110
|
+
description: |
|
|
111
|
+
Build composable LLM workflows using Unix pipe philosophy.
|
|
112
|
+
Each pipe is a YAML-configured LLM call that communicates via stdin/stdout.
|
|
113
|
+
email:
|
|
114
|
+
- david@joynetiks.com
|
|
115
|
+
executables:
|
|
116
|
+
- llmp
|
|
117
|
+
extensions: []
|
|
118
|
+
extra_rdoc_files: []
|
|
119
|
+
files:
|
|
120
|
+
- README.md
|
|
121
|
+
- bin/llmp
|
|
122
|
+
- lib/llmp.rb
|
|
123
|
+
- lib/llmp/cache.rb
|
|
124
|
+
- lib/llmp/cli.rb
|
|
125
|
+
- lib/llmp/config.rb
|
|
126
|
+
- lib/llmp/pipe.rb
|
|
127
|
+
- lib/llmp/version.rb
|
|
128
|
+
- llmp.gemspec
|
|
129
|
+
homepage: https://github.com/pcboy/llmpipe
|
|
130
|
+
licenses:
|
|
131
|
+
- MIT
|
|
132
|
+
metadata:
|
|
133
|
+
homepage_uri: https://github.com/pcboy/llmpipe
|
|
134
|
+
source_code_uri: https://github.com/pcboy/llmpipe
|
|
135
|
+
rdoc_options: []
|
|
136
|
+
require_paths:
|
|
137
|
+
- lib
|
|
138
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - ">="
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: 3.4.0
|
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
|
+
requirements:
|
|
145
|
+
- - ">="
|
|
146
|
+
- !ruby/object:Gem::Version
|
|
147
|
+
version: '0'
|
|
148
|
+
requirements: []
|
|
149
|
+
rubygems_version: 3.7.2
|
|
150
|
+
specification_version: 4
|
|
151
|
+
summary: A Ruby-based CLI for composable LLM workflows
|
|
152
|
+
test_files: []
|