TokenEstimator.java
2.63 KB
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.xly.service;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
import java.util.List;
/**
* 本地保守 token 估算器(不依赖任何模型请求)。
*
* <p>规则:CJK/全角字符 ≈ 1 token/字;其余字符 ≈ 3 字符/token。**故意高估**——
* 高估只会少带几轮历史,低估会让 Ollama 从前面静默截头(system prompt 先死),两者代价不对称。
* 校准回路:每次响应免费自带 prompt_eval_count,{@code TracingChatModelListener} 记录
* 估算 vs 实际,实际逼近 num_ctx 即告警。
*/
public final class TokenEstimator {
/** 每条消息的结构开销(role/分隔符等)。 */
public static final int MSG_OVERHEAD = 8;
/** 工具 schema + 对话模板的请求级开销(不在 messages 里;实测 7 工具中文描述约 2200-2400t)。 */
public static final int TOOLS_OVERHEAD = 2600;
private TokenEstimator() {
}
public static int estimate(String s) {
if (s == null || s.isEmpty()) {
return 0;
}
int cjk = 0;
int other = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) >= 0x2E80) {
cjk++;
} else {
other++;
}
}
return cjk + (other + 2) / 3;
}
public static int estimate(ChatMessage m) {
if (m == null) {
return 0;
}
int n = MSG_OVERHEAD;
if (m instanceof SystemMessage sm) {
n += estimate(sm.text());
} else if (m instanceof UserMessage um) {
n += estimate(um.hasSingleText() ? um.singleText() : String.valueOf(um.contents()));
} else if (m instanceof AiMessage am) {
n += estimate(am.text());
if (am.hasToolExecutionRequests()) {
for (ToolExecutionRequest r : am.toolExecutionRequests()) {
n += estimate(r.name()) + estimate(r.arguments()) + 6;
}
}
} else if (m instanceof ToolExecutionResultMessage tr) {
n += estimate(tr.text()) + estimate(tr.toolName());
} else {
n += estimate(String.valueOf(m));
}
return n;
}
public static int estimate(List<ChatMessage> messages) {
int n = 0;
for (ChatMessage m : messages) {
n += estimate(m);
}
return n;
}
}