Commit 9a291e7
Changed files (7)
lib/net/llm/anthropic.rb
@@ -11,13 +11,13 @@ module Net
end
def messages(messages, system: nil, max_tokens: 1024, tools: nil, &block)
- uri = URI("https://api.anthropic.com/v1/messages")
+ url = "https://api.anthropic.com/v1/messages"
payload = build_payload(messages, system, max_tokens, tools, block_given?)
if block_given?
- stream_request(uri, payload, &block)
+ stream_request(url, payload, &block)
else
- post_request(uri, payload)
+ post_request(url, payload)
end
end
@@ -35,51 +35,43 @@ module Net
payload
end
- def post_request(uri, payload)
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = true
+ def client
+ @client ||= Net::Hippie::Client.new(read_timeout: 3600, open_timeout: 10)
+ end
+
+ def anthropic_headers
+ {
+ "x-api-key" => api_key,
+ "anthropic-version" => "2023-06-01"
+ }
+ end
- request = Net::HTTP::Post.new(uri)
- request["x-api-key"] = api_key
- request["anthropic-version"] = "2023-06-01"
- request["Content-Type"] = "application/json"
- request.body = payload.to_json
+ def post_request(url, payload)
+ response = client.post(url, headers: anthropic_headers, body: payload)
+ handle_response(response)
+ end
- response = http.start { |h| h.request(request) }
+ def handle_response(response)
raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
end
- def stream_request(uri, payload, &block)
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = true
- http.read_timeout = 3600
-
- request = Net::HTTP::Post.new(uri)
- request["x-api-key"] = api_key
- request["anthropic-version"] = "2023-06-01"
- request["Content-Type"] = "application/json"
- request.body = payload.to_json
-
- http.start do |h|
- h.request(request) do |response|
- unless response.is_a?(Net::HTTPSuccess)
- raise "HTTP #{response.code}: #{response.body}"
- end
+ def stream_request(url, payload, &block)
+ client.post(url, headers: anthropic_headers, body: payload) do |response|
+ raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
- buffer = ""
- response.read_body do |chunk|
- buffer += chunk
+ buffer = ""
+ response.read_body do |chunk|
+ buffer += chunk
- while (event = extract_sse_event(buffer))
- next if event[:data].nil? || event[:data].empty?
- next if event[:data] == "[DONE]"
+ while (event = extract_sse_event(buffer))
+ next if event[:data].nil? || event[:data].empty?
+ next if event[:data] == "[DONE]"
- json = JSON.parse(event[:data])
- block.call(json)
+ json = JSON.parse(event[:data])
+ block.call(json)
- break if json["type"] == "message_stop"
- end
+ break if json["type"] == "message_stop"
end
end
end
lib/net/llm/ollama.rb
@@ -11,103 +11,81 @@ module Net
end
def chat(messages, &block)
- uri = build_uri("/api/chat")
+ url = build_url("/api/chat")
payload = { model: model, messages: messages, stream: block_given? }
if block_given?
- stream_request(uri, payload, &block)
+ stream_request(url, payload, &block)
else
- post_request(uri, payload)
+ post_request(url, payload)
end
end
def generate(prompt, &block)
- uri = build_uri("/api/generate")
+ url = build_url("/api/generate")
payload = { model: model, prompt: prompt, stream: block_given? }
if block_given?
- stream_request(uri, payload, &block)
+ stream_request(url, payload, &block)
else
- post_request(uri, payload)
+ post_request(url, payload)
end
end
def embeddings(input)
- uri = build_uri("/api/embed")
+ url = build_url("/api/embed")
payload = { model: model, input: input }
- post_request(uri, payload)
+ post_request(url, payload)
end
def tags
- uri = build_uri("/api/tags")
- get_request(uri)
+ url = build_url("/api/tags")
+ response = client.get(url)
+ handle_response(response)
end
def show(name)
- uri = build_uri("/api/show")
+ url = build_url("/api/show")
payload = { name: name }
- post_request(uri, payload)
+ post_request(url, payload)
end
private
- def build_uri(path)
+ def build_url(path)
base = host.start_with?("http://", "https://") ? host : "http://#{host}"
- URI("#{base}#{path}")
+ "#{base}#{path}"
end
- def get_request(uri)
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = uri.scheme == "https"
- request = Net::HTTP::Get.new(uri)
- request["Accept"] = "application/json"
-
- response = http.start { |h| h.request(request) }
- raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
- JSON.parse(response.body)
+ def client
+ @client ||= Net::Hippie::Client.new(read_timeout: 3600, open_timeout: 10)
end
- def post_request(uri, payload)
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = uri.scheme == "https"
- request = Net::HTTP::Post.new(uri)
- request["Accept"] = "application/json"
- request["Content-Type"] = "application/json"
- request.body = payload.to_json
+ def post_request(url, payload)
+ response = client.post(url, body: payload)
+ handle_response(response)
+ end
- response = http.start { |h| h.request(request) }
+ def handle_response(response)
raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
end
- def stream_request(uri, payload, &block)
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = uri.scheme == "https"
- http.read_timeout = 3600
-
- request = Net::HTTP::Post.new(uri)
- request["Accept"] = "application/json"
- request["Content-Type"] = "application/json"
- request.body = payload.to_json
-
- http.start do |h|
- h.request(request) do |response|
- unless response.is_a?(Net::HTTPSuccess)
- raise "HTTP #{response.code}: #{response.body}"
- end
+ def stream_request(url, payload, &block)
+ client.post(url, body: payload) do |response|
+ raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
- buffer = ""
- response.read_body do |chunk|
- buffer += chunk
+ buffer = ""
+ response.read_body do |chunk|
+ buffer += chunk
- while (message = extract_message(buffer))
- next if message.empty?
+ while (message = extract_message(buffer))
+ next if message.empty?
- json = JSON.parse(message)
- block.call(json)
+ json = JSON.parse(message)
+ block.call(json)
- break if json["done"]
- end
+ break if json["done"]
end
end
end
lib/net/llm/version.rb
@@ -2,6 +2,6 @@
module Net
module Llm
- VERSION = "0.2.0"
+ VERSION = "0.3.0"
end
end
lib/net/llm.rb
@@ -3,9 +3,8 @@
require_relative "llm/version"
require_relative "llm/ollama"
require_relative "llm/anthropic"
-require "net/http"
+require "net/hippie"
require "json"
-require "uri"
module Net
module Llm
@@ -22,52 +21,45 @@ module Net
end
def chat(messages, tools, timeout: DEFAULT_TIMEOUT)
- uri = URI("#{base_url}/chat/completions")
- request = Net::HTTP::Post.new(uri)
- request["Authorization"] = "Bearer #{api_key}"
- request["Content-Type"] = "application/json"
- request.body = { model: model, messages: messages, tools: tools, tool_choice: "auto" }.to_json
-
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = true
- http.open_timeout = timeout
- http.read_timeout = timeout
- http.write_timeout = timeout if http.respond_to?(:write_timeout=)
-
- response = http.start { |h| h.request(request) }
- raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
- JSON.parse(response.body)
+ response = client(timeout).post(
+ "#{base_url}/chat/completions",
+ headers: auth_headers,
+ body: { model: model, messages: messages, tools: tools, tool_choice: "auto" }
+ )
+ handle_response(response)
end
def models(timeout: DEFAULT_TIMEOUT)
- uri = URI("#{base_url}/models")
- request = Net::HTTP::Get.new(uri)
- request["Authorization"] = "Bearer #{api_key}"
-
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = true
- http.open_timeout = timeout
- http.read_timeout = timeout
-
- response = http.start { |h| h.request(request) }
- raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
- JSON.parse(response.body)
+ response = client(timeout).get(
+ "#{base_url}/models",
+ headers: auth_headers
+ )
+ handle_response(response)
end
def embeddings(input, model: "text-embedding-ada-002", timeout: DEFAULT_TIMEOUT)
- uri = URI("#{base_url}/embeddings")
- request = Net::HTTP::Post.new(uri)
- request["Authorization"] = "Bearer #{api_key}"
- request["Content-Type"] = "application/json"
- request.body = { model: model, input: input }.to_json
+ response = client(timeout).post(
+ "#{base_url}/embeddings",
+ headers: auth_headers,
+ body: { model: model, input: input }
+ )
+ handle_response(response)
+ end
+
+ private
- http = Net::HTTP.new(uri.hostname, uri.port)
- http.use_ssl = true
- http.open_timeout = timeout
- http.read_timeout = timeout
- http.write_timeout = timeout if http.respond_to?(:write_timeout=)
+ def client(timeout)
+ Net::Hippie::Client.new(
+ read_timeout: timeout,
+ open_timeout: timeout
+ )
+ end
+
+ def auth_headers
+ { "Authorization" => Net::Hippie.bearer_auth(api_key) }
+ end
- response = http.start { |h| h.request(request) }
+ def handle_response(response)
raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
end
CHANGELOG.md
@@ -1,5 +1,14 @@
## [Unreleased]
+## [0.3.0] - 2025-10-08
+### Changed
+- Refactored all providers to use net-hippie HTTP client
+- Reduced code duplication across providers
+- Improved error handling consistency
+
+### Added
+- Added net-hippie dependency for cleaner HTTP interactions
+
## [0.2.0] - 2025-10-08
- Add Ollama provider with streaming support
Gemfile
@@ -11,3 +11,5 @@ gem "rake", "~> 13.0"
gem "rspec", "~> 3.0"
gem "webmock", "~> 3.25", group: :development
+
+gem "net-hippie", "~> 1.4"
Gemfile.lock
@@ -11,6 +11,7 @@ GEM
specs:
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
+ base64 (0.3.0)
bigdecimal (3.3.0)
cgi (0.4.2)
crack (1.0.0)
@@ -26,8 +27,16 @@ GEM
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.13.2)
+ logger (1.7.0)
+ net-hippie (1.4.0)
+ base64 (~> 0.1)
+ json (~> 2.0)
+ logger (~> 1.0)
+ net-http (~> 0.6)
+ openssl (~> 3.0)
net-http (0.6.0)
uri
+ openssl (3.3.1)
psych (5.2.2)
date
stringio
@@ -65,6 +74,7 @@ PLATFORMS
DEPENDENCIES
irb
+ net-hippie (~> 1.4)
net-llm!
rake (~> 13.0)
rspec (~> 3.0)