wip: 客服Agent + RAG + 画像收尾(基于 6516ccb)

This commit is contained in:
2026-09-11 14:46:40 +08:00
parent 870fd0d44e
commit e4c4099aaa
82 changed files with 13901 additions and 2426 deletions
+74
View File
@@ -73,3 +73,77 @@ async def test_gateway_missing_secret_and_empty_route_fail_closed(monkeypatch) -
endpoint_code="primary", prompt="hello", timeout_ms=1000)
with pytest.raises(RecoverableAgentError, match="没有可用"):
await ModelGenerationService(ModelDispatchService(Gateway())).generate([], "hello")
# ---------------------------------------------------------------------------
# `DatabaseModelEndpointResolver` 按 task_type 过滤能力(补上被漏掉的契约兑现)
# ---------------------------------------------------------------------------
class _FakeScalarSession:
"""只实现 `scalars()`:解析器只用它取 active 端点列表。"""
def __init__(self, rows: list[object]) -> None:
self._rows = rows
async def scalars(self, _statement: object) -> list[object]:
return self._rows
async def __aenter__(self) -> "_FakeScalarSession":
return self
async def __aexit__(self, *args: object) -> None:
return None
def _endpoint(code: str, capabilities: list[str] | None) -> SimpleNamespace:
return SimpleNamespace(endpoint_code=code, capabilities=capabilities)
@pytest.mark.asyncio
@pytest.mark.parametrize(
("task_type", "expected"),
[
# 向量化只能走声明 embedding 的端点:交给 chat 端点会打到 /chat/completions 上。
("embedding", ["embedding-primary"]),
# 生成与意图分类都走 chat 端点:交给 embedding 端点会 404。
("chat", ["chat-primary"]),
("intent_classification", ["chat-primary"]),
# 未知任务类型无法判断该要哪种能力 → 不过滤(返回全部 active)。
("something_new", ["embedding-primary", "chat-primary"]),
],
)
async def test_resolve_filters_endpoints_by_task_type_capability(
monkeypatch: pytest.MonkeyPatch, task_type: str, expected: list[str]
) -> None:
"""必须按 `task_type` 过滤能力。
不过滤的后果:`ModelDispatchService` 的 `generate`/`embed` 只取前
`max(1, max_attempts)`(默认 2)个端点,错叫一个就吃掉一次尝试机会 ——
端点一多会直接耗尽尝试而失败(原实现 `del agent_type, task_type` 即此缺陷)。
"""
from app.service import model_gateway
rows = [_endpoint("embedding-primary", ["embedding"]), _endpoint("chat-primary", ["chat"])]
monkeypatch.setattr(model_gateway, "SessionFactory", lambda: _FakeScalarSession(rows))
resolved = await model_gateway.DatabaseModelEndpointResolver().resolve(
agent_type="customer_service", task_type=task_type
)
assert [e.endpoint_code for e in resolved] == expected
@pytest.mark.asyncio
async def test_resolve_skips_endpoints_without_declared_capabilities(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""`capabilities` 为 NULL/空 的端点不得被任何筛选选中(不能裸奔到错误的网关方法上)。"""
from app.service import model_gateway
rows = [_endpoint("broken", None), _endpoint("empty", []), _endpoint("chat-primary", ["chat"])]
monkeypatch.setattr(model_gateway, "SessionFactory", lambda: _FakeScalarSession(rows))
resolved = await model_gateway.DatabaseModelEndpointResolver().resolve(
agent_type="customer_service", task_type="chat"
)
assert [e.endpoint_code for e in resolved] == ["chat-primary"]