token、采样与上下文窗口如何决定 Agent 的行为边界

2026-11-09 18:30:13 Agent开发

Token

  • 一个 token 可以是半个字、一个字、两个字、一个词、一段字母,取决于分词词表,不是看语义上能不能拆开。
  • Tokenizer 实现id与token的映射,大模型通过id再去拿向量去计算
Text
1
2
3
4
5
6
7
8
9
10
11
人类文本:我爱AI
↓ tokenizer分词
token列表:["我", "爱", "AI"]
↓ 查词表映射
token ID序列:[345, 782, 9641]
✅ 传给大模型
↓ 模型内部Embedding层查表
向量序列 → 进入Transformer计算
↓ 计算输出,得到下一个token的概率分布
↓ 选出下一个ID
↓ detokenizer:ID → token → 拼接成文字

采样

大模型回复是一个个token输出出来的,每输出一个token需要做下一个token的选择,这里称为采样。

假设现在上下文是:春天来了,公园里开了

  1. 先经过 tokenizer,转成一串 ID 给模型
  2. Transformer 跑完计算,输出一个巨大的列表:词表里面每一个 token,作为下一个词的概率示例(简化):

概率总和 = 1。理论上,下一个 token 可以是词表里任意一个,只是大部分概率极低。

token 概率
花 0.70
树 0.15
草 0.08
鸟 0.04
坦克 0.0001
…… ……

重点:模型本身只算出这堆原始概率,它不负责 “挑哪个”。挑哪个,就是采样器干的活,temperature /top_p/top_k 都是采样器参数。

原始概率分布 → top_k 过滤 → top_p 过滤 → temperature 缩放概率 → 随机采样选出下一个 token

  • top_k:按数量砍候选,其他PASS。topK为1top_k=1:只留第一名,永远选概率最高的,结果固定不变(贪心解码)top_k=0 / 不填:关闭,不做这一步过滤如果合理的候选有 5 个,你设 k=3,强行砍掉 2 个合理选项;如果只有 2 个高概率词,k=100 也没用。

  • top_p:按累计概率砍候选,从最高概率开始累加,加到总和 ≥ p,就停,后面全部删掉

  • temperature:增大压缩概率差距

    • T < 1 放大强者优势
    • T = 1 概率不变
    • T > 1 减小强者和弱者的差距,输出更随机

注意:top_k 和 top_p 都是直接删掉一些候选 token,删掉之后,剩下的概率会重新归一化(等比例放大,总和变回 1),再进入下一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Admin@MacBook-Pro qoder % ./minimax_param_test.sh
############ 问题 1: 请随便说一个 1 到 10000 之间的随机整数,只输出这个数字本身,不要任何解释。 ############
--- 高温随机 (T=1.9, top_p=1.0, top_k=0) x5 ---
7421
7429
5273
7421
5274
--- 贪心对照 (T=0.01, top_p=1.0, top_k=1) x3 ---
7421
7314
7421

############ 问题 2: 请随便说一个不常见的汉字,只输出这一个字,不要任何解释。 ############
--- 高温随机 (T=1.9, top_p=1.0, top_k=0) x5 ---
龘
龘
爨
龘
龘
--- 贪心对照 (T=0.01, top_p=1.0, top_k=1) x3 ---
龘
龘
龘

############ 问题 3: 写一个荒诞故事的开头,一句话即可,越天马行空越好。只输出这句话。 ############
--- 高温随机 (T=1.9, top_p=1.0, top_k=0) x5 ---
宇宙飞船误闯上帝的冰箱后,全世界的星星开始按照爵士舞步轮流爆炸,而邮差正在追赶一只会背乘法表的透明奶牛。
宇宙在一场由失踪的月亮主持的婚礼上醒来,发现自己其实是一名正在逃亡的巨型冰淇淋,而地球只是它口袋里一颗会被上课点名的玻璃弹珠。
宇宙的尽头是一家只在凌晨三点营业的邮局,而它的新员工刚发现,每一封寄往过去的信都会让寄信人长出一条多余的腿。
宇宙的尽头有一家24小时营业的月球邮局,而每个寄往过去的信都会让寄信人长出一条多余的触手。
宇宙的尽头是一只会背元素周期表讲睡前故事的旧冰箱,而地球正被它塞进冷冻室当作明天醒来的早餐。
--- 贪心对照 (T=0.01, top_p=1.0, top_k=1) x3 ---
宇宙尽头那只患有星期二恐惧症的老虎,一口气吞下了整座城市的梦,却发现梦里的所有人类都在排队竞选它肚子里的国王。
宇宙听见冰箱打了个喷嚏,便决定把自己腌成一颗会发光的橄榄,然后在全宇宙的冰箱门上郑重签名。
宇宙广播员把星期二发射进宇宙后,发现整片星空都在偷偷背诵《刑法》,而他的左眼皮开始长出一座会自己报时的钟。

上下文窗口

  • 长上下文中间部分的影响力被显著削弱,首尾反而更受关注
  • 输入无关内容会造成伤害
  • Agent 循环每轮重放历史 → 上下文随步数近似线性增长 → 成本、延迟、首 token 延迟同步上升;长会话还会出现「忘了最初目标」、重复调用同一工具、不收敛

三者合起来 = Agent 的三条边界

问题

为什么t=0了,还是会输出不一样?

temperature=0 在绝大多数实现里不是「从多个候选里按概率挑」,而是贪心解码:把分布削到只剩概率最大的那个 token,其余置 -inf,argmax 取值。所以「概率大的更可能被选中」这句在 T=0 时不成立——它就是那个大的

