Terminus 2 手册

解析器内部辅助

stage-4.9Parser internal helpers11 个函数

开场解释

这个 Stage 在解决什么问题:当主循环已经拿到模型原始回复后,它要把“差一点就合法”的 XML/JSON 文本尽量救成可用结果,而不是因为一个缺标签、半截 JSON、或输出被截断就整轮失败。它的位置在解析子系统内部,属于公开解析入口后面的“修补与抢救层”。输入是上游交来的原始响应;输出是解析结果,或明确的失败结论,再加上一路累积的警告,交回外层解析边界处理。

主流程

  1. 先按格式做正常解析

    这里的第一目标不是“聪明修”,而是先判断原始回复本身能不能直接读。 因为很多回复其实已经合法,没必要动它。 这一步的产出只有两类:

    • 解析成功:直接得到结果
    • 解析失败:进入下一层修补
  2. 失败后,先做保守的 auto-fix(自动修补)

    这一层处理的是常见、低风险的小毛病。 比如:

    • XML 少了外层 <response> 标签
    • JSON 结尾被截断,结构明显没闭合
    • 一些格式相关的小修正

    之所以把这层放在前面,是因为它改动小、误修风险低。 目标不是“猜作者想说什么”,而是把明显的格式瑕疵补平,让解析器再试一次。

  3. 每次修补后重试,并把 warning(警告)累积起来

    这里的关键不是单纯“再 parse 一遍”,而是要留下痕迹:

    • 修过什么
    • 为什么修
    • 这次结果是否可信但有瑕疵

    这就是 _combine_warnings() 这一类内部 helper 存在的原因。 系统不能只返回“成功了”,还要告诉外层:这个成功是不是靠修补得来的。 这样外层后面做决策时,才知道该不该更保守地看待这份结果。

  4. 只有前面还失败,而且看起来是“被截断”时,才做 salvage(抢救)

    salvage_truncated_response() 可以理解成“最后的抢救手段”。 它不是常规路径,而是兜底路径。

    为什么要放在 auto-fix 之后? 因为 salvage 更激进。它面对的是输出只来了一半的情况,会尽量从残缺文本里抠出还能用的部分。 如果一上来就走这条路,很容易把本来只需小修的文本过度处理,反而引入误判。 所以顺序必须是:

    • 先做保守修补
    • 再做激进抢救

    这样能把“救回成功率”和“误修风险”平衡住。

  5. XML 和 JSON 各有策略,但职责相同

    这个 Stage 里同时有 XML plain parser 和 JSON plain parser 的内部 helper。 它们看起来处理细节不同,但职责是一致的:

    • 找出最常见的坏格式
    • 做最小修补
    • 重试解析
    • 记录警告
    • 必要时对截断输出做最后抢救

    比如 XML 会更关心顶层标签是否完整、能不能提取 <response> 内容; JSON 会更关心括号、数组、对象是否收尾完整。 但从流水线视角看,它们都是“格式专属修补器”。

  6. 最后把结论交回外层解析边界

    这个 Stage 不负责决定“整轮 run 要不要结束”“要不要重试模型”。 它只负责把内部解析结论整理好后交出去:

    • 成功的结果
    • 或失败结论
    • 以及修补/抢救过程中累积的警告

    外层再根据这个边界结果,决定后面的 loop 控制或 teardown(收尾)。

状态流动

与前后 Stage 的衔接

上游已经结束本轮生成,把“模型原始回复”交给解析子系统;本 Stage 负责把这份原始文本尽量变成稳定的结构化结论。它产出的是“解析结果或失败状态,加上警告”,然后返回外层解析边界,由外层决定是继续控制循环、结束,还是进入 teardown。

函数细节11

TerminusXMLPlainParser.__init__terminus_xml_plain_parser.py:25–26 ↗

解析器实例的最小化构造入口

stage 上下文: 该条目位于 parser internal helpers stage,但就给定源码可见内容而言,此函数仅执行实例属性初始化。它无条件触发于对象构造时,且本 unit 是该 stage 当前唯一已翻译条目,因此不存在可对照的兄弟实现。

这段代码在干什么

__init__(self) 只接收实例自身 self。函数体仅把 self.required_sections 赋值为新列表 ["analysis", "plan", "commands"],随后隐式返回 None。除此之外,它不读取现有实例状态,也不调用其他函数。

接口 · 参数 / IO

(self)

  • 参数: self: ? — 当前 TerminusXMLPlainParser 实例
  • 返回: 隐式返回 None;实际产出是把属性 self.required_sections 写成新列表 ["analysis", "plan", "commands"]
  • 副作用: 写入属性 self.required_sections["analysis", "plan", "commands"]

执行流

  1. 进入 __init__(self)
  2. 执行一次属性赋值:将 self.required_sections 设为字面量列表 ["analysis", "plan", "commands"]
  3. 函数结束,并隐式返回 None

源码

    def __init__(self):
        self.required_sections = ["analysis", "plan", "commands"]

Non-obvious 设计决策

  • 源码可直接观察到,required_sections 的内容采用硬编码字面量,并且顺序固定为 analysisplancommands。除此之外,给定代码未显式呈现其他可证实的设计取舍。

上下游关系

  • 调用方: 对象构造路径(具体调用方未在给定源码中出现)
  • 核心被调用: 无:函数体中没有显式函数调用
  • 配置/状态来源: 无:未读取参数以外的任何配置或实例状态; 代码内字面量列表 ["analysis", "plan", "commands"]
  • 结果去向: 实例属性 self.required_sections
TerminusXMLPlainParser._get_auto_fixesterminus_xml_plain_parser.py:171–178 ↗

生成自动修复候选列表的辅助函数

stage 上下文: 该函数位于 parser 子系统内部辅助层,职责是把当前可用的自动修复项整理成返回值,而不直接执行修复。相较同 stage 中仅做初始化赋值的 __init__,这里同样保持极小职责范围:读取一个实例方法引用并构造固定列表。

这段代码在干什么

_get_auto_fixes(self) 不接收除 self 外的任何输入。函数体直接返回一个新列表,列表当前只有一个二元组:提示文本 "Missing </response> tag was automatically inserted" 与方法引用 self._fix_missing_response_tag。该过程不写入任何实例状态,也没有外部副作用。

接口 · 参数 / IO

(self)

  • 参数: self: ? — 解析器实例,提供 _fix_missing_response_tag 方法引用
  • 读状态: self._fix_missing_response_tag
  • 返回: 返回一个新列表,当前实现的精确内容为 [("Missing </response> tag was automatically inserted", self._fix_missing_response_tag)]

执行流

  1. 进入函数后,不进行条件判断、循环或异常处理。
  2. 构造并返回一个列表字面量;该列表当前只包含一个二元组。
  3. 该二元组的第一个元素是固定说明字符串 "Missing </response> tag was automatically inserted"
  4. 该二元组的第二个元素是当前实例上的方法引用 self._fix_missing_response_tag

源码

    def _get_auto_fixes(self):
        """Return list of auto-fix functions to try in order."""
        return [
            (
                "Missing </response> tag was automatically inserted",
                self._fix_missing_response_tag,
            ),
        ]

Non-obvious 设计决策

  • 返回值采用“说明文本 + 修复函数”的二元组结构;这一点可由列表中的单个 tuple 直接定位到代码。
  • 修复项被放在列表中而非单独返回函数引用,因此候选项的顺序由列表位置编码;当前实现中列表只有一个元素。
  • 函数本身只负责暴露候选集合,不在此处调用 self._fix_missing_response_tag,从而把“列出候选项”和“执行修复”分离。

上下游关系

  • 调用方: 未知:该片段自身未显示调用方
  • 核心被调用: 无直接调用;仅返回方法引用 self._fix_missing_response_tag 而不执行它
  • 配置/状态来源: self._fix_missing_response_tag 方法属性; 函数体内硬编码字符串 "Missing </response> tag was automatically inserted"
  • 结果去向: 直接返回给本函数调用者; 返回值中的第二项携带 self._fix_missing_response_tag 供调用者后续使用
  • 同类 sibling: 与同 stage 的 TerminusXMLPlainParser.__init__ 一样都很短小;__init__ 只做实例字段初始化,而本函数只组装并返回固定候选列表。
TerminusXMLPlainParser._fix_missing_response_tagterminus_xml_plain_parser.py:187–194 ↗

补齐 response 结束标签的窄修复器

stage 上下文: 该条目位于 parser 内部辅助函数集合中,职责是对一类非常具体的字符串错误进行本地修补。它与同阶段的 __init___get_auto_fixes 不同,不配置实例状态,也不组织修复列表;这里只根据传入的 responseerror 计算返回值。

这段代码在干什么

_fix_missing_response_tag(self, response, error) 只检查 error 是否包含固定子串 "Missing </response> closing tag"。若不包含,则原样返回 responseFalse;若包含,则返回 response.rstrip() + "\n</response>"True。函数不读取任何 self 属性,self 在函数体内实际未被使用,也没有副作用。

接口 · 参数 / IO

(self, response: str, error: str) -> tuple[str, bool]

  • 参数: self: ? — 实例引用;在该函数体内未被使用; response: str — 待处理的原始响应文本;在命中修复条件时作为拼接基础; error: str — 错误描述字符串;仅通过子串包含判断决定是否执行修复
  • 返回: 二元组 (str, bool):若 error 不含 "Missing </response> closing tag",返回 (response, False);若包含,则返回 (response.rstrip() + "\n</response>", True)

执行流

  1. 先对参数 error 做一次子串包含判断:检查其中是否出现固定文本 "Missing </response> closing tag"
  2. 若判断失败,立即返回原始 response 与布尔值 False,表示本次未做任何修正。
  3. 若判断成立,先计算 corrected = response.rstrip() + "\n</response>",即去掉 response 末尾空白后补上一行关闭标签。
  4. 最后返回 corrected 与布尔值 True,把修正后的文本交还调用方。

源码

    def _fix_missing_response_tag(self, response: str, error: str) -> tuple[str, bool]:
        """Fix missing </response> closing tag by appending it."""
        if "Missing </response> closing tag" not in error:
            return response, False

        # Simply append </response> at the end
        corrected = response.rstrip() + "\n</response>"
        return corrected, True

Non-obvious 设计决策

  • 修复触发条件被收窄为对 error 固定子串的精确包含判断,而不是对 response 内容做更宽松的猜测式修补;从代码可见,这一选择限制了函数只处理一种已知错误形态。
  • 在补标签前先调用 response.rstrip(),说明实现刻意去除尾随空白后再追加 "\n</response>";这样可避免把关闭标签插在一串末尾空白之后。
  • 返回值显式携带布尔标记 True/False,而不是仅返回字符串;从接口形状可见,调用方可以直接区分“未改动的原文返回”与“已生成修正文”。

上下游关系

  • 调用方: 外部调用方可传入 responseerror 请求一次缺失结束标签修复
  • 核心被调用: str.__contains__:用于判断 error 是否包含固定错误文本; str.rstrip:用于去除 response 末尾空白
  • 配置/状态来源: 无实例配置或 self 状态输入;全部判断依据均来自形参 responseerror
  • 结果去向: 返回给调用方一个 (文本, 是否已修复) 二元组
  • 同类 sibling: TerminusXMLPlainParser.__init__:只初始化 self.required_sections,而本函数完全不接触实例状态。; TerminusXMLPlainParser._get_auto_fixes:返回修复项列表;本函数本身只实现其中一个具体修复动作。
TerminusXMLPlainParser._combine_warningsterminus_xml_plain_parser.py:180–185 ↗

警告文本拼接格式化助手

stage 上下文: 该条目对应解析器内部的一个极小型字符串整理辅助函数。它本身不处理解析状态,只负责把一条新警告按固定格式并入已有警告文本。与同 stage 中直接返回固定列表或按条件修补字符串的兄弟函数一样,这里也不读取实例内部字段。

这段代码在干什么

_combine_warnings(self, auto_warning, existing_warning) 接收两段警告文本,返回一段新的组合结果。若 existing_warning 为真值,则返回 f"- {auto_warning}\n{existing_warning}";若其为假值,则返回 f"- {auto_warning}"。函数不读取 self 上的任何属性,也不产生副作用。

接口 · 参数 / IO

(self, auto_warning: str, existing_warning: str) -> str

  • 参数: self: ? — 实例自身;在函数体内未被使用; auto_warning: str — 将被格式化为前导项目符号行的新警告文本; existing_warning: str — 已有警告文本;其真值性决定是否追加换行并保留原文本
  • 返回: 返回组合后的警告字符串:existing_warning 为真值时插入一个前导 - 和一个换行后接上原文本;为假值时仅返回 - {auto_warning}

执行流

  1. 先对参数 existing_warning 做真值判断。
  2. existing_warning 为真值,则生成并返回 f"- {auto_warning}\n{existing_warning}",即把新警告写成带 - 前缀的首行,并用一个换行连接旧文本。
  3. existing_warning 为假值,则直接返回 f"- {auto_warning}",不附加换行,也不拼接旧文本。

源码

    def _combine_warnings(self, auto_warning: str, existing_warning: str) -> str:
        """Combine auto-correction warning with existing warnings."""
        if existing_warning:
            return f"- {auto_warning}\n{existing_warning}"
        else:
            return f"- {auto_warning}"

Non-obvious 设计决策

  • 新警告始终放在返回字符串的最前面;这从两个分支都以 - {auto_warning} 起始可以直接看出,而不是把新内容追加到旧文本尾部。
  • 只有在 existing_warning 为真值时才插入换行;这样可避免在没有可接续文本时产生多余的尾随分隔符。

上下游关系

  • 调用方: 未知;该函数源码片段未展示调用点
  • 核心被调用: 无;函数体未调用其他函数
  • 配置/状态来源: 参数 auto_warning; 参数 existing_warning
  • 结果去向: 直接作为返回值交还给调用方; 返回结果内容取决于 existing_warning 的真值性分支
  • 同类 sibling: 同 stage 的 __init__ 直接设置固定实例字段;本函数则完全不接触实例状态。; 同 stage 的 _get_auto_fixes 直接返回固定列表;本函数改为依据参数真值性返回两种字符串之一。; 同 stage 的 _fix_missing_response_tag 也是条件分支后返回字符串与布尔值;本函数同样是纯函数式返回,但只产出字符串。
TerminusXMLPlainParser._extract_response_contentterminus_xml_plain_parser.py:225–236 ↗

提取 response 包裹内容的字符串助手

stage 上下文: 该条目位于 parser 内部辅助函数分组中,职责是对单个字符串做局部内容抽取。就函数体可见信息而言,它不依赖实例状态,也不像 XML 解析器那样解释结构;同组已知兄弟函数也多为这类短小、无副作用的字符串处理或修复助手。

这段代码在干什么

_extract_response_content(self, response: str) -> str 在输入字符串 response 中查找字面量 <response></response>,并返回两者之间、或从起始标签到字符串末尾的子串,再对结果执行 .strip()。若找不到起始标签,则直接返回空字符串 ""。整个过程不读取任何 self 属性,也不写入状态或产生外部副作用。它使用普通字符串搜索与切片,而不是 XML 解析,因此不验证标签结构、嵌套或配对正确性。

接口 · 参数 / IO

(self, response: str) -> str

  • 参数: self: ? — 实例自身;本函数体内未使用; response: str — 待搜索的输入字符串
  • 返回: 返回字符串结果:若不存在 "<response>",返回空字符串;若存在起始标签但不存在 "</response>",返回从起始标签之后到字符串末尾的切片并 .strip();若两个标签都存在,返回两标签之间的切片并 .strip()

执行流

  1. 先在 response 上用 response.find("<response>") 查找起始标签位置,结果保存到 start_pos
  2. start_pos == -1,说明输入中没有字面量 <response>,函数立即返回空字符串 ""
  3. 若找到起始标签,再从 start_pos 开始用 response.find("</response>", start_pos) 查找结束标签位置,结果保存到 end_pos
  4. end_pos == -1,则截取 response[start_pos + len("<response>") :],并对该子串调用 .strip() 后返回。
  5. 若结束标签存在,则截取 response[start_pos + len("<response>") : end_pos],并对该子串调用 .strip() 后返回。

