durable-llm 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3ce04ccf2ee305fcba4a22e53473f9428f32dd869577d85af7923d15cfbd63b5
4
+ data.tar.gz: bcd7a736b0ad7d199f9ab836e84b6ff7f077da0d172385ded64b24dde82da984
5
+ SHA512:
6
+ metadata.gz: d1786c13db8285c512d5fa0f83541f349f13d54e0bdc968c7aa1e94caf2c9226c9ba1c83f9525d8718cf2a3315674e9ef45322c969ccee1efc984086440a6d05
7
+ data.tar.gz: 97d7ecccb4c6ae9b4bc6e2ed321af0c22899703044f8e8c75684b306ce8e88b241b0447ffdca2afa3dcb44a8f36f8a67c780de016c0594d92eb9f8898a4ad538
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-10-09
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in durable-llm.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ gem "rubocop", "~> 1.21"
13
+
14
+ gem "ruby-openai", "~> 7.1"
15
+
16
+ gem "thor", "~> 1.3"
17
+
18
+
19
+
20
+ gem "webmock", "~> 3.24"
data/Gemfile.lock ADDED
@@ -0,0 +1,102 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ durable-llm (0.1.0)
5
+ faraday (~> 2.0)
6
+ highline (~> 3.1)
7
+ json (~> 2.6)
8
+ thor (~> 1.3)
9
+ zeitwerk (~> 2.6)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ addressable (2.8.7)
15
+ public_suffix (>= 2.0.2, < 7.0)
16
+ ast (2.4.2)
17
+ base64 (0.2.0)
18
+ bigdecimal (3.1.8)
19
+ crack (1.0.0)
20
+ bigdecimal
21
+ rexml
22
+ dotenv (2.8.1)
23
+ event_stream_parser (1.0.0)
24
+ faraday (2.12.0)
25
+ faraday-net_http (>= 2.0, < 3.4)
26
+ json
27
+ logger
28
+ faraday-multipart (1.0.4)
29
+ multipart-post (~> 2)
30
+ faraday-net_http (3.3.0)
31
+ net-http
32
+ hashdiff (1.1.1)
33
+ highline (3.1.1)
34
+ reline
35
+ io-console (0.7.2)
36
+ json (2.7.2)
37
+ language_server-protocol (3.17.0.3)
38
+ logger (1.6.1)
39
+ minitest (5.25.1)
40
+ mocha (2.4.5)
41
+ ruby2_keywords (>= 0.0.5)
42
+ multipart-post (2.4.1)
43
+ net-http (0.4.1)
44
+ uri
45
+ parallel (1.26.3)
46
+ parser (3.3.5.0)
47
+ ast (~> 2.4.1)
48
+ racc
49
+ public_suffix (6.0.1)
50
+ racc (1.8.1)
51
+ rainbow (3.1.1)
52
+ rake (13.2.1)
53
+ regexp_parser (2.9.2)
54
+ reline (0.5.10)
55
+ io-console (~> 0.5)
56
+ rexml (3.3.8)
57
+ rubocop (1.66.1)
58
+ json (~> 2.3)
59
+ language_server-protocol (>= 3.17.0)
60
+ parallel (~> 1.10)
61
+ parser (>= 3.3.0.2)
62
+ rainbow (>= 2.2.2, < 4.0)
63
+ regexp_parser (>= 2.4, < 3.0)
64
+ rubocop-ast (>= 1.32.2, < 2.0)
65
+ ruby-progressbar (~> 1.7)
66
+ unicode-display_width (>= 2.4.0, < 3.0)
67
+ rubocop-ast (1.32.3)
68
+ parser (>= 3.3.1.0)
69
+ ruby-openai (7.1.0)
70
+ event_stream_parser (>= 0.3.0, < 2.0.0)
71
+ faraday (>= 1)
72
+ faraday-multipart (>= 1)
73
+ ruby-progressbar (1.13.0)
74
+ ruby2_keywords (0.0.5)
75
+ thor (1.3.2)
76
+ unicode-display_width (2.6.0)
77
+ uri (0.13.1)
78
+ vcr (6.3.1)
79
+ base64
80
+ webmock (3.24.0)
81
+ addressable (>= 2.8.0)
82
+ crack (>= 0.3.2)
83
+ hashdiff (>= 0.4.0, < 2.0.0)
84
+ zeitwerk (2.6.18)
85
+
86
+ PLATFORMS
87
+ x86_64-linux
88
+
89
+ DEPENDENCIES
90
+ dotenv (~> 2.8)
91
+ durable-llm!
92
+ minitest (~> 5.0)
93
+ mocha (~> 2.1)
94
+ rake (~> 13.0)
95
+ rubocop (~> 1.21)
96
+ ruby-openai (~> 7.1)
97
+ thor (~> 1.3)
98
+ vcr (~> 6.0)
99
+ webmock (~> 3.24)
100
+
101
+ BUNDLED WITH
102
+ 2.4.10
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Durable Programming Team
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,96 @@
1
+ # Durable-LLM
2
+
3
+ Durable-LLM is a Ruby gem providing a unified interface for interacting with multiple Large Language Model APIs. It simplifies the integration of AI capabilities into Ruby applications by offering a consistent way to access various LLM providers.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'durable-llm'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```
22
+ $ gem install durable-llm
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Here's a basic example of how to use Durable-LLM:
28
+
29
+ ```ruby
30
+ require 'durable-llm'
31
+
32
+ client = Durable::Llm::Client.new(:openai, api_key: 'your-api-key')
33
+
34
+ response = client.completion(
35
+ model: 'gpt-3.5-turbo',
36
+ messages: [{ role: 'user', content: 'Hello, how are you?' }]
37
+ )
38
+
39
+ puts response.choices.first.message.content
40
+
41
+ ```
42
+
43
+ ## Features
44
+
45
+ - Unified interface for multiple LLM providers
46
+ - Consistent input/output format across different models
47
+ - Error handling and retries
48
+ - Streaming support
49
+ - Customizable timeout and request options
50
+
51
+ ## Supported Providers
52
+
53
+ - OpenAI
54
+ - Anthropic
55
+ - Cohere
56
+ - AI21
57
+ - (More providers to be added)
58
+
59
+ ## Configuration
60
+
61
+ You can configure Durable-LLM globally or on a per-request basis:
62
+
63
+ ```ruby
64
+ Durable::Llm.configure do |config|
65
+ config.default_provider = :openai
66
+ config.openai.api_key = 'your-openai-api-key'
67
+ config.anthropic.api_key = 'your-anthropic-api-key'
68
+ # Add other provider configurations as needed
69
+ end
70
+ ```
71
+
72
+ ## Error Handling
73
+
74
+ Durable-LLM provides a unified error handling system:
75
+
76
+ ```ruby
77
+ begin
78
+ response = client.completion(model: 'gpt-3.5-turbo', messages: [...])
79
+ rescue Durable::Llm::APIError => e
80
+ puts "API Error: #{e.message}"
81
+ rescue Durable::Llm::RateLimitError => e
82
+ puts "Rate Limit Exceeded: #{e.message}"
83
+ end
84
+ ```
85
+
86
+ ## Acknowledgements
87
+
88
+ Thank you to the lite-llm and llm.datasette.io projects for their hard work, which was invaluable to this project. The dllm command line tool is patterned after the llm tool, though not as full-featured (yet).
89
+
90
+ ## Contributing
91
+
92
+ Bug reports and pull requests are welcome on GitHub at https://github.com/durableprogramming/durable-llm.
93
+
94
+ ## License
95
+
96
+ 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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,40 @@
1
+ # Enterprise Features for Durable-LLM
2
+
3
+ Durable-LLM offers customized enterprise-grade options designed to meet the needs of large-scale organizations and mission-critical applications. Custom-fit Durable-LLM solutions enhance security, scalability, and reliability for enterprise users.
4
+
5
+ ## Advanced Security
6
+
7
+ - **End-to-End Encryption**: All communications with LLM providers are encrypted using industry-standard protocols.
8
+ - **Role-Based Access Control (RBAC)**: Granular control over user permissions and access to different LLM providers and models.
9
+ - **Audit Logging**: Comprehensive logging of all API calls and user actions for compliance and security purposes.
10
+
11
+ ## High Availability and Scalability
12
+
13
+ - **Load Balancing**: Automatically distribute requests across multiple LLM providers to ensure optimal performance and reliability.
14
+ - **Failover Mechanisms**: Seamlessly switch between providers in case of outages or performance issues.
15
+ - **Horizontal Scaling**: Easily scale your Durable-LLM deployment across multiple servers to handle increased load.
16
+
17
+ ## Advanced Monitoring and Analytics
18
+
19
+ - **Real-time Metrics**: Monitor usage, latency, and error rates across all integrated LLM providers.
20
+ - **Custom Dashboards**: Create tailored dashboards to visualize key performance indicators and usage patterns.
21
+ - **Alerting System**: Set up custom alerts for various thresholds and events to proactively manage your LLM infrastructure.
22
+
23
+ ## Customization and Integration
24
+
25
+ - **Custom Model Integration**: Easily integrate your own fine-tuned models or proprietary LLMs.
26
+ - **API Customization**: Tailor the Durable-LLM API to fit your specific enterprise needs.
27
+ - **Advanced Webhooks**: Set up complex event-driven workflows with our advanced webhook system.
28
+
29
+ ## Compliance and Governance
30
+
31
+ - **Data Residency Options**: Choose where your data is processed and stored to comply with regional regulations.
32
+ - **Policy Management**: Implement and enforce organization-wide policies for LLM usage and data handling.
33
+
34
+ ## Cost Management and Optimization
35
+
36
+ - **Usage Analytics**: Detailed breakdowns of API usage and costs across different providers and models.
37
+ - **Smart Routing**: Automatically route requests to the most cost-effective provider based on your defined rules.
38
+ - **Budget Controls**: Set spending limits and receive alerts to prevent unexpected costs.
39
+
40
+ To learn more about our customized enterprise offerings or to schedule a demo, please contact our sales team at enterprise@durableprogramming.com.
@@ -0,0 +1,122 @@
1
+ require 'thor'
2
+ require 'durable/llm'
3
+ require 'durable/llm/client'
4
+ require 'highline'
5
+
6
+ module Durable
7
+ module Llm
8
+ class CLI < Thor
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ desc "prompt PROMPT", "Run a prompt"
14
+ option :model, aliases: "-m", desc: "Specify the model to use"
15
+ option :system, aliases: "-s", desc: "Set a system prompt"
16
+ option :continue, aliases: "-c", type: :boolean, desc: "Continue the previous conversation"
17
+ option :conversation, aliases: "--cid", desc: "Continue a specific conversation by ID"
18
+ option :no_stream, type: :boolean, desc: "Disable streaming of tokens"
19
+ option :option, aliases: "-o", type: :hash, desc: "Set model-specific options"
20
+
21
+ def prompt(prompt)
22
+ config = Durable::Llm.configuration
23
+ model = options[:model] || "gpt-3.5-turbo"
24
+ provider_class = Durable::Llm::Providers.model_id_to_provider(model)
25
+
26
+ if provider_class.nil?
27
+ raise "no provider found for model '#{model}'"
28
+ end
29
+
30
+ provider_name = provider_class.name.split('::').last.downcase.to_sym
31
+ client = Durable::Llm::Client.new(provider_name)
32
+
33
+ messages = []
34
+ messages << { role: "system", content: options[:system] } if options[:system]
35
+ messages << { role: "user", content: prompt }
36
+
37
+ params = {
38
+ model: model,
39
+ messages: messages
40
+ }
41
+ params.merge!(options[:option]) if options[:option]
42
+
43
+ if options[:no_stream]
44
+ response = client.completion(params)
45
+ puts response.choices.first.to_s
46
+ else
47
+ client.stream(params) do |chunk|
48
+ print chunk.to_s
49
+ $stdout.flush
50
+ end
51
+ end
52
+ end
53
+
54
+ desc "chat", "Start an interactive chat"
55
+ option :model, aliases: "-m", desc: "Specify the model to use"
56
+ option :system, aliases: "-s", desc: "Set a system prompt"
57
+ option :continue, aliases: "-c", type: :boolean, desc: "Continue the previous conversation"
58
+ option :conversation, aliases: "--cid", desc: "Continue a specific conversation by ID"
59
+ option :option, aliases: "-o", type: :hash, desc: "Set model-specific options"
60
+ def chat
61
+ config = Durable::Llm.configuration
62
+ model = options[:model] || "gpt-3.5-turbo"
63
+ provider_class = Durable::Llm::Providers.model_id_to_provider(model)
64
+
65
+ if provider_class.nil? || provider_class.name.nil?
66
+ raise "no provider found for model '#{model}'"
67
+ end
68
+
69
+ provider_name = provider_class.name.split('::').last.downcase.to_sym
70
+ client = Durable::Llm::Client.new(provider_name)
71
+
72
+ messages = []
73
+ messages << { role: "system", content: options[:system] } if options[:system]
74
+
75
+ cli = HighLine.new
76
+
77
+ cli.say("Chatting with #{model}")
78
+ cli.say("Type 'exit' or 'quit' to exit")
79
+ cli.say("Type '!multi' to enter multiple lines, then '!end' to finish")
80
+
81
+ loop do
82
+ input = cli.ask("> ")
83
+ break if ['exit', 'quit'].include?(input.downcase)
84
+
85
+ if input == "!multi"
86
+ input = cli.ask("Enter multiple lines. Type '!end' to finish:") do |q|
87
+ q.gather = "!end"
88
+ end
89
+ end
90
+
91
+ messages << { role: "user", content: input }
92
+ params = {
93
+ model: model,
94
+ messages: messages
95
+ }
96
+ params.merge!(options[:option]) if options[:option]
97
+
98
+ response = client.completion(params)
99
+ cli.say(response.choices.first.to_s)
100
+ messages << { role: "assistant", content: response.choices.first.to_s }
101
+ end
102
+ end
103
+
104
+ desc "models", "List available models"
105
+ option :options, type: :boolean, desc: "Show model options"
106
+ def models
107
+ cli = HighLine.new
108
+ cli.say("Available models:")
109
+
110
+ Durable::Llm::Providers.providers.each do |provider_name|
111
+ provider_class = Durable::Llm::Providers.const_get(provider_name.to_s.capitalize)
112
+ provider_models = provider_class.new.models
113
+
114
+ cli.say("#{provider_name.to_s.capitalize}:")
115
+ provider_models.each do |model|
116
+ cli.say(" #{model}")
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,32 @@
1
+ require 'zeitwerk'
2
+ require 'durable/llm/providers'
3
+
4
+ module Durable
5
+ module Llm
6
+ class Client
7
+ attr_reader :provider
8
+
9
+ def initialize(provider_name, options = {})
10
+ provider_class = Durable::Llm::Providers.const_get(provider_name.to_s.capitalize)
11
+
12
+ @provider = provider_class.new(**options)
13
+ end
14
+
15
+ def completion(params = {})
16
+ @provider.completion(params)
17
+ end
18
+
19
+ def chat(params = {})
20
+ @provider.chat(params)
21
+ end
22
+
23
+ def embed(params = {})
24
+ @provider.embed(params)
25
+ end
26
+
27
+ def stream(params = {}, &block)
28
+ @provider.stream(params, &block)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,63 @@
1
+ require 'ostruct'
2
+
3
+ module Durable
4
+ module Llm
5
+ class Configuration
6
+ attr_accessor :default_provider
7
+ attr_reader :providers
8
+
9
+ def initialize
10
+ @providers = {}
11
+ load_from_env
12
+
13
+ end
14
+
15
+ def load_from_datasette
16
+
17
+ config_file = File.expand_path('~/.config/io.datasette.llm/keys.json')
18
+
19
+ if File.exist?(config_file)
20
+ config_data = JSON.parse(File.read(config_file))
21
+
22
+ Durable::Llm::Providers.providers.each do |provider|
23
+
24
+ @providers[provider.to_sym] ||= OpenStruct.new
25
+
26
+ if config_data[provider.to_s]
27
+ @providers[provider.to_sym][:api_key] = config_data[provider.to_s]
28
+ end
29
+
30
+ end
31
+ end
32
+
33
+ rescue JSON::ParserError => e
34
+ puts "Error parsing JSON file: #{e.message}"
35
+ end
36
+
37
+ def load_from_env
38
+ ENV.each do |key, value|
39
+ if key.start_with?('DLLM__')
40
+ parts = key.split('__')
41
+ provider = parts[1].downcase.to_sym
42
+ setting = parts[2].downcase.to_sym
43
+ @providers[provider] ||= OpenStruct.new
44
+ @providers[provider][setting] = value
45
+ end
46
+ end
47
+ end
48
+
49
+ def method_missing(method_name, *args, &block)
50
+ if method_name.to_s.end_with?('=')
51
+ provider = method_name.to_s.chomp('=').to_sym
52
+ @providers[provider] = args.first
53
+ else
54
+ @providers[method_name]
55
+ end
56
+ end
57
+
58
+ def respond_to_missing?(method_name, include_private = false)
59
+ method_name.to_s.end_with?('=') || @providers.key?(method_name) || super
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,33 @@
1
+ module Durable
2
+ module Llm
3
+ class Error < StandardError; end
4
+
5
+ class APIError < Error; end
6
+
7
+ class RateLimitError < Error; end
8
+
9
+ class AuthenticationError < Error; end
10
+
11
+ class InvalidRequestError < Error; end
12
+
13
+ class ResourceNotFoundError < Error; end
14
+
15
+ class TimeoutError < Error; end
16
+
17
+ class ServerError < Error; end
18
+
19
+ class UnsupportedProviderError < Error; end
20
+
21
+ class ConfigurationError < Error; end
22
+
23
+ class ModelNotFoundError < Error; end
24
+
25
+ class InsufficientQuotaError < Error; end
26
+
27
+ class InvalidResponseError < Error; end
28
+
29
+ class NetworkError < Error; end
30
+
31
+ class StreamingError < Error; end
32
+ end
33
+ end