跳到正文
DEV Community· Haley·· 4 小時前AI 評分55

模型撰寫任何內容前,先建立證據卡

Build the Evidence Card Before the Model Writes Anything

AI 導讀

作者提出在模型起草文案前先建立證據卡,並用不呼叫模型的檢查器與寫入閘門攔截證據不足的審核卡。證據卡列出負責人、現有及缺失證據、停止條件、復原路徑和後備文字,流程再以六個固定測試案例驗證。作者以 MonkeyCode 免費模型存取作起草示例,並披露文章屬其產品推廣;文中亦提醒檢查免費方案條款及伺服器資料存取權限。

正文

The review that looks like a copy edit

A reviewer opens a pull request at 4:40 on a Friday. The new error copy is shorter and friendlier. It also drops the sentence telling the user what to do next. She approves it, because the diff reads better.

The decision owner was her. The consequence lands on users with no next step. The point of reversibility was the approve button. It looked like a copy edit, so nobody treated it as one.

I keep seeing this shape. A model drafts prose, a human skims prose, and the actual decision disappears into the reading. So I want the review object to be the evidence card, not the sentence.

That is a design hypothesis, not a finding. Here is how I would test it from zero in one afternoon.

Step 1: Write the card before the prompt

Start with the fields. A card that cannot be checked is a mood board. I want each field to fail loudly when it is empty.

The card needs a surface, a person, present evidence, missing evidence, a stop condition, a recovery path, and reader-facing fallback text. Write one by hand before you touch any model.

{
  "surface": "checkout.error.card_declined",
  "decision_owner": "design-review@example.com",
  "evidence_present": ["decline-code map v3", "support macro 118"],
  "evidence_missing": ["what happens after the second failed retry"],
  "stop_condition": "evidence_missing is not empty",
  "reversibility": "copy ships in the app bundle; rollback needs a release",
  "fallback_text": "Your card was declined. Retry, or use a different card."
}

Now verify the hand-written card against the real recovery step. Open the flow. Tap retry twice. If the second tap does nothing, your missing-evidence field is right and your fallback text is probably wrong.

That is verification stage one. The card must disagree with the product before it agrees with you.

Step 2: Make the checker boring

The checker should be too dumb to be interesting. Rules in, exit codes out. No model calls inside it.

import json
import sys

REQUIRED = [
    "surface",
    "decision_owner",
    "evidence_present",
    "evidence_missing",
    "stop_condition",
    "reversibility",
    "fallback_text",
]

NOT_EVIDENCE = ("model said", "the model", "looks fine", "reads better")
NO_PATH = {"", "none", "n/a", "unknown", "rollback later"}


def load(path):
    with open(path, encoding="utf-8") as handle:
        return json.load(handle)


def problems(card):
    found = []
    for key in REQUIRED:
        if key not in card or card[key] in ("", [], None):
            found.append(f"empty or missing: {key}")

    owner = str(card.get("decision_owner", ""))
    if "@" not in owner:
        found.append("decision_owner must name a person or alias")

    evidence = [str(item).lower() for item in card.get("evidence_present", [])]
    real = [item for item in evidence if not any(bad in item for bad in NOT_EVIDENCE)]
    if not real:
        found.append("evidence_present holds no verifiable source")

    if str(card.get("reversibility", "")).strip().lower() in NO_PATH:
        found.append("reversibility has no recovery path")

    if len(str(card.get("fallback_text", ""))) < 20:
        found.append("fallback_text is too short to be actionable")

    return found


if __name__ == "__main__":
    card = load(sys.argv[1])
    found = problems(card)
    for item in found:
        print(f"BLOCK {item}")
    print("PASS" if not found else "FAIL")
    sys.exit(0 if not found else 1)

Run it on the card you wrote by hand.

python3 check_card.py cards/card_declined.json; echo "exit=$?"

Expect exit 0 only if the card is honest. If it passes while evidence_missing is still open, your stop condition is decorative. Fix the rule, not the card.

Step 3: Let the model draft, not decide

The free model access is where this gets practical. I use MonkeyCode's free model access for the drafting step only. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The operator describes a free token allowance and a free server option. Treat both as claims to check on the current plan page. I am not quoting limits, hardware, or duration I cannot verify.

Here is the call sketch. Treat it as unexecuted pseudocode. Field names and paths come from your own workspace docs, not from me.

import json
import os
import urllib.request

