fix(goals): 对话目标分析 JSON 解析失败分类与 json-repair 兜底(issue #259) - #260
Conversation
- guardrails validate_and_clean_json 解析前剥离思考标签;失败分类为 空响应/无 JSON 结构纯文本/JSON 损坏/字段校验失败;响应预览升为 WARNING 便于定位;json-repair(可选依赖)兜底恢复截断 JSON - 对话目标/意图分析:LLM 空响应与消毒后为空显式降级,不再流入 误导性的 JSON 解析报错 - 新增回归测试 test_guardrails_json_parsing.py
Reviewer's Guide该 PR 改进对话目标分析的 LLM JSON 处理链路:先剥离推理内容,按响应失败类型提供可定位日志,并通过可选 json-repair 增强修复能力;目标管理器对空响应直接默认降级,同时补充回归测试并发布 4.2.6。 Sequence diagram for resilient goal analysis JSON parsingsequenceDiagram
participant LLM
participant GoalManager as ConversationGoalManager
participant Guardrails as GuardrailsManager
participant JsonRepair as json_repair
participant Logger
GoalManager->>LLM: analyze goal or intent
LLM-->>GoalManager: response
alt response is empty
GoalManager->>Logger: warning default fallback
else response has content
GoalManager->>Guardrails: validate_and_clean_json(response)
Guardrails->>Guardrails: remove_thinking_content(response)
alt valid JSON
Guardrails-->>GoalManager: parsed result
else malformed JSON
Guardrails->>Logger: warning category and preview
Guardrails->>JsonRepair: repair_json(text, return_objects=True)
alt repair succeeds
JsonRepair-->>Guardrails: repaired object
Guardrails-->>GoalManager: parsed result
else repair unavailable or fails
Guardrails-->>GoalManager: None
GoalManager->>Logger: default fallback
end
end
end
Flow diagram for categorized JSON parsing failuresflowchart TD
A[LLM response] --> B{Input empty?}
B -->|Yes| C[Log empty response]
C --> D[Return default fallback]
B -->|No| E[remove_thinking_content]
E --> F{Content remains?}
F -->|No| G[Log response empty after cleaning]
G --> D
F -->|Yes| H[Extract JSON structure]
H --> I{JSON parses and fields validate?}
I -->|Yes| J[Return validated result]
I -->|No JSON structure| K[Log pure-text response and preview]
I -->|JSON structure damaged| L[Log damaged JSON and preview]
K --> M[Attempt standard JSON repair]
L --> M
M --> N{Repair succeeds?}
N -->|Yes| J
N -->|No| O[_json_repair_fallback]
O --> P{json-repair succeeds?}
P -->|Yes| J
P -->|No| D
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="utils/guardrails_manager.py" line_range="620-623" />
<code_context>
+ logger.warning(f" [Guardrails] 常规 JSON 修复失败: {fix_error}")
+
+ # 最后兜底:json-repair(AstrBot 环境通常自带;未安装时静默跳过)
+ repaired = self._json_repair_fallback(cleaned_text)
+ if repaired is not None:
+ logger.info(f" [Guardrails] json-repair 兜底解析成功")
+ return repaired
+
+ logger.error(f" [Guardrails] JSON 解析与修复全部失败,返回 None 交由上层降级")
</code_context>
<issue_to_address>
**issue (bug_risk):** The json-repair fallback returns any non-empty repaired value without enforcing `expected_type` or requiring an object/array. With json-repair installed, a plain-text response can be returned as a string and logged as a successful parse, while a response expected to be an object can be returned as an array or scalar, bypassing the stated JSON-structure classification and potentially reaching callers with the wrong shape.
**Triggers:** When the initial JSON parse fails and the optional `json-repair` dependency is installed.
**Suggested fix:** After repair, validate the result against `expected_type` and reject scalar values or the wrong container type before returning it.
```suggestion
repaired = self._json_repair_fallback(cleaned_text)
if repaired is not None:
if expected_type == "object":
is_valid = isinstance(repaired, dict)
elif expected_type == "array":
is_valid = isinstance(repaired, list)
else:
is_valid = isinstance(repaired, (dict, list))
if not is_valid:
logger.warning(f" [Guardrails] json-repair 结果类型不符合预期: {type(repaired).__name__}")
return None
logger.info(f" [Guardrails] json-repair 兜底解析成功")
return repaired
```
</issue_to_address>Sourcery assessment
Approval pending. 1 finding to address first.
Blocking findings: utils/guardrails_manager.py:623
| repaired = self._json_repair_fallback(cleaned_text) | ||
| if repaired is not None: | ||
| logger.info(f" [Guardrails] json-repair 兜底解析成功") | ||
| return repaired |
There was a problem hiding this comment.
issue (bug_risk): The json-repair fallback returns any non-empty repaired value without enforcing expected_type or requiring an object/array. With json-repair installed, a plain-text response can be returned as a string and logged as a successful parse, while a response expected to be an object can be returned as an array or scalar, bypassing the stated JSON-structure classification and potentially reaching callers with the wrong shape.
Triggers: When the initial JSON parse fails and the optional json-repair dependency is installed.
Suggested fix: After repair, validate the result against expected_type and reject scalar values or the wrong container type before returning it.
| repaired = self._json_repair_fallback(cleaned_text) | |
| if repaired is not None: | |
| logger.info(f" [Guardrails] json-repair 兜底解析成功") | |
| return repaired | |
| repaired = self._json_repair_fallback(cleaned_text) | |
| if repaired is not None: | |
| if expected_type == "object": | |
| is_valid = isinstance(repaired, dict) | |
| elif expected_type == "array": | |
| is_valid = isinstance(repaired, list) | |
| else: | |
| is_valid = isinstance(repaired, (dict, list)) | |
| if not is_valid: | |
| logger.warning(f" [Guardrails] json-repair 结果类型不符合预期: {type(repaired).__name__}") | |
| return None | |
| logger.info(f" [Guardrails] json-repair 兜底解析成功") | |
| return repaired |
修复内容(issue #259)
对话目标分析中模型返回纯文本/思考内容时,
validate_and_clean_json连续报JSON 解析失败/JSON 修复失败: Expecting value: line 1 column 1 (char 0),且日志无法定位原因。guardrails_manager
<think>等,推理型模型常见),不再干扰 JSON 提取json-repair(可选依赖,AstrBot 环境通常自带;未安装时静默跳过),可恢复截断 JSON 等常规正则修复覆盖不了的情况conversation_goal_manager
测试
tests/unit/test_guardrails_json_parsing.py(24 个用例):空响应、纯文本分类、思考标签包裹、单引号/尾逗号修复、截断 JSON 兜底、字段校验失败、目标管理器空响应短路径版本
Closes #259
Summary by Sourcery
Improve conversation-goal analysis resilience and diagnostics for empty, explanatory, and malformed LLM responses.
Bug Fixes:
Enhancements:
Tests:
Chores: