feat: Agent 批量测试、作业助手与上传预览;Windows 启动脚本与文档- 新增 run_agent_test_cases 与示例 JSON、(红头)agent测试用例文档

- 扩展 test_agent_execution(--homework、UTF-8 控制台)
- 后端:uploads 预览、file_read、工作流与对话落盘等
- 前端:AgentChatPreview 与设计器相关调整
- 忽略 redis二进制、agent_workspaces、uploads、tessdata 等本机产物

Made-with: Cursor
This commit is contained in:
renjianbo
2026-04-13 20:17:18 +08:00
parent 0608161c82
commit df4fab1e6e
31 changed files with 3784 additions and 251 deletions

View File

@@ -812,6 +812,25 @@ class WorkflowEngine:
logger.info(f"[rjb] 从嵌套上游输入提升 user_id 到节点 {node_id} 输入顶层")
break
# Start→下游 带 sourceHandle 时 query/USER_INPUT/attachments 常在 right 内,提升到顶层供 LLM 与其它节点读取
if isinstance(input_data, dict):
_rk = input_data.get('right')
if isinstance(_rk, dict):
for _uk in (
'query',
'USER_INPUT',
'user_input',
'attachments',
'text',
'message',
'content',
):
if input_data.get(_uk) not in (None, ''):
continue
if _uk in _rk and _rk[_uk] is not None:
input_data[_uk] = _rk[_uk]
logger.debug(f"[rjb] 从 right 提升到顶层: {_uk}")
logger.debug(f"[rjb] 节点输入结果: node_id={node_id}, input_data={input_data}")
return input_data
@@ -1443,6 +1462,58 @@ class WorkflowEngine:
if user_query:
break
# Start→LLM 常见:边带 sourceHandle=rightStart 输出在 input_data["right"] 下,
# 顶层无 query/USER_INPUT旧逻辑会退化为 JSON 整包,模型看不到附件路径。
if not user_query:
for bucket in ("right", "left", "output", "data"):
nested = input_data.get(bucket)
if not isinstance(nested, dict):
continue
for key in (
"query",
"input",
"text",
"message",
"content",
"user_input",
"USER_INPUT",
):
if key not in nested:
continue
value = nested[key]
logger.info(
f"[rjb] 从嵌套桶 {bucket}.{key} 提取 user_query 候选, type={type(value)}"
)
if isinstance(value, str):
user_query = value
logger.info(
f"[rjb] 从{bucket}.{key} 提取到字符串 user_query 长度={len(value)}"
)
break
if isinstance(value, dict):
for sub_key in (
"query",
"input",
"text",
"message",
"content",
"user_input",
"USER_INPUT",
):
if sub_key not in value:
continue
sv = value[sub_key]
if isinstance(sv, str):
user_query = sv
logger.info(
f"[rjb] 从{bucket}.{key}.{sub_key} 提取到 user_query"
)
break
if user_query:
break
if user_query:
break
# 如果还是没有使用整个input_data但排除系统字段
if not user_query:
filtered_data = {k: v for k, v in input_data.items() if not k.startswith('_')}
@@ -1607,6 +1678,12 @@ class WorkflowEngine:
_tool_extra["max_iterations"] = max(1, min(int(_mi), 64))
except (TypeError, ValueError):
pass
_rt = node_data.get("request_timeout")
if _rt is not None:
try:
_tool_extra["request_timeout"] = max(10.0, float(_rt))
except (TypeError, ValueError):
pass
result = await llm_service.call_llm_with_tools(
prompt=prompt,
tools=tools,
@@ -4916,7 +4993,12 @@ class WorkflowEngine:
if isinstance(_rj, dict):
_ex.update(_rj)
except Exception:
pass
# 非 JSON 但以 { 开头(如 Markdown仍当作正文
_ex['output'] = _ex['right']
else:
# LLM 节点 output 常为纯文本,经带 handle 的边落在 right须写入 output
# 否则下游按 dict 拼接会把 user_idpreview_xxx拼在回复末尾。
_ex['output'] = _ex['right']
input_data = _ex
final_output = input_data
@@ -4983,7 +5065,28 @@ class WorkflowEngine:
# 否则转换为纯文本不是JSON
# 尝试提取所有文本字段并组合,但排除系统字段和用户查询字段
text_parts = []
exclude_keys = {'status', 'error', 'timestamp', 'node_id', 'execution_time', 'query', 'USER_INPUT', 'user_input', 'user_query'}
exclude_keys = {
'status',
'error',
'timestamp',
'node_id',
'execution_time',
'query',
'USER_INPUT',
'user_input',
'user_query',
'user_id',
'USER_ID',
'userId',
'attachments',
'memory',
'conversation_history',
'user_profile',
'context',
'right',
'left',
'data',
}
# 优先使用input字段LLM的实际输出
if 'input' in final_output and isinstance(final_output['input'], str):
final_output = final_output['input']