pygeai-orchestration 0.1.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. pygeai_orchestration/__init__.py +99 -0
  2. pygeai_orchestration/cli/__init__.py +7 -0
  3. pygeai_orchestration/cli/__main__.py +11 -0
  4. pygeai_orchestration/cli/commands/__init__.py +13 -0
  5. pygeai_orchestration/cli/commands/base.py +192 -0
  6. pygeai_orchestration/cli/error_handler.py +123 -0
  7. pygeai_orchestration/cli/formatters.py +419 -0
  8. pygeai_orchestration/cli/geai_orch.py +270 -0
  9. pygeai_orchestration/cli/interactive.py +265 -0
  10. pygeai_orchestration/cli/texts/help.py +169 -0
  11. pygeai_orchestration/core/__init__.py +130 -0
  12. pygeai_orchestration/core/base/__init__.py +23 -0
  13. pygeai_orchestration/core/base/agent.py +121 -0
  14. pygeai_orchestration/core/base/geai_agent.py +144 -0
  15. pygeai_orchestration/core/base/geai_orchestrator.py +77 -0
  16. pygeai_orchestration/core/base/orchestrator.py +142 -0
  17. pygeai_orchestration/core/base/pattern.py +161 -0
  18. pygeai_orchestration/core/base/tool.py +149 -0
  19. pygeai_orchestration/core/common/__init__.py +18 -0
  20. pygeai_orchestration/core/common/context.py +140 -0
  21. pygeai_orchestration/core/common/memory.py +176 -0
  22. pygeai_orchestration/core/common/message.py +50 -0
  23. pygeai_orchestration/core/common/state.py +181 -0
  24. pygeai_orchestration/core/composition.py +190 -0
  25. pygeai_orchestration/core/config.py +356 -0
  26. pygeai_orchestration/core/exceptions.py +400 -0
  27. pygeai_orchestration/core/handlers.py +380 -0
  28. pygeai_orchestration/core/utils/__init__.py +37 -0
  29. pygeai_orchestration/core/utils/cache.py +138 -0
  30. pygeai_orchestration/core/utils/config.py +94 -0
  31. pygeai_orchestration/core/utils/logging.py +57 -0
  32. pygeai_orchestration/core/utils/metrics.py +184 -0
  33. pygeai_orchestration/core/utils/validators.py +140 -0
  34. pygeai_orchestration/dev/__init__.py +15 -0
  35. pygeai_orchestration/dev/debug.py +288 -0
  36. pygeai_orchestration/dev/templates.py +321 -0
  37. pygeai_orchestration/dev/testing.py +301 -0
  38. pygeai_orchestration/patterns/__init__.py +15 -0
  39. pygeai_orchestration/patterns/multi_agent.py +237 -0
  40. pygeai_orchestration/patterns/planning.py +219 -0
  41. pygeai_orchestration/patterns/react.py +221 -0
  42. pygeai_orchestration/patterns/reflection.py +134 -0
  43. pygeai_orchestration/patterns/tool_use.py +170 -0
  44. pygeai_orchestration/tests/__init__.py +1 -0
  45. pygeai_orchestration/tests/test_base_classes.py +187 -0
  46. pygeai_orchestration/tests/test_cache.py +184 -0
  47. pygeai_orchestration/tests/test_cli_formatters.py +232 -0
  48. pygeai_orchestration/tests/test_common.py +214 -0
  49. pygeai_orchestration/tests/test_composition.py +265 -0
  50. pygeai_orchestration/tests/test_config.py +301 -0
  51. pygeai_orchestration/tests/test_dev_utils.py +337 -0
  52. pygeai_orchestration/tests/test_exceptions.py +327 -0
  53. pygeai_orchestration/tests/test_handlers.py +307 -0
  54. pygeai_orchestration/tests/test_metrics.py +171 -0
  55. pygeai_orchestration/tests/test_patterns.py +165 -0
  56. pygeai_orchestration-0.1.0b2.dist-info/METADATA +290 -0
  57. pygeai_orchestration-0.1.0b2.dist-info/RECORD +61 -0
  58. pygeai_orchestration-0.1.0b2.dist-info/WHEEL +5 -0
  59. pygeai_orchestration-0.1.0b2.dist-info/entry_points.txt +2 -0
  60. pygeai_orchestration-0.1.0b2.dist-info/licenses/LICENSE +8 -0
  61. pygeai_orchestration-0.1.0b2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,134 @@
