AWS Builder Center

Classifying Images with Strands Decider 2B Decision Model

Classify images with Strands Decider, a 2B multimodal decision model. A security camera example covers setup, code, and accuracy and cost vs. Bedrock LLMs.

Classifying images without generating text

Many applications that need visual understanding send an image to a model and only need a short, fixed answer back. A loan or claims intake system checks whether the signature block on an uploaded form is filled in. An insurance app sorts claim photos by what is damaged: roof, window, siding, or nothing. A camera on a factory line grades each part's defect as none, cosmetic, or functional. A security monitoring system asks whether the fire exit in a camera frame is blocked. In each case the answer is yes or no, one option from a list, or a level on a scale, and software acts on it: it routes the claim, rejects the part, or alerts an operator.
Today there are two common ways to answer these questions:
  • A vision LLM. It works with no training, but it writes text. You pay for output tokens, parse the answer, and handle the cases where the format is wrong.
  • A trained image classifier. It is fast and cheap to run, but each new label needs its own labeled data and training.
A decision model sits between the two. Like an LLM, it takes a question in plain language and needs no training for a new question. Like a classifier, it returns a fixed answer with a probability, and it never generates text. That gives you an LLM's flexibility at a lower cost per answer: a 2B model needs far less compute than a large vision LLM and generates no output tokens, whether it runs on your own hardware or on a pay-per-token service.
This article introduces Strands Decider, an open-source decision model that reads images, and tests it on one example: frames from security cameras. On a hand-labeled set of camera stills it scored 85.2% with no fine-tuning, against 88.0% for the best vision LLMs on Amazon Bedrock.

What a decision model is

 Strands Decider  is an open-source model from the Strands Agents project. It picks from a fixed set of answers and returns a probability for each one. It supports three question types:
Type
Returns
Example
 noul 
Probability of "yes"
"Is the signature block filled in?"
 choice 
One option from a named set, with a probability for each
"What is damaged? roof, window, siding, none"
 score 
A level on an ordered scale
"How severe is the defect? none, cosmetic, functional"
The model is Qwen3.5-2B with its text output layer replaced by a small scoring head (about 1M parameters). All questions about one input are answered in a single forward pass.
The launch blog covers text only. The repo's  docs/vision.md  documents image input: load the model with vision enabled and it keeps Qwen3.5's own vision encoder (about 331M parameters). The decider was never trained on images, and it answers image questions with no retraining. On public image benchmarks the current release scores 0.776 on NaturalBench and 0.872 on POPE-adversarial.

Where it fits

A decision model fits any application where the input is an image and the output is a fixed answer. The table marks which examples I measured in the security camera test below and which are untested ideas.
Use case
Example question
Type
Tested?
Parking lot occupancy
"Is the lot more than half full?"
 noul 
Yes: 89%
Service vehicles
"Is a delivery or service vehicle at the dock?"
 noul 
Yes: 95%
Retail shelves and aisles
"Is there clutter blocking the aisle?"
 noul 
Yes: 100%
Workplace safety
"Is anyone at height without a harness?"
 noul 
Partly: broad safety questions scored about 77%
Document intake
"Is the signature block filled in?" (repo README)
 noul 
No
Screens for UI agents
"Did the payment succeed?" (repo  docs/vision.md )
 noul ,  choice 
No
Insurance or field-service photos
"What is damaged? roof, window, siding, none"
 choice 
No
Manufacturing inspection
"How severe is the defect? none, cosmetic, functional"
 score 
No
Image moderation
"Does the image break the content policy?"
 noul 
No
The tested categories are small samples, so treat them as signs, not measurements. The pattern was consistent: concrete, visible questions scored well, and questions that need judgment scored worst.

Example: security camera motion frames

A security camera with motion detection sends a frame each time something moves. Most of those frames show nothing of interest: a shadow, a tree, a car driving past. An operator still needs an answer to a fixed question for each frame, such as "Is the fire exit blocked?" or "Is anyone on the roof?" Sending every frame to a vision LLM means paying for a full LLM call on each one, including the empty ones.
On the labeled camera stills, Strands Decider 2B scored 85.2% on its own at a tuned cutoff, in about 1 second per frame on a laptop GPU. Gemma 4 31B and GPT-6 Luna on Amazon Bedrock scored 88.0%. Used as a filter in front of an LLM, the decider removed 7-8% of LLM calls with no accuracy loss, or about a third of calls for a 2-3 point loss, and every error it added was a missed event. On cost, a self-hosted decider costs less per frame than Gemma 4 on Bedrock only if one GPU instance handles about 3 or more frames per second, around the clock.
The rest of this article builds the pipeline for this example, then compares the results with the LLMs.