源码

    def _extract_response_content(self, response: str) -> str:
        """Extract content from <response> tags."""
        start_pos = response.find("<response>")
        if start_pos == -1:
            return ""

        end_pos = response.find("</response>", start_pos)
        if end_pos == -1:
            # Missing closing tag - return content from opening tag to end
            return response[start_pos + len("<response>") :].strip()

        return response[start_pos + len("<response>") : end_pos].strip()

Non-obvious 设计决策

  • 实现选择了 str.find 与切片,而非 XML 解析 API;这样可在极简成本下完成固定标签的内容提取,但相应地不会校验标签是否合法、不会处理嵌套,也不做标签平衡判断。
  • 函数把缺失结束标签视为可接受情形:当 end_pos == -1 时仍返回从起始标签后到字符串末尾的内容,而不是报错或返回空值。这体现出对不完整文本输入的容忍策略。
  • 两条成功返回路径都统一执行 .strip(),说明该函数有意去除提取片段首尾空白,使调用方获得规整化文本,而不是保留原始边界空格与换行。

上下游关系

  • 调用方: 未在本函数源码中直接体现具体调用者; 任何需要从普通字符串中抽取 <response>...</response> 包裹内容的同类内部逻辑
  • 核心被调用: response.find; len; str.strip
  • 配置/状态来源: 无配置来源;行为仅由字面量标签 "<response>""</response>" 决定
  • 结果去向: 返回给直接调用方作为提取后的纯字符串内容; 在未找到起始标签时向调用方提供空字符串哨兵值; 在缺失结束标签时向调用方提供宽容截取后的字符串结果
  • 同类 sibling: TerminusXMLPlainParser.__init__:同样是短小实现,但该函数只初始化 required_sections,而本函数只做字符串提取。; TerminusXMLPlainParser._fix_missing_response_tag:两者都围绕 response 标签文本处理;该兄弟负责修补缺失结束标签,本函数则在缺失结束标签时直接截到末尾。; TerminusXMLPlainParser._combine_warnings:同属无状态、无副作用的纯字符串加工助手。
TerminusXMLPlainParser._find_top_level_tagsterminus_xml_plain_parser.py:393–440 ↗

扫描字符串顶层标签名的辅助函数

stage 上下文: 该函数属于解析子系统内部辅助例程,职责仅限于对给定字符串做局部标签扫描并产出标签名列表。它与同组里 _extract_response_content 这类字符串级辅助函数相似,都不依赖实例状态;相比 _combine_warnings 之类纯拼接逻辑,本函数额外维护一个局部 depth 计数来区分当前是否处于零层级。

这段代码在干什么

_find_top_level_tags(self, content: str) -> List[str] 顺序扫描输入字符串 content,收集所有在当前扫描深度 depth == 0 时遇到的、非闭合标签的标签名,并按出现顺序返回。它跳过标签内容以 !? 开头的片段,遇到闭合标签时只做 depth -= 1,遇到找不到配对 > 的起始 < 时直接停止扫描。函数不读取任何 self 属性,也不写入实例或外部状态。

接口 · 参数 / IO

(self, content: str) -> List[str]

  • 参数: self: ? — 实例自身;在函数体内未被读取; content: str — 被逐字符扫描的输入字符串,标签发现与层级判断均基于它
  • 返回: 返回 List[str]:扫描过程中在 depth == 0 时遇到的非闭合标签名,保持原始出现顺序。标签名提取规则是:若标签内容含空格则取 split()[0],否则取整个标签内容;之后若结果以 / 结尾再去掉该尾随 /。由于闭合标签会无条件令 depth 减一,畸形输入可能使 depth 变成负数,从而影响后续哪些标签会被计为“顶层”。

执行流

  1. 初始化局部列表 top_level_tags = []、层级计数 depth = 0 和扫描下标 i = 0,随后用 while i < len(content) 逐字符推进整个字符串。
  2. content[i] 不是 < 时,仅把 i 加一并继续;当遇到 < 时,用 content.find(">", i) 查找本次标签的结束位置。若未找到 >(得到 -1),立即 break,后续内容不再处理。
  3. 取出 tag_content = content[i + 1 : tag_end] 作为尖括号内文本。若它以 !? 开头,则直接把 i 移到 tag_end + 1 并跳过,不参与层级与结果列表处理。
  4. tag_content/ 开头,则把它视为闭合标签,只执行 depth -= 1,然后把 i 移到 tag_end + 1 继续;这里不校验名称是否匹配,也不阻止 depth 变成负数。
  5. 对于其余标签,先用 tag_content.endswith("/") 判断是否为自闭合标签,再按“有空格取首词,否则取整体”的规则得到 tag_name;若 tag_name 末尾仍有 /,再去掉这个尾随字符。
  6. 在任何深度调整之前,先检查当前 depth == 0;若成立,就把 tag_name 追加进 top_level_tags。之后仅当该标签不是自闭合时才执行 depth += 1,最后把 i 移到 tag_end + 1
  7. 循环结束后返回 top_level_tags

