Help us shape the future of Go! Take the 2026 Go Developer Survey

textutil

package
v0.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

lines.go —— 行号标注(文本行 → 带 4 位宽行号前缀)。

sanitize.go —— 落盘文件名安全化(textutil 通用文本/标识处理)。

字符 bigram 集合 + Jaccard 相似度的近重复检测原语(沉淀自 heimdallr 挖掘管道)。 可解释、小文本下精确、零依赖;候选规模(百~千条)下直接比对足够快,不必上向量库。

Package textutil 文本处理公共函数。

Index

Constants

View Source
const FitInputNote = "输入超出模型窗口预算,已截断留痕"

FitInputNote 窗口预算截断的统一注记文案(llm.fitInput 与 llm/wrap 共用 单源——此前两处字面量漂移无编译期信号)。

View Source
const TokenCharDivisor = 3

TokenCharDivisor 字符→token 估算除数(与 ZCode ESTIMATED_TOKEN_CHAR_DIVISOR 同值——中英混合语料的经验系数;精确计量归 provider usage 回传)。

Variables

This section is empty.

Functions

func BigramSet

func BigramSet(s string) map[string]bool

BigramSet 字符 bigram 集合:小写化、去空白(空白差异不算差异); 单字文本也有指纹(该字本身)。 零值/空串返回非 nil 空集,调用方可直接 range。

func ChunkLines

func ChunkLines(s string, target, hard int) []string

ChunkLines 按行边界把文本切成 ≤target(rune)的增量片:凑片不跨行 (markdown 表格/列表行保持完整,中途渲染不碎);超长单行在 hard 处硬切 (base64/长 URL 不设上限会把节奏拖成整行卡顿)。SplitAfter 语义保留行尾 换行——join(所有片) 严格等于原文(消费方按序归并即得全文,无需分隔符约定)。 target/hard<=0 取缺省(160/400);空白文本返回 nil。

func ClampRuneBoundary

func ClampRuneBoundary(s string, n int) int

ClampRuneBoundary 把字节下标 n 回退到所在 UTF-8 字符的起始边界(切点落进 多字节字符中间时会产生非法 UTF-8——头尾切分场景先经此钳制)。n>=len(s) 返回 len(s);n<=0 返回 0。

func EstTokens

func EstTokens(runes int) int

EstTokens 已知 rune 数时的 token 估算(向上取整)。

func EstTokensCJK

func EstTokensCJK(s string) int

EstTokensCJK CJK 感知的 token 估算:Han/Hangul/Kana/全角形式计 2 单位, 其余计 1,tokens ≈ units/3 + 1(非空下限 1)。纯 rune 计数口径对中文系统性 低估(经验上中文 ≈ 1 字 0.67 token、英文 ≈ 4 字符 1 token)——中文为主的 文本(提示词/会话历史/报告)用本函数;精确计量仍归 provider usage 回传。

func EstTokensCJKRunes

func EstTokensCJKRunes(n int) int

EstTokensCJKRunes 已知 rune 数且内容以 CJK 为主时的保守计量 (units=2n → tokens=2n/3+1)——compact 等已有 rune 计数中间量的调用方 复用 CJK 口径,免字符串物化。

func EstTokensCJKRunesWithRatio added in v0.6.0

func EstTokensCJKRunesWithRatio(n int, r CJKRatio) int

EstTokensCJKRunesWithRatio 已知 rune 数且内容以 CJK 为主时的折算估算 (免字符串物化——compact 等调用方手里只有 rune 计数中间量)。

与 EstTokensCJKRunes 同口径:**按「全部字符都是 CJK」估算**。 文本实际以 ASCII 为主时这个值会偏大,调用方需自行判断是否适用。

非法系数回退到默认系数;n<=0 返回 0。

func EstTokensOf

func EstTokensOf(s string) int

EstTokensOf 字符串 token 估算(rune 数 / TokenCharDivisor 向上取整)。

func EstTokensWithRatio added in v0.6.0

func EstTokensWithRatio(s string, r CJKRatio) int

EstTokensWithRatio 用指定折算系数估算 token(逐段向上取整)。

折算口径见 CJKRatio 的说明;默认系数用 DefaultCJKRatio()。 空串返回 0;非空串至少 1。

func EstTokensWithRounding added in v0.6.0

func EstTokensWithRounding(s string, r CJKRatio, mode TokenRounding) int

EstTokensWithRounding 用指定折算系数与取整方向估算 token。

取整方向是**参数**而不是另开一个函数:两种口径下「哪个数才是这个文本的 token 数」是同一个问题的两个答案,分成两个函数只会逼调用方在两套命名之间猜。

空串返回 0;非空串至少 1。

func FTS5Query added in v0.6.0

