cd /news/ai-tools/jeb-turn-any-openai-api-into-a-decis… · home › topics › ai-tools › article
[ARTICLE · art-141713] src=github.com ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Jeb: Turn any OpenAI API into a decision model

Developer chand1012 released Jeb, an open-source tool that turns any OpenAI-compatible chat completions endpoint into a decision model by reading token log probabilities. Jeb accepts a shared `state` string plus Choice, Score, or Noul questions at `POST /v1/systemone`, sends each question as a separate chat completion request, and returns structured JSON with probabilities and a confidence value. The tool requires a provider that returns `choices[0].logprobs.content[0].top_logprobs` and accepts the fields `model`, `messages`, `logprobs`, `top_logprobs`, and `max_tokens`, and it is inspired by a NobodyWho article and TypeSafe's Jev decision model.

read8 min views5 publishedSep 29, 2026
Jeb: Turn any OpenAI API into a decision model
Image: Michielbdejong (auto-discovered)

Jev-compatible decisions from an OpenAI-compatible LLM with logprobs.

Quick start · Question types · HTTP API · Examples

Send Jeb a state and one or more Choice, Score, or Noul questions. It prompts the configured model, reads its token log probabilities, and returns structured JSON at POST /v1/systemone. The same request format works through the CLI.

Inspired by this NobodyWho article. See TypeSafe's introduction to Jev for the original decision model and its three primitives.

  • An OpenAI-compatible POST /v1/chat/completions endpoint and a model that returnschoices[0].logprobs.content[0].top_logprobs when asked forlogprobs .
  • A provider that accepts the request fields Jeb sends: model ,messages ,logprobs ,top_logprobs , andmax_tokens . Jeb also sendsreasoning_effort unless it is configured as an empty string, and sendstemperature when set.

An endpoint can be OpenAI-compatible for ordinary chat while lacking the token log probabilities Jeb needs. Check that capability before using a provider.

curl -fsSL https://raw.githubusercontent.com/chand1012/jeb/main/install.sh -o install.sh
sh install.sh # installs to ~/.local/bin by default

Download the binary from releases

Install the latest version from source with Go:

go install github.com/chand1012/jeb@latest

To install the code in your current checkout instead, run this from the repository root:

go install .

Install Jeb . 2. If you use another OpenAI-compatible provider, set its base URL and model name as described in Configuration .

export OPENAI_MODEL=qwen3.5:9b # or another model you have available

Evaluate an example request:

jeb < examples/choice.json

The CLI reads one JSON request from stdin and writes one JSON response to stdout. Model calls may vary between runs. Start with the mixed example to exercise all three question types:

jeb < examples/mixed.json

The request contains a shared state string and a questions object. Each key in questions becomes a key in the response's answers object. Jeb sends each question as a separate chat completion request against the same state.

{
  "state": "The battery is at 3%. A firmware update requires at least 30%.",
  "questions": {
    "start_update": {
      "type": "noul",
      "instructions": "Should the update start now?"
    }
  }
}

Jeb supports these question types:

Type criteria Returned fields
choice Object mapping option names to descriptions type ,choice ,probabilities ,confidence
score Ordered array of level descriptions type ,score ,legend ,probabilities ,confidence
noul Optional object with true andfalse descriptions type ,noul

The object keys name the options. Write each description to explain when that option fits; Jeb builds the model prompt and handles option numbering for you.

{
  "type": "choice",
  "instructions": "Choose the safer venue under the forecast.",
  "criteria": {
    "Outdoor": "Fits everyone but provides no shelter from heavy rain.",
    "Indoor": "Provides shelter but requires limiting attendance."
  }
}

The answer contains the selected option, probabilities, and a confidence value. Probability keys are zero-based numeric option labels assigned in alphabetical order of option name.

Put rubric levels in order from low to high. Jeb returns a legend that maps level numbers back to their descriptions. Levels start at 0, so a three-level rubric has labels 0, 1, and 2. The numeric score is a weighted average of those indices and can fall between levels.

{
  "type": "score",
  "instructions": "Rate the weather risk for an outdoor event.",
  "criteria": ["Low risk", "Moderate risk", "High risk"]
}

The answer also includes a probability for each zero-based level and a confidence value.

A Noul answer is a number from 0 to 1 representing the model's reported probability for Yes. You may omit criteria, or supply descriptions for true and false:

{
  "type": "noul",
  "instructions": "Should the refund be approved under the policy?",
  "criteria": {
    "true": "The request is within 30 days and the item is unused.",
    "false": "The request is late or the item has been used."
  }
}

Jeb uses the first token's Yes log probability when present. If only No is present and the model answered No, it returns 1 - P(No). It returns an error when it cannot derive either value. Noul does not return a separate confidence field.

The response contains the configured model name, one answer per named question, and summed upstream token usage:

{
  "model": "qwen3.5:9b",
  "answers": {
    "start_update": {
      "type": "noul",
      "noul": 0.02
    }
  },
  "usage": {
    "input_tokens": 87,
    "output_tokens": 1
  }
}