payload = {
    "model": os.environ["MONKEYCODE_MODEL"],
    "messages": [
        {"role": "system", "content": "Return JSON matching the card schema. No prose."},
        {"role": "user", "content": open("prompts/error-copy.md").read()},
    ],
}

request = urllib.request.Request(
    os.environ["MONKEYCODE_BASE_URL"].rstrip("/") + "/chat/completions",
    data=json.dumps(payload).encode(),
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer " + os.environ["MONKEYCODE_API_KEY"],
    },
)

Verify by failing closed. If the response is not parseable JSON, write nothing and exit 1. A draft that arrives as prose is a draft you will paste by hand, and hand-pasting is where the missing step vanished in the first place.

Step 4: Gate the write

The generator may not touch the copy file. Only the gate may.

#!/usr/bin/env bash
set -euo pipefail

card="${1:?usage: gate.sh path/to/card.json}"
python3 check_card.py "$card"
mkdir -p approved
cp "$card" "approved/$(basename "$card")"
echo "cleared for review: $card"

Verify by attempting a blocked write. Point the gate at a card with an empty evidence_present. Expect a nonzero exit and no file in approved/. If a file appears, the gate is a suggestion.

Step 5: Break it with fixtures

Six fixtures, fixed expectations. This is the whole test plan, and it fits in one directory.

Fixture Planted defect Expected
card_ok.json none pass
card_empty_evidence.json empty evidence list fail
card_model_only.json evidence is "the model said it reads better" fail
card_channel_owner.json owner is #design fail
card_no_path.json reversibility is "none" fail
card_terse_fallback.json fallback text is "Failed." fail
for f in fixtures/*.json; do
  if python3 check_card.py "$f" >/dev/null 2>&1; then
    printf 'PASS %s\n' "$f"
  else
    printf 'FAIL %s\n' "$f"
  fi
done

Compare the output to the table, line by line. Any mismatch means the gate has a hole, and you found it before a user did. This is the only step I would not skip under deadline.

Step 6: Put the checker on the free server

The free server option is tempting for the model. I would put the checker there instead. The checker holds no key and stores no draft.

from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import subprocess
import tempfile


class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers["Content-Length"]))
        with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as handle:
            handle.write(body.decode())
            path = handle.name
        result = subprocess.run(
            ["python3", "check_card.py", path],
            capture_output=True,
            text=True,
        )
        self.send_response(200 if result.returncode == 0 else 422)
        self.end_headers()
        self.wfile.write(result.stdout.encode())


HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()

Verify with a passing card and a failing one. Expect 200 then 422.

curl -sS -o /dev/null -w '%{http_code}\n' -X POST http://HOST:8080/ \
  -H 'Content-Type: application/json' --data @fixtures/card_ok.json

curl -sS -o /dev/null -w '%{http_code}\n' -X POST http://HOST:8080/ \
  -H 'Content-Type: application/json' --data @fixtures/card_no_path.json

Now the consent question, and it is a real one. Who else can read that host? If the answer is unclear, keep the model key off it entirely. Deployment is a decision about exposure, not a convenience.

Step 7: Review the card as a person would

A gate nobody can read is a gate nobody trusts. Check the card UI in this order, and stop at the first failure.

Keyboard first. Tab through the card. Focus must follow the reading order: owner, evidence, gap, stop, recovery. If focus jumps to the approve button before the missing evidence, the order is wrong.

Screen reader second. The blocked state must be spoken, not only colored. "Blocked: missing recovery step" beats a red left border every time. Then check the fallback text: if it repeats the original error, a screen reader user gets the same dead end twice.

Last, zoom to 200 percent. Long evidence strings are where cards break. If the missing-evidence field scrolls out of view, the reviewer approves what they can see.

What this does not do

It does not judge copy quality. It does not know your legal review process. It does not prove the model is consistent across runs.

Do not use this if nobody owns the copy surface. Do not use it for regulated text with a mandatory legal gate, because the gate will pass cards that legal would reject. Do not use it if you cannot pin a model version, since a moving target makes your fixtures meaningless.

And do not treat free tiers as a service level agreement. Free is a starting condition, not a promise.

The two questions I would ask in review

Which missing evidence should stop approval here? And which extra information would only add noise?

The first question protects the user. The second protects the reviewer. A card that answers both is short enough to read at 4:40 on a Friday.

If you want to try the drafting step without a budget conversation, the free model access and the free server option are where I would start. Put the checker on the server, though. Let the card hold the decision.

來源:DEV Community · dev.to