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.
- pygeai_orchestration/__init__.py +99 -0
- pygeai_orchestration/cli/__init__.py +7 -0
- pygeai_orchestration/cli/__main__.py +11 -0
- pygeai_orchestration/cli/commands/__init__.py +13 -0
- pygeai_orchestration/cli/commands/base.py +192 -0
- pygeai_orchestration/cli/error_handler.py +123 -0
- pygeai_orchestration/cli/formatters.py +419 -0
- pygeai_orchestration/cli/geai_orch.py +270 -0
- pygeai_orchestration/cli/interactive.py +265 -0
- pygeai_orchestration/cli/texts/help.py +169 -0
- pygeai_orchestration/core/__init__.py +130 -0
- pygeai_orchestration/core/base/__init__.py +23 -0
- pygeai_orchestration/core/base/agent.py +121 -0
- pygeai_orchestration/core/base/geai_agent.py +144 -0
- pygeai_orchestration/core/base/geai_orchestrator.py +77 -0
- pygeai_orchestration/core/base/orchestrator.py +142 -0
- pygeai_orchestration/core/base/pattern.py +161 -0
- pygeai_orchestration/core/base/tool.py +149 -0
- pygeai_orchestration/core/common/__init__.py +18 -0
- pygeai_orchestration/core/common/context.py +140 -0
- pygeai_orchestration/core/common/memory.py +176 -0
- pygeai_orchestration/core/common/message.py +50 -0
- pygeai_orchestration/core/common/state.py +181 -0
- pygeai_orchestration/core/composition.py +190 -0
- pygeai_orchestration/core/config.py +356 -0
- pygeai_orchestration/core/exceptions.py +400 -0
- pygeai_orchestration/core/handlers.py +380 -0
- pygeai_orchestration/core/utils/__init__.py +37 -0
- pygeai_orchestration/core/utils/cache.py +138 -0
- pygeai_orchestration/core/utils/config.py +94 -0
- pygeai_orchestration/core/utils/logging.py +57 -0
- pygeai_orchestration/core/utils/metrics.py +184 -0
- pygeai_orchestration/core/utils/validators.py +140 -0
- pygeai_orchestration/dev/__init__.py +15 -0
- pygeai_orchestration/dev/debug.py +288 -0
- pygeai_orchestration/dev/templates.py +321 -0
- pygeai_orchestration/dev/testing.py +301 -0
- pygeai_orchestration/patterns/__init__.py +15 -0
- pygeai_orchestration/patterns/multi_agent.py +237 -0
- pygeai_orchestration/patterns/planning.py +219 -0
- pygeai_orchestration/patterns/react.py +221 -0
- pygeai_orchestration/patterns/reflection.py +134 -0
- pygeai_orchestration/patterns/tool_use.py +170 -0
- pygeai_orchestration/tests/__init__.py +1 -0
- pygeai_orchestration/tests/test_base_classes.py +187 -0
- pygeai_orchestration/tests/test_cache.py +184 -0
- pygeai_orchestration/tests/test_cli_formatters.py +232 -0
- pygeai_orchestration/tests/test_common.py +214 -0
- pygeai_orchestration/tests/test_composition.py +265 -0
- pygeai_orchestration/tests/test_config.py +301 -0
- pygeai_orchestration/tests/test_dev_utils.py +337 -0
- pygeai_orchestration/tests/test_exceptions.py +327 -0
- pygeai_orchestration/tests/test_handlers.py +307 -0
- pygeai_orchestration/tests/test_metrics.py +171 -0
- pygeai_orchestration/tests/test_patterns.py +165 -0
- pygeai_orchestration-0.1.0b2.dist-info/METADATA +290 -0
- pygeai_orchestration-0.1.0b2.dist-info/RECORD +61 -0
- pygeai_orchestration-0.1.0b2.dist-info/WHEEL +5 -0
- pygeai_orchestration-0.1.0b2.dist-info/entry_points.txt +2 -0
- pygeai_orchestration-0.1.0b2.dist-info/licenses/LICENSE +8 -0
- 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()
|