跳到正文
DEV Community· bestbee·· 3 小時前AI 評分60

將工作負載移至免費 AI 方案前,先做翻轉測試

Before You Move a Workload to the Free AI Lane, Run the Flip Test

AI 導讀

文章提出在把工作負載移至免費 AI 方案前,先按硬性限制篩選可用方案,再以翻轉測試找出可能令選擇失效的單一變數。附帶的 Python 3.10+ 無依賴腳本以佔位假設示範篩選和排序,作者提醒應以實際書面條款取代示例數值。作者披露文章屬 MonkeyCode 產品推廣,並稱相關優惠細節未經獨立核實。

正文

The question landed at minute forty of a planning call: "why don't we just put everything on the free lane and revisit next quarter?"

It is a fair question, and it is usually answered badly — with a price comparison, or with a vibe about model quality. Both miss the thing that actually decides the outcome.

The scene is a composite, assembled from conversations I keep having with platform leads. No client numbers below, and no measured benchmarks.

The lane you pick is a statement about what you can refuse

There are three lanes most teams are actually choosing between:

  1. Hosted free allowance — zero contract, zero ops, shared infrastructure, terms you don't control.
  2. Hosted paid — a contract, per-unit billing, stronger retention and version guarantees.
  3. Self-hosted — your hardware, your patching, your pager.

Every lane forces refusals. The free lane refuses some data classes, some concurrency, some model pinning, some audit trails — exactly which ones depends on terms you should have in writing. Self-hosting refuses the team that has no named ops owner.

So the real question is not "which is cheapest?" It is: which refusals can this workload absorb for the next 90 days?

Why price goes last

Price is a continuous variable. Constraints are binary.

A lane you are not permitted to use has an infinite unit cost, no matter how good the sticker looks. I've written before about computing crossovers; this post is about the step before that math, where you decide which lanes are even admissible.

Get the admissibility set wrong and the spreadsheet becomes a very precise answer to the wrong question. Sound familiar?

The worksheet: exclude first, tie-break second

Here is the artifact. It is deliberately dumb, stdlib-only, and reproducible in one file.

"""lane_fit.py — exclude AI coding lanes by hard constraints, then break ties.

Python 3.10+. No dependencies.

    python lane_fit.py

Every number in LANE_ASSUMPTIONS is a PLACEHOLDER. Replace it with the
term you actually received in writing before trusting a line of output.
"""
from dataclasses import dataclass, replace

# --- 1. assumptions you must replace -------------------------------------
LANE_ASSUMPTIONS = {
    "hosted_free": dict(
        allowed_data={"public", "internal"},
        max_parallel=4,          # PLACEHOLDER: concurrency the shared lane tolerates
        allows_pinning=False,    # can you pin an exact model version?
        keeps_audit_log=False,   # retained, exportable request logs?
        commitments=1,           # irreversible commitments you accept
    ),
    "hosted_paid": dict(
        allowed_data={"public", "internal", "regulated"},
        max_parallel=50,
        allows_pinning=True,
        keeps_audit_log=True,
        commitments=3,
    ),
    "self_hosted": dict(
        allowed_data={"public", "internal", "regulated"},
        max_parallel=None,       # no hard cap, but you own p99
        allows_pinning=True,
        keeps_audit_log=True,
        commitments=5,
    ),
}

OPS_OWNER = False  # True only if a named human owns patching + on-call


@dataclass(frozen=True)
class Workload:
    name: str
    data_class: str              # public | internal | regulated
    peak_parallel_agents: int
    pinned_model: bool
    audit_log: bool
    reviewer_required: bool
    hours_per_week: float


def blockers(w: Workload, lane: str) -> list[str]:
    a = LANE_ASSUMPTIONS[lane]
    out = []
    if w.data_class not in a["allowed_data"]:
        out.append(f"data_class={w.data_class} not allowed")
    if a["max_parallel"] is not None and w.peak_parallel_agents > a["max_parallel"]:
        out.append(f"peak_parallel_agents={w.peak_parallel_agents} > {a['max_parallel']}")
    if w.pinned_model and not a["allows_pinning"]:
        out.append("needs a pinned model version")
    if w.audit_log and not a["keeps_audit_log"]:
        out.append("needs retained, exportable logs")
    if lane == "self_hosted" and w.data_class == "regulated" and not OPS_OWNER:
        out.append("regulated data, no named ops owner for self-hosting")
    return out


def rank(w: Workload) -> list[str]:
    ok = [lane for lane in LANE_ASSUMPTIONS if not blockers(w, lane)]
    # price never enters here; fewest irreversible commitments wins the tie
    return sorted(ok, key=lambda lane: (LANE_ASSUMPTIONS[lane]["commitments"], lane))


def flip_test(w: Workload, chosen: str) -> list[tuple[str, str]]:
    """Which single-variable change would exclude the lane we just picked?"""
    mutations = {
        "data one level stricter": replace(w, data_class=_stricter(w.data_class)),
        "peak parallelism x2": replace(w, peak_parallel_agents=w.peak_parallel_agents * 2),
        "pin an exact model": replace(w, pinned_model=True),
        "require exportable logs": replace(w, audit_log=True),
    }
    hits = []
    for label, mutated in mutations.items():
        if mutated != w and blockers(mutated, chosen):
            hits.append((label, blockers(mutated, chosen)[0]))
    return hits


