▲ 31 r/rails+1 crossposts

RubyLLM::Schema Is Now Schematist: A JSON Schema DSL for Ruby with Full Draft 2020-12 Coverage

I maintain RubyLLM, and one of its dependencies has been quietly useful to people who have nothing to do with LLMs. It was always a clean, general purpose JSON Schema DSL. It was just called RubyLLM::Schema, so unless you already used RubyLLM you'd never find it.

It's now grown to fully cover the latest JSON Schema spec, Draft 2020-12, with no dependencies at all. Which earned it its own name: Schematist.

class Order < Schematist::Schema
  string :kind, enum: %w[personal business]

  given kind: "business" do
    object :tax_details do
      string :vat_number
    end
  end
end

Order.new.to_json_schema
# => { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Order",
#      "type" => "object", "if" => {...}, "then" => {...}, ... }

Eight lines of Ruby, 47 lines of JSON Schema.

What 1.x brings:

  • It emits actual JSON Schema. to_json_schema used to return {name:, description:, schema:, strict:}, which is OpenAI's response_format wrapper with the real schema buried inside it. Now you get a Draft 2020-12 document with string keys that any validator will take.
  • Full Draft 2020-12 coverage. Composition, unevaluated properties and items, patternProperties, propertyNames, prefixItems and open ended tuples, contains, annotations, content encoding, the core $ keywords, and if/then/else branches that hold any schema rather than a fixed list of validations.
  • A schema doesn't have to be an object. A type with a name declares a property, without a name it declares what the schema itself is. So a root, or a define, can be an array, a union, or a bare $ref.
  • Zero runtime dependencies.
  • Values can be procs, resolved when the document is rendered, so one schema class produces a different document per instance. Useful when an enum comes out of the database.

Existing users: there's a final ruby_llm-schema release that depends on Schematist and aliases the old constants, so RubyLLM::Schema keeps resolving while you migrate. to_json_schema changing shape is the one thing to watch, and the README has the migration.

All of this is part of my concerted effort to make Ruby the best language to build with LLMs.

Write-up: https://paolino.me/schematist/

Repo: https://github.com/crmne/schematist

reddit.com
u/crmne — 9 days ago
▲ 113 r/rails+1 crossposts

Solid Queue 1.6.0 now supports fiber workers

I opened PR #728 because Solid Queue’s thread workers were a poor fit for long-running, I/O-bound jobs like LLM streaming.

It shipped today in Solid Queue 1.6.0.

Workers can now use `fibers: N` to run jobs on a single Async reactor thread. Fiber workers require the Async gem and fiber-scoped Rails isolation.

On Active Record 7.2+, Solid Queue’s queue database pool estimate starts at 3 connections per worker process instead of growing with the fiber count. Thread workers still use the `threads + 2` estimate.

I updated my original write-up with the final implementation and configuration. The existing benchmarks use the pre-release PR branch; I’ll rerun them against 1.6.0 over the next few weeks.

https://paolino.me/solid-queue-doesnt-need-a-thread-per-job/

I’d be interested to hear what I/O-heavy workloads people try with it, and which libraries turn out not to cooperate with the fiber scheduler.

u/crmne — 20 days ago
▲ 64 r/rails+1 crossposts

RubyLLM 1.16: concurrent tool execution, Rails-style instrumentation, and more.

When an LLM asks for 3 tools in one turn, RubyLLM used to run them one after another.

1.16 lets you run them concurrently. Video is the same chat both ways: left is the old sequential behavior, right is tool_concurrency = :fibers.

RubyLLM.configure do |config|
  config.tool_concurrency = :fibers  # or :threads, or true
end

:threads or true require no dependencies. :fibers uses the async gem. You can also set it per chat with .with_tools(..., concurrency: if you only want it in some spots. Results stream back into the conversation as each tool finishes instead of all at the end, so the UI fills in live.

Also in this release: Rails-style instrumentation (ActiveSupport::Notifications events you can subscribe to), configurable Faraday adapter, custom base URLs for every provider (proxies/gateways), and a pile of provider fixes.

Full notes: https://github.com/crmne/ruby_llm/releases/tag/1.16.0

u/crmne — 2 months ago
▲ 263 r/hyprland+1 crossposts

hyprmoncfg v1.5.0 is out with support for hyprland 0.55 lua configs.

Just released v1.5.0 with support for hyprland 0.55 lua configs.

It detects whether you're using Lua mode or not, to keep backwards compatibility.

It's available in AUR:

yay -S hyprmoncfg

Also v1.4.0 brought post-apply scripts so you can customize what happens after apply.

https://hyprmoncfg.dev

u/crmne — 3 months ago
▲ 54 r/rails+1 crossposts

I released RubyLLM 1.15 today.

The main theme is: stop making every app write the same glue code.

Image editing now works through the same RubyLLM.paint API:

RubyLLM.paint(
  "Turn the logo green and keep the background transparent",
  model: "gpt-image-1",
  with: "logo.png"
)

Cost tracking is built in now too. RubyLLM already has the token usage, the model, and the pricing in the model registry, so every app should not have to handroll the same math:

response.cost.total
chat.cost.total
agent.cost.total
image.cost.total

Also in 1.15:

  • cleaner token accounting for prompt caching
  • simple tool params inferred from execute(...)
  • additive callbacks like before_message / after_tool_result
  • Rails fixes for Action Text, eager loading, acts_as, and Active Storage blob reuse
  • refreshed model registry with cache/reasoning/image pricing
u/crmne — 3 months ago
▲ 47 r/rails+1 crossposts

I run Chat with Work on Kamal and needed backups. There are Kamal accessories for database backups already, but none also back up Active Storage, none use restic, none ship a CLI with restores and drills, and none produce evidence for a security review.

So I built one. Two pieces: a gem (the CLI) and a Docker image (the accessory). They point at a restic repository you bring yourself.

Docs: https://kamal-backup.dev Source: https://github.com/crmne/kamal-backup

Happy to answer questions.

u/crmne — 4 months ago
▲ 60 r/rails+1 crossposts

Since I wrote about async Ruby and patched Solid Queue to support fibers, people keep asking the same questions. What happens when a fiber blocks? Don’t you still need threads? What about database transactions? What about Ractors?

This post answers all of it. From the ground up.

u/crmne — 4 months ago