refactor: 重构 Agent 模块为 AgentScope,删除旧版 CrewAI/LiteLLM 实现

This commit is contained in:
qzl
2026-03-11 20:51:56 +08:00
parent 177ed616bf
commit 145e3dc615
149 changed files with 5120 additions and 11356 deletions
@@ -0,0 +1,163 @@
from __future__ import annotations
from uuid import uuid4
import pytest
from core.agentscope.persistence.user_context_cache import UserContextCache
from core.agentscope.schemas.user_context import (
UserAgentContext,
parse_profile_settings,
)
class _FakeRedis:
def __init__(self) -> None:
self.store: dict[str, dict[str, str]] = {}
self.set_store: dict[str, set[str]] = {}
self.expire_calls: list[tuple[str, int]] = []
self.delete_calls: list[str] = []
self.hincrby_calls: list[tuple[str, str, int]] = []
async def hgetall(self, key: str) -> dict[str, str]:
return dict(self.store.get(key, {}))
async def hset(self, key: str, mapping: dict[str, str]) -> int:
self.store[key] = dict(mapping)
return 1
async def hincrby(self, key: str, field: str, amount: int = 1) -> int:
self.hincrby_calls.append((key, field, amount))
data = self.store.setdefault(key, {})
current = int(data.get(field, "0"))
next_value = current + amount
data[field] = str(next_value)
return next_value
async def expire(self, key: str, seconds: int) -> int:
self.expire_calls.append((key, seconds))
return 1
async def delete(self, *keys: str) -> int:
for key in keys:
self.delete_calls.append(key)
self.store.pop(key, None)
self.set_store.pop(key, None)
return len(keys)
async def sadd(self, key: str, *values: str) -> int:
bucket = self.set_store.setdefault(key, set())
before = len(bucket)
for value in values:
bucket.add(value)
return len(bucket) - before
async def smembers(self, key: str) -> set[str]:
return set(self.set_store.get(key, set()))
class _BrokenRedis:
async def hgetall(self, key: str) -> dict[str, str]:
del key
raise RuntimeError("redis down")
async def hset(self, key: str, mapping: dict[str, str]) -> int:
del key, mapping
raise RuntimeError("redis down")
async def hincrby(self, key: str, field: str, amount: int = 1) -> int:
del key, field, amount
raise RuntimeError("redis down")
async def expire(self, key: str, seconds: int) -> int:
del key, seconds
raise RuntimeError("redis down")
async def delete(self, *keys: str) -> int:
del keys
raise RuntimeError("redis down")
async def sadd(self, key: str, *values: str) -> int:
del key, values
raise RuntimeError("redis down")
async def smembers(self, key: str) -> set[str]:
del key
raise RuntimeError("redis down")
def _build_context() -> UserAgentContext:
return UserAgentContext(
user_id=uuid4(),
username="demo-user",
bio="demo bio",
settings=parse_profile_settings({"preferences": {"ai_language": "en-US"}}),
)
@pytest.mark.asyncio
async def test_user_context_cache_set_and_get_hit() -> None:
redis = _FakeRedis()
cache = UserContextCache(
client=redis,
key_prefix="agent:user-context",
ttl_seconds=600,
max_turns=3,
)
session_id = uuid4()
context = _build_context()
await cache.set(session_id=session_id, context=context)
loaded = await cache.get(session_id=session_id)
assert loaded is not None
assert loaded.user_id == context.user_id
assert loaded.username == "demo-user"
assert redis.expire_calls == [
(f"agent:user-context:{session_id}", 600),
(f"agent:user-context:sessions:{context.user_id}", 600),
]
assert redis.hincrby_calls == [
(f"agent:user-context:{session_id}", "turns_used", 1)
]
@pytest.mark.asyncio
async def test_user_context_cache_invalidate_user_deletes_all_sessions() -> None:
redis = _FakeRedis()
cache = UserContextCache(
client=redis,
key_prefix="agent:user-context",
ttl_seconds=600,
max_turns=3,
)
context = _build_context()
s1 = uuid4()
s2 = uuid4()
await cache.set(session_id=s1, context=context)
await cache.set(session_id=s2, context=context)
deleted = await cache.invalidate_user(user_id=context.user_id)
assert deleted == 2
assert f"agent:user-context:{s1}" in redis.delete_calls
assert f"agent:user-context:{s2}" in redis.delete_calls
assert f"agent:user-context:sessions:{context.user_id}" in redis.delete_calls
@pytest.mark.asyncio
async def test_user_context_cache_degrades_gracefully_on_redis_error() -> None:
cache = UserContextCache(
client=_BrokenRedis(),
key_prefix="agent:user-context",
ttl_seconds=600,
max_turns=3,
)
session_id = uuid4()
context = _build_context()
loaded = await cache.get(session_id=session_id)
await cache.set(session_id=session_id, context=context)
assert loaded is None