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.
50 lines
1.7 KiB
Ruby
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
|