qinghuan
← 回到知识探索

AI 直觉 / 可下载小项目

工具说报告写好了,助手可以直接交付吗?

实际启动七次固定子进程,把退出码、文件存在、格式与任务内容分别验收,生成一份不会被成功提示骗过的交付检查表。

展开本文目录
  1. 下载完整练习包
  2. 三张检查单,分别回答三个问题
  3. 先写任务要求,再看产物说了什么
  4. 每次从空目录开始,给本次任务单独编号
  5. 有文件但退出失败,与没文件却退出成功
  6. 能打开文件之后,还要核对它装了什么
  7. 运行完整脚本,七次子进程只接纳一次
  8. 交付时写出通过了什么,失败停在哪里
  9. 真实执行了子进程,仍要守住验证边界
  10. 记住这三点
  11. 别把例子用过头
  12. 自己试一小步
  13. 查原始资料
开始前,会这些就够了
  • 能运行 Python 文件,读懂字典、列表与 assert
  • 理解进程输出文字和进程退出码是不同信息
  • 知道 JSON 文件需要解析后才能检查里面的字段

助手让一个工具导出两条已选便条,屏幕出现“Report ready.”。这句话可能来自正常程序,也可能在文件写入前就已经打印。即使退出码是零,工具仍可能根本没有生成文件,或者导出了另一个任务的结果。把日志原样转述给用户,不能完成产物验收。

本篇运行七次真实的本地子进程。它们都打印同一句成功提示,父流程却只接纳其中一次。学习产物是一份 JSON 验收报告和一张 CSV 对照表;所有便条都是脚本内的虚构数据,只运行随文附带的固定工作代码,不调用语言模型或任意生成代码。

把例子带到自己的电脑上

下载完整练习包

需要 Python 3.10 或更新版本,无第三方依赖;制作时实测 Python 3.14.0。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。

下载 ZIP · 5 个文件
包里有什么
  • artifact_acceptance.py:完整示例
  • README.md:环境、范围与独立练习
  • observed.stdout.txt:制作时的实际输出
  • verification.json:源码与产物验证记录
  • SHA256SUMS.txt:文件内容摘要

在这个文件夹打开 PowerShell,逐行运行

python --version
python -B artifact_acceptance.py

解压到独立练习文件夹,在含源文件的目录打开终端运行。程序会在当前目录写入教学报告;先预测结果,再查看当次输出。真实启动七个受控子进程,按任务身份及文件内容验收;不执行任意外部程序,也不构成代码沙箱。

找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。

三张检查单,分别回答三个问题

第一张检查进程有没有按约定退出;第二张检查本次约定位置是否有文件、能否解析;第三张检查文件是否属于这项任务,内容是否与约定一致。任何一张不通过,都不能使用最终的 deliverable 状态。stdout 只留下诊断文字,不参与成功判定。

Python subprocess 文档分别提供 returncode、stdout 与 stderr。它说明零通常代表进程运行成功,却没有替你的应用定义“导出任务已经满足”。本例主动组合退出码与内容检查,规则写在父流程,避免让工作程序一句自报成功决定全部结果。

先写任务要求,再看产物说了什么

任务明确要求导出 project-alpha-selected-notes 范围里的 N1、N2 两条便条,编号和原文都在 request.json 中。产物要包含 task_id、scope 和 records。验收器从原请求建立预期映射,再逐条核对结果的编号与文字;它没有采用工作程序自己声称的数量作为验收依据。

这是一份准确复制已选记录的任务,因此可以做逐值检查。若任务改为摘要、翻译或图片生成,不能照搬“文字必须完全相等”;需要先定义对应的内容质量标准,并说明仍无法自动判定的部分。本篇刻意选择边界小、结果可明确核对的任务,把执行证据链讲清楚。

每次从空目录开始,给本次任务单独编号

每次 run_case 都创建新的临时目录与任务编号,写入本次请求后才启动子进程。运行前还断言 result.json 不存在。这样 zero_no_file 不会误捡上次成功运行留下的同名文件。文件名固定只方便约定位置,不能承担任务身份。

TemporaryDirectory 文档说明上下文结束后会清理临时目录。本例在清理前读取结果,把解析后的快照放进最终报告;七个原始临时结果不会继续留在磁盘上。最终 JSON 与 CSV 是持久交付的验收记录,不能把已清理的临时路径发给用户当下载地址。

