feat(system):优化后端

1.新增后端测试
2.增加了后端的加密
3.增加了i18n(国际化)
This commit is contained in:
2026-05-31 15:39:34 +00:00
parent affe460180
commit 99520c69d7
118 changed files with 8174 additions and 1491 deletions
+290
View File
@@ -0,0 +1,290 @@
"""Workflow graph 引擎本身(节点跳转 / 失败处理 / 派发 / HITL)的单元测试。
不经过 ConsciousnessNode 入口,也不依赖 ray runtime —— 直接调用
``run_workflow_graph(workflow_data, trace_id, deps=...)``,注入一套全部用
``AsyncMock``/lambda 实现的 ``WorkflowDeps``。验证 graph 驱动确实把状态机跑通。
"""
from __future__ import annotations
from typing import Any, Dict, List, Tuple
from unittest.mock import AsyncMock
import pytest
from kilostar.core.work.workflow.workflow_engine import (
WorkflowDeps,
WorkflowGraphState,
run_workflow_graph,
)
from kilostar.core.work.workflow.model import WorkflowStatus
def _make_deps(
*,
skill_outputs: List[Tuple[str, bool]] | None = None,
consciousness_outputs: List[Tuple[str, bool]] | None = None,
received_replies: List[str] | None = None,
) -> tuple[WorkflowDeps, Dict[str, List[Any]]]:
"""构造一对 ``(deps, sink)``sink 收集所有 IO 调用,方便断言。
``skill_outputs`` / ``consciousness_outputs`` 是按顺序消费的 ``(text, success)``
队列,run_skill / run_consciousness 每被调一次取一个。``received_replies``
用于 HumanApproval 节点的 get_received。
"""
sink: Dict[str, List[Any]] = {
"upsert": [],
"status": [],
"pending": [],
"received_calls": [],
"skill_calls": [],
"consciousness_calls": [],
}
skill_queue = list(skill_outputs or [])
consc_queue = list(consciousness_outputs or [])
reply_queue = list(received_replies or [])
upsert = AsyncMock(side_effect=lambda *a, **kw: sink["upsert"].append((a, kw)))
status = AsyncMock(side_effect=lambda tid, st: sink["status"].append((tid, st)))
pending = AsyncMock(side_effect=lambda tid, msg: sink["pending"].append((tid, msg)))
async def _get_received(tid: str) -> str:
sink["received_calls"].append(tid)
return reply_queue.pop(0) if reply_queue else ""
async def _run_skill(step, state):
sink["skill_calls"].append((step, state.current_step_index))
if not skill_queue:
return "(no fixture)", True
return skill_queue.pop(0)
async def _run_consciousness(step, state):
sink["consciousness_calls"].append((step, state.current_step_index))
if not consc_queue:
return "(no fixture)", True
return consc_queue.pop(0)
return (
WorkflowDeps(
upsert_workflow_context=upsert,
update_workflow_status=status,
put_pending=pending,
get_received=_get_received,
run_skill=_run_skill,
run_consciousness=_run_consciousness,
),
sink,
)
# ─── 基本路径 ─────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_skill_individual_path_completes_linear_steps():
"""两步顺序工作流 (skill_individual):成功 → 成功,应推进到 COMPLETED。"""
deps, sink = _make_deps(skill_outputs=[("ok1", True), ("ok2", True)])
workflow_data = {
"work_link": [
{"step": 1, "name": "s1", "action": "do1", "outputs": "o1",
"node": "skill_individual", "agent_id": "a1"},
{"step": 2, "name": "s2", "action": "do2", "outputs": "o2",
"node": "skill_individual", "agent_id": "a2"},
]
}
final = await run_workflow_graph(workflow_data, "trace-ok", deps=deps)
assert final == WorkflowStatus.COMPLETED.value
# run_skill 被调了两次,consciousness 没被调
assert len(sink["skill_calls"]) == 2
assert len(sink["consciousness_calls"]) == 0
@pytest.mark.asyncio
async def test_consciousness_node_path_dispatches_to_consciousness():
"""node=consciousness_node 的 step 应被派给 run_consciousness。"""
deps, sink = _make_deps(
consciousness_outputs=[("ok-from-consciousness", True)]
)
workflow_data = {
"work_link": [
{"step": 1, "name": "s1", "action": "summarize",
"node": "consciousness_node"},
]
}
final = await run_workflow_graph(workflow_data, "trace-c", deps=deps)
assert final == WorkflowStatus.COMPLETED.value
assert len(sink["consciousness_calls"]) == 1
assert len(sink["skill_calls"]) == 0
@pytest.mark.asyncio
async def test_mixed_path_dispatches_correctly():
"""混合 step:第一步 skill_individual,第二步 consciousness_node。"""
deps, sink = _make_deps(
skill_outputs=[("o1", True)],
consciousness_outputs=[("o2", True)],
)
workflow_data = {
"work_link": [
{"step": 1, "name": "s1", "action": "do",
"node": "skill_individual", "agent_id": "a1"},
{"step": 2, "name": "s2", "action": "review",
"node": "consciousness_node"},
]
}
final = await run_workflow_graph(workflow_data, "trace-mix", deps=deps)
assert final == WorkflowStatus.COMPLETED.value
assert len(sink["skill_calls"]) == 1
assert len(sink["consciousness_calls"]) == 1
@pytest.mark.asyncio
async def test_unknown_node_type_falls_to_failed():
"""未识别的 node 类型应直接收尾 FAILED,不静默跑成功。"""
deps, sink = _make_deps()
workflow_data = {
"work_link": [
{"step": 1, "name": "s1", "action": "do",
"node": "phantom_node"},
]
}
final = await run_workflow_graph(workflow_data, "trace-unknown", deps=deps)
assert final == WorkflowStatus.FAILED.value
assert len(sink["skill_calls"]) == 0
assert len(sink["consciousness_calls"]) == 0
# ─── logic_gate 跳转语义 ──────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_if_pass_exit_short_circuits():
"""``logic_gate.if_pass=exit`` 应在该步成功后立即收尾。"""
deps, sink = _make_deps(skill_outputs=[("ok", True)])
workflow_data = {
"work_link": [
{"step": 1, "name": "s1", "action": "do", "outputs": "o",
"node": "skill_individual", "agent_id": "a1",
"logic_gate": {"if_pass": "exit", "if_fail": "jump_to_step_1"}},
{"step": 2, "name": "s2", "action": "skipped",
"node": "skill_individual", "agent_id": "a2"},
]
}
final = await run_workflow_graph(workflow_data, "trace-exit", deps=deps)
assert final == WorkflowStatus.COMPLETED.value
# 第二步不应被派发
assert len(sink["skill_calls"]) == 1
@pytest.mark.asyncio
async def test_failure_without_jump_marks_failed():
"""run_skill 报失败且 if_fail 不指向跳转 → FAILED。"""
deps, sink = _make_deps(skill_outputs=[("boom", False)])
workflow_data = {
"work_link": [
{"step": 1, "name": "s1", "action": "do",
"node": "skill_individual", "agent_id": "a1"},
]
}
final = await run_workflow_graph(workflow_data, "trace-fail", deps=deps)
assert final == WorkflowStatus.FAILED.value
@pytest.mark.asyncio
async def test_failure_jumps_back_then_succeeds():
"""步 2 失败但 if_fail=jump_to_step_1,回到步 1 重跑后继续到步 2 成功。"""
deps, sink = _make_deps(
skill_outputs=[
("o1-first", True), # step 1 第一次成功
("boom", False), # step 2 第一次失败 → 跳回 step 1
("o1-retry", True), # step 1 重跑
("o2-final", True), # step 2 第二次成功
]
)
workflow_data = {
"work_link": [
{"step": 1, "name": "s1", "action": "do",
"node": "skill_individual", "agent_id": "a1"},
{"step": 2, "name": "s2", "action": "do",
"node": "skill_individual", "agent_id": "a2",
"logic_gate": {"if_fail": "jump_to_step_1", "if_pass": "continue"}},
]
}
final = await run_workflow_graph(workflow_data, "trace-jump", deps=deps)
assert final == WorkflowStatus.COMPLETED.value
# skill 应被调 4 次
assert len(sink["skill_calls"]) == 4
# ─── HITL ────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_human_approval_approve_continues():
"""require_approval=True 时进入 HumanApproval;用户回 approve 后继续执行。"""
deps, sink = _make_deps(
skill_outputs=[("ok", True)],
received_replies=["approve"],
)
workflow_data = {
"work_link": [
{"step": 1, "name": "danger", "action": "rm -rf /",
"node": "skill_individual", "agent_id": "a1",
"require_approval": True},
]
}
final = await run_workflow_graph(workflow_data, "trace-hitl-ok", deps=deps)
assert final == WorkflowStatus.COMPLETED.value
# get_received 被调用过一次
assert sink["received_calls"] == ["trace-hitl-ok"]
# 应该有一条"需要人工审批"的 SSE
msgs = [m for _, m in sink["pending"]]
assert any("需要人工审批" in m for m in msgs)
# skill 才被实际派发
assert len(sink["skill_calls"]) == 1
@pytest.mark.asyncio
async def test_human_approval_reject_aborts():
"""用户回 reject → 不执行 step,整个工作流落到 FAILED。"""
deps, sink = _make_deps(
skill_outputs=[("ok", True)],
received_replies=["reject"],
)
workflow_data = {
"work_link": [
{"step": 1, "name": "danger", "action": "do",
"node": "skill_individual", "agent_id": "a1",
"require_approval": True},
]
}
final = await run_workflow_graph(workflow_data, "trace-hitl-no", deps=deps)
assert final == WorkflowStatus.FAILED.value
# skill 不应被派发
assert len(sink["skill_calls"]) == 0
# ─── 边界 ─────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_empty_work_link_completes_immediately():
"""空 work_linkInitialize → Dispatch 直接判定越界 → Finalize(COMPLETED)。"""
deps, sink = _make_deps()
final = await run_workflow_graph({"work_link": []}, "trace-empty", deps=deps)
assert final == WorkflowStatus.COMPLETED.value
msgs = [m for _, m in sink["pending"]]
assert not any("执行步骤" in m for m in msgs)
assert any("工作流执行完成" in m for m in msgs)
def test_workflow_graph_state_defaults():
"""State 默认值确保各字段类型契约稳定。"""
state = WorkflowGraphState(trace_id="t")
assert state.blackboard == {}
assert state.work_link == []
assert state.current_step_index == 0
assert state.final_status == WorkflowStatus.RUNNING.value
assert state.logs == []
assert state.original_command == ""