- Renamed `check_environment` to `check_api_key_configured` for clarity, simplifying the API key validation logic. - Removed the blocking behavior of the API key check during application startup, allowing the app to run while providing a prompt for configuration. - Updated `LocalAgentApp` to accept an `api_configured` parameter, enabling conditional messaging for API key setup. - Enhanced the `SandboxRunner` to support backup management and improved execution result handling with detailed metrics. - Integrated data governance strategies into the `HistoryManager`, ensuring compliance and improved data management. - Added privacy settings and metrics tracking across various components to enhance user experience and application safety.
6.6 KiB
6.6 KiB
P1-02 重试策略修复说明
问题描述
问题标题: 重试策略声明与实际行为不一致
问题类型: 技术/稳定性
所在位置: llm/client.py:68, 149, 218
核心问题
网络异常(Timeout、ConnectionError)先被包装为 LLMClientError,后续 _should_retry 方法只能通过字符串匹配判断是否重试,导致大部分网络异常无法被正确识别为可重试异常,弱网环境下稳定性下降。
影响范围
- 意图识别模块
- 生成计划模块
- 代码生成模块
- 所有 LLM 调用场景
在网络抖动环境下,这些模块的失败率显著升高。
修复方案
1. 异常分类系统
为 LLMClientError 添加了错误类型分类:
class LLMClientError(Exception):
# 异常类型分类
TYPE_NETWORK = "network" # 网络错误(超时、连接失败等)
TYPE_SERVER = "server" # 服务器错误(5xx)
TYPE_CLIENT = "client" # 客户端错误(4xx)
TYPE_PARSE = "parse" # 解析错误
TYPE_CONFIG = "config" # 配置错误
def __init__(self, message: str, error_type: str = TYPE_CLIENT,
original_exception: Optional[Exception] = None):
super().__init__(message)
self.error_type = error_type
self.original_exception = original_exception
2. 统一重试判断逻辑
重构 _should_retry 方法,基于异常类型而非字符串匹配:
def _should_retry(self, exception: Exception) -> bool:
"""
判断是否应该重试
可重试的异常类型:
- 网络错误(超时、连接失败)
- 服务器错误(5xx)
- 限流错误(429)
"""
# LLMClientError 根据错误类型判断
if isinstance(exception, LLMClientError):
# 网络错误和服务器错误可以重试
if exception.error_type in (LLMClientError.TYPE_NETWORK,
LLMClientError.TYPE_SERVER):
return True
# 检查原始异常
if exception.original_exception:
if isinstance(exception.original_exception,
(requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.ChunkedEncodingError)):
return True
return False
3. 保留原始异常信息
在所有异常包装点保留原始异常:
非流式请求 (chat):
except requests.exceptions.Timeout as e:
raise LLMClientError(
f"请求超时({timeout}秒)",
error_type=LLMClientError.TYPE_NETWORK,
original_exception=e
)
流式请求 (chat_stream):
except requests.exceptions.ConnectionError as e:
raise LLMClientError(
"网络连接失败",
error_type=LLMClientError.TYPE_NETWORK,
original_exception=e
)
4. 状态码分类
根据 HTTP 状态码自动分类错误类型:
if response.status_code >= 500:
error_type = LLMClientError.TYPE_SERVER # 可重试
elif response.status_code == 429:
error_type = LLMClientError.TYPE_SERVER # 限流,可重试
else:
error_type = LLMClientError.TYPE_CLIENT # 不重试
5. 增强重试度量
在 _do_request_with_retry 中增强度量记录:
- 记录重试次数
- 记录错误类型
- 记录重试后成功/失败
- 输出更详细的重试日志
测试验证
测试结果
✅ 所有测试通过
测试 1: 异常分类
✓ 网络错误类型: network
✓ 服务器错误类型: server
✓ 客户端错误类型: client
测试 2: 重试判断逻辑
✓ 网络错误应该重试: True
✓ 超时错误应该重试: True
✓ 服务器错误应该重试: True
✓ 客户端错误不应该重试: False
✓ 解析错误不应该重试: False
✓ 配置错误不应该重试: False
✓ 带原始异常的网络错误应该重试: True
测试 3: 错误类型保留
✓ 状态码 500-504 (服务器错误): server
✓ 状态码 429 (限流错误): server
✓ 状态码 400-404 (客户端错误): client
修复效果
可重试的异常类型
| 异常类型 | 修复前 | 修复后 |
|---|---|---|
| 网络超时 (Timeout) | ❌ 不重试 | ✅ 重试 |
| 连接失败 (ConnectionError) | ❌ 不重试 | ✅ 重试 |
| 服务器错误 (5xx) | ⚠️ 部分重试 | ✅ 重试 |
| 限流错误 (429) | ❌ 不重试 | ✅ 重试 |
| 客户端错误 (4xx) | ❌ 不重试 | ❌ 不重试 |
| 解析错误 | ❌ 不重试 | ❌ 不重试 |
| 配置错误 | ❌ 不重试 | ❌ 不重试 |
预期改进
- 稳定性提升: 弱网环境下的请求成功率显著提高
- 用户体验: 网络抖动时自动恢复,无需手动重试
- 可观测性: 更详细的重试日志和度量指标
- 准确性: 只重试真正可恢复的错误,避免无效重试
度量指标
建议监控的指标
- LLM 请求成功率: 总成功次数 / 总请求次数
- 平均重试次数: 总重试次数 / 总请求次数
- 超时后恢复成功率: 重试成功次数 / 超时次数
- 网络错误分布: 各类网络错误的占比
- 重试延迟: 重试导致的额外延迟时间
度量数据位置
- 配置度量:
workspace/.metrics/config_metrics.json - 重试日志: 控制台输出
向后兼容性
✅ 完全向后兼容
LLMClientError仍然是标准异常,可以正常捕获- 新增的
error_type和original_exception属性是可选的 - 现有代码无需修改即可受益于修复
使用示例
捕获特定类型的错误
from llm.client import get_client, LLMClientError
try:
client = get_client()
response = client.chat(messages=[...], model="...")
except LLMClientError as e:
if e.error_type == LLMClientError.TYPE_NETWORK:
print("网络错误,已自动重试")
elif e.error_type == LLMClientError.TYPE_CONFIG:
print("配置错误,请检查 .env 文件")
else:
print(f"其他错误: {e}")
检查原始异常
try:
response = client.chat(...)
except LLMClientError as e:
if e.original_exception:
print(f"原始异常: {type(e.original_exception).__name__}")
相关文件
llm/client.py: 主要修复文件llm/config_metrics.py: 度量指标增强test_retry_fix.py: 验证测试脚本
总结
此次修复解决了重试策略声明与实际行为不一致的核心问题,通过引入异常分类系统和保留原始异常信息,确保网络异常能够被正确识别并重试。预期在弱网环境下,系统稳定性将显著提升。