即使目录隔离了,wrong_task 仍故意写入旧任务编号。验收器会把它拒绝,展示“文件在这次目录里”与“文件声明属于这次任务”是两项不同检查。编号本身不是身份认证,本题只是防止受控流程中的串单和旧结果混入,没有声称抵御恶意程序伪造。

有文件但退出失败,与没文件却退出成功

nonzero_with_file 先写出内容合格的文件,再以七退出。内容检查返回 accepted,最终仍是 rejected,因为本题交付政策要求进程成功且内容合格。zero_no_file 恰好相反:退出码零,日志也说准备好了,但结果文件不存在,因此同样被拒绝。

内容合格不等于整个生产步骤完成。非零退出可能意味着后续整理步骤失败,也可能只是某个工具采用另一套退出码约定;真实接入应先核对它的合同。本题不尝试抢救失败进程留下的文件,也不把这种保守政策描述为所有命令行程序都必须遵守的普遍规则。

能打开文件之后,还要核对它装了什么

bad_json 只写出半截对象,解析失败;wrong_scope 写入另一项目范围;wrong_rows 只保留一条便条。三种错误分别得到 invalid_json、scope_mismatch 和 records_mismatch,不能都归成一个含糊的“工具失败”。报告仍保留退出码和输出文字,方便定位到底哪层没有满足要求。

Python json 文档说明解析错误与 object_pairs_hook,也提醒默认解析器会接受重复成员并保留最后一个。本例用钩子拒绝重复 JSON 成员,再检查记录编号是否重复、字段是否为文字。结构解析、任务归属和内容一致性依次检查,各自承担不同责任。

当前任务不要求便条列表保持输入顺序,所以验收先转成编号到文字的映射,但转之前必须拒绝重复编号。否则两条同编号记录会在字典里互相覆盖,错误可能被隐藏。若后续产品要求顺序一致,就需要增加顺序条件,不能凭本题通过便声称所有展示要求都已满足。

运行完整脚本,七次子进程只接纳一次

保存为 artifact_acceptance.py,在单独练习目录执行。主进程使用当前解释器启动同一文件的 --worker 分支,只接受预设的七种情形。参数以列表传入,不经过 shell;每次等待上限为十秒。没有下载程序,也没有执行模型产出的代码。

脚本生成 artifact-acceptance-report.json 和 artifact-acceptance.csv。七次工作进程都会打印 Report ready.,只有 valid 得到 deliverable。断言还覆盖重复 JSON 成员、重复便条编号和错误字段类型;随机任务编号每次不同,验收状态和终端显示顺序保持可对照。

展开收起代码 · 小实验python · 130 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

小实验 · python
"""Run only this bundled worker; demonstrate output acceptance, NOT a sandbox."""
import csv
import json
from pathlib import Path
import subprocess
import sys
import tempfile
from uuid import uuid4

SCRIPT = Path(__file__).resolve()
MODES = ("nonzero_with_file", "zero_no_file", "bad_json", "wrong_task", "wrong_scope", "wrong_rows", "valid")


def worker(mode, task_id):
    # The fixed worker reads only the synthetic request in its working directory.
    task = json.loads(Path("request.json").read_text(encoding="utf-8"))
    assert task["task_id"] == task_id
    artifact = {"task_id": task_id, "scope": task["scope"], "records": task["records"]}
    if mode == "wrong_task":
        artifact["task_id"] = "a-prior-task"
    if mode == "wrong_scope":
        artifact["scope"] = "another-project"
    if mode == "wrong_rows":
        artifact["records"] = artifact["records"][:1]
    if mode == "bad_json":
        Path("result.json").write_text('{"records":', encoding="utf-8")
    elif mode != "zero_no_file":
        Path("result.json").write_text(json.dumps(artifact), encoding="utf-8")
    print("Report ready.")  # Every mode says the same thing.
    return 7 if mode == "nonzero_with_file" else 0


