File size: 2,201 Bytes
7e0a8c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""剥掉 Youtu-LLM-A7B-Base 输出开头的 <think>/</think> 残留标记。

背景
----
这个模型是 base 模型(官方 README:仅推荐 raw text continuation,chat_template
只是附带)。它在 assistant 轮起始处会固定先吐 `</think>`(token id 128014),
"关闭"模板预先打开的思考块,然后才写正文:

    文本:<|Assistant|><think>   <- 模板写的,在提示词里
          </think>光合作用是...     <- 模型写的

    视觉:<|vision_start|>...描述这张图片。   <- 提示词里没有 assistant 标记
          </think></think>这是一张...        <- 模型仍会先吐 1~2 个

llama.cpp 的自动解析器(common/chat-diff-analyzer.cpp)识别不出这对标记,
于是 `</think>` 原样留在 message.content 里。本脚本在输出侧把它剥掉。

用法
----
    llama-cli ... | python3 strip_think.py
    llama-mtmd-cli ... | python3 strip_think.py --from-first-think

    --from-first-think
        先跳到首个 <think>/</think> 标记处再剥。llama-mtmd-cli 会在日志里夹带
        3 行 chat template 示例(不带时间戳前缀,grep 滤不掉),用这个模式可以
        直接从正文开始输出。

也可作为库使用:from strip_think import strip_leading_think, clip_to_first_think
"""
from __future__ import annotations

import re
import sys

# 行首(允许前置空白)连续出现的思考标记
_LEADING = re.compile(r"^\s*(?:(?:</?think>)\s*)+")


def strip_leading_think(text: str) -> str:
    """删除文本开头连续的 <think>/</think> 标记及其后空白。"""
    return _LEADING.sub("", text, count=1)


def clip_to_first_think(text: str) -> str:
    """从首个 <think> 或 </think> 处开始截取(找不到则原样返回)。"""
    hits = [i for i in (text.find("<think>"), text.find("</think>")) if i >= 0]
    if not hits:
        return text
    return text[min(hits):]


def main() -> int:
    text = sys.stdin.read()
    if "--from-first-think" in sys.argv[1:]:
        text = clip_to_first_think(text)
    sys.stdout.write(strip_leading_think(text))
    return 0


if __name__ == "__main__":
    sys.exit(main())