Dynamic requests and reusable workflows

September 18, 2026 ยท View on GitHub

The workflow stores answer types, options, and scoring criteria. Its instructions are optional default questions. Each call supplies fresh context or conversation history and may replace the question instructions. This does not change model weights, candidate scoring, parallel branching, or optimization settings.

Try it interactively

From the project directory in PowerShell, using the installed local environment:

$env:HF_HOME = Join-Path (Get-Location) '.cache/decision-engine/huggingface'
.\.cache\decision-engine\venv\Scripts\python.exe -m jevify --interactive --device cuda --local-files-only

The model loads once, then runs a synthetic initialization call before displaying Model ready. This moves first-use runtime overhead into startup; it does not cache answers or train the model. --no-warmup skips it. Paste context across multiple lines, type /run on a line by itself, then enter a prompt for each configured question. Press Enter to use that question's saved default. Type /quit to exit. There is no automatic history: each new request starts fresh. Paste the complete history you want evaluated. --output outputs/result.json saves the latest result, overwriting the prior one.

For the routing rubric (which deliberately has no fixed question):

.\.cache\decision-engine\venv\Scripts\python.exe -m jevify --workflow examples/decision_engine/router.workflow.json --interactive --device cuda --local-files-only

Use a prompt such as Which model should handle the next response?. The example fast/reasoning/coding labels describe hypothetical capabilities; replace these with your own model names and descriptions. This selects a label, not a downstream model invocation, and the sample is not a validated routing policy.

One request from the command line

.\.cache\decision-engine\venv\Scripts\python.exe -m jevify --workflow examples/decision_engine/router.workflow.json --prompt "Which model should answer this?" --context "Write and debug a Python CSV parser." --device cuda --local-files-only

Use --context-file conversation.txt for a multiline UTF-8 text file. Each CLI invocation reloads the model; use interactive mode or the Python API for reuse.

A request JSON can contain context, prompt, prompts, and messages only:

{
  "prompt": "Which model should handle the next response?",
  "context": "Prefer the least expensive model capable of the task.",
  "messages": [
    {"role": "user", "content": "Please fix this Python traceback..."}
  ]
}

Pass it with --workflow examples/decision_engine/router.workflow.json --request examples/decision_engine/router.request.json. The request supplies new input; no context is inherited from the workflow. prompt applies to a single-question workflow. With multiple questions, use prompts keyed by their workflow names:

{
  "context": "I was charged twice. Please refund the extra payment.",
  "prompts": {
    "department": "Which team should investigate this message?",
    "refund_requested": "Is a refund explicitly requested?"
  }
}

Unspecified questions use their saved instructions. Missing required prompts, unknown question names, and invalid schemas fail before loading the model. Overrides replace instructions rather than append to them; keep stable grading criteria in criteria. All configured questions still run. The engine does not infer extra branches from free-form prose.

Use in an application, with one resident model

from jevify import Workflow
from jevify.engine import DecisionEngine

# Once at application startup. Use your existing engine settings here.
engine = DecisionEngine.from_pretrained(device="cuda", local_files_only=True)
engine.warmup()  # Once at startup, before accepting requests.
workflow = Workflow.load("examples/decision_engine/router.workflow.json")

def route(conversation, context="", prompt="Which model should handle the next response?"):
    result = workflow.run(
        engine,
        prompt=prompt,
        context=context,
        messages=conversation,
    )
    return result["answers"]["model"]

answer = route([
    {"role": "user", "content": "Please implement a Python CSV parser."}
])
print(answer["choice"], answer["probabilities"])

messages accepts ordered text-only {role, content} objects, with roles system, developer, user, assistant, or tool. They are serialized as context data for the routing decision, not executed as the engine's chat instructions. No multimodal content, tool-call structures, automatic history storage, or truncation is added. Serialization is not a security boundary against prompt injection. The normal input-token limit applies to context, history, question, and rubric together.

The API calls the same engine.decide path. Keep optimization flags appropriate to your platform; dynamic inputs may change shapes and require new graph captures. The engine refreshes request data, and no previous answer or conversation is reused. No network server, OpenAI endpoint, or Ollama dependency is involved.

Existing requests

--input examples/decision_engine/refund.json still accepts the original combined context/questions format. A pre-existing combined workflow.json still runs without extra flags. The supplied default workflow.json now contains only the reusable refund rubric, so supply dynamic inputs or choose --interactive. The lower-level engine.decide(context, questions) API remains unchanged.

Prompt format and timing

Choice and Score present option names and descriptions, JSON-escaped to preserve boundaries, and score their A/B/C IDs. Noul still uses false/true. The default is --answer-encoding letters; literal labels are available explicitly with --answer-encoding labels. --prompt-format delimited is the default and separates input text from evaluation instructions automatically. --prompt-format readable preserves the earlier layout; --prompt-format json selects the older JSON catalog. Neither changes the answer encoding. See literal-label scoring for multi-token labels and latency implications. Changing answer encoding changes the model task; it can change probabilities substantially and does not guarantee better accuracy.

--cuda-graphs remains opt-in. It can reduce repeated matching-shape latency on Windows too, but the first new shape still pays capture cost. Startup warmup avoids capturing a synthetic graph and does not pre-capture unknown user request shapes. One-shot runs skip warmup by default; --warmup enables it explicitly. Loading, initialization, and cold graph capture are separate from steady inference latency.