T=0只是关掉了模型本身随机选字的逻辑,但GPU底层计算、服务器调度这些外部环节没法保证完全一致,所以输出依然可能不一样。

温度改的是分布形状,T=0 的「确定性」是工程近似,不是数学保证。

为什么会出现重复调用工具?

重复调用本身是症状,先看它是不是被闸门挡住了。排查要能区分三种可能

  1. 闸门缺失(没有重复检测、没有步数上限)
  2. 采样路径分叉(同一 query 走出不同参数)
  3. 上下文稀释(目标被挤掉,模型重新发明一个任务)

现象 A: 参数完全一样,重复出现 判据:params_hash 连续两轮相同。这是工程闸门缺失,不是模型笨——模型看不到”我刚做过这件事”。修:编排层加”无进展熔断”。

现象 B: 参数在变,越改越花 判据:params_hash 每轮都不同,且偏离原始目标越来越远。这是目标漂移:目标句被工具结果挤到上下文尾部、被截断、或处在中间位置被忽略(长上下文里中段信息最容易被跳过)。修:目标固定在不被裁剪的位置;工具结果用”摘要 + 指针”替换原文;给模型一份结构化的”已尝试清单”。

现象 C: 参数几乎一样,模型在”再确认一次” 判据:工具返回没有明确终态——没有 has_more: false、没有 cursor 结束标记、没有”未找到”的语义。模型无法判断该继续还是该停。修:返回 schema 里把终态写清楚,错误信息也要可读(”未找到 ERROR 级别日志” ≠ 空字符串)。

如果检索出来5w行日志,agent如何处理?

  1. L0 工具契约层: 工具需要照顾到如果查询内容多如何去处理,比如说,限制最大limit、或者定义filter。原则一句话:工具返回”决策所需的最小充分信息”,不是数据源的全量导出。

    1. 比如说查询一段时间的日志,可以返回总行数、样本topN、错误码分组
  2. L1 上下文装配层(token 预算分配器): 系统提示 / 工具定义 / 历史 / 工具结果 / 预留输出留一部分预算,告诉AI在遇到工具返回超预算的时候,保留 top-N + 聚合统计 + 显式标注” 这是 5 万行中的 200 行样本,完整数据在 SLS 查询 X”。不标注,模型就会以为它看全了 → 自信编造(能力边界那条直接落地)

  3. L2 模型调用层(硬参数) max_tokens、context_length,加上”自动截断最旧消息”这件事要知道——它会静默删掉你的早期目标句。

  4. L3 编排层(Agent 循环真正的闸门) max_steps、单工具调用上限、参数指纹熔断(同一 tool + 归一化参数 hash 连续命中 N 次 → 停止或强制换路径)、token/成本闸门。

  5. L4 状态层 checkpoint 里存的是指针 + 摘要(doc id 列表、聚合数、ES 查询 URL),不是 5 万行原文。你的 Redis checkpoint 正好是这个位置。

  6. L5 观测层 Trace 字段要能回答”这一轮发生了什么”:prompt_tokens、tool_result_tokens、params_hash、truncated、circuit_broken。你现有 Trace Hook 缺的就是 params_hash 和 truncated 这两个字段——没有它们,线上重复调用你只能靠猜。

如何设计日志工具返回的json?

先说结论:这个 JSON 有两个可能的消费者——人和 LLM。如果它是喂给模型的 tool 结果,设计的硬约束只有三条:体积有界、状态显式、留好续查句柄。你提的”topN / 剩余量 / 是否读完 / 索引 / sheet”正好对应这三条。

关键点:matched 必须来自全量扫描后的统计,不是”当前返回窗口”的计数。否则 remaining 永远是错的,模型也无法判断”是不是还有没读到的 ERROR”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{
"ok": true,
"query": { "sheet": "2026-11-09", "group_by": "level", "top_n": 20, "offset": 0 },
"source": {
"file": "app-2026-11-09.log",
"sheet": "2026-11-09",
"sheet_index": 2,
"total_sheets": 5,
"total_rows": 50000,
"scanned_rows": 50000
},
"coverage": {
"status": "complete",
"matched": 3,
"returned": 3,
"remaining": 0,
"truncated": false,
"truncated_reason": null
},
"groups": [
{
"key": "level:ERROR",
"label": "ERROR",
"matched": 3,
"returned": 3,
"remaining": 0,
"offset": 0,
"next_offset": 3,
"has_more": false,
"items": [
{
"index": 0, "row": 12341,
"ts": "2026-11-09T10:02:11.412Z",
"level": "ERROR",
"logger": "biz.OrderService",
"msg": "create order failed: timeout after 3s",
"fields": { "order_id": "O202611090001", "trace_id": "7f2ca1" }
},
{
"index": 1, "row": 23887,
"ts": "2026-11-09T12:44:03.901Z",
"level": "ERROR",
"logger": "biz.PayCallback",
"msg": "sign verify failed, mismatch",
"fields": { "order_id": "O202611090077", "trace_id": "a10b93" }
},
{
"index": 2, "row": 44902,
"ts": "2026-11-09T19:31:55.220Z",
"level": "ERROR",
"logger": "biz.UserRepo",
"msg": "mysql: connection refused after retry 3",
"fields": { "user_id": 88213, "trace_id": "c33f70" }
}
]
}
],
"next": null,
"warnings": []
}