🎯 CLM-8B: a model that scores instead of writing · CLM-8B โมเดลที่ให้คะแนน แทนการเขียน · CLM-8B:一个只打分、不写字的模型

Stanford and NVIDIA opened it on 23 September 2026. It never writes a word. It ranks the actions you already have. Build the same idea in five files, then read the real numbers. · สแตนฟอร์ดกับ NVIDIA เปิดมันเมื่อ 23 กันยายน 2026 มันไม่เขียนสักคำ มันจัดอันดับการกระทำที่คุณมีอยู่แล้ว สร้างแนวคิดเดียวกันในห้าไฟล์ แล้วไปอ่านตัวเลขจริง · 斯坦福和 NVIDIA 在 2026 年 9 月 23 日开源了它。它一个字都不写,只给你已有的动作排名。用五个文件做出同样的想法,然后去看真实数字。

🇬🇧 English

Most models answer by writing. They make one word, then the next, then the next. That takes time.

CLM never writes. It turns the situation into numbers. It turns each action into numbers too. Then it picks the closest pair.

Picking is much faster than writing. This page shows why, and what it costs you.

🇹🇭 ไทย

โมเดลส่วนใหญ่ตอบด้วยการเขียน มันสร้างคำหนึ่ง แล้วคำถัดไป แล้วคำถัดไป นั่นใช้เวลา

CLM ไม่เขียนเลย มันเปลี่ยนสถานการณ์ให้เป็นตัวเลข และเปลี่ยนการกระทำแต่ละอย่างเป็นตัวเลขด้วย แล้วมันเลือกคู่ที่ใกล้กันที่สุด

การเลือกเร็วกว่าการเขียนมาก หน้านี้จะบอกว่าทำไม และคุณต้องจ่ายอะไรแลกมา

🇨🇳 中文

大多数模型靠写字来回答。先造一个词,再下一个,再下一个。这要花时间。

CLM 从不写字。它把处境变成数字,也把每个动作变成数字,然后挑出最接近的一对。

挑,比写快得多。这一页说明为什么,以及你要为此付出什么。

Jev and LayaJev กับ LayaJev 和 LayaCLM-8BCLM-8BCLM-8B
What comes outสิ่งที่ออกมา出来的是什么a typed answer it writesคำตอบมีชนิดที่มันเขียนขึ้น它写出来的一个带类型的答案a score for each choice you gave itคะแนนของแต่ละตัวเลือกที่คุณให้ไป你给的每个选项各得一个分
Can it invent a new answerมันคิดคำตอบใหม่ได้ไหม它能想出新答案吗yesได้能no, neverไม่ได้เลย不能,绝对不能
Work per requestงานต่อหนึ่งคำขอ每次请求的工作量grows with the answerมากขึ้นตามความยาวคำตอบ随答案变长而变多one pass, then cheap sumsรอบเดียว แล้วบวกเลขถูก ๆ跑一遍,然后是便宜的加法
Same choices every timeตัวเลือกเดิมทุกครั้ง每次都是同一批选项no help at allไม่ช่วยอะไรเลย一点帮助都没有the big win, see step 5นี่คือจุดที่ได้เยอะ ดูขั้นที่ 5这才是大头,看第 5 步
WHAT YOU NEED FOR THIS PAGE You do not need CLM-8B itself. It wants a big GPU and a vLLM server. You need Ollama and one small embedding model. Run ollama pull nomic-embed-text once. That is 274 MB. Every file here uses only Python that comes with Python. คุณไม่จำเป็นต้องมี CLM-8B ตัวจริง มันต้องการ GPU ใหญ่และเซิร์ฟเวอร์ vLLM คุณแค่ต้องมี Ollama กับโมเดลฝังตัวเล็ก ๆ หนึ่งตัว รัน ollama pull nomic-embed-text ครั้งเดียว ขนาด 274 MB ทุกไฟล์ในหน้านี้ใช้แต่ของที่ติดมากับ Python 你不需要 CLM-8B 本体。它要一块大显卡和一个 vLLM 服务。你只需要 Ollama 和一个小的嵌入模型。跑一次 ollama pull nomic-embed-text,274 MB。这一页的每个文件只用 Python 自带的东西。

🎯 Tutorial 1 — Scoring, not writing · บทที่ 1 — ให้คะแนน ไม่ใช่เขียน · 教程 1 —— 打分,不是写字

🇬🇧 English

First, build the idea with a small model. Three actions, one customer message, three scores.

The first file gets the answer wrong. That is on purpose. The wrong answer is the reason CLM exists.

🇹🇭 ไทย

เริ่มจากสร้างแนวคิดด้วยโมเดลเล็ก ๆ สามการกระทำ ข้อความลูกค้าหนึ่งข้อความ สามคะแนน

ไฟล์แรกตอบผิด และนั่นตั้งใจ คำตอบที่ผิดนั่นแหละคือเหตุผลที่ CLM มีอยู่

🇨🇳 中文

先用一个小模型把想法搭出来。三个动作,一条客户消息,三个分数。

第一个文件会答错。这是故意的。那个错答案,正是 CLM 存在的理由。

1 Three actions, three numbers · สามการกระทำ สามตัวเลข · 三个动作,三个数字

🇬🇧 English

An embedding turns text into a row of numbers. Two texts that mean the same thing get two close rows.

So: embed the customer message. Embed each action. Measure which pair is closest. No writing anywhere.

🇹🇭 ไทย

การฝังตัวเปลี่ยนข้อความให้เป็นแถวตัวเลข ข้อความสองอันที่ความหมายเหมือนกันจะได้แถวที่ใกล้กัน

ดังนั้น ฝังข้อความลูกค้า ฝังการกระทำแต่ละอัน แล้ววัดว่าคู่ไหนใกล้ที่สุด ไม่มีการเขียนที่ไหนเลย

🇨🇳 中文

嵌入把文字变成一行数字。两段意思相同的文字,会得到两行很接近的数字。

所以:把客户消息嵌入,把每个动作嵌入,量一下哪一对最近。全程没有写字。

📄 NEW FILE · clm1.py

"""clm1.py - score every action, then take the best one."""
import json
import math
import urllib.request

OLLAMA = "http://localhost:11434/api/embed"
MODEL = "nomic-embed-text"


def embed(lines):
    """Turn a list of sentences into a list of number rows."""
    payload = json.dumps({"model": MODEL, "input": lines}).encode()
    request = urllib.request.Request(
        OLLAMA, data=payload, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=300) as answer:
        return json.load(answer)["embeddings"]


def closeness(one, two):
    """Cosine similarity. 1.0 is the same direction, 0.0 is unrelated."""
    dot = sum(a * b for a, b in zip(one, two))
    size_one = math.sqrt(sum(a * a for a in one))
    size_two = math.sqrt(sum(b * b for b in two))
    return dot / (size_one * size_two)


STATE = "my parcel never arrived and I want my money back"

# What the agent is allowed to do. The text after the colon is the whole
# description the model gets. It is written the way a tool list is written.
ACTIONS = [
    "open_refund: start a refund for the customer",
    "track_parcel: look up where the parcel is now",
    "reset_password: send a password reset link",
]

# One call embeds the state and all three actions together.
rows = embed([STATE] + ACTIONS)
state_row = rows[0]
action_rows = rows[1:]

print("one row is", len(state_row), "numbers long")
print()
for action, row in zip(ACTIONS, action_rows):
    print(f"{closeness(state_row, row):.4f}  {action.split(':')[0]}")

scores = [closeness(state_row, row) for row in action_rows]
print()
print("picked:", ACTIONS[scores.index(max(scores))].split(":")[0])

🖥️ WHAT IT PRINTED · 27 September 2026