def unique_object(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError("duplicate_json_member")
        result[key] = value
    return result


def validate(artifact, task):
    if not isinstance(artifact, dict) or set(artifact) != {"task_id", "scope", "records"}:
        return "wrong_shape"
    if artifact["task_id"] != task["task_id"]:
        return "task_mismatch"
    if artifact["scope"] != task["scope"]:
        return "scope_mismatch"
    rows = artifact["records"]
    if not isinstance(rows, list) or any(not isinstance(r, dict) or
            set(r) != {"id", "text"} or not all(isinstance(v, str) for v in r.values()) for r in rows):
        return "invalid_records"
    ids = [r["id"] for r in rows]
    if len(ids) != len(set(ids)):
        return "duplicate_record_id"
    actual = {r["id"]: r["text"] for r in rows}
    expected = {r["id"]: r["text"] for r in task["records"]}
    if actual != expected:
        return "records_mismatch"
    return "accepted"


def inspect_artifact(path, task):
    if not path.is_file():
        return "missing_file", None
    try:
        artifact = json.loads(path.read_text(encoding="utf-8"), object_pairs_hook=unique_object)
    except (ValueError, UnicodeError):
        return "invalid_json", None
    return validate(artifact, task), artifact


def run_case(mode):
    task = {"task_id": uuid4().hex, "scope": "project-alpha-selected-notes",
            "records": [{"id": "N1", "text": "Read the input."}, {"id": "N2", "text": "Check the output."}]}
    # Fresh directory and task ID for EACH invocation prevent reuse of an old result.
    with tempfile.TemporaryDirectory(prefix="artifact-case-", dir=Path.cwd()) as temp:
        folder = Path(temp)
        path = folder / "result.json"
        assert not path.exists()
        (folder / "request.json").write_text(json.dumps(task), encoding="utf-8")
        result = subprocess.run([sys.executable, str(SCRIPT), "--worker", mode, task["task_id"]],
                                cwd=folder, capture_output=True, text=True, encoding="utf-8",
                                timeout=10, check=False)
        artifact_status, artifact = inspect_artifact(path, task)
        decision = "deliverable" if result.returncode == 0 and artifact_status == "accepted" else "rejected"
        return {"mode": mode, "task_id": task["task_id"], "returncode": result.returncode,
                "stdout": result.stdout.strip(), "stderr": result.stderr.strip(),
                "artifact_exists": path.is_file(), "artifact_status": artifact_status,
                "decision": decision, "artifact_snapshot": artifact}


def main():
    cases = [run_case(mode) for mode in MODES]
    assert len({r["task_id"] for r in cases}) == len(cases)
    assert all(r["stdout"] == "Report ready." for r in cases)
    assert [r["decision"] for r in cases] == ["rejected"] * 6 + ["deliverable"]
    assert [r["artifact_status"] for r in cases] == [
        "accepted", "missing_file", "invalid_json", "task_mismatch", "scope_mismatch", "records_mismatch", "accepted"]
    assert cases[0]["returncode"] == 7 and cases[0]["artifact_exists"]
    assert cases[1]["returncode"] == 0 and not cases[1]["artifact_exists"]
    assert all(r["stderr"] == "" for r in cases)
    try:
        json.loads('{"x":1,"x":2}', object_pairs_hook=unique_object)
    except ValueError:
        pass
    else:
        raise AssertionError("duplicate member was accepted")
    probe = {"task_id": "probe", "scope": "s", "records": [{"id": "1", "text": "x"}]}
    assert validate({**probe, "records": probe["records"] * 2}, probe) == "duplicate_record_id"
    assert validate({**probe, "records": [{"id": "1", "text": False}]}, probe) == "invalid_records"
    report = {"scope": "fixed bundled subprocess; exact export of two selected synthetic notes",
              "policy": "zero exit AND accepted artifact; stdout is diagnostic only",
              "cases": cases, "assertions": "passed"}
    Path("artifact-acceptance-report.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    columns = ["mode", "task_id", "returncode", "artifact_exists", "artifact_status", "decision"]
    with Path("artifact-acceptance.csv").open("w", encoding="utf-8", newline="") as handle:
        writer = csv.DictWriter(handle, fieldnames=columns, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(cases)
    for row in cases:
        print(f"{row['mode']}: exit={row['returncode']}, artifact={row['artifact_status']}, {row['decision']}")
    print("PASS: 7 real child runs; one deliverable; independent directories and task IDs")


if __name__ == "__main__":
    if len(sys.argv) == 4 and sys.argv[1] == "--worker" and sys.argv[2] in MODES:
        raise SystemExit(worker(sys.argv[2], sys.argv[3]))
    main()

怎样运行:Python 3.10 或更新版本,在独立练习目录执行:python artifact_acceptance.py。仅标准库。 2026-10-15 已用 Python 3.14.0 执行这份代码并通过自带检查;下方是当次输出。真实启动七个受控子进程,按任务身份及文件内容验收;不执行任意外部程序,也不构成代码沙箱。

运行后对照结果
nonzero_with_file: exit=7, artifact=accepted, rejected
zero_no_file: exit=0, artifact=missing_file, rejected
bad_json: exit=0, artifact=invalid_json, rejected
wrong_task: exit=0, artifact=task_mismatch, rejected
wrong_scope: exit=0, artifact=scope_mismatch, rejected
wrong_rows: exit=0, artifact=records_mismatch, rejected
valid: exit=0, artifact=accepted, deliverable
PASS: 7 real child runs; one deliverable; independent directories and task IDs

交付时写出通过了什么,失败停在哪里

CSV 适合并排看退出码、文件存在性、内容状态与最后判定;JSON 则保留任务编号、stdout、stderr 和解析后的产物快照。读者可以先找到非零但有文件那一行,再和零退出无文件那一行比较,确认报告没有把任何单一信号当作完成凭据。

程序没有生成自然语言总结,而是把可检查事实交给后续展示层。助手可以据此说“本次导出的两条指定便条已核对”,不能扩写为“这个项目所有便条都完整导出了”。这里的范围来自明确输入的两条记录,既没有搜索真实账号,也没有验证上游资料是否已经列全。

不要把 rejected 理解成“这次什么都没有发生”。第一组已经产生文件,只是按本题政策暂不交付;其他真实工具还可能留下更重要的副作用。验收结论用于决定这份产物能否按当前任务交付,不能替代对外部变化的调查或撤销。

真实执行了子进程,仍要守住验证边界

2026-10-15 已实际执行完整脚本,七个固定工作进程均结束,得到六次拒绝、一次可交付,全部内置断言通过。文章代码由执行文件直接嵌入;报告里的快照来自本次临时产物读取,没有用手填的预期结果代替工作进程输出。

临时目录、无 shell 和等待上限都不是运行任意代码的安全沙箱。工作程序仍以当前用户权限运行,本题只能运行随附的固定代码。没有验证恶意程序隔离、子孙进程清理、磁盘写满或操作系统崩溃;超时与启动错误会让实验抛错停止,也没有伪装成已完成的验收报告。

记住这三点

  • 日志、退出码、文件存在与内容验收是不同证据,需要按任务组合。
  • 每次任务使用独立目录与编号,产物还要与原请求核对,避免旧文件或串单伪通过。
  • 交付结论必须保留实际验收范围,准确复制两条输入不能证明真实项目已全部导出。

别把例子用过头

  • 把工作程序打印的成功文字直接当成最终完成状态。
  • 复用输出目录而不检查任务身份,误把上次文件当作本次产物。
  • 解析成功后只看声明的数量,不核对记录内容;把子进程运行方式称为安全沙箱。

自己试一小步

如果导出任务要求保留便条顺序并附带一个新增字段,你会怎样扩展验收器?

  1. 另建三条虚构记录,明确字段、顺序和允许的输出形式;先写验收清单,再改工作程序。
  2. 构造至少五个失败产物,包含顺序错误、重复记录、错任务、非零退出留文件与格式损坏。
  3. 设计一份用户能读懂的交付摘要,把进程结果、产物检查与尚未证明的范围分别交代。
  4. 交付独立临时目录中的实际运行记录、JSON 与 CSV;继续只运行自带固定代码。

做到这些,就算完成

  • 预期是否来自原任务,而不是产物自己声明的成功或数量?
  • 旧文件、错任务和重复编号是否均无法得到可交付状态?
  • 是否明确限制内容质量、上游完整性与代码隔离方面的结论?

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

  • Python 3.10:subprocess 运行与返回结果 (新标签页)↗Python Software Foundation · 英文

    核读 run、CompletedProcess、参数列表与 sys.executable 说明;支持取得退出码、标准输出、错误输出及设置工作目录与等待上限。

    2026-10-15 已实际打开公开原始正文并核读;无需登录。未把子进程 API 当作任意代码安全沙箱。

  • Python 3.10:TemporaryDirectory (新标签页)↗Python Software Foundation · 英文

    核读 TemporaryDirectory 创建、上下文清理及 dir 参数;支持为每次固定工作进程创建独立目录。

    2026-10-15 已实际打开公开原始正文并核读;无需登录。目录只是防止受控练习复用旧结果,不提供进程权限隔离。

  • Python 3.10:JSON 解析与重复成员 (新标签页)↗Python Software Foundation · 英文

    核读 loads、JSONDecodeError、object_pairs_hook 与 Repeated Names Within an Object;支持解析错误识别和自定义重复成员拒绝。

    2026-10-15 已实际打开公开原始正文并核读;无需登录。字段与任务内容校验由本题代码另行实现,不是解析器自动证明。