源码

    def _find_top_level_tags(self, content: str) -> List[str]:
        """Find all top-level XML tags (direct children of response), not
        nested tags."""
        top_level_tags = []
        depth = 0
        i = 0

        while i < len(content):
            if content[i] == "<":
                # Find the end of this tag
                tag_end = content.find(">", i)
                if tag_end == -1:
                    break

                tag_content = content[i + 1 : tag_end]

                # Skip comments, CDATA, etc.
                if tag_content.startswith("!") or tag_content.startswith("?"):
                    i = tag_end + 1
                    continue

                # Check if this is a closing tag
                if tag_content.startswith("/"):
                    depth -= 1
                    i = tag_end + 1
                    continue

                # Check if this is a self-closing tag
                is_self_closing = tag_content.endswith("/")

                # Extract tag name (first word)
                tag_name = tag_content.split()[0] if " " in tag_content else tag_content
                if tag_name.endswith("/"):
                    tag_name = tag_name[:-1]

                # If we're at depth 0, this is a top-level tag
                if depth == 0:
                    top_level_tags.append(tag_name)

                # Adjust depth for opening tags (but not self-closing)
                if not is_self_closing:
                    depth += 1

                i = tag_end + 1
            else:
                i += 1

        return top_level_tags

Non-obvious 设计决策

  • 该实现采用字符串扫描与局部层级计数,而不是正式 XML 解析器:代码只依赖 <>、前导 / 与尾随 / 等表面标记即可工作,因此实现短小,但也只提供语法层面的近似判断。
  • 对以 !? 开头的标签内容采取整段跳过策略,这是代码里唯一显式排除的特殊标签类别;它不再细分这些内容的内部结构,取舍点是简化处理分支。
  • “是否记为顶层”发生在打开标签的 depth += 1 之前,因此某个非闭合标签会以其进入前的层级来分类;这保证了零层级处的开始标签会被记录,而其内部嵌套内容不会因随后增深而倒置判断。
  • 闭合标签路径只做 depth -= 1 而不验证合法性,意味着不匹配或多余的闭合标签会把 depth 拉成负数;该取舍降低了容错处理复杂度,但后续顶层判定会受此状态偏移影响。
  • 遇到没有后续 >< 时直接终止扫描,而不是把它当普通字符继续搜索;这一策略避免在不完整标签后继续做可能失真的解析。

上下游关系

  • 调用方: 未在给定源码片段中展示调用方; 外部调用关系无法仅凭此函数体确定
  • 核心被调用: 字符串方法 content.find(">", i):定位当前标签结束位置; 字符串方法 tag_content.startswith("!"):跳过前导 ! 的标签片段; 字符串方法 tag_content.startswith("?"):跳过前导 ? 的标签片段; 字符串方法 tag_content.startswith("/"):识别闭合标签; 字符串方法 tag_content.endswith("/"):识别自闭合标签; 字符串方法 tag_content.split():在存在空格时提取首词作为标签名; 列表方法 top_level_tags.append(tag_name):记录零深度遇到的标签名
  • 配置/状态来源: 无:函数体不读取任何 self 属性; 无:函数体不访问任何 register 或外部配置
  • 结果去向: 返回给直接调用者的 List[str] 结果; 结果仅通过返回值对外暴露,不写入实例状态
  • 同类 sibling: 同组的 TerminusXMLPlainParser._extract_response_content 也是纯字符串处理辅助函数,但它做固定边界切片;本函数则做逐字符扫描与层级分类。; 同组的 TerminusXMLPlainParser._combine_warnings 只组合文本,不维护扫描状态;本函数通过局部变量 depth 表达嵌套层级。
TerminusJSONPlainParser.__init__terminus_json_plain_parser.py:26–27 ↗

JSON/plain 解析器构造初始化

stage 上下文: 该条目对应解析器内部辅助类的构造函数。就源码可见事实而言,它仅在实例初始化时写入一项实例属性;未展示任何进一步解析逻辑,也未与同 stage 其他辅助函数发生可见交互。

这段代码在干什么

__init__(self) 只接收实例自身 self。函数体将 self.required_fields 直接赋值为一个新的列表字面量 ["analysis", "plan", "commands"],随后隐式返回 None。除这次属性写入外,源码中没有其他状态读取、函数调用或外部副作用。

接口 · 参数 / IO

(self)

  • 参数: self: ? — 当前解析器实例
  • 返回: 隐式返回 None;实际产出是把 self.required_fields 设为新的列表字面量
  • 副作用: 写入 self.required_fields

执行流

  1. 接收参数 self,不读取其他输入。
  2. self.required_fields 赋值为新列表 ["analysis", "plan", "commands"]
  3. 函数结束并隐式返回 None

源码

    def __init__(self):
        self.required_fields = ["analysis", "plan", "commands"]

Non-obvious 设计决策

  • 构造时直接使用硬编码列表字面量,而不是从参数或其他状态推导其值。
  • 赋值采用覆盖写入 self.required_fields 的方式;每次调用都会放入一个新的列表对象。

上下游关系

  • 调用方: 源码片段未展示
  • 核心被调用: 无
  • 配置/状态来源: 无;仅使用函数体内列表字面量
  • 结果去向: self.required_fields
TerminusJSONPlainParser._get_auto_fixesterminus_json_plain_parser.py:305–313 ↗

JSON 解析器的自动修复表构造器

stage 上下文: 该函数属于 parser 子系统内部辅助逻辑,职责是在 JSON/plain 解析失败后的修复阶段提供候选策略表。它本身不执行修复,只把可尝试的修复器按顺序组织出来。与同 stage 中 XML 版本的 _get_auto_fixes 相对应,这里返回的是 JSON/plain 专用的两条修复项。

这段代码在干什么

_get_auto_fixes(self) 接收 TerminusJSONPlainParser 实例自身 self,返回一个新的列表字面量。列表中固定包含两个二元组:说明文本与对应的绑定方法引用,分别是 self._fix_incomplete_jsonself._fix_mixed_content。函数体内不调用这些方法,也不写入任何实例状态或外部状态。

