Commit 6a2beaa

mo khan <mo@mokhan.ca>
2026-01-20 22:41:08
refactor: cleanup net code
1 parent fa0926e
Changed files (1)
lib
lib/elelem/ollama.rb
@@ -4,17 +4,17 @@ module Elelem
   module Net
     class Ollama
       def initialize(model:, host: "localhost:11434", http: Elelem::Net.http)
-        @url = "#{host.start_with?('http') ? host : "http://#{host}"}/api/chat"
-        @model, @http = model, http
+        @url = normalize_url(host)
+        @model = model
+        @http = http
       end
 
       def fetch(messages, tools = [], &block)
         tool_calls = []
+        body = build_request_body(messages, tools)
 
-        stream({ model: @model, messages:, tools:, stream: true }) do |json|
-          msg = json["message"] || {}
-          block.call(content: msg["content"], thinking: msg["thinking"]) unless json["done"]
-          tool_calls.concat(parse_tools(msg["tool_calls"])) if msg["tool_calls"]
+        stream(body) do |event|
+          handle_event(event, tool_calls, &block)
         end
 
         tool_calls
@@ -22,20 +22,49 @@ module Elelem
 
       private
 
-      def stream(body, &block)
-        @http.post(@url, body:) do |res|
-          raise "HTTP #{res.code}: #{res.body}" unless res.is_a?(::Net::HTTPSuccess)
-          buf = ""
-          res.read_body do |chunk|
-            buf += chunk
-            while (i = buf.index("\n"))
-              block.call(JSON.parse(buf.slice!(0, i + 1)))
-            end
+      def normalize_url(host)
+        base = host.start_with?("http") ? host : "http://#{host}"
+        "#{base}/api/chat"
+      end
+
+      def build_request_body(messages, tools)
+        { model: @model, messages:, tools:, stream: true }
+      end
+
+      def handle_event(event, tool_calls, &block)
+        message = event["message"] || {}
+
+        unless event["done"]
+          block.call(content: message["content"], thinking: message["thinking"])
+        end
+
+        if message["tool_calls"]
+          tool_calls.concat(parse_tool_calls(message["tool_calls"]))
+        end
+      end
+
+      def stream(body)
+        @http.post(@url, body:) do |response|
+          raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(::Net::HTTPSuccess)
+
+          read_ndjson_stream(response) { |event| yield event }
+        end
+      end
+
+      def read_ndjson_stream(response)
+        buffer = String.new
+
+        response.read_body do |chunk|
+          buffer << chunk
+
+          while (index = buffer.index("\n"))
+            line = buffer.slice!(0, index + 1)
+            yield JSON.parse(line)
           end
         end
       end
 
-      def parse_tools(tool_calls)
+      def parse_tool_calls(tool_calls)
         tool_calls.map do |tool_call|
           {
             id: tool_call["id"],