Architecture


Strands Decider Architecture

Each camera has one or more fixed questions. When motion triggers a frame, the frame and its questions go to the decider in one request. The decider returns one probability per question, and your code compares each one to a cutoff.
The server has no authentication, so if other hosts need to call it, keep it in a private subnet behind your own access controls.

Install

python3.12 -m venv .venv
source .venv/bin/activate
pip install "strands-decider[vision]==0.3.0"
The first call downloads the model from Hugging Face. On my machine, the download and first load took about 3 minutes. Later loads take seconds.
Test it on one frame:
strands-decider ask StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 \
--image frame.jpg --state "Security camera image." \
--noul "Is the fire exit blocked?"
Versions I used: checkpoint  StrandsAgents/strands-decider-2B-qwen3.5-v1-2610  (released 10/9/26), Python 3.12, transformers 5.19.0, torch 2.14.1. Vision needs transformers 5.18 or later.

Hardware requirements

Strands Decider is not currently available on Amazon Bedrock (as of November 2026), so for now you host it yourself and pay for the instance by the hour, whether frames arrive or not.

GPU
CPU only
Model weights
about 4.7 GB (bf16)
about 9.4 GB (fp32)
Memory
8 GB GPU memory
16 GB RAM (32 GB safer)
Speed
0.5-0.9 s per frame (measured on Apple M3 Pro)
several seconds per frame (not measured)
The memory figures are my estimates from the parameter count. The repo does not list them.
Instance (us-east-1, on demand)
Hardware
$/hour
$/month, always on
Use it for
m7i.2xlarge
8 vCPU, 32 GB RAM
$0.403
about $294
Testing only
g4dn.xlarge
NVIDIA T4, 16 GB
$0.526
about $384
Lowest-cost GPU that fits. No native bf16, so expect slower runs.
g6.xlarge
NVIDIA L4, 24 GB
$0.805
about $588
Recommended minimum for production. The repo's vision benchmarks ran on an L4.
The repo also has deploy guides for Amazon SageMaker (a real-time endpoint,  ml.g6.xlarge  by default) and AWS Lambda. The repo's Lambda deployment runs a text-only build of the model without the vision encoder, so it refuses requests with images and does not work for this use case as published.

Calling the API

Option 1: HTTP server

strands-decider serve StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 --vision --port 8000
Request ( POST /v1/systemone ):
{
"state": "Security camera image.",
"images": ["<base64 JPEG>"],
"questions": {
"exit_blocked": {"type": "noul", "instructions": "Is the fire exit blocked?"},
"person_on_roof": {"type": "noul", "instructions": "Is anyone on the roof?"}
}
}
Response:
{
"answers": {
"exit_blocked": {"type": "noul", "noul": 0.1153},
"person_on_roof": {"type": "noul", "noul": 0.0412}
},
"usage": {"input_tokens": 0, "output_tokens": 0}
}
The  state  field is context text and can be empty. The  images  field takes up to 4 base64 images in PNG, JPEG, WebP, or GIF format. Images, text, and questions share a 4,096-token window. The server shrinks each image so its longer side is at most 448 px, and that setting is fixed in the server.

Option 2: Python, with a larger image size

In my tests, 768 px images were more accurate than 448 px at every cutoff (85.2% against 76.1% at the 0.25 cutoff). To set the size, use the Python API:
import base64
from strands_decider.infer import EngineConfig
from strands_decider.schema import NoulQuestion
from strands_decider.vision import load_vision_engine

engine = load_vision_engine(
"StrandsAgents/strands-decider-2B-qwen3.5-v1-2610",
EngineConfig(device="cuda"), # "mps" on a Mac, "cpu" otherwise
image_long_side=768,
)

with open("frame.jpg", "rb") as f:
img = base64.b64encode(f.read()).decode("ascii")

resp = engine.ask_images(
"Security camera image.",
{"q": NoulQuestion(instructions="Is the fire exit blocked?")},
[img],
)
p_yes = resp.answers["q"].noul
Speed on the M3 Pro GPU, one frame at a time: median 0.46 s at 448 px and 0.89 s at 768 px.

Parsing results

A  noul  answer is a probability, not a yes or no. You choose the cutoff that turns it into a decision.
CUTOFF = 0.25 # tune this on your own labeled frames