one row is 768 numbers long

0.5513  open_refund
0.6262  track_parcel
0.4493  reset_password

picked: track_parcel
LineWhat it does · ทำอะไร · 做什么
embed([STATE] + ACTIONS)One call, four texts. The list comes back in the same order.เรียกครั้งเดียว สี่ข้อความ ลิสต์กลับมาเรียงลำดับเดิม一次调用,四段文字。列表按原顺序回来。
768How long one row is. Every text becomes this many numbers.ความยาวของหนึ่งแถว ทุกข้อความกลายเป็นตัวเลขจำนวนเท่านี้一行有多长。每段文字都变成这么多个数字。
closeness(one, two)The angle between two rows. Length does not matter, direction does.มุมระหว่างสองแถว ความยาวไม่สำคัญ ทิศทางสำคัญ两行之间的夹角。长度不重要,方向才重要。
scores.index(max(scores))Where the best score sits. That position is the answer.ตำแหน่งของคะแนนที่ดีที่สุด ตำแหน่งนั้นคือคำตอบ最高分在第几个。那个位置就是答案。
IT PICKED THE WRONG ONE, AND THAT IS THE LESSON The customer wants money back. The right action is open_refund. The model chose track_parcel, at 0.6262 against 0.5513. It is not broken. It is doing its job. It measures what a text is about. The message is about a parcel, so the parcel action wins. Nothing in this model knows what an agent ought to do. That gap is the whole reason CLM-8B was trained. ลูกค้าต้องการเงินคืน การกระทำที่ถูกคือ open_refund แต่โมเดลเลือก track_parcel ที่ 0.6262 เทียบกับ 0.5513 มันไม่ได้เสีย มันทำงานของมัน มันวัดว่าข้อความนั้นเกี่ยวกับอะไร ข้อความนี้เกี่ยวกับพัสดุ การกระทำเรื่องพัสดุจึงชนะ ไม่มีอะไรในโมเดลนี้ที่รู้ว่าเอเจนต์ควรทำอะไร ช่องว่างนั้นคือเหตุผลทั้งหมดที่ CLM-8B ถูกฝึกขึ้นมา 客户想要退钱。正确的动作是 open_refund。模型选了 track_parcel,0.6262 对 0.5513。它没坏,它在做本职工作:它量的是一段文字关于什么。这条消息关于包裹,所以包裹那个动作赢了。这个模型里没有任何东西知道一个智能体该做什么。这道缝,就是 CLM-8B 被训练出来的全部理由。
YOUR TURN
  1. Run it. Check you get 768 numbers too.
  2. Change the state to "I want a refund please". Now it is right.
  3. Add a fourth action of your own. Watch all four scores.
  4. Print the scores sorted. Which two are always close?
  1. รันดู ตรวจว่าคุณได้ 768 ตัวเลขเหมือนกัน
  2. เปลี่ยนสถานะเป็น "I want a refund please" ทีนี้มันถูกแล้ว
  3. เพิ่มการกระทำที่สี่ของคุณเอง แล้วดูคะแนนทั้งสี่
  4. พิมพ์คะแนนแบบเรียงลำดับ สองอันไหนที่ใกล้กันตลอด
  1. 跑一遍。看看你是不是也得到 768 个数字。
  2. 把状态换成 "I want a refund please"。现在它对了。
  3. 自己加第四个动作,看四个分数。
  4. 把分数排序打出来。哪两个总是挨得很近?

2 Change the words, watch the gap close · เปลี่ยนคำ แล้วดูช่องว่างแคบลง · 换个说法,看差距缩小

🇬🇧 English

The action list was written like a manual. Start a refund. That is what the tool does.

Try writing it as what the customer says instead. The customer wants their money back. Same action, different voice.

🇹🇭 ไทย

รายการการกระทำถูกเขียนแบบคู่มือ เริ่มการคืนเงิน นั่นคือสิ่งที่เครื่องมือทำ

ลองเขียนเป็นสิ่งที่ลูกค้าพูดแทน ลูกค้าต้องการเงินคืน การกระทำเดิม แต่คนละน้ำเสียง

🇨🇳 中文

这份动作清单是照说明书写的。开始退款。那是工具做的事。

试着改成写客户说的话。客户想要他们的钱回来。同一个动作,换一个口吻。

📄 NEW FILE · clm2.py

"""clm2.py - the same actions, described in the caller s own words."""
import json
import math
import urllib.request

OLLAMA = "http://localhost:11434/api/embed"
MODEL = "nomic-embed-text"


def embed(lines):
    """Turn a list of sentences into a list of number rows."""
    payload = json.dumps({"model": MODEL, "input": lines}).encode()
    request = urllib.request.Request(
        OLLAMA, data=payload, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=300) as answer:
        return json.load(answer)["embeddings"]


def closeness(one, two):
    """Cosine similarity. 1.0 is the same direction, 0.0 is unrelated."""
    dot = sum(a * b for a, b in zip(one, two))
    size_one = math.sqrt(sum(a * a for a in one))
    size_two = math.sqrt(sum(b * b for b in two))
    return dot / (size_one * size_two)


STATE = "my parcel never arrived and I want my money back"

# Written as a tool list: what the function does.
TOOL_WORDS = [
    "open_refund: start a refund for the customer",
    "track_parcel: look up where the parcel is now",
    "reset_password: send a password reset link",
]

# The same three actions, written as what the customer says instead.
STATE_WORDS = [
    "open_refund: the customer wants their money back",
    "track_parcel: the customer asks where their parcel is",
    "reset_password: the customer cannot log in",
]

state_row = embed([STATE])[0]

for label, actions in (("tool words ", TOOL_WORDS),
                       ("state words", STATE_WORDS)):
    rows = embed(actions)
    scores = [closeness(state_row, row) for row in rows]
    refund, parcel = scores[0], scores[1]
    winner = actions[scores.index(max(scores))].split(":")[0]
    print(f"{label}  refund {refund:.4f}   parcel {parcel:.4f}   "
          f"gap {parcel - refund:+.4f}   picked {winner}")

🖥️ WHAT IT PRINTED

tool words   refund 0.5513   parcel 0.6262   gap +0.0749   picked track_parcel
state words  refund 0.6329   parcel 0.6372   gap +0.0043   picked track_parcel
READ THE GAP The gap fell from 0.0749 to 0.0043. Seventeen times smaller. And the answer is still wrong.ช่องว่างลดจาก 0.0749 เหลือ 0.0043 เล็กลงสิบเจ็ดเท่า แต่คำตอบก็ยังผิดอยู่ดี差距从 0.0749 掉到 0.0043,小了十七倍。而答案还是错的。
WHY THIS MATTERS MORE THAN THE FIX Better words got you most of the way. They did not get you there. You could rewrite for a week. You would still be on the wrong side. That is the honest limit here. You did not train this model. CLM does not rewrite the words. It trains a small layer instead. That layer reads a million real agent runs. It learns that wants money back means the refund tool. Words cannot teach that. Examples can. คำที่ดีขึ้นพาคุณไปได้เกือบถึง แต่ไม่ถึง คุณเขียนใหม่ได้ทั้งสัปดาห์ แล้วก็ยังอยู่ผิดฝั่งของเส้นผมอยู่ดี นี่คือขีดจำกัดที่ซื่อตรงของโมเดลฝังตัวที่คุณไม่ได้ฝึกเอง CLM ไม่ได้แก้คำ มันฝึกชั้นเล็ก ๆ ที่เรียนรู้จากการทำงานจริงของเอเจนต์นับล้านครั้ง ว่า ต้องการเงินคืน ควรชี้ไปที่เครื่องมือคืนเงิน คำสอนเรื่องนั้นไม่ได้ ตัวอย่างสอนได้ 更好的措辞把你带到了大半路,但没带到终点。你可以改一个星期,仍然停在一根头发的错误那一侧。这就是一个你没亲手训练过的嵌入模型的诚实上限。CLM 不去改措辞。它训练一个小小的层,从上百万次真实的智能体运行里学到:想要退钱应该指向退款工具。措辞教不会这件事,例子可以。
YOUR TURN
  1. Run it. Compare the two gaps.
  2. Try to word the refund action so it finally wins.
  3. Now check your wording on "where is my order" too.
  4. Fixing one usually breaks the other. That is the point.
  1. รันดู เทียบช่องว่างทั้งสองอัน
  2. ลองเขียนการกระทำเรื่องคืนเงินให้มันชนะให้ได้
  3. แล้วเอาคำที่คุณเขียนไปลองกับ "where is my order" ด้วย
  4. แก้อันหนึ่งมักทำอีกอันพัง นั่นแหละประเด็น
  1. 跑一遍。把两个差距比一比。
  2. 试着把退款那个动作写到它终于能赢。
  3. 再把你的写法拿去试 "where is my order"。
  4. 修好一个,通常会弄坏另一个。这正是重点。