接口 · 参数 / IO

(self)

  • 参数: self: ? — 当前 TerminusJSONPlainParser 实例;用于读取修复器方法属性并生成返回表
  • 读状态: self._fix_incomplete_json, self._fix_mixed_content
  • 返回: 返回一个新列表,包含两个 (description, fixer_callable) 二元组:("Fixed incomplete JSON by adding missing closing brace", self._fix_incomplete_json)("Extracted JSON from mixed content", self._fix_mixed_content);其中 callable 以绑定方法引用形式返回,未在本函数内执行。

执行流

  1. 构造并准备返回一个新的列表字面量,作为自动修复策略表。
  2. 在第一个元素中放入说明文本 "Fixed incomplete JSON by adding missing closing brace",并通过属性读取取得绑定方法引用 self._fix_incomplete_json;该方法在此处仅被返回,不被调用。
  3. 在第二个元素中放入说明文本 "Extracted JSON from mixed content",并通过属性读取取得绑定方法引用 self._fix_mixed_content;同样仅返回引用,不在此处执行。
  4. 将上述两项按源码中的既定顺序整体返回。

源码

    def _get_auto_fixes(self):
        """Return list of auto-fix functions to try in order."""
        return [
            (
                "Fixed incomplete JSON by adding missing closing brace",
                self._fix_incomplete_json,
            ),
            ("Extracted JSON from mixed content", self._fix_mixed_content),
        ]

Non-obvious 设计决策

  • 函数把“说明文本 + 修复器”成对返回,而不是只返回 callable,这一设计使每个修复策略都自带可读诊断标签;这一点可直接从两个二元组的结构看出。
  • 返回顺序被硬编码在列表字面量中,说明策略次序是显式的解析器政策,而不是无序集合或动态发现结果。
  • 源码使用 return [ ... ] 直接返回列表字面量,意味着每次调用都会产生一个新的列表容器,而不是复用先前保存的列表对象。

上下游关系

  • 调用方: TerminusJSONPlainParser.parse_response
  • 核心被调用: self._fix_incomplete_json(作为返回的绑定方法引用读取,不在此处调用); self._fix_mixed_content(作为返回的绑定方法引用读取,不在此处调用)
  • 配置/状态来源: 无外部配置输入; self._fix_incomplete_json 属性查找; self._fix_mixed_content 属性查找
  • 结果去向: 返回给 TerminusJSONPlainParser.parse_response; 作为后续自动修复尝试的候选策略表; 为每个修复器提供对应的人类可读说明文本
  • 同类 sibling: 与 TerminusXMLPlainParser._get_auto_fixes 同属“返回有序自动修复表”的兄弟函数,但该函数返回的是 JSON/plain 路径下的两条修复项。; 与 TerminusJSONPlainParser.__init__ 相比,本函数不设置 self.required_fields 一类实例字段,只读取两个方法属性来组装返回值。
TerminusJSONPlainParser._fix_incomplete_jsonterminus_json_plain_parser.py:315–328 ↗

JSON 截断响应的局部补全助手

stage 上下文: 该单元位于 parser 内部辅助层,职责仅限于对单次 JSON 修复尝试做局部判断与改写。就源码可见事实而言,它不访问解析器实例状态,只基于 responseerror 两个入参决定是否补上缺失的 }。与同 stage 中若干兄弟函数一致,这里也采用“返回修复后文本与是否修复成功”的纯函数式接口,不在函数内持久化任何状态。

这段代码在干什么

_fix_incomplete_json(self, response: str, error: str) -> tuple[str, bool] 检查错误文本 error 是否包含四类固定提示之一:"Invalid JSON""Expecting""Unterminated""No valid JSON found"。只有在这些条件之一成立,且 response.count("{") - response.count("}") 大于 0 时,函数才会在原始 response 末尾追加对应数量的右花括号 },并返回 (fixed, True);其余情况都原样返回 (response, False)。函数不读取任何 self 属性,也没有实例状态或外部副作用。

接口 · 参数 / IO

(self, response: str, error: str) -> tuple[str, bool]

  • 参数: self: TerminusJSONPlainParser — 实例自身;在该函数体内未被读取; response: str — 待检查并可能补全的原始响应字符串; error: str — 上游提供的错误描述文本,用于决定是否进入补全分支
  • 返回: 返回二元组 (文本, 是否已修复):仅当 error 含四个支持子串之一,且 response.count('{') - response.count('}') > 0 时,返回在原字符串末尾补足 } 后的新字符串与 True;否则返回原始 responseFalse

执行流

  1. 先对入参 error 做四个固定子串的包含判断:"Invalid JSON""Expecting""Unterminated""No valid JSON found";若全部不匹配,则直接走到底部返回 (response, False)
  2. 只有在上述错误门槛命中时,才计算 brace_count = response.count("{") - response.count("}"),即当前文本中未闭合左花括号的净数量。
  3. brace_count > 0,则构造 fixed = response + "}" * brace_count,把同样数量的右花括号追加到字符串末尾,并立即返回 (fixed, True)
  4. 若错误门槛虽然命中,但 brace_count <= 0,函数不会做任何修改,最终仍返回 (response, False)

源码

    def _fix_incomplete_json(self, response: str, error: str) -> tuple[str, bool]:
        """Fix incomplete JSON by adding missing closing braces."""
        if (
            "Invalid JSON" in error
            or "Expecting" in error
            or "Unterminated" in error
            or "No valid JSON found" in error
        ):
            # Try adding closing braces
            brace_count = response.count("{") - response.count("}")
            if brace_count > 0:
                fixed = response + "}" * brace_count
                return fixed, True
        return response, False

