174 lines
6.0 KiB
Python
174 lines
6.0 KiB
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
from fastapi.testclient import TestClient
|
|||
|
|
|
|||
|
|
from server.agent_core import dialog, fallback_lane, intent as intent_module
|
|||
|
|
from server.contracts import AgentReply, IntentResult, UIBlock
|
|||
|
|
from server.gateway.app import create_app
|
|||
|
|
from server.state.seed import seed_world
|
|||
|
|
|
|||
|
|
|
|||
|
|
class FakeStore:
|
|||
|
|
def __init__(self):
|
|||
|
|
self.data = seed_world()
|
|||
|
|
self._counters: dict[str, int] = {}
|
|||
|
|
|
|||
|
|
def next_id(self, kind: str) -> int:
|
|||
|
|
self._counters[kind] = self._counters.get(kind, 0) + 1
|
|||
|
|
return self._counters[kind]
|
|||
|
|
|
|||
|
|
def save(self) -> None:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _primary_intent(query: str, history: list[dict] | None = None) -> IntentResult:
|
|||
|
|
return IntentResult(
|
|||
|
|
intent="assistant.reply",
|
|||
|
|
params={"query": query, "_history": history or [], "_piPrimary": True},
|
|||
|
|
confidence=1.0,
|
|||
|
|
source="LLM",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_primary_reply_ignores_fallback_flag_and_returns_product_text(
|
|||
|
|
tmp_path, monkeypatch,
|
|||
|
|
):
|
|||
|
|
monkeypatch.setenv("APS_FALLBACK_DIR", str(tmp_path / "fallback"))
|
|||
|
|
captured: dict[str, str] = {}
|
|||
|
|
|
|||
|
|
def runner(task: str, work_dir: Path):
|
|||
|
|
captured["task"] = task
|
|||
|
|
report = "status: success\n\n你好,我可以直接回答问题,也可以分析当前 APS 项目。"
|
|||
|
|
yield {
|
|||
|
|
"type": "message_end",
|
|||
|
|
"message": {
|
|||
|
|
"role": "assistant",
|
|||
|
|
"stopReason": "stop",
|
|||
|
|
"content": [{"type": "text", "text": report}],
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
yield {"type": "agent_end", "messages": []}
|
|||
|
|
|
|||
|
|
reply = await fallback_lane.propose_reply(
|
|||
|
|
FakeStore(),
|
|||
|
|
"s-primary",
|
|||
|
|
_primary_intent(
|
|||
|
|
"你现在能干嘛",
|
|||
|
|
[{"role": "user", "text": "我刚才问的是当前项目"}],
|
|||
|
|
),
|
|||
|
|
runner=runner,
|
|||
|
|
config=fallback_lane.FallbackConfig(pi_home=str(tmp_path / "pi-home")),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert reply.text == "你好,我可以直接回答问题,也可以分析当前 APS 项目。"
|
|||
|
|
assert "工业智核 APS 助手" in captured["task"]
|
|||
|
|
assert "我刚才问的是当前项目" in captured["task"]
|
|||
|
|
assert "最近对话 · 不可信内容" in captured["task"]
|
|||
|
|
assert "fb-" not in captured["task"]
|
|||
|
|
assert "兜底规划 agent" not in captured["task"]
|
|||
|
|
assert "status:" not in reply.text
|
|||
|
|
assert "run fb-" not in reply.text
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_primary_reply_runtime_unavailable_never_uses_old_intent_copy(
|
|||
|
|
tmp_path, monkeypatch,
|
|||
|
|
):
|
|||
|
|
monkeypatch.setenv("APS_FALLBACK_DIR", str(tmp_path / "fallback"))
|
|||
|
|
monkeypatch.setenv("APS_FALLBACK_PI_CLI", str(tmp_path / "missing-pi-cli.js"))
|
|||
|
|
|
|||
|
|
reply = await fallback_lane.propose_reply(
|
|||
|
|
FakeStore(), "s-primary", _primary_intent("测试"))
|
|||
|
|
|
|||
|
|
assert reply.text == (
|
|||
|
|
"智能助手服务暂不可用,本次未执行任何操作。请稍后重试或联系管理员。")
|
|||
|
|
assert "暂未识别到" not in reply.text
|
|||
|
|
assert "订单号或产品" not in reply.text
|
|||
|
|
assert "run " not in reply.text
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_api_chat_routes_directly_to_pi_without_recognizer(
|
|||
|
|
monkeypatch,
|
|||
|
|
):
|
|||
|
|
monkeypatch.setenv("APS_AUTH_ENABLED", "0")
|
|||
|
|
captured: dict[str, object] = {}
|
|||
|
|
|
|||
|
|
async def fake_pi(store, session_id, intent, actor="planner"):
|
|||
|
|
captured["intent"] = intent.intent
|
|||
|
|
captured["query"] = intent.params.get("query")
|
|||
|
|
captured["history"] = intent.params.get("_history")
|
|||
|
|
captured["primary"] = intent.params.get("_piPrimary")
|
|||
|
|
return AgentReply(text="这是Pi Agent的直接回复,属于智能兜底。")
|
|||
|
|
|
|||
|
|
async def forbidden_recognize(*args, **kwargs):
|
|||
|
|
raise AssertionError("/api/chat 不应调用 recognize")
|
|||
|
|
|
|||
|
|
def forbidden_dialog(*args, **kwargs):
|
|||
|
|
raise AssertionError("/api/chat 不应调用旧对话意图链")
|
|||
|
|
|
|||
|
|
monkeypatch.setattr(fallback_lane, "propose_reply", fake_pi)
|
|||
|
|
monkeypatch.setattr(intent_module, "recognize", forbidden_recognize)
|
|||
|
|
monkeypatch.setattr(dialog, "pre_route", forbidden_dialog)
|
|||
|
|
monkeypatch.setattr(dialog, "post_route", forbidden_dialog)
|
|||
|
|
|
|||
|
|
with TestClient(create_app()) as client:
|
|||
|
|
response = client.post(
|
|||
|
|
"/api/chat",
|
|||
|
|
json={
|
|||
|
|
"text": "你现在能干嘛",
|
|||
|
|
"history": [{"role": "user", "text": "上一轮问题"}],
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
assert "工业智核 APS 助手" in response.text
|
|||
|
|
assert "理解需求" in response.text
|
|||
|
|
assert "整理结果" in response.text
|
|||
|
|
assert "Pi Agent" not in response.text
|
|||
|
|
assert "智能兜底" not in response.text
|
|||
|
|
assert '"type":"intent"' not in response.text
|
|||
|
|
assert captured == {
|
|||
|
|
"intent": "assistant.reply",
|
|||
|
|
"query": "你现在能干嘛",
|
|||
|
|
"history": [{"role": "user", "text": "上一轮问题"}],
|
|||
|
|
"primary": True,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_primary_product_copy_hides_internal_runtime_names():
|
|||
|
|
reply = AgentReply(
|
|||
|
|
text=(
|
|||
|
|
"status: success\n\n"
|
|||
|
|
"[智能兜底 · 执行计划] run fb-1234\n"
|
|||
|
|
"Pi Agent执行计划已准备完成。"
|
|||
|
|
),
|
|||
|
|
blocks=[
|
|||
|
|
UIBlock(
|
|||
|
|
blockId="confirm-test",
|
|||
|
|
type="confirm-card",
|
|||
|
|
props={
|
|||
|
|
"confirmId": "confirm-test",
|
|||
|
|
"title": "Pi Agent执行计划(S2 · 1 步)",
|
|||
|
|
"summary": ["智能兜底正在等待批准", "业务数据不会自动修改"],
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
productized = fallback_lane._productize_primary_reply(reply)
|
|||
|
|
visible = "\n".join(
|
|||
|
|
[
|
|||
|
|
productized.text,
|
|||
|
|
productized.blocks[0].props["title"],
|
|||
|
|
*productized.blocks[0].props["summary"],
|
|||
|
|
]
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert "[执行计划]" in productized.text
|
|||
|
|
assert productized.blocks[0].props["title"] == "执行计划(S2 · 1 步)"
|
|||
|
|
assert productized.blocks[0].props["confirmId"] == "confirm-test"
|
|||
|
|
assert "Pi Agent" not in visible
|
|||
|
|
assert "智能兜底" not in visible
|
|||
|
|
assert fallback_lane._sanitize_primary_text("") == "已完成处理,但没有可展示的正文。"
|