🧠 Tutorial 2 — Why CLM trains two heads · บทที่ 2 — ทำไม CLM ต้องฝึกสองหัว · 教程 2 —— CLM 为什么要训练两个头

🇬🇧 English

Step 2 showed the problem. Now here is the shape of the answer.

No code to run in this step. Read it, then go back to the keyboard in step 4.

🇹🇭 ไทย

ขั้นที่ 2 แสดงปัญหาให้เห็นแล้ว ทีนี้มาดูรูปร่างของคำตอบ

ขั้นนี้ไม่มีโค้ดให้รัน อ่านให้จบ แล้วกลับไปที่แป้นพิมพ์ในขั้นที่ 4

🇨🇳 中文

第 2 步把问题摆出来了。现在看看答案长什么样。

这一步没有代码要跑。读完,第 4 步再回到键盘前。

3 A state head and an action head · หัวสถานะ และหัวการกระทำ · 一个状态头,一个动作头

🇬🇧 English

CLM-8B keeps a big model frozen. It adds two tiny layers on top. One reads the state. One reads the actions.

They are trained together on real agent runs. The training pulls a state and its correct action close. It pushes the wrong ones away.

🇹🇭 ไทย

CLM-8B แช่โมเดลใหญ่ไว้ไม่ให้เปลี่ยน แล้วเติมชั้นเล็ก ๆ สองชั้นไว้ข้างบน ชั้นหนึ่งอ่านสถานะ อีกชั้นอ่านการกระทำ

ทั้งสองถูกฝึกไปด้วยกันบนการทำงานจริงของเอเจนต์ การฝึกจะดึงสถานะกับการกระทำที่ถูกต้องให้เข้ามาใกล้กัน และผลักการกระทำที่ผิดออกไป

🇨🇳 中文

CLM-8B 把一个大模型冻住,在上面加两个很小的层。一个读状态,一个读动作。

它们在真实的智能体运行记录上一起训练。训练会把状态和它正确的动作拉近,把错的动作推开。

Qwen3-8Bfrozen, never trainedแช่ไว้ ไม่ถูกฝึก冻住,不训练
state head~20M, reads the situation~20M อ่านสถานการณ์~20M,读处境
action head~20M, reads each choice~20M อ่านแต่ละตัวเลือก~20M,读每个选项
compareclosest pair winsคู่ที่ใกล้ที่สุดชนะ最近的一对赢
Training stageขั้นการฝึก训练阶段What it sawเห็นอะไร看了什么Top-1 after itTop-1 หลังจากนั้น之后的 Top-1
Pre-trainingฝึกก่อน预训练~60M Nemotron question and answer pairsคู่คำถามคำตอบ Nemotron ~60M คู่约 6000 万对 Nemotron 问答52.1%
Mid-trainingฝึกกลาง中期训练~30M hard wrong answers, written by Gemini 2.5 Flash-Liteคำตอบผิดแบบยาก ~30M อัน เขียนโดย Gemini 2.5 Flash-Lite约 3000 万个刁钻的错答案,由 Gemini 2.5 Flash-Lite 写69.2%
Post-trainingฝึกหลัง后期训练~1M real agent runsการทำงานจริงของเอเจนต์ ~1M ครั้ง约 100 万次真实智能体运行see step 7ดูขั้นที่ 7看第 7 步
THE ORDER OF THE TRAINING MATTERS, AND THEY MEASURED IT Easy examples first, hard ones second, gave 69.2%. Starting with the hard ones gave 62.4%. Then it got worse, not better. Same data, worse order, seven points lost. Teachers already know this one. Give a class the hardest sentence on day one. They learn less, not more. These numbers are the authors' own, from the model card. We did not measure them. ตัวอย่างง่ายก่อน ยากทีหลัง ได้ 69.2% ถ้าเริ่มจากของยากเลย ได้ 62.4% แล้วยังแย่ลงเรื่อย ๆ ไม่ใช่ดีขึ้น ข้อมูลชุดเดิม ลำดับแย่กว่า หายไปเจ็ดแต้ม ครูรู้เรื่องนี้อยู่แล้ว ถ้าให้ประโยคที่ยากที่สุดในวันแรก นักเรียนจะเรียนได้น้อยลง ไม่ใช่มากขึ้น ตัวเลขพวกนี้เป็นของผู้สร้างเอง จากการ์ดโมเดล เราไม่ได้วัดเอง 先易后难,得 69.2%。一上来就用难的,得 62.4%,而且越练越差,不是越练越好。同一批数据,顺序差一点,丢了七分。老师早就懂这个道理:第一天就给最难的句子,学生学到的更少,不是更多。这些数字是作者自己的,来自模型卡。我们没有亲手测过。
Partส่วน部件Sizeขนาด大小Noteหมายเหตุ说明
Base modelโมเดลฐาน底座模型Qwen3-8Bfrozen, so it is never changedแช่ไว้ จึงไม่เคยถูกเปลี่ยน冻住的,所以从不被改动
The two headsสองหัว两个头~20M each~20M ต่อหัว各约 2000 万the whole download is 75 MBไฟล์ที่ต้องโหลดทั้งหมด 75 MB整个下载只有 75 MB
Licenceสัญญาอนุญาต许可证Apache 2.0the heads are open, like Layaสองหัวนี้เปิด เหมือน Laya这两个头是开放的,和 Laya 一样
How much it readsอ่านได้เท่าไร能读多少2048 tokens2048 โทเคน2048 个词元short. A long state must be cut down.สั้น สถานะที่ยาวต้องถูกตัดให้สั้นลง很短。长的状态必须先裁掉。

⚡ Tutorial 3 — Why it is fast · บทที่ 3 — ทำไมมันถึงเร็ว · 教程 3 —— 它为什么快

🇬🇧 English

Here is the part you can measure on your own machine, today.

The speed does not come from the model being small. It comes from one old idea. Do not do the same work twice.

🇹🇭 ไทย

นี่คือส่วนที่คุณวัดเองบนเครื่องของคุณได้ วันนี้เลย

ความเร็วไม่ได้มาจากการที่โมเดลเล็ก มันมาจากแนวคิดเก่าข้อเดียว คืออย่าทำงานเดิมสองครั้ง

🇨🇳 中文

这一段你今天就能在自己机器上量出来。

快不是因为模型小。快来自一个很老的想法:同样的活别干两遍。

4 Keep the action vectors · เก็บเวกเตอร์ของการกระทำไว้ · 把动作向量留着

🇬🇧 English

