Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pytests/test_maisaka_builtin_query_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ def _patch_maisaka_config(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
query_memory_tool,
"global_config",
SimpleNamespace(memory=SimpleNamespace(memory_query_default_limit=5)),
SimpleNamespace(
a_memorix=SimpleNamespace(
integration=SimpleNamespace(
memory_query_default_limit=5,
)
)
),
)


Expand Down
155 changes: 155 additions & 0 deletions pytests/test_maisaka_builtin_query_person_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
from types import SimpleNamespace
from typing import Any, Dict

import pytest

from src.core.tooling import ToolInvocation
from src.maisaka.builtin_tool import (
get_all_builtin_tool_specs,
query_person_profile as query_person_profile_tool,
)
from src.maisaka.builtin_tool.context import BuiltinToolRuntimeContext


def _build_tool_ctx() -> BuiltinToolRuntimeContext:
runtime = SimpleNamespace(
session_id="session-1",
chat_stream=SimpleNamespace(platform="qq", user_id="alice", group_id=""),
log_prefix="[session-1]",
)
return BuiltinToolRuntimeContext(engine=SimpleNamespace(), runtime=runtime)


def _build_invocation(arguments: Dict[str, Any]) -> ToolInvocation:
return ToolInvocation(
tool_name="query_person_profile",
arguments=dict(arguments),
call_id="call-query-person-profile",
)


@pytest.mark.asyncio
async def test_query_person_profile_requires_identifier() -> None:
result = await query_person_profile_tool.handle_tool(
_build_tool_ctx(),
_build_invocation({}),
)

assert result.success is False
assert "person_id 或 person_name" in result.error_message


@pytest.mark.asyncio
async def test_query_person_profile_supports_person_id(monkeypatch: pytest.MonkeyPatch) -> None:
captured: Dict[str, Any] = {}

async def fake_profile_admin(*, action: str, **kwargs: Any) -> Dict[str, Any]:
captured["action"] = action
captured["kwargs"] = dict(kwargs)
return {
"success": True,
"person_id": "pid-alice",
"person_name": "Alice",
"profile_text": "Alice 喜欢咖啡。",
"profile_source": "auto_snapshot",
"has_manual_override": False,
"evidence": [{"content": "不应返回"}],
}

monkeypatch.setattr(query_person_profile_tool.memory_service, "profile_admin", fake_profile_admin)

result = await query_person_profile_tool.handle_tool(
_build_tool_ctx(),
_build_invocation({"person_id": "pid-alice", "limit": 4}),
)

assert result.success is True
assert captured == {
"action": "query",
"kwargs": {"person_id": "pid-alice", "limit": 4},
}
assert result.content == "Alice 喜欢咖啡。"
assert isinstance(result.structured_content, dict)
assert result.structured_content["person_id"] == "pid-alice"
assert result.structured_content["person_name"] == "Alice"
assert result.structured_content["summary"] == "Alice 喜欢咖啡。"
assert "evidence" not in result.structured_content


@pytest.mark.asyncio
async def test_query_person_profile_supports_person_name(monkeypatch: pytest.MonkeyPatch) -> None:
captured: Dict[str, Any] = {}

async def fake_profile_admin(*, action: str, **kwargs: Any) -> Dict[str, Any]:
captured["action"] = action
captured["kwargs"] = dict(kwargs)
return {
"success": True,
"person_id": "pid-bob",
"person_name": "Bob",
"summary": "- Bob 常聊游戏\n- Bob 晚上更活跃",
"profile_source": "manual_override",
"has_manual_override": True,
}

monkeypatch.setattr(query_person_profile_tool.memory_service, "profile_admin", fake_profile_admin)

result = await query_person_profile_tool.handle_tool(
_build_tool_ctx(),
_build_invocation({"person_name": "Bob", "limit": 30}),
)

assert result.success is True
assert captured == {
"action": "query",
"kwargs": {"person_keyword": "Bob", "limit": 20},
}
assert result.structured_content["person_id"] == "pid-bob"
assert result.structured_content["profile_source"] == "manual_override"
assert result.structured_content["has_manual_override"] is True
assert result.structured_content["traits"] == ["Bob 常聊游戏", "Bob 晚上更活跃"]


def test_builtin_tool_list_contains_query_memory_and_profile_when_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
from src.maisaka import builtin_tool as builtin_tool_module

monkeypatch.setattr(
builtin_tool_module,
"global_config",
SimpleNamespace(
a_memorix=SimpleNamespace(
integration=SimpleNamespace(
enable_memory_query_tool=True,
enable_person_profile_query_tool=True,
)
)
),
)

specs = get_all_builtin_tool_specs()
names = {spec.name for spec in specs if spec.enabled}

assert "query_memory" in names
assert "query_person_profile" in names


def test_builtin_tool_list_respects_profile_query_config(monkeypatch: pytest.MonkeyPatch) -> None:
from src.maisaka import builtin_tool as builtin_tool_module

monkeypatch.setattr(
builtin_tool_module,
"global_config",
SimpleNamespace(
a_memorix=SimpleNamespace(
integration=SimpleNamespace(
enable_memory_query_tool=True,
enable_person_profile_query_tool=False,
)
)
),
)

specs = get_all_builtin_tool_specs()
profile_spec = next(spec for spec in specs if spec.name == "query_person_profile")

assert profile_spec.enabled is False
33 changes: 33 additions & 0 deletions pytests/test_maisaka_person_profile_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from src.config.official_configs import AMemorixIntegrationConfig
from src.webui.config_schema import ConfigSchemaGenerator


def test_person_profile_integration_config_fields_are_exposed() -> None:
schema = ConfigSchemaGenerator.generate_schema(AMemorixIntegrationConfig)

profile_tool_field = next(
field for field in schema["fields"] if field["name"] == "enable_person_profile_query_tool"
)
profile_injection_field = next(
field for field in schema["fields"] if field["name"] == "enable_person_profile_injection"
)
profile_limit_field = next(
field for field in schema["fields"] if field["name"] == "person_profile_injection_max_profiles"
)

assert profile_tool_field["type"] == "boolean"
assert profile_tool_field["default"] is True
assert profile_tool_field.get("x-widget") == "switch"
assert profile_tool_field.get("x-icon") == "user-round-search"

assert profile_injection_field["type"] == "boolean"
assert profile_injection_field["default"] is True
assert profile_injection_field.get("x-widget") == "switch"
assert profile_injection_field.get("x-icon") == "user-round-check"

assert profile_limit_field["type"] == "integer"
assert profile_limit_field["default"] == 3
assert profile_limit_field.get("x-widget") == "input"
assert profile_limit_field.get("x-icon") == "users"
assert profile_limit_field.get("minValue") == 1
assert profile_limit_field.get("maxValue") == 5
Loading
Loading