Skip to content

Commit 902c03e

Browse files
authored
Merge pull request #18 from ZyphrZero/pre
fix(app): 优化认证流程和 JSON 修复功能
2 parents bd86308 + 6703e61 commit 902c03e

11 files changed

Lines changed: 1057 additions & 197 deletions

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ wheels/
2525
.installed.cfg
2626
*.egg
2727
MANIFEST
28+
tests/
29+
docs/
2830

2931
# 虚拟环境
3032
venv/

app/core/zai_transformer.py

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def generate_uuid() -> str:
101101

102102
def get_auth_token_sync() -> str:
103103
"""同步获取认证令牌(用于非异步场景)"""
104-
# 首先尝试匿名模式(如果启用)
104+
# 如果启用匿名模式,只尝试获取访客令牌
105105
if settings.ANONYMOUS_MODE:
106106
try:
107107
headers = get_dynamic_headers()
@@ -116,29 +116,32 @@ def get_auth_token_sync() -> str:
116116
except Exception as e:
117117
logger.warning(f"获取访客令牌失败: {e}")
118118

119-
# 使用token池获取备份令牌
119+
# 匿名模式下,如果获取访客令牌失败,直接返回空
120+
logger.error("❌ 匿名模式下获取访客令牌失败")
121+
return ""
122+
123+
# 非匿名模式:首先使用token池获取备份令牌
120124
token_pool = get_token_pool()
121125
if token_pool:
122126
token = token_pool.get_next_token()
123127
if token:
124128
logger.debug(f"从token池获取令牌: {token[:20]}...")
125129
return token
126130

127-
# 如果没有配置匿名模式且没有备份token,尝试降级到匿名模式
128-
if not settings.ANONYMOUS_MODE:
129-
logger.warning("⚠️ 没有可用的备份token,尝试降级到匿名模式...")
130-
try:
131-
headers = get_dynamic_headers()
132-
with httpx.Client() as client:
133-
response = client.get("https://chat.z.ai/api/v1/auths/", headers=headers, timeout=10.0)
134-
if response.status_code == 200:
135-
data = response.json()
136-
token = data.get("token", "")
137-
if token:
138-
logger.info(f"✅ 降级到匿名模式成功: {token[:20]}...")
139-
return token
140-
except Exception as e:
141-
logger.warning(f"降级到匿名模式失败: {e}")
131+
# 如果没有备份token,尝试降级到匿名模式
132+
logger.warning("⚠️ 没有可用的备份token,尝试降级到匿名模式...")
133+
try:
134+
headers = get_dynamic_headers()
135+
with httpx.Client() as client:
136+
response = client.get("https://chat.z.ai/api/v1/auths/", headers=headers, timeout=10.0)
137+
if response.status_code == 200:
138+
data = response.json()
139+
token = data.get("token", "")
140+
if token:
141+
logger.info(f"✅ 降级到匿名模式成功: {token[:20]}...")
142+
return token
143+
except Exception as e:
144+
logger.warning(f"降级到匿名模式失败: {e}")
142145

143146
# 没有可用的token
144147
logger.error("❌ 所有认证方式都失败了")
@@ -165,7 +168,7 @@ def __init__(self):
165168

166169
async def get_token(self) -> str:
167170
"""异步获取认证令牌"""
168-
# 首先尝试匿名模式(如果启用)
171+
# 如果启用匿名模式,只尝试获取访客令牌
169172
if settings.ANONYMOUS_MODE:
170173
try:
171174
headers = get_dynamic_headers()
@@ -180,29 +183,32 @@ async def get_token(self) -> str:
180183
except Exception as e:
181184
logger.warning(f"异步获取访客令牌失败: {e}")
182185

183-
# 使用token池获取备份令牌
186+
# 匿名模式下,如果获取访客令牌失败,直接返回空
187+
logger.error("❌ 匿名模式下获取访客令牌失败")
188+
return ""
189+
190+
# 非匿名模式:首先使用token池获取备份令牌
184191
token_pool = get_token_pool()
185192
if token_pool:
186193
token = token_pool.get_next_token()
187194
if token:
188195
logger.debug(f"从token池获取令牌: {token[:20]}...")
189196
return token
190197

191-
# 如果没有配置匿名模式且没有备份token,尝试降级到匿名模式
192-
if not settings.ANONYMOUS_MODE:
193-
logger.warning("⚠️ 没有可用的备份token,尝试降级到匿名模式...")
194-
try:
195-
headers = get_dynamic_headers()
196-
async with httpx.AsyncClient() as client:
197-
response = await client.get(self.auth_url, headers=headers, timeout=10.0)
198-
if response.status_code == 200:
199-
data = response.json()
200-
token = data.get("token", "")
201-
if token:
202-
logger.info(f"✅ 降级到匿名模式成功: {token[:20]}...")
203-
return token
204-
except Exception as e:
205-
logger.warning(f"降级到匿名模式失败: {e}")
198+
# 如果没有备份token,尝试降级到匿名模式
199+
logger.warning("⚠️ 没有可用的备份token,尝试降级到匿名模式...")
200+
try:
201+
headers = get_dynamic_headers()
202+
async with httpx.AsyncClient() as client:
203+
response = await client.get(self.auth_url, headers=headers, timeout=10.0)
204+
if response.status_code == 200:
205+
data = response.json()
206+
token = data.get("token", "")
207+
if token:
208+
logger.info(f"✅ 降级到匿名模式成功: {token[:20]}...")
209+
return token
210+
except Exception as e:
211+
logger.warning(f"降级到匿名模式失败: {e}")
206212

207213
# 没有可用的token
208214
logger.error("❌ 所有认证方式都失败了")

app/utils/logger.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ def get_logger():
6969
"""Get the logger instance"""
7070
global app_logger
7171
if app_logger is None:
72-
72+
# 如果没有设置过logger,使用默认配置
73+
logger.remove() # 移除所有现有处理器
74+
logger.add(sys.stderr, level="INFO", format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>")
7375
app_logger = logger
74-
logger.add(sys.stderr, level="INFO")
7576
return app_logger
7677

7778

app/utils/sse_tool_handler.py

Lines changed: 103 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -366,80 +366,134 @@ def _fix_tool_arguments(self, raw_args: str) -> str:
366366

367367
logger.debug(f"🔧 开始修复参数: {raw_args[:1000]}{'...' if len(raw_args) > 1000 else ''}")
368368

369-
# 尝试直接解析
369+
# 统一的修复流程:预处理 -> json-repair -> 后处理
370370
try:
371-
args_obj = json.loads(raw_args)
372-
result = json.dumps(args_obj, ensure_ascii=False)
373-
logger.debug(f"✅ 参数无需修复: {result}")
374-
return result
375-
except json.JSONDecodeError:
376-
pass
371+
# 1. 预处理:只处理 json-repair 无法处理的问题
372+
processed_args = self._preprocess_json_string(raw_args.strip())
377373

378-
# 预处理:提取纯 JSON 部分和修复转义引号
379-
processed_args = self._preprocess_json_string(raw_args.strip())
380-
381-
# 使用 json-repair 库进行修复
382-
from json_repair import repair_json
383-
384-
try:
374+
# 2. 使用 json-repair 进行主要修复
375+
from json_repair import repair_json
385376
repaired_json = repair_json(processed_args)
386377
logger.debug(f"🔧 json-repair 修复结果: {repaired_json}")
387378

388-
# 验证修复结果
379+
# 3. 解析并后处理
389380
args_obj = json.loads(repaired_json)
390-
fixed_result = json.dumps(args_obj, ensure_ascii=False)
381+
args_obj = self._post_process_args(args_obj)
391382

392-
# 记录修复前后对比
393-
logger.info(f"✅ JSON 修复成功:")
394-
logger.info(f" 修复前: {raw_args[:1000]}{'...' if len(raw_args) > 1000 else ''}")
395-
logger.info(f" 修复后: {fixed_result}")
383+
# 4. 生成最终结果
384+
fixed_result = json.dumps(args_obj, ensure_ascii=False)
396385

397386
return fixed_result
398387

399388
except Exception as e:
400389
logger.error(f"❌ JSON 修复失败: {e}, 原始参数: {raw_args[:1000]}..., 使用空参数")
401390
return "{}"
402391

392+
def _post_process_args(self, args_obj: Dict[str, Any]) -> Dict[str, Any]:
393+
"""统一的后处理方法"""
394+
# 修复路径中的过度转义
395+
args_obj = self._fix_path_escaping_in_args(args_obj)
396+
397+
# 修复命令中的多余引号
398+
args_obj = self._fix_command_quotes(args_obj)
399+
400+
return args_obj
401+
403402
def _preprocess_json_string(self, text: str) -> str:
404-
"""预处理 JSON 字符串,修复转义引号和提取纯 JSON 部分"""
403+
"""预处理 JSON 字符串,只处理 json-repair 无法处理的问题"""
405404
import re
406405

407-
# 1. 如果包含额外内容(如 "result" 字段),提取纯 JSON 部分
408-
if '"result"' in text:
409-
brace_count = 0
410-
json_end = -1
411-
for i, char in enumerate(text):
412-
if char == '{':
413-
brace_count += 1
414-
elif char == '}':
415-
brace_count -= 1
416-
if brace_count == 0:
417-
json_end = i + 1
418-
break
419-
420-
if json_end > 0:
421-
text = text[:json_end]
422-
logger.debug(f"🔧 提取纯 JSON 部分: {text[:1000]}...")
423-
424-
# 2. 修复缺少开始括号的情况
406+
# 只保留 json-repair 无法处理的预处理步骤
407+
408+
# 1. 修复缺少开始括号的情况(json-repair 无法处理)
425409
if not text.startswith('{') and text.endswith('}'):
426410
text = '{' + text
427411
logger.debug(f"🔧 补全开始括号")
428412

429-
# 3. 修复转义引号问题
430-
# 处理 "key:\"value\"," 模式 -> "key":"value",
431-
pattern = r'(["\w]+):\\"([^"\\]*)\\"'
413+
# 2. 修复末尾多余的反斜杠和引号(json-repair 可能处理不当)
414+
# 匹配模式:字符串值末尾的 \" 后面跟着 } 或 ,
415+
# 例如:{"url":"https://www.bilibili.com\"} -> {"url":"https://www.bilibili.com"}
416+
# 例如:{"url":"https://www.bilibili.com\",} -> {"url":"https://www.bilibili.com",}
417+
pattern = r'([^\\])\\"([}\s,])'
432418
if re.search(pattern, text):
433-
text = re.sub(pattern, r'\1:"\2"', text)
434-
logger.debug(f"🔧 修复字段转义引号模式")
435-
436-
# 处理剩余的转义引号 \" -> "
437-
if '\\"' in text:
438-
text = text.replace('\\"', '"')
439-
logger.debug(f"🔧 替换剩余转义引号")
419+
text = re.sub(pattern, r'\1"\2', text)
420+
logger.debug(f"🔧 修复末尾多余的反斜杠")
440421

441422
return text
442423

424+
def _fix_path_escaping_in_args(self, args_obj: Dict[str, Any]) -> Dict[str, Any]:
425+
"""修复参数对象中路径的过度转义问题"""
426+
import re
427+
428+
# 需要检查的路径字段
429+
path_fields = ['file_path', 'path', 'directory', 'folder']
430+
431+
for field in path_fields:
432+
if field in args_obj and isinstance(args_obj[field], str):
433+
path_value = args_obj[field]
434+
435+
# 检查是否是Windows路径且包含过度转义
436+
if path_value.startswith('C:') and '\\\\' in path_value:
437+
logger.debug(f"🔍 检查路径字段 {field}: {repr(path_value)}")
438+
439+
# 分析路径结构:正常路径应该是 C:\Users\...
440+
# 但过度转义的路径可能是 C:\Users\\Documents(多了一个反斜杠)
441+
# 我们需要找到不正常的双反斜杠模式并修复
442+
443+
# 先检查是否有不正常的双反斜杠(不在路径开头)
444+
# 正常:C:\Users\Documents
445+
# 异常:C:\Users\\Documents 或 C:\Users\\\\Documents
446+
447+
# 使用更精确的模式:匹配路径分隔符后的额外反斜杠
448+
# 但要保留正常的路径分隔符
449+
fixed_path = path_value
450+
451+
# 检查是否有连续的多个反斜杠(超过正常的路径分隔符)
452+
if '\\\\' in path_value:
453+
# 计算反斜杠的数量,如果超过正常数量就修复
454+
parts = path_value.split('\\')
455+
# 重新组装路径,去除空的部分(由多余的反斜杠造成)
456+
clean_parts = [part for part in parts if part]
457+
if len(clean_parts) > 1:
458+
fixed_path = '\\'.join(clean_parts)
459+
460+
logger.debug(f"🔍 修复后路径: {repr(fixed_path)}")
461+
462+
if fixed_path != path_value:
463+
args_obj[field] = fixed_path
464+
logger.debug(f"🔧 修复字段 {field} 的路径转义: {path_value} -> {fixed_path}")
465+
else:
466+
logger.debug(f"🔍 路径无需修复: {path_value}")
467+
468+
return args_obj
469+
470+
def _fix_command_quotes(self, args_obj: Dict[str, Any]) -> Dict[str, Any]:
471+
"""修复命令中的多余引号问题"""
472+
import re
473+
474+
# 检查命令字段
475+
if 'command' in args_obj and isinstance(args_obj['command'], str):
476+
command = args_obj['command']
477+
478+
# 检查是否以双引号结尾(多余的引号)
479+
if command.endswith('""'):
480+
logger.debug(f"🔧 发现命令末尾多余引号: {command}")
481+
# 移除最后一个多余的引号
482+
fixed_command = command[:-1]
483+
args_obj['command'] = fixed_command
484+
logger.debug(f"🔧 修复命令引号: {command} -> {fixed_command}")
485+
486+
# 检查其他可能的引号问题
487+
# 例如:路径末尾的 \"" 模式
488+
elif re.search(r'\\""+$', command):
489+
logger.debug(f"🔧 发现命令末尾引号模式问题: {command}")
490+
# 修复路径末尾的引号问题
491+
fixed_command = re.sub(r'\\""+$', '\\"', command)
492+
args_obj['command'] = fixed_command
493+
logger.debug(f"🔧 修复命令引号模式: {command} -> {fixed_command}")
494+
495+
return args_obj
496+
443497
def _create_content_chunk(self, content: str) -> Dict[str, Any]:
444498
"""创建内容块"""
445499
return {

0 commit comments

Comments
 (0)