Your three actions never change. So embedding them on every request is wasted work.

Embed them once. Keep the rows. After that, only the state is new.

🇹🇭 ไทย

การกระทำสามอย่างของคุณไม่เคยเปลี่ยน ดังนั้นการฝังมันทุกครั้งที่มีคำขอจึงเป็นงานที่เสียเปล่า

ฝังมันครั้งเดียว เก็บแถวไว้ หลังจากนั้นมีแค่สถานะที่ใหม่

🇨🇳 中文

你那三个动作从来不变。所以每次请求都把它们嵌入一遍,是白干。

嵌入一次,把那几行留着。之后只有状态是新的。

📄 NEW FILE · clm3.py

"""clm3.py - embed the actions once, then keep them."""
import json
import math
import time
import urllib.request

OLLAMA = "http://localhost:11434/api/embed"
MODEL = "nomic-embed-text"


def embed(lines):
    """Turn a list of sentences into a list of number rows."""
    payload = json.dumps({"model": MODEL, "input": lines}).encode()
    request = urllib.request.Request(
        OLLAMA, data=payload, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=300) as answer:
        return json.load(answer)["embeddings"]


def closeness(one, two):
    """Cosine similarity. 1.0 is the same direction, 0.0 is unrelated."""
    dot = sum(a * b for a, b in zip(one, two))
    size_one = math.sqrt(sum(a * a for a in one))
    size_two = math.sqrt(sum(b * b for b in two))
    return dot / (size_one * size_two)


ACTIONS = [
    "open_refund: start a refund for the customer",
    "track_parcel: look up where the parcel is now",
    "reset_password: send a password reset link",
]

STATES = [
    "my parcel never arrived and I want my money back",
    "where is my order right now",
    "I cannot log in any more",
]

ROUNDS = 10

# The plain way. Every request embeds the state and all three actions.
start = time.time()
for turn in range(ROUNDS):
    rows = embed([STATES[turn % len(STATES)]] + ACTIONS)
    scores = [closeness(rows[0], row) for row in rows[1:]]
plain = (time.time() - start) / ROUNDS

# The CLM way. The actions never change, so embed them once and keep them.
cached = embed(ACTIONS)
start = time.time()
for turn in range(ROUNDS):
    state_row = embed([STATES[turn % len(STATES)]])[0]
    scores = [closeness(state_row, row) for row in cached]
quick = (time.time() - start) / ROUNDS

print(f"embed the lot every time : {plain * 1000:7.1f} ms a request")
print(f"actions kept, state only : {quick * 1000:7.1f} ms a request")
print(f"times faster             : {plain / quick:7.2f}")

🖥️ WHAT IT PRINTED · on a DGX Spark, three actions

embed the lot every time :    33.5 ms a request
actions kept, state only :    16.7 ms a request
times faster             :    2.01
LineWhat it does · ทำอะไร · 做什么
cached = embed(ACTIONS)Runs once, before the loop. This one line is the whole trick.ทำงานครั้งเดียว ก่อนเข้าลูป บรรทัดเดียวนี้คือทริกทั้งหมด在循环之前跑一次。就这一行,是全部的招数。
embed([STATES[...]])[0]One text now, not four. That is where the time goes.ทีนี้ข้อความเดียว ไม่ใช่สี่ เวลาหายไปตรงนั้นแหละ现在只有一段文字,不是四段。时间就省在这。
closeness(state_row, row)Plain arithmetic. No model, no network, almost no time.เลขคณิตธรรมดา ไม่มีโมเดล ไม่มีเครือข่าย แทบไม่ใช้เวลา纯算术。没有模型,没有网络,几乎不花时间。
ROUNDS = 10Ten runs, then divide. One run alone tells you nothing.รันสิบรอบ แล้วหาร รอบเดียวไม่บอกอะไรคุณเลย跑十次再除。只跑一次什么也说明不了。
YOUR TURN
  1. Run it. Your machine will give different numbers.
  2. Run it twice. The second run is usually faster. Why?
  3. Set ROUNDS = 1. Now the number jumps around.
  4. Set it back to 10. Averages are how you measure.
  1. รันดู เครื่องของคุณจะให้ตัวเลขคนละแบบ
  2. รันสองครั้ง ครั้งที่สองมักเร็วกว่า ทำไม
  3. ตั้ง ROUNDS = 1 ทีนี้ตัวเลขจะเด้งไปมา
  4. ตั้งกลับเป็น 10 การวัดต้องใช้ค่าเฉลี่ย
  1. 跑一遍。你的机器会给出不一样的数。
  2. 跑两次。第二次通常更快。为什么?
  3. 把 ROUNDS 设成 1。现在数字开始乱跳。
  4. 再改回 10。要量东西,就得用平均值。

5 Thirty actions, three hundred actions · สามสิบ และสามร้อยการกระทำ · 三十个动作,三百个动作

🇬🇧 English

Two times faster is not much. But three actions is a toy. Real agents have dozens or hundreds.

Run the same test at three sizes. Watch what happens to the gap.

🇹🇭 ไทย

เร็วขึ้นสองเท่าไม่เยอะ แต่สามการกระทำคือของเล่น เอเจนต์จริงมีเป็นสิบหรือเป็นร้อย

รันการทดสอบเดิมที่สามขนาด แล้วดูว่าช่องว่างเป็นยังไง

🇨🇳 中文

快两倍不算多。但三个动作是玩具。真正的智能体有几十个甚至几百个。

在三个规模上跑同一个测试,看差距会怎样。

📄 NEW FILE · clm4.py

"""clm4.py - the more actions there are, the more the cache is worth."""
import json
import math
import time
import urllib.request

OLLAMA = "http://localhost:11434/api/embed"
MODEL = "nomic-embed-text"


def embed(lines):
    """Turn a list of sentences into a list of number rows."""
    payload = json.dumps({"model": MODEL, "input": lines}).encode()
    request = urllib.request.Request(
        OLLAMA, data=payload, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=300) as answer:
        return json.load(answer)["embeddings"]


def closeness(one, two):
    """Cosine similarity. 1.0 is the same direction, 0.0 is unrelated."""
    dot = sum(a * b for a, b in zip(one, two))
    size_one = math.sqrt(sum(a * a for a in one))
    size_two = math.sqrt(sum(b * b for b in two))
    return dot / (size_one * size_two)


STATE = "my parcel never arrived and I want my money back"
ROUNDS = 5

print(f"{'actions':>8}  {'no cache':>10}  {'cached':>9}  {'faster':>7}")
for count in (3, 30, 300):
    actions = [f"tool_{n}: handle support job number {n}" for n in range(count)]

    start = time.time()
    for turn in range(ROUNDS):
        rows = embed([STATE] + actions)
        scores = [closeness(rows[0], row) for row in rows[1:]]
    plain = (time.time() - start) / ROUNDS

    cached = embed(actions)
    start = time.time()
    for turn in range(ROUNDS):
        state_row = embed([STATE])[0]
        scores = [closeness(state_row, row) for row in cached]
    quick = (time.time() - start) / ROUNDS

    print(f"{count:8d}  {plain * 1000:9.1f}ms  {quick * 1000:8.1f}ms  "
          f"{plain / quick:6.2f}x")

🖥️ WHAT IT PRINTED · on a DGX Spark

 actions    no cache     cached   faster
       3       32.1ms      13.7ms    2.34x
      30      202.4ms      21.1ms    9.60x
     300     1560.8ms      29.4ms   53.02x