Non-obvious 设计决策

  • 代码把修复尝试严格限制在四种错误文本模式之内,而不是对所有解析失败都补花括号;这一约束直接体现在开头的 if (... in error or ...) 条件上。
  • 即使错误文本命中,函数也只有在 response 中左花括号数量多于右花括号数量时才改写输出;这表明“错误提示匹配”只是必要条件,不是充分条件。
  • 修复范围只涉及花括号平衡:源码只统计 {},并仅追加 },不尝试补引号、方括号、逗号或其他 JSON 结构。相较更宽泛的改写,这里的行为边界更容易从返回条件直接判定。

上下游关系

  • 调用方: 未在该源码片段中显式出现
  • 核心被调用: error.__contains__(通过 "..." in error 触发的字符串包含判断); response.count; str.__mul__("}" * brace_count); str.__add__(response + ...
  • 配置/状态来源: 参数 response; 参数 error
  • 结果去向: 返回给本函数的直接调用方,形式为 (possibly_fixed_response, did_fix)
  • 同类 sibling: 同为 TerminusJSONPlainParser 的内部辅助函数;与已翻译的 __init___get_auto_fixes 一样,本函数本体不修改解析器持久状态。
TerminusJSONPlainParser._combine_warningsterminus_json_plain_parser.py:345–350 ↗

警告文本拼接格式化辅助函数

stage 上下文: 该函数在本 stage 中承担极小粒度的字符串组合职责:把 auto_warning 按固定项目符号格式并入 existing_warning。从函数体可见,它只做一次对 existing_warning 真值性的分支判断,不读取任何解析器实例状态。与同类辅助函数一致,这里返回新字符串而非修改对象内部字段。

这段代码在干什么

_combine_warnings(self, auto_warning, existing_warning) 接收两段字符串输入,并返回一段新的警告文本。若 existing_warning 为真值,返回值精确为 "- {auto_warning}\n{existing_warning}";若其为假值,返回值精确为 "- {auto_warning}"。函数体不读取 self 上的任何属性,也不写入实例或外部状态。

接口 · 参数 / IO

(self, auto_warning: str, existing_warning: str) -> str

  • 参数: self: ? — 实例自身;在函数体内未被读取; auto_warning: str — 将被格式化为前导 - 项的警告文本; existing_warning: str — 用于真值判断;若为真值则作为后续文本原样拼接
  • 返回: 返回一个新构造的警告字符串:要么是仅含 - {auto_warning} 的单行文本,要么是在其后追加换行和 existing_warning 的两段文本。

执行流

  1. 接收 auto_warningexisting_warning 两个参数,并进入 if existing_warning 的真值判断分支。
  2. existing_warning 为真值时,返回格式为 - {auto_warning} 开头、后接换行与 existing_warning 原文的新字符串。
  3. existing_warning 为假值时,返回只包含 - {auto_warning} 的新字符串,不追加换行或其他内容。

源码

    def _combine_warnings(self, auto_warning: str, existing_warning: str) -> str:
        """Combine auto-correction warning with existing warnings."""
        if existing_warning:
            return f"- {auto_warning}\n{existing_warning}"
        else:
            return f"- {auto_warning}"

Non-obvious 设计决策

  • 分支条件使用的是 if existing_warning 真值判断,而不是显式比较空字符串;可观察结果是所有假值输入都会走同一路径。
  • 当存在 existing_warning 时,函数把新条目放在返回字符串的第一行,且对 existing_warning 不做重新加前缀、拆行或标准化处理,只是原样接在换行之后。
  • 函数通过直接返回格式化结果完成工作,不在 self 上缓存中间结果;其直接后果是调用方若需保留组合后的文本,必须接收返回值。

上下游关系

  • 调用方: 未在给定源码片段中出现; 需由外层调用方传入 auto_warningexisting_warning
  • 核心被调用: 无显式函数调用; 仅使用 f-string 构造返回字符串
  • 配置/状态来源: 参数 auto_warning; 参数 existing_warning
  • 结果去向: 直接作为函数返回值返回给调用方; 返回结果内容完全由两个入参决定
  • 同类 sibling: 同 stage 的 TerminusXMLPlainParser._combine_warnings 采用相同的二分支返回结构:有现有文本时前置新条目并追加换行,否则只返回单条项目符号文本。
TerminusXMLPlainParser.salvage_truncated_responseterminus_xml_plain_parser.py:528–580 ↗

截断回复的保守式补救判定器

stage 上下文: 该条目位于 parser 内部辅助层,源码只体现它对一段 truncated_response 做一次最佳努力的补救判定。它先从字符串中寻找完整的 </commands> 与后续 </response> 边界,再把截出的片段交给 self.parse_response(clean_response) 复验;若结构不完整、复验报错、出现特定多块警告,或解析抛出异常,则直接放弃补救。

这段代码在干什么

salvage_truncated_response 接收一段可能被截断的响应文本 truncated_response,尝试从中截出一个以 </response> 结束的完整片段,并判断该片段是否可作为有效回复返回。它依赖 self.parse_response(clean_response) 的结果对象中的 warningerror 字段来决定输出。返回值是 (salvaged_response, has_multiple_blocks):成功时返回清理后的字符串与 False,失败时返回 None,并在识别出特定“只能一次发一个块”的警告时把第二项置为 True。函数本身不写入实例状态,也无外部副作用。

接口 · 参数 / IO

(self, truncated_response: str) -> tuple[str | None, bool]

  • 参数: self: ? — 解析器实例;函数通过 self.parse_response(clean_response) 复验截出的片段,并依据返回对象的 warning/error 字段决定是否补救成功; truncated_response: str — 可能被截断的原始回复文本,作为字符串搜索与切片的输入
  • 读状态: 可调用成员 self.parse_response`, self.parse_response(clean_response) 返回对象的 warning 字段, self.parse_response(clean_response) 返回对象的 error 字段`
  • 返回: 返回二元组 (salvaged_response, has_multiple_blocks)。当文本中能找到 </commands> 与其后的 </response>,且 self.parse_response(clean_response) 未给出 error、也未触发特定多块警告时,返回 (clean_response, False);其余情况下返回 (None, has_multiple_blocks_or_False)

执行流

  1. 先在 truncated_response 中查找 "</commands>";若 commands_end == -1,立即返回 (None, False),不再尝试后续补救。
  2. 若找到 </commands>,再从该位置之后查找 "</response>";若 response_end == -1,同样立即返回 (None, False)
  3. 当两个闭合标签都存在时,以 response_end + len("</response>") 为边界切出 clean_response,即只保留到完整 </response> 结束的前缀。
  4. try 块内调用 self.parse_response(clean_response) 得到 parse_result,随后把 has_multiple_blocks 初始化为 False
  5. parse_result.warning 为真值,则先转成小写 warning_lower,并仅在同时包含 "only issue one""block at a time" 这两个子串时,把 has_multiple_blocks 设为 True
  6. parse_result.error 为假值且 has_multiple_blocks 仍为 False,则把当前 clean_response 视为可用结果,返回 (clean_response, False)
  7. 否则,若存在解析错误或识别出多块警告,则放弃补救,返回 (None, has_multiple_blocks)
  8. self.parse_response(clean_response) 或后续字段访问在 try 中抛出任意异常,except Exception 会统一吞掉异常并返回 (None, False)

源码

    def salvage_truncated_response(
        self, truncated_response: str
    ) -> tuple[str | None, bool]:
        """
        Try to salvage a valid response from truncated output.

        Args:
            truncated_response: The truncated response from the LLM

        Returns:
            Tuple of (salvaged_response, has_multiple_blocks)
            - salvaged_response: Clean response up to </response> if salvageable, "
                "None otherwise
            - has_multiple_blocks: True if multiple response/commands blocks "
                "were detected
        """
        # Check if we can find a complete response structure
        commands_end = truncated_response.find("</commands>")
        if commands_end == -1:
            return None, False

        # Find the </response> tag after </commands>
        response_end = truncated_response.find("</response>", commands_end)
        if response_end == -1:
            return None, False

        # We have a complete response up to at least </commands>
        # Truncate cleanly at </response>
        clean_response = truncated_response[: response_end + len("</response>")]

        # Check if this is a valid response with no critical issues
        try:
            parse_result = self.parse_response(clean_response)

            # Check if there are no errors and no "multiple blocks" warnings
            has_multiple_blocks = False
            if parse_result.warning:
                warning_lower = parse_result.warning.lower()
                has_multiple_blocks = (
                    "only issue one" in warning_lower
                    and "block at a time" in warning_lower
                )

            if not parse_result.error and not has_multiple_blocks:
                # Valid response! Return the clean truncated version
                return clean_response, False
            else:
                # Has errors or multiple blocks
                return None, has_multiple_blocks

        except Exception:
            # If parsing fails, not salvageable
            return None, False

Non-obvious 设计决策

  • 函数把“是否值得尝试补救”前置为两个显式闭合标签检查:缺少 </commands> 或缺少其后的 </response> 时直接失败。这意味着它只接受源码中可定位到完整结束边界的片段,不对更模糊的不完整文本做猜测性修复。
  • 即使已经通过字符串搜索得到 clean_response,代码仍强制再走一次 self.parse_response(clean_response)。这里的取舍是:闭合标签齐全并不足以证明内容有效,真正的通过条件还取决于 parse_result.error 为空且未触发特定警告。
  • 多块判定并不是把任意 warning 都视为失败,而是只匹配小写化后同时含有 "only issue one""block at a time" 的警告文本。这个选择把失败条件收窄到一个特定告警模式,其它警告不会单独导致 has_multiple_blocks=True
  • 成功返回时固定输出 (clean_response, False),即便此前计算过 has_multiple_blocks 变量;只有在失败分支中才可能把第二项作为 True 返回。代码由此把“补救成功”与“检测到多块”设为互斥结果。
  • 异常处理采用宽泛的 except Exception 并统一降级为 (None, False)。直接后果是:任何解析器异常都会被当作“不可补救”而非继续传播,从接口上维持一个纯返回值式的失败信号。

上下游关系

  • 调用方: 源码片段未提供,无法从该函数体直接确认调用方
  • 核心被调用: self.parse_response(clean_response); truncated_response.find("</commands>"); truncated_response.find("</response>", commands_end); parse_result.warning.lower()
  • 配置/状态来源: truncated_response 的字符串内容决定两个闭合标签是否存在以及 clean_response 的截断边界; self.parse_response 的可调用行为决定复验是否成功或是否抛出异常; parse_result.warning 的文本内容决定是否把结果标记为多块; parse_result.error 的真值决定是否接受 clean_response
  • 结果去向: 直接作为本函数返回值输出给其调用者:(clean_response, False); 直接作为本函数返回值输出给其调用者:(None, False); 直接作为本函数返回值输出给其调用者:(None, True)