1
+ from typing import Any, Dict, Optional
2
+ from ..core.base import BasePattern, PatternConfig, PatternResult, PatternType
3
+ from ..core.common import Message, MessageRole, Conversation, State, StateStatus
4
+ from ..core.utils import get_logger
5
+
6
+ logger = get_logger()
7
+
8
+
9
+ class ReflectionPattern(BasePattern):
10
+ """
11
+ Reflection pattern for iterative self-improvement of agent responses.
12
+
13
+ This pattern enables agents to critique and refine their own outputs through
14
+ multiple iterations. Each iteration consists of generating a response, reflecting
15
+ on its quality, and producing an improved version. The process continues until
16
+ convergence or max_iterations is reached.
17
+
18
+ The reflection pattern is particularly effective for tasks requiring high-quality
19
+ outputs such as content creation, analysis, and problem-solving where iterative
20
+ refinement leads to better results.
21
+ """
22
+
23
+ def __init__(
24
+ self, agent, config: Optional[PatternConfig] = None, reflection_prompt: Optional[str] = None
25
+ ):
26
+ """
27
+ Initialize the Reflection pattern.
28
+
29
+ :param agent: BaseAgent - The agent to use for generation and reflection.
30
+ :param config: Optional[PatternConfig] - Pattern configuration. If None, uses default with max_iterations=3.
31
+ :param reflection_prompt: Optional[str] - Custom reflection prompt. If None, uses default prompt.
32
+ """
33
+ if config is None:
34
+ config = PatternConfig(
35
+ name="reflection", pattern_type=PatternType.REFLECTION, max_iterations=3
36
+ )
37
+ super().__init__(config)
38
+ self.agent = agent
39
+ self.reflection_prompt = reflection_prompt or (
40
+ "Review your previous response. Identify any issues, inaccuracies, or areas for improvement. "
41
+ "Provide a refined and improved version of your response."
42
+ )
43
+ self._conversation = Conversation(id=f"reflection-{id(self)}")
44
+ self._state = State()
45
+
46
+ async def execute(self, task: str, context: Optional[Dict[str, Any]] = None) -> PatternResult:
47
+ """
48
+ Execute the reflection pattern on the given task.
49
+
50
+ The method iteratively improves the agent's response through self-reflection.
51
+ Each iteration generates a response, reflects on it, and produces an improved
52
+ version. The process stops when max_iterations is reached or convergence is detected.
53
+
54
+ :param task: str - The task or prompt to execute.
55
+ :param context: Optional[Dict[str, Any]] - Additional context for execution. Defaults to None.
56
+ :return: PatternResult - Contains success status, final refined result, iteration count, and metadata.
57
+ :raises ValueError: If task is empty or None.
58
+
59
+ Example:
60
+ >>> pattern = ReflectionPattern(agent=my_agent)
61
+ >>> result = await pattern.execute("Explain quantum computing in simple terms")
62
+ >>> print(f"Result after {result.iterations} iterations: {result.result}")
63
+ """
64
+ self.reset()
65
+ self._state.update_status(StateStatus.RUNNING)
66
+
67
+ logger.info(f"Starting reflection pattern for task: {task[:50]}...")
68
+
69
+ try:
70
+ self._conversation.add_message(Message(role=MessageRole.USER, content=task))
71
+
72
+ current_response = None
73
+
74
+ while self.current_iteration < self.config.max_iterations:
75
+ self.increment_iteration()
76
+ logger.debug(
77
+ f"Reflection iteration {self.current_iteration}/{self.config.max_iterations}"
78
+ )
79
+
80
+ state_data = {
81
+ "iteration": self.current_iteration,
82
+ "task": task,
83
+ "current_response": current_response,
84
+ }
85
+
86
+ step_result = await self.step(state_data)
87
+ current_response = step_result.get("response")
88
+
89
+ if step_result.get("should_stop", False):
90
+ logger.info(f"Reflection converged at iteration {self.current_iteration}")
91
+ break
92
+
93
+ self._state.update_status(StateStatus.COMPLETED)
94
+
95
+ return PatternResult(
96
+ success=True,
97
+ result=current_response,
98
+ iterations=self.current_iteration,
99
+ metadata={
100
+ "conversation_length": len(self._conversation.messages),
101
+ "final_iteration": self.current_iteration,
102
+ },
103
+ )
104
+
105
+ except Exception as e:
106
+ logger.error(f"Reflection pattern failed: {str(e)}")
107
+ self._state.update_status(StateStatus.FAILED)
108
+ return PatternResult(
109
+ success=False, result=None, iterations=self.current_iteration, error=str(e)
110
+ )
111
+
112
+ async def step(self, state: Dict[str, Any]) -> Dict[str, Any]:
113
+ iteration = state.get("iteration", 1)
114
+ task = state.get("task")
115
+ current_response = state.get("current_response")
116
+
117
+ if iteration == 1:
118
+ logger.debug("Generating initial response")
119
+ response = await self.agent.generate(task)
120
+ else:
121
+ logger.debug(f"Reflecting on iteration {iteration}")
122
+ reflection_request = (
123
+ f"Previous response:\n{current_response}\n\n{self.reflection_prompt}"
124
+ )
125
+ response = await self.agent.generate(reflection_request)
126
+
127
+ self._conversation.add_message(Message(role=MessageRole.ASSISTANT, content=response))
128
+
129
+ should_stop = iteration >= self.config.max_iterations
130
+
131
+ return {"response": response, "should_stop": should_stop, "iteration": iteration}
132
+
133
+ def get_conversation(self) -> Conversation:
134
+ return self._conversation
@@ -0,0 +1,170 @@
1
+ from typing import Any, Dict, List, Optional
2
+ from ..core.base import BasePattern, BaseTool, PatternConfig, PatternResult, PatternType
3
+ from ..core.common import Message, MessageRole, Conversation, State, StateStatus
4
+ from ..core.utils import get_logger
5
+
6
+ logger = get_logger()
7
+
8
+
9
+ class ToolUsePattern(BasePattern):
10
+ """
11
+ Tool use pattern for agent interaction with external tools.
12
+
13
+ This pattern enables agents to leverage external tools and APIs to complete
14
+ tasks that require external information or capabilities. The agent can
15
+ intelligently select and use appropriate tools based on the task requirements,
16
+ parse tool results, and integrate them into the final response.
17
+
18
+ The tool use pattern is essential for tasks requiring:
19
+ - External data retrieval (APIs, databases, search engines)
20
+ - Computations (calculators, data processors)
21
+ - Actions (file operations, notifications, commands)
22
+
23
+ The pattern orchestrates a conversation loop where the agent can:
24
+ 1. Analyze the task and available tools
25
+ 2. Select and invoke appropriate tools with parameters
26
+ 3. Process tool results and integrate them
27
+ 4. Iterate until the task is complete
28
+ """
29
+
30
+ def __init__(self, agent, tools: List[BaseTool], config: Optional[PatternConfig] = None):
31
+ """
32
+ Initialize the Tool Use pattern.
33
+
34
+ :param agent: BaseAgent - The agent to use for tool selection and result processing.
35
+ :param tools: List[BaseTool] - List of available tools the agent can use.
36
+ :param config: Optional[PatternConfig] - Pattern configuration. If None, uses default with max_iterations=5.
37
+ """
38
+ if config is None:
39
+ config = PatternConfig(
40
+ name="tool_use", pattern_type=PatternType.TOOL_USE, max_iterations=5
41
+ )
42
+ super().__init__(config)
43
+ self.agent = agent
44
+ self.tools = {tool.name: tool for tool in tools}
45
+ self._conversation = Conversation(id=f"tool-use-{id(self)}")
46
+ self._state = State()
47
+
48
+ async def execute(self, task: str, context: Optional[Dict[str, Any]] = None) -> PatternResult:
49
+ """
50
+ Execute the tool use pattern on the given task.
51
+
52
+ The method enables the agent to iteratively select and use tools to complete
53
+ the task. The agent receives descriptions of available tools and can invoke
54
+ them by responding with specific commands. Tool results are integrated into
55
+ the conversation until the agent provides a final answer.
56
+
57
+ :param task: str - The task or question to execute with tool assistance.
58
+ :param context: Optional[Dict[str, Any]] - Additional context for execution. Defaults to None.
59
+ :return: PatternResult - Contains success status, final answer, tools used, and metadata.
60
+ :raises ToolExecutionError: If a tool execution fails.
61
+
62
+ Example:
63
+ >>> tools = [CalculatorTool(), SearchTool()]
64
+ >>> pattern = ToolUsePattern(agent=my_agent, tools=tools)
65
+ >>> result = await pattern.execute("What is 15% of 240?")
66
+ >>> print(f"Answer: {result.result}, Tools used: {result.metadata['tools_used']}")
67
+ """
68
+ self.reset()
69
+ self._state.update_status(StateStatus.RUNNING)
70
+
71
+ logger.info(f"Starting tool use pattern for task: {task[:50]}...")
72
+
73
+ try:
74
+ tools_description = self._get_tools_description()
75
+ system_prompt = (
76
+ f"You have access to the following tools:\n{tools_description}\n\n"
77
+ "To use a tool, respond with: TOOL_CALL: <tool_name> <parameters>\n"
78
+ "When you have the final answer, respond with: FINAL_ANSWER: <answer>"
79
+ )
80
+
81
+ self._conversation.add_message(Message(role=MessageRole.SYSTEM, content=system_prompt))
82
+ self._conversation.add_message(Message(role=MessageRole.USER, content=task))
83
+
84
+ final_answer = None
85
+
86
+ while self.current_iteration < self.config.max_iterations:
87
+ self.increment_iteration()
88
+ logger.debug(
89
+ f"Tool use iteration {self.current_iteration}/{self.config.max_iterations}"
90
+ )
91
+
92
+ state_data = {
93
+ "iteration": self.current_iteration,
94
+ "task": task,
95
+ "conversation": self._conversation.to_dict_list(),
96
+ }
97
+
98
+ step_result = await self.step(state_data)
99
+
100
+ if step_result.get("is_final"):
101
+ final_answer = step_result.get("answer")
102
+ logger.info(f"Tool use completed at iteration {self.current_iteration}")
103
+ break
104
+
105
+ self._state.update_status(StateStatus.COMPLETED)
106
+
107
+ return PatternResult(
108
+ success=True,
109
+ result=final_answer or "No final answer provided",
110
+ iterations=self.current_iteration,
111
+ metadata={
112
+ "tools_used": step_result.get("tools_used", []),
113
+ "conversation_length": len(self._conversation.messages),
114
+ },
115
+ )
116
+
117
+ except Exception as e:
118
+ logger.error(f"Tool use pattern failed: {str(e)}")
119
+ self._state.update_status(StateStatus.FAILED)
120
+ return PatternResult(
121
+ success=False, result=None, iterations=self.current_iteration, error=str(e)
122
+ )
123
+
124
+ async def step(self, state: Dict[str, Any]) -> Dict[str, Any]:
125
+ conversation = state.get("conversation", [])
126
+ conversation_str = "\n".join([f"{m['role']}: {m['content']}" for m in conversation[-5:]])
127
+
128
+ response = await self.agent.generate(conversation_str)
129
+
130
+ if response.startswith("TOOL_CALL:"):
131
+ tool_call = response[len("TOOL_CALL:") :].strip()
132
+ parts = tool_call.split(maxsplit=1)
133
+ tool_name = parts[0]
134
+ tool_params = parts[1] if len(parts) > 1 else ""
135
+
136
+ logger.debug(f"Calling tool: {tool_name} with params: {tool_params}")
137
+
138
+ if tool_name in self.tools:
139
+ tool_result = await self.tools[tool_name].execute(input=tool_params)
140
+ result_msg = f"Tool '{tool_name}' result: {tool_result.result}"
141
+ self._conversation.add_message(Message(role=MessageRole.TOOL, content=result_msg))
142
+
143
+ return {
144
+ "is_final": False,
145
+ "answer": None,
146
+ "tool_called": tool_name,
147
+ "tools_used": [tool_name],
148
+ }
149
+ else:
150
+ error_msg = f"Unknown tool: {tool_name}"
151
+ self._conversation.add_message(Message(role=MessageRole.SYSTEM, content=error_msg))
152
+ return {"is_final": False, "answer": None, "tools_used": []}
153
+
154
+ elif response.startswith("FINAL_ANSWER:"):
155
+ answer = response[len("FINAL_ANSWER:") :].strip()
156
+ self._conversation.add_message(Message(role=MessageRole.ASSISTANT, content=answer))
157
+ return {"is_final": True, "answer": answer, "tools_used": []}
158
+
159
+ else:
160
+ self._conversation.add_message(Message(role=MessageRole.ASSISTANT, content=response))
161
+ return {"is_final": False, "answer": None, "tools_used": []}
162
+
163
+ def _get_tools_description(self) -> str:
164
+ descriptions = []
165
+ for tool in self.tools.values():
166
+ descriptions.append(f"- {tool.name}: {tool.description}")
167
+ return "\n".join(descriptions)
168
+
169
+ def get_conversation(self) -> Conversation:
170
+ return self._conversation
@@ -0,0 +1 @@
1
+ """Package module."""
@@ -0,0 +1,187 @@
1
+ import unittest
2
+ import asyncio
3
+ from pygeai_orchestration import (
4
+ AgentConfig,
5
+ BaseAgent,
6
+ PatternConfig,
7
+ BasePattern,
8
+ PatternType,
9
+ PatternResult,
10
+ OrchestratorConfig,
11
+ BaseOrchestrator,
12
+ ToolConfig,
13
+ BaseTool,
14
+ ToolResult,
15
+ ToolCategory
16
+ )
17
+
18
+
19
+ class ConcreteAgent(BaseAgent):
20
+ """Concrete implementation for testing."""
21
+
22
+ async def execute(self, task: str, context=None):
23
+ return {"success": True, "result": "Executed"}
24
+
25
+ async def generate(self, prompt: str, **kwargs):
26
+ return "Generated response"
27
+
28
+
29
+ class ConcretePattern(BasePattern):
30
+ """Concrete implementation for testing."""
31
+
32
+ async def execute(self, task: str, context=None):
33
+ return PatternResult(success=True, result="Done", iterations=1)
34
+
35
+ async def step(self, state):
36
+ return {"status": "ok"}
37
+
38
+
39
+ class ConcreteOrchestrator(BaseOrchestrator):
40
+ """Concrete implementation for testing."""
41
+
42
+ async def orchestrate(self, task: str, pattern: str, context=None):
43
+ return PatternResult(success=True, result="Orchestrated", iterations=1)
44
+
45
+ async def coordinate_agents(self, agents, task: str):
46
+ return {"success": True, "results": {}}
47
+
48
+
49
+ class ConcreteTool(BaseTool):
50
+ """Concrete implementation for testing."""
51
+
52
+ async def execute(self, **kwargs):
53
+ return ToolResult(success=True, result="Tool executed")
54
+
55
+ def validate_parameters(self, parameters):
56
+ return True
57
+
58
+
59
+ class TestBaseAgent(unittest.TestCase):
60
+
61
+ def test_creation(self):
62
+ config = AgentConfig(name="test-agent", model="gpt-4")
63
+ agent = ConcreteAgent(config)
64
+
65
+ self.assertEqual(agent.name, "test-agent")
66
+ self.assertEqual(agent.config.model, "gpt-4")
67
+ self.assertEqual(len(agent.get_history()), 0)
68
+
69
+ def test_history(self):
70
+ config = AgentConfig(name="test-agent", model="gpt-4")
71
+ agent = ConcreteAgent(config)
72
+
73
+ agent.add_to_history({"test": "data"})
74
+ self.assertEqual(len(agent.get_history()), 1)
75
+
76
+ agent.clear_history()
77
+ self.assertEqual(len(agent.get_history()), 0)
78
+
79
+ def test_execute(self):
80
+ config = AgentConfig(name="test-agent", model="gpt-4")
81
+ agent = ConcreteAgent(config)
82
+
83
+ result = asyncio.run(agent.execute("Test task"))
84
+ self.assertTrue(result["success"])
85
+
86
+
87
+ class TestBasePattern(unittest.TestCase):
88
+
89
+ def test_creation(self):
90
+ config = PatternConfig(
91
+ name="test-pattern",
92
+ pattern_type=PatternType.REFLECTION,
93
+ max_iterations=5
94
+ )
95
+ pattern = ConcretePattern(config)
96
+
97
+ self.assertEqual(pattern.name, "test-pattern")
98
+ self.assertEqual(pattern.pattern_type, PatternType.REFLECTION)
99
+ self.assertEqual(pattern.current_iteration, 0)
100
+
101
+ def test_iteration_tracking(self):
102
+ config = PatternConfig(name="test", pattern_type=PatternType.REFLECTION)
103
+ pattern = ConcretePattern(config)
104
+
105
+ self.assertEqual(pattern.current_iteration, 0)
106
+ pattern.increment_iteration()
107
+ self.assertEqual(pattern.current_iteration, 1)
108
+ pattern.reset()
109
+ self.assertEqual(pattern.current_iteration, 0)
110
+
111
+ def test_execute(self):
112
+ config = PatternConfig(name="test", pattern_type=PatternType.REFLECTION)
113
+ pattern = ConcretePattern(config)
114
+
115
+ result = asyncio.run(pattern.execute("Test"))
116
+ self.assertTrue(result.success)
117
+ self.assertEqual(result.iterations, 1)
118
+
119
+
120
+ class TestBaseOrchestrator(unittest.TestCase):
121
+
122
+ def test_creation(self):
123
+ config = OrchestratorConfig(name="test-orchestrator")
124
+ orch = ConcreteOrchestrator(config)
125
+
126
+ self.assertEqual(orch.name, "test-orchestrator")
127
+ self.assertEqual(len(orch.list_agents()), 0)
128
+ self.assertEqual(len(orch.list_patterns()), 0)
129
+
130
+ def test_register_agent(self):
131
+ config = OrchestratorConfig(name="test-orchestrator")
132
+ orch = ConcreteOrchestrator(config)
133
+
134
+ agent_config = AgentConfig(name="agent1", model="gpt-4")
135
+ agent = ConcreteAgent(agent_config)
136
+ orch.register_agent(agent)
137
+
138
+ self.assertEqual(len(orch.list_agents()), 1)
139
+ self.assertIsNotNone(orch.get_agent("agent1"))
140
+
141
+ def test_register_pattern(self):
142
+ config = OrchestratorConfig(name="test-orchestrator")
143
+ orch = ConcreteOrchestrator(config)
144
+
145
+ pattern_config = PatternConfig(name="pattern1", pattern_type=PatternType.REFLECTION)
146
+ pattern = ConcretePattern(pattern_config)
147
+ orch.register_pattern(pattern)
148
+
149
+ self.assertEqual(len(orch.list_patterns()), 1)
150
+ self.assertIsNotNone(orch.get_pattern("pattern1"))
151
+
152
+
153
+ class TestBaseTool(unittest.TestCase):
154
+
155
+ def test_creation(self):
156
+ config = ToolConfig(
157
+ name="test-tool",
158
+ description="A test tool",
159
+ category=ToolCategory.CUSTOM
160
+ )
161
+ tool = ConcreteTool(config)
162
+
163
+ self.assertEqual(tool.name, "test-tool")
164
+ self.assertEqual(tool.category, ToolCategory.CUSTOM)
165
+
166
+ def test_schema(self):
167
+ config = ToolConfig(
168
+ name="test-tool",
169
+ description="A test tool",
170
+ category=ToolCategory.SEARCH
171
+ )
172
+ tool = ConcreteTool(config)
173
+
174
+ schema = tool.get_schema()
175
+ self.assertEqual(schema["name"], "test-tool")
176
+ self.assertEqual(schema["category"], "search")
177
+
178
+ def test_execute(self):
179
+ config = ToolConfig(name="test-tool", description="Test", category=ToolCategory.CUSTOM)
180
+ tool = ConcreteTool(config)
181
+
182
+ result = asyncio.run(tool.execute())
183
+ self.assertTrue(result.success)
184
+
185
+
186
+ if __name__ == '__main__':
187
+ unittest.main()
@@ -0,0 +1,184 @@
1
+ import time
2
+ import unittest
3
+
4
+ from pygeai_orchestration.core.utils.cache import CacheEntry, LRUCache, PatternCache
5
+
6
+
7
+ class TestCacheEntry(unittest.TestCase):
8
+ def test_cache_entry_creation(self):
9
+ entry = CacheEntry("test_value")
10
+ self.assertEqual(entry.value, "test_value")
11
+ self.assertEqual(entry.access_count, 0)
12
+ self.assertIsNotNone(entry.created_at)
13
+ self.assertIsNone(entry.ttl)
14
+
15
+ def test_cache_entry_with_ttl(self):
16
+ entry = CacheEntry("test_value", ttl=1.0)
17
+ self.assertEqual(entry.ttl, 1.0)
18
+ self.assertFalse(entry.is_expired())
19
+
20
+ def test_cache_entry_expiration(self):
21
+ entry = CacheEntry("test_value", ttl=0.1)
22
+ self.assertFalse(entry.is_expired())
23
+ time.sleep(0.2)
24
+ self.assertTrue(entry.is_expired())
25
+
26
+ def test_cache_entry_access(self):
27
+ entry = CacheEntry("test_value")
28
+ self.assertEqual(entry.access_count, 0)
29
+ value = entry.access()
30
+ self.assertEqual(value, "test_value")
31
+ self.assertEqual(entry.access_count, 1)
32
+ entry.access()
33
+ self.assertEqual(entry.access_count, 2)
34
+
35
+
36
+ class TestLRUCache(unittest.TestCase):
37
+ def setUp(self):
38
+ self.cache = LRUCache(max_size=3)
39
+
40
+ def test_cache_creation(self):
41
+ cache = LRUCache(max_size=10, default_ttl=60.0)
42
+ self.assertEqual(cache.max_size, 10)
43
+ self.assertEqual(cache.default_ttl, 60.0)
44
+ self.assertEqual(cache.size(), 0)
45
+
46
+ def test_set_and_get(self):
47
+ self.cache.set("key1", "value1")
48
+ value = self.cache.get("key1")
49
+ self.assertEqual(value, "value1")
50
+
51
+ def test_cache_miss(self):
52
+ value = self.cache.get("nonexistent")
53
+ self.assertIsNone(value)
54
+
55
+ def test_lru_eviction(self):
56
+ self.cache.set("key1", "value1")
57
+ self.cache.set("key2", "value2")
58
+ self.cache.set("key3", "value3")
59
+ self.cache.set("key4", "value4")
60
+
61
+ self.assertIsNone(self.cache.get("key1"))
62
+ self.assertEqual(self.cache.get("key2"), "value2")
63
+ self.assertEqual(self.cache.get("key3"), "value3")
64
+ self.assertEqual(self.cache.get("key4"), "value4")
65
+
66
+ def test_lru_ordering(self):
67
+ self.cache.set("key1", "value1")
68
+ self.cache.set("key2", "value2")
69
+ self.cache.set("key3", "value3")
70
+
71
+ self.cache.get("key1")
72
+
73
+ self.cache.set("key4", "value4")
74
+
75
+ self.assertIsNone(self.cache.get("key2"))
76
+ self.assertEqual(self.cache.get("key1"), "value1")
77
+
78
+ def test_ttl_expiration(self):
79
+ cache = LRUCache(max_size=10, default_ttl=0.1)
80
+ cache.set("key1", "value1")
81
+ self.assertEqual(cache.get("key1"), "value1")
82
+
83
+ time.sleep(0.2)
84
+ self.assertIsNone(cache.get("key1"))
85
+
86
+ def test_custom_ttl(self):
87
+ self.cache.set("key1", "value1", ttl=0.1)
88
+ self.cache.set("key2", "value2", ttl=10.0)
89
+
90
+ time.sleep(0.2)
91
+ self.assertIsNone(self.cache.get("key1"))
92
+ self.assertEqual(self.cache.get("key2"), "value2")
93
+
94
+ def test_invalidate(self):
95
+ self.cache.set("key1", "value1")
96
+ self.assertTrue(self.cache.invalidate("key1"))
97
+ self.assertIsNone(self.cache.get("key1"))
98
+ self.assertFalse(self.cache.invalidate("key1"))
99
+
100
+ def test_clear(self):
101
+ self.cache.set("key1", "value1")
102
+ self.cache.set("key2", "value2")
103
+ self.assertEqual(self.cache.size(), 2)
104
+
105
+ self.cache.clear()
106
+ self.assertEqual(self.cache.size(), 0)
107
+ self.assertIsNone(self.cache.get("key1"))
108
+
109
+ def test_cache_stats(self):
110
+ self.cache.get("miss1")
111
+ self.cache.get("miss2")
112
+
113
+ self.cache.set("key1", "value1")
114
+ self.cache.get("key1")
115
+ self.cache.get("key1")
116
+
117
+ stats = self.cache.get_stats()
118
+ self.assertEqual(stats["hits"], 2)
119
+ self.assertEqual(stats["misses"], 2)
120
+ self.assertEqual(stats["total_requests"], 4)
121
+ self.assertEqual(stats["hit_rate"], 50.0)
122
+
123
+ def test_complex_key(self):
124
+ key = {"param1": "value1", "param2": [1, 2, 3]}
125
+ self.cache.set(key, "result")
126
+ value = self.cache.get(key)
127
+ self.assertEqual(value, "result")
128
+
129
+ def test_key_generation_consistency(self):
130
+ key1 = {"b": 2, "a": 1}
131
+ key2 = {"a": 1, "b": 2}
132
+
133
+ self.cache.set(key1, "value1")
134
+ value = self.cache.get(key2)
135
+ self.assertEqual(value, "value1")
136
+
137
+
138
+ class TestPatternCache(unittest.TestCase):
139
+ def setUp(self):
140
+ PatternCache.reset()
141
+
142
+ def tearDown(self):
143
+ PatternCache.reset()
144
+
145
+ def test_singleton_pattern(self):
146
+ cache1 = PatternCache()
147
+ cache2 = PatternCache()
148
+ self.assertIs(cache1, cache2)
149
+
150
+ def test_initialize(self):
151
+ pattern_cache = PatternCache()
152
+ pattern_cache.initialize(max_size=50, default_ttl=120.0)
153
+
154
+ cache = pattern_cache.get_cache()
155
+ self.assertEqual(cache.max_size, 50)
156
+ self.assertEqual(cache.default_ttl, 120.0)
157
+
158
+ def test_auto_initialize(self):
159
+ pattern_cache = PatternCache()
160
+ cache = pattern_cache.get_cache()
161
+ self.assertIsNotNone(cache)
162
+ self.assertEqual(cache.max_size, 100)
163
+
164
+ def test_clear(self):
165
+ pattern_cache = PatternCache()
166
+ cache = pattern_cache.get_cache()
167
+ cache.set("key1", "value1")
168
+
169
+ pattern_cache.clear()
170
+ self.assertEqual(cache.size(), 0)
171
+
172
+ def test_reset(self):
173
+ pattern_cache1 = PatternCache()
174
+ pattern_cache1.initialize(max_size=10)
175
+
176
+ PatternCache.reset()
177
+
178
+ pattern_cache2 = PatternCache()
179
+ cache = pattern_cache2.get_cache()
180
+ self.assertEqual(cache.max_size, 100)
181
+
182
+
183
+ if __name__ == "__main__":
184
+ unittest.main()