READ THE LAST COLUMN Ten times the actions, but the cached time barely moved. 13.7 ms to 29.4 ms. The other column went past a second.การกระทำมากขึ้นสิบเท่า แต่เวลาแบบเก็บแคชแทบไม่ขยับ จาก 13.7 ms เป็น 29.4 ms ขณะที่อีกคอลัมน์ทะลุหนึ่งวินาทีไปแล้ว动作多了十倍,但缓存那一列几乎没动。13.7 毫秒到 29.4 毫秒,而另一列已经冲过一秒。
THIS IS THE WHOLE ARGUMENT FOR CLM An agent with three tools does not need any of this. An agent with three hundred does. The action list is the part that grows. It is also the part you can store. So the work stops growing with your tool list. That is the claim. The authors report the same shape at their own scale. About 13 times faster than Jev, with a thousand candidates. เอเจนต์ที่มีสามเครื่องมือไม่ต้องใช้เรื่องนี้เลย เอเจนต์ที่มีสามร้อยเครื่องมือต้องใช้ รายการการกระทำคือส่วนที่โตขึ้น และรายการการกระทำก็คือส่วนที่คุณเก็บไว้ได้พอดี งานจึงหยุดโตตามรายการเครื่องมือของคุณ นั่นคือข้อกล่าวอ้าง ผู้สร้างรายงานรูปแบบเดียวกันในระดับของเขาเอง คือเร็วกว่า Jev ราว 13 เท่า เมื่อมีตัวเลือกราวหนึ่งพันอัน 只有三个工具的智能体根本不需要这套。有三百个的就需要。动作清单是会变长的那部分,而动作清单恰好也是你能存起来的那部分。于是工作量不再随你的工具清单增长。这就是那个主张。作者在他们自己的规模上报告了同样的形状:在大约一千个候选时,比 Jev 快约 13 倍。
OUR 53x AND THEIR 9x ARE NOT THE SAME MEASUREMENT Do not put these two numbers side by side. Ours compares one small model against itself. With a cache, then without. Theirs compares CLM-8B against Jev writing an answer. Different models, different machines, different baselines. Only the shape is shared. Caching helps more as the list grows. Borrow the shape. Do not borrow the number. อย่าเอาตัวเลขสองตัวนี้มาวางข้างกัน ของเราเทียบโมเดลฝังตัวเล็ก ๆ กับตัวมันเอง แบบมีแคชกับไม่มีแคช ของเขาเทียบ CLM-8B กับ Jev ที่กำลังเขียนคำตอบ คนละโมเดล คนละเครื่อง คนละฐานเปรียบเทียบ สิ่งที่เหมือนกันมีแค่รูปแบบ คือแคชช่วยมากขึ้นเมื่อรายการการกระทำยาวขึ้น ยืมรูปแบบไปได้ อย่ายืมตัวเลข 别把这两个数字并排放。我们的是拿一个小嵌入模型和它自己比,有缓存对没缓存。他们的是拿 CLM-8B 和正在写答案的 Jev 比。模型不同,机器不同,基准不同。相同的只有形状:动作清单越长,缓存越有用。借那个形状,别借那个数字。
YOUR TURN
  1. Run it. Three sizes, one table.
  2. Add 1000 to the list of sizes. Be patient.
  3. Time only the closeness sums. They are tiny.
  4. So the cost is the embedding, not the comparing.
  1. รันดู สามขนาด ตารางเดียว
  2. เพิ่ม 1000 เข้าไปในรายการขนาด ใจเย็น ๆ
  3. จับเวลาเฉพาะการบวกใน closeness มันเล็กมาก
  4. แปลว่าต้นทุนอยู่ที่การฝังตัว ไม่ใช่การเปรียบเทียบ
  1. 跑一遍。三个规模,一张表。
  2. 在规模列表里加上 1000。耐心点。
  3. 只给 closeness 的加法计时。它非常小。
  4. 所以代价在嵌入,不在比较。

🚧 Tutorial 4 — Where it breaks · บทที่ 4 — มันพังตรงไหน · 教程 4 —— 它在哪里失灵

🇬🇧 English

A picker always picks. Ask it about nothing on its list. It still hands you something.

That is the danger, and the fix is short.

🇹🇭 ไทย

ตัวเลือกย่อมเลือกเสมอ ถ้าให้คำถามที่ไม่เกี่ยวกับอะไรในรายการเลย มันก็ยังยื่นอะไรสักอย่างมาให้

นั่นคืออันตราย และวิธีแก้ก็สั้น

🇨🇳 中文

会挑的东西总是会挑。给它一个和清单上任何东西都无关的问题,它照样递给你一个。

这就是危险所在,而解法很短。

6 A floor under the best score · ตั้งพื้นไว้ใต้คะแนนที่ดีที่สุด · 给最高分设一条底线

🇬🇧 English

Ask it about the capital of Peru. None of the three actions fit at all.

It still answers. But the score is low, and a low score is information. Set a floor and use it.

🇹🇭 ไทย

ลองถามมันเรื่องเมืองหลวงของเปรู ไม่มีการกระทำไหนในสามอันเข้าเค้าเลย

มันก็ยังตอบ แต่คะแนนต่ำ และคะแนนต่ำคือข้อมูล ตั้งพื้นไว้ แล้วใช้มัน

🇨🇳 中文

问它秘鲁的首都。三个动作没有一个沾边。

它照样答。但分数很低,而低分本身就是信息。设一条底线,然后用它。

📄 NEW FILE · clm5.py

"""clm5.py - a floor, because the best of three may still be wrong."""
import json
import math
import urllib.request

OLLAMA = "http://localhost:11434/api/embed"
MODEL = "nomic-embed-text"


def embed(lines):
    """Turn a list of sentences into a list of number rows."""
    payload = json.dumps({"model": MODEL, "input": lines}).encode()
    request = urllib.request.Request(
        OLLAMA, data=payload, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=300) as answer:
        return json.load(answer)["embeddings"]


def closeness(one, two):
    """Cosine similarity. 1.0 is the same direction, 0.0 is unrelated."""
    dot = sum(a * b for a, b in zip(one, two))
    size_one = math.sqrt(sum(a * a for a in one))
    size_two = math.sqrt(sum(b * b for b in two))
    return dot / (size_one * size_two)


ACTIONS = [
    "open_refund: start a refund for the customer",
    "track_parcel: look up where the parcel is now",
    "reset_password: send a password reset link",
]

STATES = [
    "my parcel never arrived and I want my money back",
    "what is the capital of Peru",
]

# Below this, no action is close enough to trust.
FLOOR = 0.60

cached = embed(ACTIONS)
for state in STATES:
    state_row = embed([state])[0]
    scores = [closeness(state_row, row) for row in cached]
    best = max(scores)
    name = ACTIONS[scores.index(best)].split(":")[0]
    print(f"state : {state}")
    print(f"best  : {name} at {best:.4f}")
    if best < FLOOR:
        print("        under the floor, so hand it to a person")
    print()

🖥️ WHAT IT PRINTED

state : my parcel never arrived and I want my money back
best  : track_parcel at 0.6262

state : what is the capital of Peru
best  : track_parcel at 0.4077
        under the floor, so hand it to a person