func FTS5Query(s string) string

FTS5Query 把文本切成词元并拼成 SQLite FTS5 安全的 MATCH 查询串。

每个词元加双引号后用空格连接:FTS5 的空格是隐式 AND,于是 "磁" "盘" = 同时含「磁」与「盘」,正是中文子串检索要的效果。

加引号是**安全边界**,不是装饰:FTS5 的 AND / OR / NOT / NEAR 是**裸词**才生效的 运算符,用户输入里的 NOT 会被当成「排除后面那个词」而不是「查 NOT 这个词」; 引号把它降回字面量。引号同样能让 `"`、`*`、`(` 之类不构成语法错误。

空串(或切不出词元的串)返回空串——空 MATCH 是 FTS5 的语法错误, 调用方应据此跳过查询而不是拼上去。

func FirstNonEmpty

func FirstNonEmpty(vals ...string) string

FirstNonEmpty 返回首个非空串(全空返回 "")。多候选回退语义单源 (模型/配置/平台字段逐级兜底的高频形态)。

func FitCharsLimit

func FitCharsLimit(windowTokens, reserveTokens int) int

FitCharsLimit 模型窗口的 rune 预算:(窗口-预留)×3/2×0.9(CJK 感知系数 1.5 token/rune 上界——v0.12.1 起两处共用;×0.9 留 10% 余量)。窗口无效 或预留缺省(<=0 取 窗口/20)不可算时返回 0(调用方跳过拟合)。

func GlobMatch

func GlobMatch(pattern, path string) bool

GlobMatch 极简 glob:支持 `**`(跨目录)与 `*`/`?`(单段)。 语义对齐 .gitignore 常见用法:`web/**` 匹配 web/ 下一切(不含 web 自身); `*.vue` 匹配任意目录下的 .vue;`?` 消耗一个 rune(多字节文件名安全)。

func Jaccard

func Jaccard(a, b map[string]bool) float64

Jaccard 两个集合的 Jaccard 系数 [0,1];两者皆空视为完全相同(返回 1)。 迭代较小集合,map 探测次数取 min(|a|,|b|)。

func NearDuplicate

func NearDuplicate(a, b string, threshold float64) bool

NearDuplicate 相似度 ≥ threshold 视为近重复。

func NumberLines

func NumberLines(s string) string

NumberLines 逐行加 4 位宽行号前缀(" 1|")。 用途:给 agent 的文件内容原文加行号——无行号原文会逼模型凭空编造 file:line 证据;行号前缀必须先于任何截断。尾随换行不产生幽灵空行。

func RuneUnitsCJK

func RuneUnitsCJK(s string) int

RuneUnitsCJK 文本的 CJK 单位总量(CJK/全角计 2、其余计 1)。

func SanitizeFileStem

func SanitizeFileStem(s string) string

SanitizeFileStem 文件名安全化:标识串(owner/repo/number 等外部标识)里的 路径分隔与引用语法字符归一为 "-"。外部标识常含 "/"(GitLab 嵌套组)、"#"、 ":"(分支名)、甚至 ".."——未消毒直接拼进留档文件名会导致写入失败甚至 目录穿越。只处理文件名主干(不含扩展名),调用方自行拼接后缀。

func Similarity

func Similarity(a, b string) float64

Similarity 两段文本的相似度 = 字符 bigram 集合的 Jaccard 系数 [0,1]。 全等短路(含双双归一为空的等价情形交给 BigramSet/Jaccard,不在此误判)。

func SplitRunes

func SplitRunes(s string, n int) []string

SplitRunes 按 rune 等分为 ≤n 字符的块(最后一块可不足)。 n<=0 返回整串单块。大文本分块送 LLM 的公共原语。

func StripCodeFence

func StripCodeFence(s string) string

StripCodeFence 剥离 markdown 代码围栏(全仓**代码围栏**语义单一事实源: jsonrepair 宽容解析与快路径切片共用)。取**第一个** ``` 围栏块;无栅栏原样 TrimSpace 返回。多围栏块输出(模型补多段代码)语义为「取第一块」——与宽容 解析链一致。注意与 fence 包的「数据区围栏」(fence.Data,注入卫生面)是两个 正交概念:本函数处理 markdown ``` 栅栏,后者圈定不可信数据边界。

func Tokens added in v0.6.0

func Tokens(s string) []string

Tokens 把文本切成**检索用词元**。

切法:

  • CJK 逐字成词元("磁盘" → "磁", "盘")——汉字/假名/谚文按单字切;
  • 拉丁与数字按空白与标点切词("disk is" → "disk", "is"),**原样保留大小写**;
  • 只产出**裸词元**:标点、运算符、引号一律丢弃。