def _stricter(data_class: str) -> str:
    order = ["public", "internal", "regulated"]
    return order[min(order.index(data_class) + 1, len(order) - 1)]


WORKLOADS = [
    Workload("docs-and-tests", "public", 2, False, False, True, 6.0),
    Workload("internal-refactor", "internal", 3, False, False, True, 12.0),
    Workload("billing-migration", "regulated", 2, True, True, True, 4.0),
]


def main() -> None:
    for w in WORKLOADS:
        admissible = rank(w)
        print(f"\n{w.name}  (data={w.data_class}, parallel={w.peak_parallel_agents}, "
              f"{w.hours_per_week}h/wk)")
        for lane in LANE_ASSUMPTIONS:
            why = blockers(w, lane)
            print(f"  {'OK  ' if not why else 'OUT '} {lane:<12} {'; '.join(why)}")
        if not admissible:
            print("  => no admissible lane: split the workload or reduce scope")
            continue
        chosen = admissible[0]
        fallback = admissible[1] if len(admissible) > 1 else "none"
        print(f"  => pick {chosen}; fallback {fallback}")
        flips = flip_test(w, chosen)
        if not flips:
            print("     flip: none of the four single-variable changes break this lane")
        for label, reason in flips:
            print(f"     flip: {label} -> {reason}")


if __name__ == "__main__":
    main()

Run it. Then argue with the output. That argument is the meeting you actually needed to have.

What it prints (illustrative, from the placeholders above)

docs-and-tests  (data=public, parallel=2, 6.0h/wk)
  OK   hosted_free
  OK   hosted_paid
  OK   self_hosted
  => pick hosted_free; fallback hosted_paid
     flip: pin an exact model -> needs a pinned model version
     flip: require exportable logs -> needs retained, exportable logs

internal-refactor  (data=internal, parallel=3, 12.0h/wk)
  OK   hosted_free
  OK   hosted_paid
  OK   self_hosted
  => pick hosted_free; fallback hosted_paid
     flip: peak parallelism x2 -> peak_parallel_agents=6 > 4
     flip: pin an exact model -> needs a pinned model version
     flip: require exportable logs -> needs retained, exportable logs

billing-migration  (data=regulated, parallel=2, 4.0h/wk)
  OUT  hosted_free    data_class=regulated not allowed
  OK   hosted_paid
  OUT  self_hosted    regulated data, no named ops owner for self-hosting
  => pick hosted_paid; fallback none
     flip: none of the four single-variable changes break this lane

These are placeholder assumptions, not measurements. Feed it your own terms.

Read the output like a lead, not like a spreadsheet

  • OUT rows are decisions, not preferences. If the free lane is out on data class, stop negotiating with it and move on.
  • "No admissible lane" is a finding, not a failure. billing-migration has no fallback because self-hosting without an ops owner is not a lane — it is a future incident with a start date.
  • A fallback is not a plan. It is the name of the person you call when the primary breaks.
  • The flip list is the actual deliverable. It tells you what you are one decision away from losing.

Where the free lane genuinely earns its place

The free lane is not the "cheap lane." It is the lane for workloads whose refusals you can absorb: documentation, test scaffolding, mechanical refactors, anything that a human reviewer would catch anyway.

The lane I am running this quarter on the free side is MonkeyCode — free model access plus a free server option, which the operator describes as an open-source project. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

As of 2026-10-26, the operator's pitch is free model access (a ten-million-token allowance, per their own copy) plus a free server option. I have not independently verified the allowance, the model list, the server specifications, how long either offer lasts, or the license terms. Treat every one of those as an unverified input to the worksheet — a row you still have to fill in, not a number you get to budget against.

What makes it a reasonable first lane for docs-and-tests is structural, not promotional: it asks for the fewest commitments. No contract. No hardware. No pager. That is also exactly why it runs out of road for billing-migration.

The flip test is the part people skip

Anyone can list tradeoffs. The useful move is naming the single variable that would reverse your call, and assigning it an owner.

For each flip line, write down three things:

  1. Who watches that variable.
  2. What threshold triggers a re-run of the worksheet.
  3. What the team does in the first hour after the trigger.

If nobody owns "peak parallelism x2," then the free lane is not a decision — it is a coin flip with documentation.

Who should not use this

  • Teams without a data classification scheme. Fix that first; this script will happily launder your ambiguity into a tidy OK.
  • Regulated workloads with no ops owner. Self-hosting is not available to you yet, and pretending otherwise wastes the meeting.
  • Anyone who needs a score to justify a decision already made. This worksheet excludes lanes. It does not bless one.
  • Teams expecting measured numbers. Every value above is a placeholder until you replace it.

Limitations I want on the record

The commitments field is ordinal, not cardinal — it ranks lanes, it does not price them. The model ignores reviewer hours entirely, and reviewer hours are usually the largest real cost of a free lane. The sensitivity analysis mutates one variable at a time; two simultaneous changes can produce outcomes this script will never show you. And the whole thing is a conversation tool, not objective truth — two reasonable leads can fill in the assumptions differently and both be right.

Owner, expiry, exit

  • Owner: platform lead.
  • Expires: 2026-11-25. Re-run or archive, do not silently inherit.
  • Exit criteria: either every workload has a named lane plus an owned flip trigger, or the team writes down which workload it is deliberately parking.

One soft ask: if you run this, bring back the flip list for the workload you were about to move to the free lane. That single list tells me more about your risk posture than any architecture diagram.

來源:DEV Community · dev.to