WITHOUT A FLOOR, YOUR AGENT TRACKS A PARCEL FOR A QUESTION ABOUT PERU Read the second answer. It picked track_parcel for a geography question. With no floor, the agent would have run it. The number 0.4077 was there the whole time, saying "none of these". A picker cannot say no on its own. You have to give it a way to. One if does it. อ่านคำตอบที่สอง มันเลือก track_parcel ให้คำถามภูมิศาสตร์ ถ้าไม่มีพื้น เอเจนต์ก็จะรันมันไปแล้ว เลข 0.4077 อยู่ตรงนั้นมาตลอด และมันกำลังบอกว่า "ไม่มีอันไหนเลย" ตัวเลือกพูดว่าไม่เองไม่ได้ คุณต้องให้ทางกับมัน if เดียวก็พอ 读第二个答案。它给一道地理题选了 track_parcel。没有底线的话,智能体就真去跑了。0.4077 这个数一直都在,它在说"一个都不合适"。一个只会挑的东西自己说不了"不"。你得给它一条路。一个 if 就够了。
THE SAME LESSON AS THE JEV PAGE The Jev page made the same point. A number you can threshold beats a word you must read. Here it is again, from the other end. CLM gives you a number for every choice. The number is not just a ranking. It is also a confidence, and you should spend it. ใน หน้า Jev ประเด็นคือตัวเลขที่คุณตั้งเกณฑ์ได้ ดีกว่าคำที่คุณต้องอ่านเอง ตรงนี้ก็เรื่องเดียวกัน แต่มาจากอีกด้าน CLM ให้ตัวเลขกับทุกตัวเลือก ตัวเลขนั้นไม่ใช่แค่อันดับ มันคือความมั่นใจด้วย และคุณควรใช้มัน 在 Jev 那一页,重点是:一个你能设阈值的数字,胜过一个你得去读的词。这里是同一课,从另一头来。CLM 给每个选项一个数字。那个数字不只是排名,它同时是信心,你该把它用掉。
YOUR TURN
  1. Run it. Note the two scores.
  2. Add three questions of your own that fit nothing.
  3. Write down the highest score any of them got.
  4. Set FLOOR just above it. That is how you choose one.
  1. รันดู จดคะแนนทั้งสองไว้
  2. เพิ่มคำถามของคุณเองสามข้อที่ไม่เข้ากับอะไรเลย
  3. จดคะแนนสูงสุดที่ข้อไหนสักข้อทำได้
  4. ตั้ง FLOOR ให้สูงกว่านั้นนิดเดียว นั่นคือวิธีเลือกค่า
  1. 跑一遍。把两个分数记下来。
  2. 自己加三个和清单毫无关系的问题。
  3. 记下它们当中最高的那个分。
  4. 把 FLOOR 设得比它高一点点。底线就是这样定的。

📊 Tutorial 5 — What CLM-8B really scored · บทที่ 5 — CLM-8B ทำคะแนนได้เท่าไรจริง ๆ · 教程 5 —— CLM-8B 到底考了多少

🇬🇧 English

Now the real model. Everything below is published by the authors. We did not run CLM-8B.

The headline says nine times faster. The table says something more useful.

🇹🇭 ไทย

ทีนี้มาดูโมเดลจริง ทุกอย่างข้างล่างนี้ผู้สร้างเป็นคนเผยแพร่ เราไม่ได้รัน CLM-8B เอง

พาดหัวบอกว่าเร็วกว่าเก้าเท่า แต่ตารางบอกอะไรที่มีประโยชน์กว่านั้น

🇨🇳 中文

现在看真正的模型。下面所有内容都是作者发布的。我们没有跑过 CLM-8B。

标题说快九倍。表格说的东西更有用。

7 Where it wins, and where it loses · ชนะตรงไหน และแพ้ตรงไหน · 哪里赢,哪里输

🇬🇧 English

Four tasks, no fine-tuning. Green is the winner in that row. Read the last two columns, not the first two.

🇹🇭 ไทย

สี่งาน ไม่มีการปรับจูน สีเขียวคือผู้ชนะในแถวนั้น อ่านสองคอลัมน์สุดท้าย ไม่ใช่สองคอลัมน์แรก

🇨🇳 中文

四个任务,没有微调。绿色是那一行的赢家。看最后两列,别看前两列。

Taskงาน任务CLM timeเวลา CLMCLM 用时Jev timeเวลา JevJev 用时CLM got rightCLM ทำถูกCLM 做对Jev got rightJev ทำถูกJev 做对
T-Rex gameเกม T-RexT-Rex 小游戏16.5 ms149.8 ms5/55/5
Super Marioซูเปอร์มาริโอ超级马力欧33.5 ms132.6 ms5/55/5
Tool callingการเรียกเครื่องมือ工具调用76.8 ms125.5 ms95.2%99.2%
WikiRacingWikiRacingWikiRacing79.8 ms225 ms26/3030/30
THE NINE TIMES IS THE DINOSAUR GAME 16.5 against 149.8 is the T-Rex row. Nine times comes from there. It is the easiest task on the list. The same few actions come round again and again. So the stored vectors always fit. Look down the table and the lead shrinks. On tool calling it is 1.6 times. CLM gets 95.2. Jev gets 99.2. On WikiRacing it loses four runs out of thirty. You are buying speed with accuracy. How good the deal is depends on how often your actions repeat. 16.5 เทียบกับ 149.8 คือแถว T-Rex และนั่นคือที่มาของเก้าเท่า มันเป็นงานที่ง่ายที่สุดในรายการ การกระทำไม่กี่อย่างวนกลับมาซ้ำแล้วซ้ำอีก เวกเตอร์ที่เก็บไว้จึงใช้ได้ตลอด ไล่ลงมาในตาราง ระยะนำจะหดลง ในการเรียกเครื่องมือเหลือ 1.6 เท่า และ CLM ได้ 95.2 ขณะที่ Jev ได้ 99.2 ใน WikiRacing มันแพ้ไปสี่ครั้งจากสามสิบ คุณกำลังซื้อความเร็วด้วยความแม่นยำ ดีลจะคุ้มแค่ไหนขึ้นอยู่กับว่าการกระทำของคุณซ้ำบ่อยแค่ไหน 16.5 对 149.8 是 T-Rex 那一行,九倍就是从这来的。它是清单上最简单的任务。同样那几个动作一遍遍回来,存好的向量总是用得上。往下看,领先就缩水了。工具调用只剩 1.6 倍,而且 CLM 拿 95.2,Jev 拿 99.2。WikiRacing 里它三十次输掉四次。你是在用准确率买速度。这笔买卖划不划算,取决于你的动作重复得有多频繁。

🇬🇧 English

Trained for one job, it does better. Here it grades other models' work instead of acting.

🇹🇭 ไทย

ถ้าฝึกมาเพื่องานเดียว มันทำได้ดีกว่า ตรงนี้มันตรวจงานของโมเดลอื่น แทนที่จะลงมือทำเอง

🇨🇳 中文

只为一件事训练过,它表现更好。这里它是在给别的模型的作业打分,不是自己动手。