为什么 CJK 要逐字:SQLite FTS5 的 unicode61 分词器把连续汉字算作**一个词元**, 于是索引里的「磁盘长期偏小」是一个词元、查询「磁盘」是另一个词元, 两者永不相等 → **0 命中**,而且是**静默**返回空集(用户看到的是「库里没有这条」)。 真实中文文本没有空格分词,用户查的就是子串。

语义边界(重要,别当成通用分词器用):

  • 返回的是**词的合集**,不是连续子串。"磁盘" 与 "盘磁" 的词元集合相同, 但两者在原文里位置相反——本函数不保留位置信息,**分不出顺序**。
  • **不支持短语检索**:"disk is" 会被当成两个词的合取(都含),而不是相邻短语。 将来要支持短语,至少得能拿到词元 → 原文下标的位置信息。
  • 与 BigramSet / Similarity **并存、不互相替代**:bigram 是字符二元组, 两者在**判定阈值**上不等价(中文「已修复」vs「未修复」:词元口径 Jaccard=0.5,二元组口径≈0.33)——阈值是按词元口径标定的,换口径会翻转判定。

词元**不去重**(保留出现次数,TF 语义对调用方更有用)。

func TokensJoined added in v0.6.0

func TokensJoined(s string) string

TokensJoined 把 Tokens 的结果用空格连接。

**索引用**:FTS5 的 unicode61 分词器按空白切词,所以入库前把文本切成 空格分隔的词元串再存,索引里才会是「磁 盘 长 期」这样一个个单字词元。 查询侧用 FTS5Query(不是本函数——查询串要额外防运算符)。

func TruncBytes

func TruncBytes(s string, budget int) (string, bool)

TruncBytes 按字节预算截断(rune 对齐:预算落点切进多字节字符时回退到 RuneStart 边界,不产生非法 UTF-8 尾字节)。第二个返回值 = 是否真发生了 截断。budget<0 视为非法上限,按"全部截断"处理(返回空串),不 panic。 字节预算面的唯一原语(skill 清单降级/全文上限共用——此前各写一份回退逻辑)。

func TruncEllipsis

func TruncEllipsis(s string, n int) string

TruncEllipsis 截断并追加省略号(列表/标题/事件载荷展示面统一语义)。 n<=0 返回省略号;无需截断时原样返回。

func TruncNote

func TruncNote(s string, n int, note string) string

TruncNote 截断并追加中文注记后缀(截断留痕语义单源:超限截断的输出带 「…(注记)」尾巴,调用方/模型可感知截断发生)。无需截断时原样返回; 注记以「…(」开头自行拼接,调用方传纯文字(如 "超长行截断")。

func TruncRunes

func TruncRunes(s string, n int) (string, bool)

TruncRunes 按 rune 截断,避免多字节字符被腰斩产生非法 UTF-8。 第二个返回值 = 是否真发生了截断。 n<0 视为非法上限,按"全部截断"处理(返回空串),不 panic。

func UnitsToTokensCJK

func UnitsToTokensCJK(units int) int

UnitsToTokensCJK 单位量 → token(units/3 向上取整,非空下限 1)。

Types

type CJKRatio added in v0.6.0

type CJKRatio struct {
	// CJKCharsPerToken CJK 字符数 / token(如 2 = CJK 每 2 字符 1 token)。
	CJKCharsPerToken int
	// ASCIICharsPerToken 其余字符数 / token(如 4 = ASCII 每 4 字符 1 token)。
	ASCIICharsPerToken int
}

CJKRatio token 折算系数:多少字符折算成 1 个 token。

非法值(<=0)在计算时回退到 DefaultCJKRatio 对应项——除零是硬错误, 而「系数配错」是可预期的配置问题,不该让调用方拿 panic 兜。

func DefaultCJKRatio added in v0.6.0

func DefaultCJKRatio() CJKRatio

DefaultCJKRatio 默认折算系数:CJK 每 2 字符 1 token、ASCII 每 4 字符 1 token。

type TokenRounding added in v0.6.0

type TokenRounding int

TokenRounding 折算取整方向。

两种取整在**混合文本**上差系统性:把同类字符合并后再取整,会把每段的零头 一起吞掉——多段混合文本累加起来是成规模的低估,不是随机误差。

const (
	// RoundingSegment 逐段向上取整(默认):按**连续同类字符游程**切段,
	// 每段各自向上取整后求和。
	//
	// 「中a中a中a中a」在逐段口径下是 8 段(每段 1 字符)→ 8 token;
	// 合并口径下只有 3 token。差的正是那 5 个「半截 token」。
	RoundingSegment TokenRounding = iota
	// RoundingTotal 总量向上取整:同类字符合计后各向上取整一次再求和。
	RoundingTotal
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL