fix(aibox): Batch N 波次1~2——第八轮审计 T 系列速赢与并发正确性修复
波次1 速赢批(7 项): - T8 ProcessManager watermark 取号与回显记账原子化(entry.lock 改 RLock) - T9 LLM 非 200 响应抛出前 close,流式路径清 _current_response - T13 _sanitize_log_msg 增加裸 sk- key 脱敏,异常构造点统一过脱敏 - T12 流式判定改共享 saw_complete 标志(排空后 empty() 恒 False 死逻辑), 超长截断前移堵住非流式直发绕过 N8 输出预算 - T16/T17 三处 Popen 显式 encoding=utf-8 + errors=replace - T18 detach/log_file/force 统一过 _coerce_bool(字符串 "false" 误判) 波次2 并发正确性批(6 项): - T4 registry 输出队列入口快照,不再向共享工具实例传播(跨会话串扰) - T5 executor 单槽 _current_process 改令牌多槽注册表,_terminate_tree 显式传 (proc, pgid),finally 只注销自己槽位(防并发误杀进程组) - T6 FailoverClient 冷却键改 id(client),与 P6 去重键语义一致 - T7 stream_chat 拆普通方法+内部生成器,消除生成器延迟副作用清除取消标志 - T15 工具结果统一 TOOL_MAX_RESULT_LENGTH 截断(两条分支同口径) - T24 非流式 execute() timeout 下限夹取 1s(该路径无 3600s 兜底) 测试:532 passed / 4 failed(平台环境项)/ 1 skipped,零回归 审计报告补记 §23 第八轮审计 + §24 Batch N 波次1~2 修复记录
This commit is contained in:
1 parent
3e860c16e0
commit
5894e64287
8 files changed
+583
-110
No files matched your search
@@ -133,6 +133,11 @@ class BgRunTool(Tool):
|
||||
if not _enabled():
|
||||
return "❌ 后台进程功能已禁用(commands.enabled 或 background_processes.enabled 为 false)"
|
||||
|
||||
# T18:LLM 可能传字符串 "false",裸 bool("false") is True——
|
||||
# detach 误开致进程脱离管理、log_file 误开,统一规整
|
||||
detach = _coerce_bool(detach, False)
|
||||
log_file = _coerce_bool(log_file, False)
|
||||
|
||||
result = self.manager.start(command, name=proc_name, work_dir=work_dir,
|
||||
env=env, detach=detach, log_file=log_file)
|
||||
if not result.get("ok"):
|
||||
@@ -393,6 +398,9 @@ class BgKillTool(Tool):
|
||||
if handle is None:
|
||||
return "❌ 错误:handle 必须是整数句柄"
|
||||
|
||||
# T18:force 字符串 "false" 会被裸 bool 判为 True,跳过宽限直接 SIGKILL
|
||||
force = _coerce_bool(force, False)
|
||||
|
||||
result = self.manager.kill(handle, force=force, grace=grace)
|
||||
if not result.get("ok"):
|
||||
return self.format_result(False, f"终止后台进程 #{handle} 失败",
|
||||
|
||||
+33
-15
@@ -67,29 +67,42 @@ class ToolRegistry:
|
||||
def get_tools_schemas(self) -> List[Dict]:
|
||||
"""获取所有工具的OpenAI函数调用格式"""
|
||||
return [tool.to_function_call_format() for tool in self._tools.values()]
|
||||
|
||||
@staticmethod
|
||||
def _truncate_result(result) -> str:
|
||||
"""T15:工具返回值统一长度预算(非流式与流式最终结果同口径)"""
|
||||
if not isinstance(result, str):
|
||||
return result
|
||||
limit = constants.TOOL_MAX_RESULT_LENGTH
|
||||
if len(result) > limit:
|
||||
return (result[:limit] +
|
||||
f"\n⚠️ 结果超过 {limit} 字符上限,已截断")
|
||||
return result
|
||||
|
||||
def set_output_queue(self, queue: queue.Queue):
|
||||
"""设置输出队列"""
|
||||
"""设置输出队列(T4:仅记注册表级引用,不再传播到共享工具实例——
|
||||
实例属性由 execute_streaming 每次调用自行注入局部队列,全局传播
|
||||
会让并发会话互相覆盖对方的输出通路,造成跨会话输出串扰)"""
|
||||
self.current_output_queue = queue
|
||||
# 同时设置所有工具的队列
|
||||
for tool in self._tools.values():
|
||||
tool.output_queue = queue
|
||||
|
||||
|
||||
def execute_tool(self, name: str, **kwargs) -> str:
|
||||
"""执行工具"""
|
||||
tool = self.get_tool(name)
|
||||
if not tool:
|
||||
return f"❌ 错误:找不到工具 '{name}'"
|
||||
|
||||
|
||||
self.logger.debug(f"工具调用: {name}({_sanitize_kwargs(kwargs)})")
|
||||
start_time = time.time()
|
||||
|
||||
|
||||
try:
|
||||
# T4:入口快照会话队列。转发循环可能持续数秒~小时,期间并发
|
||||
# 会话的 set_output_queue 会覆盖单值引用——全程用快照,保证
|
||||
# 本会话输出只进本会话队列,结束清理也不影响在途转发
|
||||
session_queue = self.current_output_queue
|
||||
# 检查是否有队列可用,并且工具支持流式输出
|
||||
if self.current_output_queue and hasattr(tool, 'execute_streaming'):
|
||||
if session_queue and hasattr(tool, 'execute_streaming'):
|
||||
# 设置工具的流式输出属性
|
||||
tool.streaming_output = True
|
||||
tool.output_queue = self.current_output_queue
|
||||
|
||||
# 创建队列来收集流式输出
|
||||
output_queue = queue.Queue()
|
||||
@@ -134,8 +147,8 @@ class ToolRegistry:
|
||||
# 预算耗尽:停止转发,仅提示一次
|
||||
if not truncation_noted:
|
||||
truncation_noted = True
|
||||
if self.current_output_queue:
|
||||
self.current_output_queue.put(
|
||||
if session_queue:
|
||||
session_queue.put(
|
||||
("line", f"\n⚠️ 流式输出超过 {line_budget} 字符上限,已停止转发剩余内容\n"))
|
||||
continue
|
||||
remaining = line_budget - forwarded
|
||||
@@ -146,13 +159,13 @@ class ToolRegistry:
|
||||
else:
|
||||
forwarded += len(piece)
|
||||
# 发送到当前队列(会通过WebSocket发送)
|
||||
if self.current_output_queue:
|
||||
self.current_output_queue.put(("line", piece))
|
||||
if session_queue:
|
||||
session_queue.put(("line", piece))
|
||||
elif msg_type == "complete":
|
||||
final_result = content
|
||||
# 也发送完整结果
|
||||
if self.current_output_queue:
|
||||
self.current_output_queue.put(("complete", content))
|
||||
if session_queue:
|
||||
session_queue.put(("complete", content))
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
@@ -173,6 +186,11 @@ class ToolRegistry:
|
||||
else:
|
||||
# 普通执行
|
||||
result = tool.execute(**kwargs)
|
||||
|
||||
# T15:两条分支的最终返回值统一过 TOOL_MAX_RESULT_LENGTH——
|
||||
# 此前仅流式转发有 line_budget(N8),非流式结果无上限直入
|
||||
# 对话上下文(memory/memo get 超长 content,token/内存放大)
|
||||
result = self._truncate_result(result)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
self.logger.debug(f"工具结果 ({elapsed:.2f}s): {result[:100]}...")
|
||||
|
||||
Reference in new issue
Block a user