Benchmarkชุดทดสอบ测试集No graderไม่มีผู้ตรวจ没有评分者CLM grades itCLM ตรวจCLM 来评Jev grades itJev ตรวจJev 来评Time eachเวลาต่ออัน各自用时
DeepSWE73.7%81.6%71.1%79 ms vs 449 ms79 ms เทียบ 449 ms79 毫秒 对 449 毫秒
Terminal-Bench 2.184.0%87.6%83.1%32 ms vs 131 ms32 ms เทียบ 131 ms32 毫秒 对 131 毫秒
SMALL TEST SETS, AND NOT THE OFFICIAL LEADERBOARD DeepSWE here is 38 tasks. Terminal-Bench is 30. The authors say so plainly. These are held-out subsets, not full runs. Thirty tasks is not many. One or two going the other way moves the number a lot. Treat these as promising, not settled. Wait for someone outside the team. DeepSWE ตรงนี้คือ 38 งาน ส่วน Terminal-Bench คือ 30 งาน ผู้สร้างบอกตรง ๆ ว่านี่คือชุดย่อยที่กันไว้ ไม่ใช่การรันบนกระดานผู้นำเต็มรูปแบบ สามสิบงานไม่เยอะ ถ้าพลิกไปสักหนึ่งหรือสองงาน ตัวเลขจะขยับเยอะ ให้ถือว่านี่น่าสนใจ ยังไม่ใช่ข้อสรุป จนกว่าจะมีคนนอกทีมรันดู 这里的 DeepSWE 是 38 个任务,Terminal-Bench 是 30 个。作者明说了这是留出来的子集,不是完整的排行榜跑分。三十个任务不算多,一两个翻过去,数字就会动不少。在团队之外有人跑过之前,把它当成有希望,而不是有定论。
YOUR TURN
  1. Count your own agent's tools. Three, or three hundred?
  2. Count how often the same tool runs twice in a row.
  3. Those two numbers tell you if CLM would help you.
  4. Ask one more: can you live with 95 instead of 99?
  1. นับเครื่องมือของเอเจนต์คุณเอง สามอัน หรือสามร้อยอัน
  2. นับว่าเครื่องมือเดิมถูกเรียกซ้ำติดกันบ่อยแค่ไหน
  3. สองตัวเลขนั้นจะบอกคุณว่า CLM จะช่วยคุณได้ไหม
  4. ถามอีกข้อ คุณรับ 95 แทน 99 ได้หรือเปล่า
  1. 数数你自己智能体的工具。三个,还是三百个?
  2. 数数同一个工具连着跑两次的频率有多高。
  3. 这两个数字会告诉你 CLM 对你有没有用。
  4. 再问一个:95 换 99,你受得了吗?

🏆 Tutorial 6 — Five challenges · บทที่ 6 — โจทย์ห้าข้อ · 教程 6 —— 五道题

🇬🇧 English

Each one starts from a file you already have. Try it before you open the answer.

🇹🇭 ไทย

ทุกข้อเริ่มจากไฟล์ที่คุณมีอยู่แล้ว ลองทำเองก่อนเปิดเฉลย

🇨🇳 中文

每一题都从你已有的文件出发。先自己试,再打开答案。

8 Try first. The answers are folded away. · ลองเองก่อน เฉลยพับซ่อนไว้ · 先自己试。答案折起来放着。

1Show the second choice too · แสดงตัวเลือกที่สองด้วย · 把第二名也显示出来

🇬🇧 English

Print all three actions in order, best first, with their scores.

A near tie is worth seeing. Step 2 was one.

🇹🇭 ไทย

พิมพ์การกระทำทั้งสามเรียงตามลำดับ ดีที่สุดก่อน พร้อมคะแนน

ผลที่เกือบเสมอกันควรได้เห็น ขั้นที่ 2 ก็เป็นแบบนั้น

🇨🇳 中文

把三个动作按顺序打出来,最好的在前,带上分数。

接近平手的情况值得看见。第 2 步就是一个。

Show the answerดูเฉลย看答案
# Add this to the end of clm1.py, in place of the single print.
# zip pairs each action with its score. sorted puts the best first,
# because reverse=True counts down instead of up.
pairs = sorted(zip(scores, ACTIONS), reverse=True)
for place, (score, action) in enumerate(pairs, start=1):
    print(f"{place}. {score:.4f}  {action.split(':')[0]}")

# The distance between first and second is how sure it is.
print(f"\nlead over second place: {pairs[0][0] - pairs[1][0]:.4f}")

🖥️ WHAT IT PRINTED

1. 0.6262  track_parcel
2. 0.5513  open_refund
3. 0.4493  reset_password

lead over second place: 0.0749

2Refuse a close call · ปฏิเสธเมื่อคะแนนสูสี · 太接近就别答

🇬🇧 English

Step 6 refused when the best score was low. Now refuse for a second reason.

If first and second are very close, the model is guessing. Say so.

🇹🇭 ไทย

ขั้นที่ 6 ปฏิเสธเมื่อคะแนนดีที่สุดยังต่ำ ทีนี้ปฏิเสธด้วยเหตุผลที่สอง

ถ้าที่หนึ่งกับที่สองใกล้กันมาก แปลว่าโมเดลกำลังเดา ให้บอกออกมา

🇨🇳 中文

第 6 步在最高分太低时拒绝。现在为第二个理由拒绝。

如果第一和第二非常接近,模型是在猜。把这件事说出来。

Show the answerดูเฉลย看答案
# Two ways to be unsure, so two tests. Put this in place of the
# single check in clm5.py.
FLOOR = 0.60      # nothing is close enough
LEAD = 0.05       # the top two are too near each other

ranked = sorted(zip(scores, ACTIONS), reverse=True)
best, second = ranked[0][0], ranked[1][0]

if best < FLOOR:
    print("nothing fits, hand it to a person")
elif best - second < LEAD:
    print(f"too close to call: {best - second:.4f} apart, ask the customer")
else:
    print("picked:", ranked[0][1].split(":")[0])

🖥️ WHAT IT PRINTED · first with the step 1 wording, then the step 2 wording

picked: track_parcel
too close to call: 0.0043 apart, ask the customer
THIS TURNS STEP 2 INTO A QUESTION INSTEAD OF A MISTAKE In step 2 the two best actions were 0.0043 apart. Under this rule the agent stops and asks. A refund is not a parcel search. The customer feels that difference. Asking costs one message. Getting it wrong costs money. ในขั้นที่ 2 การกระทำสองอันที่ดีที่สุดห่างกัน 0.0043 ภายใต้กฎนี้เอเจนต์จะหยุดแล้วถาม การคืนเงินกับการตามหาพัสดุเป็นคนละเรื่องกันมากสำหรับลูกค้า การถามมีต้นทุนหนึ่งข้อความ การทำผิดมีต้นทุนเป็นเงิน 在第 2 步里,最好的两个动作只差 0.0043。按这条规则,智能体会停下来问。对客户来说,退款和查包裹是很不一样的两件事。问一句的代价是一条消息,做错的代价是钱。

3Save the cache to a file · บันทึกแคชลงไฟล์ · 把缓存存进文件

🇬🇧 English

Right now the cache dies when the program ends. Write it to a file instead.

Load it if it is there. Build it if it is not.

🇹🇭 ไทย

ตอนนี้แคชตายเมื่อโปรแกรมจบ เขียนมันลงไฟล์แทน

ถ้ามีไฟล์อยู่ก็โหลด ถ้าไม่มีก็สร้างใหม่

🇨🇳 中文

现在程序一结束缓存就没了。把它写进文件吧。

有文件就读,没有就建。

Show the answerดูเฉลย看答案
# A list of number rows is plain JSON, so no extra library is needed.
import os

CACHE = "actions.json"

if os.path.exists(CACHE):
    with open(CACHE) as saved:
        cached = json.load(saved)
    print("loaded", len(cached), "action rows from", CACHE)
else:
    cached = embed(ACTIONS)
    with open(CACHE, "w") as saved:
        json.dump(cached, saved)
    print("built and saved", len(cached), "action rows")

🖥️ WHAT IT PRINTED · run it twice

built and saved 3 action rows
loaded 3 action rows from actions.json
THE FILE AND THE ACTION LIST CAN DRIFT APART Change the wording of an action. The saved rows are now wrong. They describe the old words. Nothing warns you. Save the action text next to the rows. Compare it on load. If they differ, build the cache again. ถ้าเปลี่ยนคำอธิบายของการกระทำ แถวที่บันทึกไว้จะผิดทันที เพราะมันอธิบายคำเก่า และไม่มีอะไรเตือนคุณ ให้บันทึกข้อความของการกระทำไว้ข้าง ๆ แถวด้วย แล้วเทียบกันตอนโหลด ถ้าต่างกัน ให้สร้างแคชใหม่ 改了某个动作的措辞,存下来的那些行立刻就错了——它们描述的是旧措辞,而且没有任何提示。把动作原文和那些行一起存下来,加载时比对一下。不一样就重建缓存。

