|
| 1 | +import asyncio |
| 2 | +import asyncio |
| 3 | +import os |
| 4 | +import sys |
| 5 | +from unittest.mock import MagicMock |
| 6 | +import types |
| 7 | + |
| 8 | +# Ensure modules under src/backend are importable |
| 9 | +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| 10 | + |
| 11 | +# Mock Azure dependencies required by app_config |
| 12 | +azure_module = types.ModuleType("azure") |
| 13 | +sys.modules["azure"] = azure_module |
| 14 | +sys.modules["azure.ai"] = types.ModuleType("azure.ai") |
| 15 | +sys.modules["azure.ai.projects"] = types.ModuleType("azure.ai.projects") |
| 16 | +projects_aio = types.ModuleType("azure.ai.projects.aio") |
| 17 | +projects_aio.AIProjectClient = MagicMock() |
| 18 | +sys.modules["azure.ai.projects.aio"] = projects_aio |
| 19 | +cosmos_module = types.ModuleType("azure.cosmos") |
| 20 | +sys.modules["azure.cosmos"] = cosmos_module |
| 21 | +cosmos_aio = types.ModuleType("azure.cosmos.aio") |
| 22 | +cosmos_aio.CosmosClient = MagicMock() |
| 23 | +sys.modules["azure.cosmos.aio"] = cosmos_aio |
| 24 | +partition_key_module = types.ModuleType("azure.cosmos.partition_key") |
| 25 | +partition_key_module.PartitionKey = MagicMock() |
| 26 | +sys.modules["azure.cosmos.partition_key"] = partition_key_module |
| 27 | +azure_monitor_module = types.ModuleType("azure.monitor") |
| 28 | +sys.modules["azure.monitor"] = azure_monitor_module |
| 29 | +events_module = types.ModuleType("azure.monitor.events") |
| 30 | +sys.modules["azure.monitor.events"] = events_module |
| 31 | +events_ext_module = types.ModuleType("azure.monitor.events.extension") |
| 32 | +events_ext_module.track_event = MagicMock() |
| 33 | +sys.modules["azure.monitor.events.extension"] = events_ext_module |
| 34 | +sys.modules["azure.monitor.opentelemetry"] = types.ModuleType("azure.monitor.opentelemetry") |
| 35 | +identity_module = types.ModuleType("azure.identity") |
| 36 | +identity_module.ManagedIdentityCredential = MagicMock() |
| 37 | +identity_module.DefaultAzureCredential = MagicMock() |
| 38 | +sys.modules["azure.identity"] = identity_module |
| 39 | +identity_aio_module = types.ModuleType("azure.identity.aio") |
| 40 | +identity_aio_module.ManagedIdentityCredential = MagicMock() |
| 41 | +identity_aio_module.DefaultAzureCredential = MagicMock() |
| 42 | +sys.modules["azure.identity.aio"] = identity_aio_module |
| 43 | + |
| 44 | +# Mock semantic kernel dependencies |
| 45 | +sys.modules["semantic_kernel"] = types.ModuleType("semantic_kernel") |
| 46 | +kernel_module = types.ModuleType("semantic_kernel.kernel") |
| 47 | +class Kernel: # pragma: no cover - simple stub for testing |
| 48 | + pass |
| 49 | +kernel_module.Kernel = Kernel |
| 50 | +sys.modules["semantic_kernel.kernel"] = kernel_module |
| 51 | +sys.modules["semantic_kernel.agents"] = types.ModuleType("semantic_kernel.agents") |
| 52 | +sys.modules["semantic_kernel.agents.azure_ai"] = types.ModuleType("semantic_kernel.agents.azure_ai") |
| 53 | +azure_ai_agent_module = types.ModuleType("semantic_kernel.agents.azure_ai.azure_ai_agent") |
| 54 | +class AzureAIAgent: # pragma: no cover - simple stub for testing |
| 55 | + def __init__(self, *args, **kwargs): |
| 56 | + pass |
| 57 | +azure_ai_agent_module.AzureAIAgent = AzureAIAgent |
| 58 | +sys.modules["semantic_kernel.agents.azure_ai.azure_ai_agent"] = azure_ai_agent_module |
| 59 | +functions_module = types.ModuleType("semantic_kernel.functions") |
| 60 | +class KernelFunction: # pragma: no cover - simple stub for testing |
| 61 | + pass |
| 62 | +functions_module.KernelFunction = KernelFunction |
| 63 | +sys.modules["semantic_kernel.functions"] = functions_module |
| 64 | +numpy_module = types.ModuleType("numpy") |
| 65 | +class ndarray: # pragma: no cover - stub |
| 66 | + pass |
| 67 | +numpy_module.ndarray = ndarray |
| 68 | +sys.modules["numpy"] = numpy_module |
| 69 | +sys.modules["semantic_kernel.memory"] = types.ModuleType("semantic_kernel.memory") |
| 70 | +memory_record_module = types.ModuleType("semantic_kernel.memory.memory_record") |
| 71 | +class MemoryRecord: # pragma: no cover - stub |
| 72 | + pass |
| 73 | +memory_record_module.MemoryRecord = MemoryRecord |
| 74 | +sys.modules["semantic_kernel.memory.memory_record"] = memory_record_module |
| 75 | +memory_store_module = types.ModuleType("semantic_kernel.memory.memory_store_base") |
| 76 | +class MemoryStoreBase: # pragma: no cover - stub |
| 77 | + pass |
| 78 | +memory_store_module.MemoryStoreBase = MemoryStoreBase |
| 79 | +sys.modules["semantic_kernel.memory.memory_store_base"] = memory_store_module |
| 80 | +contents_module = types.ModuleType("semantic_kernel.contents") |
| 81 | +class ChatMessageContent: # pragma: no cover - stub |
| 82 | + def __init__(self, *args, **kwargs): |
| 83 | + pass |
| 84 | +class ChatHistory(list): |
| 85 | + pass |
| 86 | +class AuthorRole: # pragma: no cover - stub |
| 87 | + pass |
| 88 | +contents_module.ChatMessageContent = ChatMessageContent |
| 89 | +contents_module.ChatHistory = ChatHistory |
| 90 | +contents_module.AuthorRole = AuthorRole |
| 91 | +sys.modules["semantic_kernel.contents"] = contents_module |
| 92 | +kernel_pydantic_module = types.ModuleType("semantic_kernel.kernel_pydantic") |
| 93 | +class Field: # pragma: no cover - stub |
| 94 | + def __init__(self, *args, **kwargs): |
| 95 | + pass |
| 96 | +class KernelBaseModel: # pragma: no cover - stub |
| 97 | + def __init__(self, **data): |
| 98 | + for k, v in data.items(): |
| 99 | + setattr(self, k, v) |
| 100 | + |
| 101 | + def model_dump(self): |
| 102 | + return self.__dict__ |
| 103 | +kernel_pydantic_module.Field = Field |
| 104 | +kernel_pydantic_module.KernelBaseModel = KernelBaseModel |
| 105 | +sys.modules["semantic_kernel.kernel_pydantic"] = kernel_pydantic_module |
| 106 | + |
| 107 | +# Provide required environment variables for AppConfig |
| 108 | +os.environ.setdefault("AZURE_OPENAI_DEPLOYMENT_NAME", "test") |
| 109 | +os.environ.setdefault("AZURE_OPENAI_API_VERSION", "2024-05-01") |
| 110 | +os.environ.setdefault("AZURE_OPENAI_ENDPOINT", "https://test") |
| 111 | +os.environ.setdefault("AZURE_AI_SUBSCRIPTION_ID", "sub") |
| 112 | +os.environ.setdefault("AZURE_AI_RESOURCE_GROUP", "rg") |
| 113 | +os.environ.setdefault("AZURE_AI_PROJECT_NAME", "proj") |
| 114 | +os.environ.setdefault("AZURE_AI_AGENT_ENDPOINT", "https://agent") |
| 115 | + |
| 116 | +from src.backend.kernel_agents.agent_base import BaseAgent |
| 117 | +from src.backend.models.messages_kernel import AgentMessage |
| 118 | + |
| 119 | + |
| 120 | +class StubMemoryStore: |
| 121 | + def __init__(self): |
| 122 | + self.items = [] |
| 123 | + |
| 124 | + async def add_item(self, item): |
| 125 | + self.items.append(item) |
| 126 | + |
| 127 | + |
| 128 | +class DummyAgent(BaseAgent): |
| 129 | + def __init__(self, memory_store, summary_after_n_turns=4): |
| 130 | + # Bypass parent initialization for testing summarization helpers |
| 131 | + self._agent_name = "dummy" |
| 132 | + self._session_id = "session" |
| 133 | + self._user_id = "user" |
| 134 | + self._memory_store = memory_store |
| 135 | + self._tools = [] |
| 136 | + self._system_message = "system" |
| 137 | + self._chat_history = [{"role": "system", "content": self._system_message}] |
| 138 | + self._summary_after_n_turns = summary_after_n_turns |
| 139 | + self.name = self._agent_name |
| 140 | + |
| 141 | + @classmethod |
| 142 | + async def create(cls, **kwargs) -> "BaseAgent": |
| 143 | + raise NotImplementedError |
| 144 | + |
| 145 | + |
| 146 | +def test_chat_history_summarization_and_truncation(): |
| 147 | + store = StubMemoryStore() |
| 148 | + agent = DummyAgent(store, summary_after_n_turns=4) |
| 149 | + |
| 150 | + async def run(): |
| 151 | + for i in range(6): |
| 152 | + await agent._add_message_to_history("user", f"message {i}") |
| 153 | + |
| 154 | + asyncio.run(run()) |
| 155 | + |
| 156 | + # system message + summary + last 4 messages |
| 157 | + assert len(agent._chat_history) == 6 |
| 158 | + summary_entry = agent._chat_history[1] |
| 159 | + assert summary_entry["role"] == "system" |
| 160 | + assert "message 0" in summary_entry["content"] |
| 161 | + assert "message 1" in summary_entry["content"] |
| 162 | + assert agent._chat_history[-1]["content"] == "message 5" |
| 163 | + |
| 164 | + assert len(store.items) >= 1 |
| 165 | + assert store.items[-1].content == summary_entry["content"] |
0 commit comments