The numbers above illustrate the shape; they are not the result of a measured model run. See all example requests.

Start the server with the same provider settings used by the CLI:

jeb serve --host 127.0.0.1 --port 6102

Then send a JSON request:

curl --fail-with-body \
  -H 'Content-Type: application/json' \
  --data-binary @examples/mixed.json \
  http://127.0.0.1:6102/v1/systemone

The endpoint accepts POST only. Invalid JSON receives HTTP 400; method mismatches receive 405; processing and upstream errors currently receive 502. The server logs method, path, status, duration, and remote address. It does not provide authentication or TLS, and its default host is 0.0.0.0; bind it to 127.0.0.1 for local use or place access controls in front of it.

Jeb reads optional config.yaml from the working directory, ./config/, or ~/.config/jeb/. Set JEB_CONFIG_FILE to use a specific path. Copy config.example.yaml as a starting point. The precedence is explicit CLI flags > environment variables > config file > built-in defaults.

Setting Environment variable Default Purpose
server.host JEB_HOST 0.0.0.0 HTTP listen address
server.port JEB_PORT 6102 HTTP listen port
openai.base_url OPENAI_BASE_URL http://localhost:11434/v1 Provider API base URL
openai.api_key OPENAI_API_KEY Empty Bearer token, if needed
openai.model OPENAI_MODEL qwen3.5:9b Upstream model ID
openai.max_tokens OPENAI_MAX_TOKENS 10 Maximum generated tokens per question
openai.reasoning_effort OPENAI_REASONING_EFFORT none Optional provider hint
openai.temperature OPENAI_TEMPERATURE Unset Optional sampling temperature
openai.timeout OPENAI_TIMEOUT 60s Timeout per completion request
openai.max_retries OPENAI_MAX_RETRIES 3 Configured value; retries are not implemented yet
concurrency.max_requests JEB_MAX_REQUESTS 1 Maximum simultaneous model requests

For providers that reject reasoning_effort, set openai.reasoning_effort: "" in your YAML config. For providers that need an API key, supply it through an environment variable or a local config file kept out of version control. The current .gitignore does not exclude config.yaml.

CLI flags include --base-url, --api-key, --model (-m), --max-tokens, --reasoning-effort, --timeout, --max-retries, and --max-requests. serve also accepts --host (-H) and --port (-p). Run jeb --help or jeb serve --help for the full flag list.

Build and run the image locally:

docker build -t jeb .
docker run --rm -p 127.0.0.1:6102:6102 \
  --env OPENAI_BASE_URL \
  --env OPENAI_MODEL \
  --env OPENAI_API_KEY \
  jeb

Set those variables in your shell first. A container cannot use its own localhost to reach a provider on the host; configure a provider URL reachable from inside the container.

GitHub Actions builds Linux amd64 and arm64 images for GHCR on pushes to main and v* tags. It also builds Linux, macOS, and Windows binaries for amd64 and arm64. Tagged builds publish archives and checksums.txt to a GitHub release. Binaries and images record the version tag (or dev), commit SHA, and UTC build date; inspect a binary with jeb version.

For each question, Jeb sends a system prompt and a user prompt containing the state, instructions, and numbered options. It asks the provider for one short answer and top log probabilities for the first generated token. Choice and Score normalize the probabilities for the recognized numeric labels; Score then calculates a weighted average. Noul derives the chance of Yes from its reported token probability. The configured concurrency limit controls how many question requests are in flight at once.

These values depend on the provider's tokenizer, token ranking, and top_logprobs cap. If a valid option is absent from the returned top tokens, the normalized distribution is incomplete and may be misleading. Jeb currently does not calibrate those probabilities against outcome data. For decisions with real consequences, evaluate the chosen model on your own labeled cases and keep application rules or human review in control of the final action.

  • Missing token log probabilities: Confirm the provider supports bothlogprobs andtop_logprobs on chat completions for the selected model.
  • Unexpected option or low confidence: Inspect the provider's first token and its top log probabilities. Choice and Score expect one numeric option label; Noul expectsYes orNo .
  • Provider rejects a request field: Check its support forreasoning_effort ,temperature ,top_logprobs , andmax_tokens . Configure an empty reasoning effort in YAML when that field is unsupported.
  • The container cannot reach a local provider: Replacelocalhost inOPENAI_BASE_URL with an address reachable from the container.
  • HTTP 502: The handler uses 502 for processing failures as well as upstream failures. Check the response body and server logs.
go test ./...
go vet ./...
go build ./...

The Justfile provides just build, just serve, and other local tasks. There are currently no Go test files; the CI checks compile packages and run go vet. The request examples in examples/ provide manual integration cases for a compatible provider.

── more in #ai-tools 4 stories · sorted by recency
── more on @jeb 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/jeb-turn-any-openai-…] indexed:0 read:8min 2026-09-29 · —