def decide(resp, image_present: bool) -> dict:
if not image_present:
raise ValueError("no image in request; the decider will still answer")
return {
name: {"yes": ans.noul >= CUTOFF, "p_yes": ans.noul}
for name, ans in resp.answers.items()
}
Three rules from my tests:
  1. Do not use 0.5 as the cutoff. At 0.5 the decider leaned hard toward "no" and missed 43% of actual events. On my data the useful range was 0.2 to 0.3.
  1. Tune the cutoff on labeled frames from your own cameras, then check it on a separate set. A cutoff tuned on the test set looks better than it will on new frames.
  1. Check that every request has an image. With the image removed, the decider's accuracy falls to chance, but it still answers with high confidence (the repo reports 0.71 to 0.83 on average). Its confidence does not tell you the image is missing.

Results: the decider against vision LLMs on Amazon Bedrock

How I tested

The test set was a hand-labeled set of security camera stills from working cameras and public sources, including still frames I pulled from videos in the  CCTV Action Recognition Dataset  on Kaggle, 57% "yes" and 43% "no", across dozens of different yes/no questions. Each LLM got the same frame and question and returned  {"result": true|false, "description": "..."} . I counted answers I could not parse as wrong, so every model is scored over every frame. The decider ran through the Python API above, with  "Security camera image."  as context, at 448 px and 768 px.

Accuracy

Model
Where it runs
Accuracy
Gemma 4 31B
Amazon Bedrock
88.0%
GPT-6 Luna, full detail
Amazon Bedrock
88.0%
GPT-6 Luna,  detail=low 
Amazon Bedrock
86.1%
GPT-6 Luna,  detail=low , no reasoning
Amazon Bedrock
85.2%
Strands Decider 2B, 768 px, cutoff 0.25
Self-hosted
85.2%
Claude Haiku 5.5
Amazon Bedrock
78.0% (8% of answers unparsed)
Amazon Nova 2 Lite, no reasoning
Amazon Bedrock
77.0%
Strands Decider 2B, 768 px, cutoff 0.5 (default)
Self-hosted
74.6%
I picked the 0.25 cutoff by looking at these same answers and have not yet tested it on a separate set. The peak is narrow (84.7% at 0.24, 83.3% at 0.23 and 0.26), so expect about 83% on new frames.

The decider as a filter in front of an LLM

In security monitoring, a yes/no answer is often not enough on its own. When something does happen, the operator who gets the alert needs a short description, such as "a pallet is blocking the lower half of the exit door", to decide what to do without opening the video. A decision model cannot write that description, but a vision LLM can. Most frames are "no" and need no description, so the decider and the LLM can split the work.

Decision Model and LLM

The decider sees every frame first. Below the cutoff, the answer is "no" and no LLM call happens. Otherwise the frame goes to the LLM, which confirms the answer and writes the description. You pay for an LLM call only on frames that might show an event.
# Sketch: call_llm stands for your existing Bedrock call.
def answer(frame, question, cutoff=0.1):
p_yes = decider_yes_probability(frame, question) # see "Calling the API"
if p_yes < cutoff:
return {"result": False, "description": ""} # no LLM call
return call_llm(frame, question)
I simulated this with the stored LLM answers. Each LLM call is independent, so the filter changes which frames reach the LLM, not how the LLM answers them.
In front of Gemma 4 31B, with the 768 px decider:
Cutoff
Sent to LLM
Accuracy
Change
LLM cost saved
Events missed
0.1
92%
88.0%
0.0
7%
0%
0.15
79%
86.6%
-1.4
19%
4%
0.2
66%
85.2%
-2.9
32%
10%
0.3
49%
81.8%
-6.2
49%
23%
GPT-6 Luna gave almost the same results: 8% of calls saved with no loss at 0.1, and 34% saved for a 1.9 point loss at 0.2.
The weaker LLMs gained accuracy at a 0.2 cutoff with the 448 px decider. Nova 2 Lite rose 2.4 points, because the filter removed almost twice as many false alarms as it added missed events. Claude Haiku 5.5 rose 0.5 points: the filter fixed some false alarms and some answers I could not parse, and added slightly fewer missed events than that. The filter can only change a "yes" to a "no", so it helps when the LLM raises false alarms and hurts when the LLM was right about an event.

Cost per frame