4Find the two actions that confuse each other · หาสองการกระทำที่สับสนกันเอง · 找出互相混淆的那两个动作

🇬🇧 English

Two actions that sit close together will be mixed up often. Find them before your users do.

Score every action against every other action.

🇹🇭 ไทย

การกระทำสองอันที่อยู่ใกล้กันจะถูกสลับกันบ่อย หาให้เจอก่อนผู้ใช้ของคุณจะเจอ

ให้คะแนนการกระทำทุกอันเทียบกับทุกอันที่เหลือ

🇨🇳 中文

两个挨得很近的动作会经常被搞混。在你的用户发现之前先找出来。

把每个动作和其他每个动作都打一次分。

Show the answerดูเฉลย看答案
# Compare each action with the ones after it, so no pair is done twice.
rows = embed(ACTIONS)
pairs = []
for first in range(len(ACTIONS)):
    for second in range(first + 1, len(ACTIONS)):
        score = closeness(rows[first], rows[second])
        pairs.append((score, ACTIONS[first].split(":")[0],
                      ACTIONS[second].split(":")[0]))

for score, one, two in sorted(pairs, reverse=True):
    warning = "  <- too close" if score > 0.75 else ""
    print(f"{score:.4f}  {one} vs {two}{warning}")

🖥️ WHAT IT PRINTED · with the three actions from clm1.py

0.6258  open_refund vs reset_password
0.6013  open_refund vs track_parcel
0.5414  track_parcel vs reset_password
THIS TEST DID NOT FIND THE BUG FROM STEP 1 Read the top line. The closest pair is open_refund and reset_password, at 0.6258. But step 1 went wrong a different way: track_parcel beat open_refund. That pair is only second here. So this test missed it. Comparing two actions asks one thing. Do these two sound alike? Comparing an action with a state asks another. Does this action suit this customer? A pair can be safe on one test. And dangerous on the other. Run both. Neither one covers for the other. อ่านบรรทัดบนสุด คู่ที่ใกล้กันที่สุดคือ open_refund กับ reset_password ที่ 0.6258 แต่ขั้นที่ 1 ผิดคนละแบบ คือ track_parcel ชนะ open_refund คู่นั้นอยู่อันดับสองตรงนี้เท่านั้น การทดสอบนี้จึงพลาดมันไป การเทียบการกระทำสองอันคือถามว่า "สองอันนี้ฟังดูเหมือนกันไหม" ส่วนการเทียบการกระทำกับสถานะคือถามว่า "การกระทำนี้เหมาะกับลูกค้าคนนี้ไหม" นั่นคือคนละคำถาม และคู่หนึ่งอาจปลอดภัยในคำถามหนึ่ง แต่อันตรายในอีกคำถาม ให้รันทั้งสองอย่าง ไม่มีอันไหนแทนอีกอันได้ 看最上面一行。最接近的一对是 open_refund 和 reset_password,0.6258。但第 1 步错的是另一种:track_parcel 赢了 open_refund。那一对在这里只排第二。所以这个检查漏掉了它。比较两个动作,问的是"这两个听起来像吗";比较动作和状态,问的是"这个动作适合这位客户吗"。这是两个不同的问题,一对动作可能在一个问题上安全,在另一个问题上危险。两个都跑。谁也替不了谁。

5Make the cache pay for itself · ทำให้แคชคุ้มค่าตัวเอง · 让缓存把自己赚回来

🇬🇧 English

Building the cache costs time up front. How many requests before it has paid that back?

Measure it. Do not guess.

🇹🇭 ไทย

การสร้างแคชมีต้นทุนเวลาตอนเริ่ม ต้องใช้กี่คำขอมันถึงจะคืนทุน

วัดเอา อย่าเดา

🇨🇳 中文

建缓存一开始要花时间。要多少次请求才把这笔时间赚回来?

量出来,别猜。

Show the answerดูเฉลย看答案
# Time the one-off cost of building the cache.
start = time.time()
cached = embed(ACTIONS)
build = time.time() - start

# plain and quick are the two per-request times from clm3.py.
saved_each = plain - quick
requests = build / saved_each

print(f"building the cache took : {build * 1000:.1f} ms")
print(f"saved on each request   : {saved_each * 1000:.1f} ms")
print(f"pays for itself after   : {requests:.1f} requests")

🖥️ WHAT IT PRINTED · three actions, on a DGX Spark

building the cache took : 19.2 ms
saved on each request   : 23.0 ms
pays for itself after   : 0.8 requests
WHY THIS IS THE LAST CHALLENGE 0.8 requests. The cache pays for itself before the first request has even finished. With three hundred actions it takes longer to build. But it saves far more each time. The answer stays small. That is why this idea works in real systems. Not only in tests. There is almost no case where building it was not worth it. ถ้ามีสามการกระทำ แคชคืนทุนแทบจะทันที ถ้ามีสามร้อยอัน มันใช้เวลาสร้างนานกว่า แต่ก็ประหยัดได้มากกว่ามากในแต่ละครั้ง ไม่ว่าทางไหน คำตอบคือคำขอจำนวนไม่กี่ครั้ง เซิร์ฟเวอร์ทำได้ในหนึ่งวินาที นี่คือเหตุผลที่แนวคิดนี้ใช้ได้จริงในระบบจริง ไม่ใช่แค่ในการทดสอบ 只有三个动作时,缓存几乎立刻回本。三百个时建起来更久,但每次省得多得多。不管哪种,答案都是很少的几次请求。服务器一秒就处理掉了。这就是为什么这个想法在真实系统里成立,而不只是在测试里成立。

🇬🇧 English

You built a picker in five files. It was wrong on the first try, and you found out why.

CLM-8B is the same shape, trained properly. Two small heads on a frozen model. A million real runs taught them.

It is faster because it does not write. It also remembers the choices. It is less accurate for the same reason. Now you can tell whether that trade suits you.

🇹🇭 ไทย

คุณสร้างตัวเลือกขึ้นมาในห้าไฟล์ มันตอบผิดตั้งแต่ครั้งแรก และคุณได้รู้ว่าทำไม

CLM-8B คือรูปร่างเดียวกัน แต่ฝึกมาอย่างถูกต้อง หัวเล็ก ๆ สองหัวบนโมเดลที่แช่ไว้ สอนด้วยการทำงานจริงนับล้านครั้ง

มันเร็วกว่าเพราะมันไม่เขียน และเพราะมันจำตัวเลือกไว้ มันแม่นน้อยกว่าด้วยเหตุผลเดียวกัน ทีนี้คุณบอกเองได้แล้วว่าการแลกแบบนี้เหมาะกับคุณไหม

🇨🇳 中文

你用五个文件做出了一个挑选器。它第一次就答错了,而你知道了为什么。

CLM-8B 是同样的形状,只是训练得当。冻住的模型上两个小头,由上百万次真实运行教出来。

它快,因为它不写字,也因为它记住了选项。它准确率低,也是同样的原因。现在你能自己判断这笔交易适不适合你了。

You wantคุณอยากได้你想要Look upไปหาคำว่า去查
The model itselfตัวโมเดลเอง模型本体pip install contrastive-lm
The weightsค่าน้ำหนัก权重Contrastive-LM/CLM-v0.1-8B
Millions of vectors, searched fastเวกเตอร์เป็นล้าน ค้นหาเร็ว上百万向量,快速搜索faiss
Better embeddings than nomicการฝังตัวที่ดีกว่า nomic比 nomic 更好的嵌入mxbai-embed-large
To train heads like these yourselfฝึกหัวแบบนี้ด้วยตัวเอง自己训练这样的头InfoNCE loss