ajq

Filter JSON by meaning and route JSONL by intent.

Use explicit semantic predicates when jq knows the structure and the wording varies,
with ordinary jq execution remaining deterministic.

# fuzzy JSON filter: keep jq deterministic, make only the predicate semantic
cat tickets.json | ajq --backend local '.[] | select(.msg =~ "angry/frustrated") | .id'

Install a release binary, or fall back to Go source:

curl -fsSL https://raw.githubusercontent.com/ricardocabral/ajq/main/scripts/install.sh | sh
go install github.com/ricardocabral/ajq/cmd/ajq@latest

One Go binary · local + cloud backends · persistent cache

ajq brings semantic matching and bounded classification to JSON streams, using the jq language you already know. It runs the ordinary parts of your query through a real jq engine and only calls a language model for explicit fuzzy operators, one small field value at a time.

So most of your pipeline stays byte-for-byte reproducible, and what you pay tracks how many fuzzy decisions you actually make, not how big your input is.

Deterministic core

Real jq semantics, powered by gojq. Most of every pipeline is byte-reproducible: the same input gives you the same bytes on every run. Pure jq paths never contact AI backends; only explicit semantic operators reach for a model.

Semantic operators

select(.x =~ "spec") and sem_classify are jq-shaped operators for fuzzy filters and labels. sem_score and sem_norm are available only in supported contexts, while standalone sem_extract and sem_redact are registered but unsupported. There is no new grammar to learn, so semantic predicates compose with ordinary jq pipelines.

Local-first and cheap

A provisioned ~1 GB local model runs fuzzy decisions on your machine via --backend local, while --cloud selects the shipped Anthropic backend. Identical field values get deduplicated and written to the persistent judgement cache, so a second run can replay repeated decisions without another model call.

How split execution works

A real ajq query is mostly plain jq. Only the explicit fuzzy operator needs a model:

cat data.json | ajq --backend local '.users[] | select(.feedback =~ "angry/frustrated") | .id'
                      └────────┬───────┘  └────────────┬────────────┘  └┬┘
                      deterministic path       semantic predicate       proj
                      (pure gojq)              (LLM, per value)          (pure gojq)

ajq parses your query, runs everything deterministic through jq in process, and only calls the model on the smallest slices of data it can get away with. Because the model sees one field value at a time, the context stays tiny and a 1.5B model is plenty. There’s more in Split execution.

How ajq compares

ajqllm-jqjq / awkAd-hoc LLM scriptgrep / ripgrep
Fuzzy, semantic matchingYesPrompted jqNoYesNo
Deterministic, byte-reproducible coreYesAfter generationYesNoYes
Structured JSON in and outYesYesjq onlyDIYLines only
Cost tracks fuzzy decisions, not input sizeYesOne prompt + samplen/aNo, per rown/a
Runs locally, no API keyLocal backendDepends on LLMYesUsually cloudYes
Structural output guarantee (schema, enum)GBNF-constrainedGenerated jqExactPrompt-hopeExact
One-line pipeline ergonomicsYesYesYesNoYes

Here’s the gap ajq fills. grep, awk, and jq are deterministic but literal. llm-jq uses an LLM to write a jq program from your prompt, then runs that program over the data. A hand-rolled LLM script can be fuzzy, but it pays per row, drifts over time, and leaves you to plumb the JSON yourself. ajq keeps jq’s ergonomics and adds explicit fuzzy JSON filters and classification, deduplicated and cached.

Performance

Local-model latency depends on the model, runtime, hardware, and repeated-value ratio. These are medians from five independent cold-start runs; see the raw reports before comparing them with your own workload.

~611ms
median cold start, including model load and first readiness check
~86ms
median warm latency for one fuzzy judgement
~11.9/s
median sequential throughput for 8 distinct judgements
~12.8/s
median bounded-parallel throughput (4 local slots)
~14.7µs
median cached replay of the same judgement batch
Reference run: Apple M3 Pro (Metal), llama.cpp 9910, Qwen2.5-1.5B Q5_K_M (SHA-256 b4666107…5896f8c), 64-record sem_match array with 8 post-dedup judgements. Cold start ranged from 611–916 ms; warm latency from 84–95 ms. These are workload-specific measurements, not a product guarantee. Inspect the five raw JSON reports.

It’s open source

ajq is MIT-licensed and built in the open. Issues, ideas, and pull requests are all welcome.

Read more

Read the docs

Walkthroughs, task recipes, reference pages, and design notes for ajq.

Read more

Install it now

Install with the release script or go install, then run ajq provision to fetch the default model and locate a llama-server engine — no API key required for local work.

Read more