llm_gateway 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 912c28c117bad46986f56b7571497ff276cb3ac5b9e39b442c7b29aeb50692e6
4
- data.tar.gz: 2a087a8344992f50e0a4258d6aecbbeb7b5670cba62cfefed7c8fe054ed98c40
3
+ metadata.gz: 98a42a5943a3b9797fa7ea4d782785e4932c65f7183eab6a452a86b2ca57e585
4
+ data.tar.gz: cbe9e03dc5d76ebfdc8ba911b6c077b803113922f3c470c67fdadcac1b779ff8
5
5
  SHA512:
6
- metadata.gz: 541fb18a405c58505913a7b5dc57fe109dacec72644224cdfb9746e5da290ca93cab2670731ae41f865cc79a20f28d6ee5dab3ea6690ab2161f82fe82b3c81f2
7
- data.tar.gz: 2d6b3294fb5140e7e8b3754ae4c16bd983a133db5a3dcf61fd1adc0f07eed18003f4be724e2b90d98ca98f0aa78678d87648d434893836b52934e7da4fb7a979
6
+ metadata.gz: f58cb7936e3dcc9d643c1747f2d7404b2c1ca7474ad444f31178ce403351fba5ba1c981f91fabd1971097e7cb639b17709c9b62be9f784444d47d516801404b4
7
+ data.tar.gz: 45de5da177dbdd30d1490d1f5f2115a5b5d4e7adeb213d5a4a9c874bf99ed84d557e0e9988a77194bdcac2c491eb5903e4b66659d985284032bb6bfccba6cd1f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
- ## [Unreleased]
1
+ # Changelog
2
2
 
3
- ## [0.1.0] - 2025-08-04
3
+ ## [Unreleased](https://github.com/Hyper-Unearthing/llm_gateway/tree/HEAD)
4
4
 
