Prd/components/backend/wiki/embedder.rb
Lucy Doupalů be9f14ce34 Helpdesk - operator console + patched GrapheneOS Dialer for call handling
A small helpdesk system: an office Pixel running a patched GrapheneOS Dialer
answers technician calls, records both call legs as separate channels, and a
Ruby backend transcribes them through Whisper and files an AI summary against
the caller.

Squashed to a single commit for sharing. No credentials are included; secrets
live outside the repo in /etc/helpdesk/env on the server or a gitignored
.claude/env.local locally. See .claude/env.local.example for the shape.

Start at README.md, then docs/architecture.md.
2026-07-27 18:50:32 +02:00

50 lines
1.7 KiB
Ruby

# frozen_string_literal: true
# Embedder — turns text into vectors via OpenRouter's /embeddings endpoint (OpenAI-compatible),
# reusing the same key + HTTP/auth pattern as the summariser. Standard library only. The HTTP transport is
# injectable so the RAG index/retrieval is unit-testable offline (no network/key).
require "json"
require "net/http"
require "uri"
module Helpdesk
class Embedder
MODEL = "openai/text-embedding-3-small" # 1536-dim, ~$0.02/M tokens, handles Czech
URL = "https://openrouter.ai/api/v1/embeddings"
def initialize(api_key: ENV["OPENROUTER_API_KEY"], model: MODEL, transport: nil)
@api_key = api_key
@model = model
@transport = transport # ->(request_body_json) { response_body_json } — for tests
end
# texts: String | Array<String> → Array<Array<Float>> (order preserved). Raises on API error.
def embed(texts)
arr = texts.is_a?(Array) ? texts : [texts]
return [] if arr.empty?
body = JSON.generate(model: @model, input: arr)
raw = @transport ? @transport.call(body) : post(body)
data = JSON.parse(raw)
raise "embedding error: #{data["error"]}" if data["error"]
data.fetch("data").sort_by { |d| d["index"] }.map { |d| d.fetch("embedding") }
end
def embed_one(text) = embed(text).first
private
def post(body)
uri = URI(URL)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 15
http.read_timeout = 60
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{@api_key}"
req["Content-Type"] = "application/json"
req.body = body
http.request(req).body
end
end
end