建议用密封 prompt、私藏 rubric 和参考方案来评估免费模型,避免被流畅叙述蒙蔽——模型可能谈得清但写不出。
聊天和实战之间隔着一道鸿沟,这和现场面试暴露的问题一模一样。
一个流畅的模型可以口若悬河地讲解令牌桶限流器的原理,却实现不出来。它会大谈 token、补充速率,然后直接修改一个全局 dict 并称之为"线程安全"。人类候选人在代码写得比嘴慢的时候也会这样做。解决方案不是换一个更热情的 prompt,而是把 take-home 做得足够小——小到十分钟能批完、锐利到吹牛要付出代价。
你给模型一份包含指令的文件,不在聊天里讨价还价。你根据一个先于结果存在的评分表对返回的文件树打分,这个顺序就是全部方法。如果你看到一份漂亮的 NOTES.md 之后才写评分表,那你评的是故事,不是工作。
把模型当作一个无法到场的外包人员来对待。你不会凭一次走廊里的闲聊就录用那个人。你会发出一份密封的任务书,藏好答案,然后先看测试结果再读求职信。
把下面的内容保存为 TAKEHOME.md。每次重用这个包时都要更换 seed 行,这样网上搜到的博客解答就没法蒙混过关。经典谜题会泄露。你的 seed 是你发现问题的手段。
# Take-home: freeze-clock token bucket
Seed: 2026-09-03-devio / do not paste public blog solutions.
Implement a token-bucket rate limiter in Python 3.11 as `bucket.py` plus `test_bucket.py`.
Constraints:
- Standard library only.
- No `time.sleep` inside the limiter. Time comes from a `Clock` protocol
with `now() -> float` (seconds) and `advance(seconds)` for tests.
- `TokenBucket(rate_per_sec: float, capacity: float, clock: Clock)`
- `allow(n: float = 1.0) -> bool` must be deterministic on the same clock trace.
- Refill is continuous: tokens += rate * elapsed, capped at capacity.
- Reject `n > capacity` without consuming tokens.
- `allow` must not throw on normal inputs. Raise `ValueError` for
non-finite or negative rate, capacity, or n.
- Tests must cover: burst up to capacity, refill after exact elapsed time,
rejection when empty, no token leak on oversize reject, freeze clock
never calling `time.time`.
Deliver:
1. `bucket.py`
2. `test_bucket.py` passing under `python -m unittest`
3. `NOTES.md` with one paragraph on a race you did *not* solve (threads,
process forks, etc.) and why you left it unsolved.
Do not add features. Do not install packages. Do not call the network.
这个 prompt 是故意写得无聊的。听起来像产品推介的 take-home 会引诱模型去发明架构。你要的是工作样本,不是融资路演。如果模型返回 Redis、asyncio 和一个仪表盘,那不是雄心,那是候选人没读需求文档。
你用四个维度打分。每个维度 0、1 或 2,满分 8 分。只有当总分至少为 6 且 Correctness 和 Tests 都不是 0 时,你才继续使用这个端点。一份迷人的笔记文件救不了一个坏掉的 allow 方法。
Correctness 问的是 allow 在冻结时钟下是否匹配连续补充逻辑。得 2 分意味着 burst、refill、empty 和 oversize-reject 全部行为正确。得 1 分意味着正常路径能用但一个边界情况模糊。得 0 分意味着 token 泄漏、时间来自墙钟,或者超量请求仍然会抽干 bucket。
Tests 问的是这个测试套件在你在 refill 或超量规则上动手脚之后会不会变红。得 2 分意味着测试钉死了这些行为。得 1 分意味着测试只证明了"某东西返回了 True"。得 0 分意味着文件缺失、跳过,或者没有任何可能失败的断言。
Discipline 问的是模块是否坚持使用标准库并避免了 sleep。Judgment 看的是笔记文件。如果 NOTES.md 声称 bucket 是线程安全的但没有锁,这个维度得 0 分。如果它命名了一个竞态条件并承认没有假装解决它,这个维度得 2 分。道歉但不命名竞态条件得 1 分。
把这张表存在一个不发给模型的文件夹里。你不是在讨论品味,你是在使用答案。
不要把下一段代码粘贴进 prompt。你要保密,就像你会对人类候选人的 take-home 答案保密一样。下面的代码是一份参考答案,不是在声称某个特定端点产生了它。
from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol
def _finite_nonneg(value: float) -> bool:
return value == value and value not in (float("inf"), float("-inf")) and value >= 0
class Clock(Protocol):
def now(self) -> float: ...
def advance(self, seconds: float) -> None: ...
@dataclass
class FreezeClock:
_t: float = 0.0
def now(self) -> float:
return self._t
def advance(self, seconds: float) -> None:
if not _finite_nonneg(seconds):
raise ValueError("seconds must be finite and >= 0")
self._t += seconds
class TokenBucket:
def __init__(self, rate_per_sec: float, capacity: float, clock: Clock) -> None:
if not _finite_nonneg(rate_per_sec):
raise ValueError("rate_per_sec must be finite and >= 0")
if not _finite_nonneg(capacity) or capacity == 0:
raise ValueError("capacity must be finite and > 0")
self._rate = rate_per_sec
self._capacity = capacity
self._tokens = capacity
self._clock = clock
self._last = clock.now()
def allow(self, n: float = 1.0) -> bool:
if not _finite_nonneg(n):
raise ValueError("n must be finite and >= 0")
if n > self._capacity:
return False
now = self._clock.now()
elapsed = now - self._last
if elapsed > 0:
self._tokens = min(self._capacity, self._tokens + elapsed * self._rate)
self._last = now
if self._tokens >= n:
self._tokens -= n
return True
return False
一份简短的测试模块应该放在旁边。注意这个 freeze-clock 从不导入 time。如果某个提交在 allow 内部调用了 time.time,测试套件仍然只有在你的测试太弱时才能通过。先写答案就是这个意思。
import unittest
from bucket import FreezeClock, TokenBucket
class TokenBucketTests(unittest.TestCase):
def test_burst_then_empty(self):
clock = FreezeClock()
bucket = TokenBucket(rate_per_sec=1.0, capacity=2.0, clock=clock)
self.assertTrue(bucket.allow(2.0))
self.assertFalse(bucket.allow(1.0))
def test_refill_after_exact_elapsed(self):
clock = FreezeClock()
bucket = TokenBucket(rate_per_sec=2.0, capacity=2.0, clock=clock)
self.assertTrue(bucket.allow(2.0))
clock.advance(0.5)
self.assertTrue(bucket.allow(1.0))
self.assertFalse(bucket.allow(0.1))
def test_oversize_reject_does_not_consume(self):
clock = FreezeClock()
bucket = TokenBucket(rate_per_sec=1.0, capacity=1.0, clock=clock)
self.assertFalse(bucket.allow(2.0))
self.assertTrue(bucket.allow(1.0))
def test_invalid_n_raises(self):
clock = FreezeClock()
bucket = TokenBucket(1.0, 1.0, clock)
with self.assertRaises(ValueError):
bucket.allow(-1.0)
在本地跑一遍这份答案以确保它可信:
python -m unittest test_bucket
如果你的答案都跑不过,你就没有资格批改任何人的答案。这条规则比模型还要古老。
下面这个脚本是一个建议的评分器。把它当作一个未执行的示例,直到你用它跑你实际收集的文件。它不会替你给 Judgment 维度打分。它只抓那些便宜的零分:notes 缺失、套件失败,或者把 freeze-clock 变成睡觉的 sleep。
# grade_takehome.py — proposed local grader, unexecuted example
import json, pathlib, re, subprocess, sys
root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")
report = {"zeros": [], "notes": False, "unittest": False, "sleep": False}
bucket = root / "bucket.py"
tests = root / "test_bucket.py"
if not bucket.exists() or not tests.exists():
report["zeros"].append("missing bucket.py or test_bucket.py")
print(json.dumps(report, indent=2))
sys.exit(1)
src = bucket.read_text(encoding="utf-8")
if re.search(r"\btime\.sleep\b", src):
report["sleep"] = True
report["zeros"].append("discipline: time.sleep")
notes = root / "NOTES.md"
report["notes"] = notes.exists() and notes.stat().st_size > 40
if not report["notes"]:
report["zeros"].append("judgment: NOTES.md missing or empty")
proc = subprocess.run(
[sys.executable, "-m", "unittest", "test_bucket"],
cwd=root,
capture_output=True,
text=True,
)
report["unittest"] = proc.returncode == 0
if proc.returncode != 0:
report["zeros"].append("tests: unittest failed")
report["stderr_tail"] = proc.stderr[-800:]
print(json.dumps(report, indent=2))
sys.exit(0 if not report["zeros"] else 1)
你仍然要用自己的眼睛读 NOTES.md。模型可以写一个提到"线程"的段落然后仍然发一个全局 dict。测试架是一个门卫,你是面试官。
第一次崩溃是 sleep。模型不知道怎么测试时间,于是暂停墙钟然后指望蒙混过关。这样的提交在演示里看起来可能是并发的,但实际上是没法测试的。如果 time.sleep 出现在 bucket.py 里,Discipline 直接得零分然后打住。你不是在考验耐心。
第二次崩溃是整数 token。连续补充变成了 if elapsed >= 1。半秒的时间前进因此不起作用,而你为 rate_per_sec=2.0 写的那个测试会变红。这就是模型把一道数学题四舍五入成一个计数器——因为计数器看起来更像代码。
第三次崩溃是拒绝时仍然消耗。allow(2) 对容量为 1 的 bucket 返回 False 但仍然抽干了 bucket。演示很少能抓住这个。你的 oversize 测试就是为这种偷窃而存在的。
第四次崩溃是墙钟藏在测试里。套件在安静的笔记本上通过,在 CI 里却 flaky。如果 test_bucket.py 导入了 time 并调用了 time.time,那个 freeze-clock 就是一场表演。
第五次崩溃是附加分。Redis、Prometheus、一个 async context manager、一个 CLI 参数解析器。看起来像资深工程师。实际上是一个没能遵循一页纸需求文档的候选人。在人类面试流程里你会担心这种人。在这里你也应该担心。
第六次崩溃是道歉式的笔记。"在生产环境里还需要更多工作"不是 Judgment。命名那个竞态条件。说你之所以不管线程是因为需求禁止了锁和额外文件。然后闭嘴。
运行这个包不需要付费方案。工作样本很小,评分器是本地的,你唯一应该做的网络调用是向模型请求三个文件。
Disclosure: 本文是 MonkeyCode 产品推广的一部分。如果你已经有 MonkeyCode 的免费模型访问或免费服务器选项,把这个密封的 prompt 指向那个端点,保存原始文件,并把 JSON 记分卡放在旁边。这是唯一和产品相关的步骤。如果你明天换掉端点,评分表仍然有效。
你不是在测量智力。你是在测量这个端点、在这个时间点,能不能坐在一个写小型库和测试的循环里。在记分卡上标注日期。免费端点会流动。没有时间戳的分数只是谣言。
不要用这个包来代理人类候选人的评分。模型 take-home 不是一个人工作样本的合法或合适的替代品。不要把它当作安全审查、负载测试或 SLA。一个通过 unittest 的令牌桶在两个线程共享它的瞬间仍然可能是错的,这就是为什么笔记文件必须拒绝那个说法。
如果你在看到一段流畅的段落之后会修改评分表,就不要用这个。如果你用的 prompt 是一个没有 seed 的著名谜题,也不要用。记忆下来的答案不是工作样本。不要把它当作跳过阅读代码的理由。测试架会抓住 sleep 和缺失的测试。但如果一个 bucket 不是从剩余 token 而是从容量补充,它就抓不住——除非你的测试说了它。
一次 take-home 不代表生产就绪。它只是一个过滤吹牛的筛子。如果端点得了 7 分而你仍然不敢让它在没有监督的情况下开 pull request,相信这种感觉。这个包告诉你的是模型能遵循需求。它没有告诉你模型能对一次事故负责。
当有人贴出下一份光鲜的聊天记录时,打开记分卡而不是争论直觉。在你已有的文件上跑评分器。保留 JSON。那个文件比演示安静,而且它老去得更诚实。