From the token counts in each run, at on-demand Standard prices in us-east-1 (November 2026). All rows are Amazon Bedrock except the OpenAI Decisions API row.
Model
Input tokens per frame
Cost per frame
GPT-6 Luna, OpenAI Decisions API (simulated, see below)
498
$0.000050
Gemma 4 31B
496
$0.000080
GPT-6 Luna,  detail=low 
493
$0.000127
GPT-6 Luna, full detail
1,450
$0.000225
Amazon Nova 2 Lite
461
$0.000243
Claude Haiku 5.5
1,469
$0.000292
Gemma 4 on Bedrock bills a flat 266 image tokens per image at every size I tested, from 64x36 to 1920x1080. Shrinking frames does not lower its cost.
GPT-6 Luna through the OpenAI Decisions API (simulated). OpenAI offers a  Decisions API  for GPT-6 Luna directly through the OpenAI API. It is not currently available on Amazon Bedrock. Like Strands Decider, it returns a probability, a choice from a fixed set, or a score instead of text, and it accepts images. OpenAI bills only input tokens ($0.10 per 1M uncached) and does not charge for output tokens. I did not call it. Instead, I simulated it with my GPT-6 Luna run on Bedrock at  detail=low  with reasoning off, priced the input tokens at the Decisions API rate, and dropped the output cost: $0.000050 per frame at 85.2% accuracy. That is the same accuracy as the decider and the lowest cost per frame in this test, with no hosting to manage. A real Decisions API call may use a different number of image tokens, and it returns no description, so the operator gets a yes/no only.

When does self-hosting cost less?

A g6.xlarge costs $0.000224 per second, so its cost per frame depends on how many frames per second it handles: $0.000224 at 1 frame per second, $0.000075 at 3, and $0.000045 at 5.
Used instead of an LLM, the decider costs less per frame than Gemma 4 at about 2.8 frames per second or more, and less than the simulated OpenAI Decisions API at about 4.5 or more. Used as a filter, it has a higher break-even point, because you pay for the decider on every frame but only save the LLM calls it removes.
LLM behind the filter
Cutoff
LLM cost saved
Break-even, frames per second
Gemma 4 31B
0.1 (no accuracy loss)
7%
about 40
Gemma 4 31B
0.2 (-2.9 points)
32%
about 9
GPT-6 Luna, full detail
0.1 (no accuracy loss)
8%
about 12
GPT-6 Luna, full detail
0.2 (-1.9 points)
34%
about 3
I have not measured how many frames per second one L4 handles. On the M3 Pro, one frame at a time, it was about 1.1 per second at 768 px and 2.2 at 448 px. If an L4 is in that range, the filter only saves money in front of an expensive model like GPT-6 Luna at full detail.
Three things change the math. This dataset is 57% "yes", so on typical motion traffic, which is mostly "no", a filter removes more calls. A GPU you already run at the edge with spare capacity costs nothing extra. And if you ask several questions per frame, the decider answers them all in one pass.

Benefits: accuracy, speed, and cost

Accuracy. With no training, the decider scored 85.2% on the camera example, 2.8 points below the best Bedrock vision LLMs. Every answer is valid, because there is no text to parse and so no format failures. Each answer also comes with a probability, so you set the cutoff to trade false alarms against missed events.
Speed. The decider took 0.46 s per frame at 448 px and 0.89 s at 768 px on a laptop GPU. Several questions about one frame are answered in one pass; the repo says the image is encoded once and shared across the questions. I did not measure LLM latency on Bedrock in these tests, so I make no speed comparison with the LLMs.
Cost. On your own instance there is no per-token charge. You pay for the instance by the hour, whether frames arrive or not, so the cost per frame falls as volume rises, and frames stay on your instance unless you forward them to an LLM. At low volume the decider costs more than Gemma 4 on Bedrock because of idle hours, and it pays off at steady, high volume or on a GPU you already run. Wherever they run, decision models should cost less per answer than a vision LLM, because they are small and generate no output. The OpenAI Decisions API already shows this: it bills input tokens only, and it had the lowest cost per frame in this test. A managed, pay-per-token offering of a model like Strands Decider would remove the idle-instance cost as well.

Cleanup

If you launched an EC2 instance to test:
aws ec2 terminate-instances --instance-ids <your-instance-id>
Also delete any EBS volumes or security groups you created for the test.

Try it on your own images

Start with one yes/no question that your application already sends to a vision LLM. Label a sample of your own images with the correct answer, run the decider on them, and pick the cutoff from those results instead of the 0.5 default. Then compare the decider with the LLM you use today on the same images, for both accuracy and cost per answer, and decide whether it replaces the LLM, filters in front of it, or is not a fit.
Concrete questions about something visible in the frame worked best in my test. If your application asks a broad question such as "Are there any safety issues?", split it into specific checks such as "Is anyone at height without a harness?" before you test.
If the decider falls short on your questions, fine-tuning is possible. Published text fine-tunes of the decider show large gains (one PII fine-tune went from 67.7% to 96.7%), but the official training recipe is text-only, and I found no published image fine-tune.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article