5
- - Initial release
5
+ [Full Changelog](https://github.com/Hyper-Unearthing/llm_gateway/compare/v0.1.0...HEAD)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - feat: add prompt base class [\#3](https://github.com/Hyper-Unearthing/llm_gateway/pull/3) ([billybonks](https://github.com/billybonks))
10
+ - lint files and add coverage [\#2](https://github.com/Hyper-Unearthing/llm_gateway/pull/2) ([billybonks](https://github.com/billybonks))
11
+ - test: vcr lookup was not working when using different commands [\#1](https://github.com/Hyper-Unearthing/llm_gateway/pull/1) ([billybonks](https://github.com/billybonks))
12
+
13
+ ## [v0.1.0](https://github.com/Hyper-Unearthing/llm_gateway/tree/v0.1.0) (2025-08-04)
14
+
15
+ [Full Changelog](https://github.com/Hyper-Unearthing/llm_gateway/compare/505c78116a2e778b23f319a380cd4bf6e300db89...v0.1.0)
16
+
17
+
18
+
19
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/README.md CHANGED
@@ -43,6 +43,87 @@ result = LlmGateway::Client.chat(
43
43
  )
44
44
  ```
45
45
 
46
+ ### Prompt Class
47
+
48
+ You can also create reusable prompt classes by subclassing `LlmGateway::Prompt`:
49
+
50
+ ```ruby
51
+ # Simple text completion with prompt class
52
+ class GeographyQuestionPrompt < LlmGateway::Prompt
53
+ def initialize(model, question)
54
+ super(model)
55
+ @question = question
56
+ end
57
+
58
+ def prompt
59
+ @question
60
+ end
61
+ end
62
+
63
+ # Usage
64
+ geography_prompt = GeographyQuestionPrompt.new('claude-sonnet-4-20250514', 'What is the capital of France?')
65
+ result = geography_prompt.run
66
+
67
+ # With system message
68
+ class GeographyTeacherPrompt < LlmGateway::Prompt
69
+ def initialize(model, question)
70
+ super(model)
71
+ @question = question
72
+ end
73
+
74
+ def prompt
75
+ @question
76
+ end
77
+
78
+ def system_prompt
79
+ 'You are a helpful geography teacher.'
80
+ end
81
+ end
82
+
83
+ # Usage
84
+ teacher_prompt = GeographyTeacherPrompt.new('gpt-4', 'What is the capital of France?')
85
+ result = teacher_prompt.run
86
+ ```
87
+
88
+ ### Using Prompt with Tools
89
+
90
+ You can combine the Prompt class with tools for more complex interactions:
91
+
92
+ ```ruby
93
+ class WeatherAssistantPrompt < LlmGateway::Prompt
94
+ def initialize(model, location)
95
+ super(model)
96
+ @location = location
97
+ end
98
+
99
+ def prompt
100
+ "What's the weather like in #{@location}?"
101
+ end
102
+
103
+ def system_prompt
104
+ 'You are a helpful weather assistant.'
105
+ end
106
+
107
+ def tools
108
+ [{
109
+ name: 'get_weather',
110
+ description: 'Get current weather for a location',
111
+ input_schema: {
112
+ type: 'object',
113
+ properties: {
114
+ location: { type: 'string', description: 'City name' }
115
+ },
116
+ required: ['location']
117
+ }
118
+ }]
119
+ end
120
+ end
121
+
122
+ # Usage
123
+ weather_prompt = WeatherAssistantPrompt.new('claude-sonnet-4-20250514', 'Singapore')
124
+ result = weather_prompt.run
125
+ ```
126
+
46
127
  ### Tool Usage (Function Calling)
47
128
 
48
129
  ```ruby
@@ -66,6 +147,78 @@ result = LlmGateway::Client.chat(
66
147
  tools: [weather_tool],
67
148
  system: 'You are a helpful weather assistant.'
68
149
  )
150
+
151
+ # Note: Tools are not automatically executed. The LLM will indicate when a tool should be called,
152
+ # but it's up to you to find the appropriate tool and execute it based on the response.
153
+
154
+ # Example of handling tool execution with conversation transcript:
155
+ class WeatherAssistant
156
+ def initialize
157
+ @transcript = []
158
+ end
159
+
160
+ def process_message(content)
161
+ # Add user message to transcript
162
+ @transcript << { role: 'user', content: [{ type: 'text', text: content }] }
163
+
164
+ result = LlmGateway::Client.chat(
165
+ 'claude-sonnet-4-20250514',
166
+ @transcript,
167
+ tools: [weather_tool],
168
+ system: 'You are a helpful weather assistant.'
169
+ )
170
+
171
+ process_response(result[:choices][0][:content])
172
+ end
173
+
174
+ private
175
+
176
+ def process_response(response)
177
+ # Add assistant response to transcript
178
+ @transcript << { role: 'assistant', content: response }
179
+
180
+ response.each do |message|
181
+ if message[:type] == 'text'
182
+ puts message[:text]
183
+ elsif message[:type] == 'tool_use'
184
+ result = handle_tool_use(message)
185
+
186
+ # Add tool result to transcript
187
+ tool_result = {
188
+ type: 'tool_result',
189
+ tool_use_id: message[:id],
190
+ content: result
191
+ }
192
+ @transcript << { role: 'user', content: [tool_result] }
193
+
194
+ # Continue conversation with full transcript context
195
+ follow_up = LlmGateway::Client.chat(
196
+ 'claude-sonnet-4-20250514',
197
+ @transcript,
198
+ tools: [weather_tool],
199
+ system: 'You are a helpful weather assistant.'
200
+ )
201
+
202
+ process_response(follow_up[:choices][0][:content])
203
+ end
204
+ end
205
+ end
206
+
207
+ def handle_tool_use(message)
208
+ tool_class = WeatherAssistantPrompt.find_tool(message[:name])
209
+ raise "Unknown tool: #{message[:name]}" unless tool_class
210
+
211
+ # Execute the tool with the provided input
212
+ tool_instance = tool_class.new
213
+ tool_instance.execute(message[:input])
214
+ rescue StandardError => e
215
+ "Error executing tool: #{e.message}"
216
+ end
217
+ end
218
+
219
+ # Usage
220
+ assistant = WeatherAssistant.new
221
+ assistant.process_message("What's the weather in Singapore?")
69
222
  ```
70
223
 
71
224
  ### Response Format
data/Rakefile CHANGED
@@ -10,12 +10,12 @@ require "rubocop/rake_task"
10
10
  RuboCop::RakeTask.new
11
11
 
12
12
  begin
13
- require "gem-release"
13
+ require "gem/release"
14
14
 
15
15
  desc "Release with changelog"
16
- task :release do
16
+ task :gem_release do
17
17
  # Generate changelog first
18
- sh "github_changelog_generator" # or your preferred changelog tool
18
+ sh "bundle exec github_changelog_generator -u Hyper-Unearthing -p llm_gateway"
19
19
  sh "git add CHANGELOG.md"
20
20
  sh "git commit -m 'Update changelog' || echo 'No changelog changes'"
21
21
 
@@ -2,7 +2,9 @@
2
2
 
3
3
  module LlmGateway
4
4
  module Errors
5
- class BaseError < StandardError
5
+ class BaseError < StandardError; end
6
+
7
+ class ClientError < BaseError
6
8
  attr_reader :code
7
9
 
8
10
  def initialize(message = nil, code = nil)
@@ -11,20 +13,26 @@ module LlmGateway
11
13
  end
12
14
  end
13
15
 
14
- class BadRequestError < BaseError; end
15
- class AuthenticationError < BaseError; end
16
- class PermissionDeniedError < BaseError; end
17
- class NotFoundError < BaseError; end
18
- class ConflictError < BaseError; end
19
- class UnprocessableEntityError < BaseError; end
20
- class RateLimitError < BaseError; end
21
- class InternalServerError < BaseError; end
22
- class APIStatusError < BaseError; end
23
- class APITimeoutError < BaseError; end
24
- class APIConnectionError < BaseError; end
25
- class OverloadError < BaseError; end
26
- class UnknownError < BaseError; end
16
+ class BadRequestError < ClientError; end
17
+ class AuthenticationError < ClientError; end
18
+ class PermissionDeniedError < ClientError; end
19
+ class NotFoundError < ClientError; end
20
+ class ConflictError < ClientError; end
21
+ class UnprocessableEntityError < ClientError; end
22
+ class RateLimitError < ClientError; end
23
+ class InternalServerError < ClientError; end
24
+ class APIStatusError < ClientError; end
25
+ class APITimeoutError < ClientError; end
26
+ class APIConnectionError < ClientError; end
27
+ class OverloadError < ClientError; end
28
+ class UnknownError < ClientError; end
27
29
  class PromptTooLong < BadRequestError; end
28
- class UnsupportedModel < BaseError; end
30
+ class UnsupportedModel < ClientError; end
31
+
32
+ class PromptError < BaseError; end
33
+
34
+ class HallucinationError < PromptError; end
35
+ class UnknownModel < PromptError; end
36
+ class InvalidResponseGrammar < PromptError; end
29
37
  end
30
38
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LlmGateway
4
+ class Prompt
5
+ attr_reader :model
6
+
7
+ def self.before_execute(*methods, &block)
8
+ before_execute_callbacks.concat(methods)
9
+ before_execute_callbacks << block if block_given?
10
+ end
11
+
12
+ def self.after_execute(*methods, &block)
13
+ after_execute_callbacks.concat(methods)
14
+ after_execute_callbacks << block if block_given?
15
+ end
16
+
17
+ def self.before_execute_callbacks
18
+ @before_execute_callbacks ||= []
19
+ end
20
+
21
+ def self.after_execute_callbacks
22
+ @after_execute_callbacks ||= []
23
+ end
24
+
25
+ def self.inherited(subclass)
26
+ super
27
+ subclass.instance_variable_set(:@before_execute_callbacks, before_execute_callbacks.dup)
28
+ subclass.instance_variable_set(:@after_execute_callbacks, after_execute_callbacks.dup)
29
+ end
30
+
31
+ def initialize(model)
32
+ @model = model
33
+ end
34
+
35
+ def run
36
+ run_callbacks(:before_execute, prompt)
37
+
38
+ response = post
39
+
40
+ response_content = if respond_to?(:extract_response)
41
+ extract_response(response)
42
+ else
43
+ response[:choices][0][:content]
44
+ end
45
+
46
+ result = if respond_to?(:parse_response)
47
+ parse_response(response_content)
48
+ else
49
+ response_content
50
+ end
51
+
52
+ run_callbacks(:after_execute, response, response_content)
53
+
54
+ result
55
+ end
56
+
57
+ def post
58
+ LlmGateway::Client.chat(model, prompt, tools: tools, system: system_prompt)
59
+ end
60
+
61
+ def tools
62
+ nil
63
+ end
64
+
65
+ def self.find_tool(tool_name)
66
+ tools.find { |tool| tool.tool_name == tool_name }
67
+ end
68
+
69
+ def system_prompt
70
+ nil
71
+ end
72
+
73
+ private
74
+
75
+ def run_callbacks(callback_type, *args)
76
+ callbacks = self.class.send("#{callback_type}_callbacks")
77
+ callbacks.each do |callback|
78
+ if callback.is_a?(Proc)
79
+ instance_exec(*args, &callback)
80
+ elsif callback.is_a?(Symbol) || callback.is_a?(String)
81
+ send(callback, *args)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LlmGateway
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/llm_gateway.rb CHANGED
@@ -6,6 +6,7 @@ require_relative "llm_gateway/errors"
6
6
  require_relative "llm_gateway/fluent_mapper"
7
7
  require_relative "llm_gateway/base_client"
8
8
  require_relative "llm_gateway/client"
9
+ require_relative "llm_gateway/prompt"
9
10
 
10
11
  # Load adapters - order matters for inheritance
11
12
  require_relative "llm_gateway/adapters/claude/client"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_gateway
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - billybonks
@@ -39,6 +39,7 @@ files:
39
39
  - lib/llm_gateway/client.rb
40
40
  - lib/llm_gateway/errors.rb
41
41
  - lib/llm_gateway/fluent_mapper.rb
42
+ - lib/llm_gateway/prompt.rb
42
43
  - lib/llm_gateway/utils.rb
43
44
  - lib/llm_gateway/version.rb
44
45
  - sig/llm_gateway.rbs