aisolate-agent 0.1.0__tar.gz
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.
- aisolate_agent-0.1.0/PKG-INFO +51 -0
- aisolate_agent-0.1.0/README.md +26 -0
- aisolate_agent-0.1.0/pyproject.toml +38 -0
- aisolate_agent-0.1.0/setup.cfg +4 -0
- aisolate_agent-0.1.0/src/aisolate/__init__.py +0 -0
- aisolate_agent-0.1.0/src/aisolate/agent/__init__.py +0 -0
- aisolate_agent-0.1.0/src/aisolate/agent/agent.py +516 -0
- aisolate_agent-0.1.0/src/aisolate/agent/config/__init__.py +5 -0
- aisolate_agent-0.1.0/src/aisolate/agent/config/agent_settings.py +39 -0
- aisolate_agent-0.1.0/src/aisolate/agent/config/prompts/__init__.py +0 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/__init__.py +16 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/context.py +451 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/events.py +103 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/exceptions.py +85 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/protocols.py +270 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/snapshot.py +69 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/tests/test_context_snapshots.py +238 -0
- aisolate_agent-0.1.0/src/aisolate/agent/core/tests/test_snapshot.py +217 -0
- aisolate_agent-0.1.0/src/aisolate/agent/logging/__init__.py +14 -0
- aisolate_agent-0.1.0/src/aisolate/agent/logging/config.py +83 -0
- aisolate_agent-0.1.0/src/aisolate/agent/logging/factory.py +141 -0
- aisolate_agent-0.1.0/src/aisolate/agent/logging/langfuse_integration.py +164 -0
- aisolate_agent-0.1.0/src/aisolate/agent/logging/unified_logger.py +695 -0
- aisolate_agent-0.1.0/src/aisolate/agent/main.py +203 -0
- aisolate_agent-0.1.0/src/aisolate/agent/memory/__init__.py +10 -0
- aisolate_agent-0.1.0/src/aisolate/agent/memory/models.py +66 -0
- aisolate_agent-0.1.0/src/aisolate/agent/states/__init__.py +17 -0
- aisolate_agent-0.1.0/src/aisolate/agent/states/base.py +157 -0
- aisolate_agent-0.1.0/src/aisolate/agent/states/error_recovery.py +63 -0
- aisolate_agent-0.1.0/src/aisolate/agent/states/executing.py +293 -0
- aisolate_agent-0.1.0/src/aisolate/agent/states/finished.py +42 -0
- aisolate_agent-0.1.0/src/aisolate/agent/states/planning.py +128 -0
- aisolate_agent-0.1.0/src/aisolate/agent/states/reflection.py +50 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/__init__.py +9 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/llm/__init__.py +9 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/llm/harmony.py +449 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/llm/oai.py +572 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/__init__.py +7 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/in_memory.py +159 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/tests/test_in_memory.py +273 -0
- aisolate_agent-0.1.0/src/aisolate/agent/strategies/memory/vector.py +35 -0
- aisolate_agent-0.1.0/src/aisolate/agent/telemetry/__init__.py +33 -0
- aisolate_agent-0.1.0/src/aisolate/agent/telemetry/decorators.py +375 -0
- aisolate_agent-0.1.0/src/aisolate/agent/telemetry/factory.py +87 -0
- aisolate_agent-0.1.0/src/aisolate/agent/telemetry/interface.py +103 -0
- aisolate_agent-0.1.0/src/aisolate/agent/telemetry/noop.py +80 -0
- aisolate_agent-0.1.0/src/aisolate/agent/telemetry/opentelemetry_impl.py +167 -0
- aisolate_agent-0.1.0/src/aisolate/agent/telemetry/utils.py +58 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/__init__.py +1 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/conftest.py +154 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_core/__init__.py +1 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_core/test_state_transitions.py +73 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_integration/__init__.py +1 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_integration/test_full_workflow.py +33 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_states/__init__.py +1 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_states/test_executing_metadata.py +384 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_states/test_planning_metadata.py +276 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_strategies/__init__.py +1 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_strategies/test_llm_providers.py +36 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_tools/__init__.py +1 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tests/test_tools/test_three_tier_tools.py +42 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/__init__.py +24 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/__init__.py +13 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/base.py +518 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/final_answer.py +248 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/visit_page_action.py +67 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/web_search_action.py +101 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/actions/wikipedia_search_action.py +149 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/base.py +57 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/execution.py +231 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/functions/__init__.py +11 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/functions/base.py +96 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/models.py +137 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/__init__.py +13 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/base.py +51 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/python_executor.py +273 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/subagent.py +205 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/platform/web_search.py +135 -0
- aisolate_agent-0.1.0/src/aisolate/agent/tools/routing.py +69 -0
- aisolate_agent-0.1.0/src/aisolate/agent/utils.py +97 -0
- aisolate_agent-0.1.0/src/aisolate/common/__init__.py +0 -0
- aisolate_agent-0.1.0/src/aisolate/common/codec.py +161 -0
- aisolate_agent-0.1.0/src/aisolate/common/errors.py +16 -0
- aisolate_agent-0.1.0/src/aisolate/common/models.py +74 -0
- aisolate_agent-0.1.0/src/aisolate/common/tests/__init__.py +0 -0
- aisolate_agent-0.1.0/src/aisolate/common/tests/fixtures.py +160 -0
- aisolate_agent-0.1.0/src/aisolate/common/tests/utils.py +117 -0
- aisolate_agent-0.1.0/src/aisolate_agent.egg-info/PKG-INFO +51 -0
- aisolate_agent-0.1.0/src/aisolate_agent.egg-info/SOURCES.txt +91 -0
- aisolate_agent-0.1.0/src/aisolate_agent.egg-info/dependency_links.txt +1 -0
- aisolate_agent-0.1.0/src/aisolate_agent.egg-info/entry_points.txt +2 -0
- aisolate_agent-0.1.0/src/aisolate_agent.egg-info/requires.txt +17 -0
- aisolate_agent-0.1.0/src/aisolate_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aisolate-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI Agent with intelligent planning and reasoning
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: openai-harmony>=0.0.8
|
|
9
|
+
Requires-Dist: openai>=1.0.0
|
|
10
|
+
Requires-Dist: jinja2>=3.1.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
12
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
13
|
+
Requires-Dist: aiofiles>=23.0.0
|
|
14
|
+
Requires-Dist: tiktoken>=0.5.0
|
|
15
|
+
Requires-Dist: structlog>=23.2.0
|
|
16
|
+
Requires-Dist: opentelemetry-api>=1.20.0
|
|
17
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation>=0.41b0
|
|
19
|
+
Requires-Dist: opentelemetry-exporter-otlp>=1.20.0
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.41b0
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-aiohttp-client>=0.41b0
|
|
22
|
+
Requires-Dist: langfuse>=2.0.0
|
|
23
|
+
Requires-Dist: rich>=13.0.0
|
|
24
|
+
Requires-Dist: wikipedia-api>=0.6.0
|
|
25
|
+
|
|
26
|
+
# aisolate-agent
|
|
27
|
+
|
|
28
|
+
AI Agent with intelligent planning and reasoning
|
|
29
|
+
|
|
30
|
+
## Local Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install path/to/aisolate_agent-0.1.0-py3-none-any.whl
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# Import the main module
|
|
40
|
+
import aisolate.agent
|
|
41
|
+
|
|
42
|
+
# Use the package...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
- Python 3.10+
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
Apache License 2.0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# aisolate-agent
|
|
2
|
+
|
|
3
|
+
AI Agent with intelligent planning and reasoning
|
|
4
|
+
|
|
5
|
+
## Local Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install path/to/aisolate_agent-0.1.0-py3-none-any.whl
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
# Import the main module
|
|
15
|
+
import aisolate.agent
|
|
16
|
+
|
|
17
|
+
# Use the package...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Python 3.10+
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
Apache License 2.0
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aisolate-agent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI Agent with intelligent planning and reasoning"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "Apache-2.0"}
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
"openai-harmony>=0.0.8",
|
|
15
|
+
"openai>=1.0.0",
|
|
16
|
+
"jinja2>=3.1.0",
|
|
17
|
+
"pydantic>=2.0.0",
|
|
18
|
+
"pydantic-settings>=2.0.0",
|
|
19
|
+
"aiofiles>=23.0.0",
|
|
20
|
+
"tiktoken>=0.5.0",
|
|
21
|
+
"structlog>=23.2.0",
|
|
22
|
+
"opentelemetry-api>=1.20.0",
|
|
23
|
+
"opentelemetry-sdk>=1.20.0",
|
|
24
|
+
"opentelemetry-instrumentation>=0.41b0",
|
|
25
|
+
"opentelemetry-exporter-otlp>=1.20.0",
|
|
26
|
+
"opentelemetry-instrumentation-httpx>=0.41b0",
|
|
27
|
+
"opentelemetry-instrumentation-aiohttp-client>=0.41b0",
|
|
28
|
+
"langfuse>=2.0.0",
|
|
29
|
+
"rich>=13.0.0",
|
|
30
|
+
"wikipedia-api>=0.6.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where = ["src"]
|
|
35
|
+
include = ["aisolate*"]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
aisolate-agent = "aisolate.agent.main:main"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""Main Agent class - public API for the three-tier tool architecture."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any, List, Iterable, TYPE_CHECKING
|
|
4
|
+
import time
|
|
5
|
+
import traceback
|
|
6
|
+
|
|
7
|
+
from .config.agent_settings import AgentSettings
|
|
8
|
+
from .core.context import AgentContext
|
|
9
|
+
from .core.protocols import LLMStrategy, MemoryStrategy
|
|
10
|
+
from .core.snapshot import AgentSnapshot
|
|
11
|
+
from .core.events import (
|
|
12
|
+
AgentStartedEvent,
|
|
13
|
+
TurnCompletedEvent,
|
|
14
|
+
AgentFinishedEvent,
|
|
15
|
+
AgentErrorEvent,
|
|
16
|
+
)
|
|
17
|
+
from openai_harmony import Author, Message, Role, TextContent
|
|
18
|
+
from .states.planning import PlanningState
|
|
19
|
+
from .utils import get_logger, setup_logging
|
|
20
|
+
from .logging import LoggingConfig
|
|
21
|
+
|
|
22
|
+
from .tools.platform.subagent import SubAgentTool
|
|
23
|
+
from .tools.platform.python_executor import PythonExecutorTool
|
|
24
|
+
from .tools.actions.final_answer import FinalAnswerAction
|
|
25
|
+
from .tools.actions.base import validate_action
|
|
26
|
+
|
|
27
|
+
from aisolate.client.sandbox import Sandbox
|
|
28
|
+
|
|
29
|
+
from .tools.models import FinalAnswerResult
|
|
30
|
+
|
|
31
|
+
logger = get_logger("aisolate.agent")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Agent:
|
|
35
|
+
"""Main agent class that orchestrates the three-tier tool architecture.
|
|
36
|
+
|
|
37
|
+
This class provides the public API for creating and running agents with
|
|
38
|
+
different strategies for LLM interaction, memory and tool execution.
|
|
39
|
+
|
|
40
|
+
Example usage:
|
|
41
|
+
```python
|
|
42
|
+
# Create agent with pre-configured strategies
|
|
43
|
+
harmony_llm = HarmonyLLMStrategy(base_url="https://api.harmony.ai/v1")
|
|
44
|
+
in_memory = InMemoryStrategy()
|
|
45
|
+
|
|
46
|
+
async with Sandbox() as sandbox:
|
|
47
|
+
agent = Agent(
|
|
48
|
+
llm_strategy=harmony_llm,
|
|
49
|
+
memory_strategy=in_memory,
|
|
50
|
+
sandbox=sandbox,
|
|
51
|
+
settings=AgentSettings(max_turns=20),
|
|
52
|
+
actions=[WebSearchAction()]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
result = await agent.run("What is the weather in San Francisco?")
|
|
56
|
+
print(result)
|
|
57
|
+
```
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
name: str,
|
|
63
|
+
description: str,
|
|
64
|
+
llm_strategy: LLMStrategy,
|
|
65
|
+
memory_strategy: MemoryStrategy,
|
|
66
|
+
sandbox: Sandbox,
|
|
67
|
+
settings: Optional[AgentSettings] = None,
|
|
68
|
+
managed_agents: Optional[List["Agent"]] = None,
|
|
69
|
+
builtin_tools: Optional[Iterable[Any]] = None,
|
|
70
|
+
function_tools: Optional[Iterable[Any]] = None,
|
|
71
|
+
actions: Optional[List[Any]] = None,
|
|
72
|
+
):
|
|
73
|
+
"""Initialize the Agent with configured strategies.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
llm_strategy: Pre-configured LLM strategy implementation
|
|
77
|
+
memory_strategy: Pre-configured memory strategy implementation
|
|
78
|
+
sandbox: Active sandbox instance for code execution
|
|
79
|
+
settings: Agent-specific configuration settings
|
|
80
|
+
builtin_tools: Optional list of builtin platform tools
|
|
81
|
+
function_tools: Optional list of function tools
|
|
82
|
+
actions: List of action instances
|
|
83
|
+
"""
|
|
84
|
+
self.name = name
|
|
85
|
+
self.description = description
|
|
86
|
+
self.llm_strategy = llm_strategy
|
|
87
|
+
self.memory_strategy = memory_strategy
|
|
88
|
+
self.sandbox = sandbox
|
|
89
|
+
self.settings = settings or AgentSettings()
|
|
90
|
+
|
|
91
|
+
# --- Defaults & override merging ---
|
|
92
|
+
def _default_builtin() -> list[Any]:
|
|
93
|
+
python_tool = PythonExecutorTool()
|
|
94
|
+
# Apply strategy-specific tool name override for reserved names
|
|
95
|
+
python_tool.name = self.llm_strategy.get_tool_name("python")
|
|
96
|
+
return [python_tool]
|
|
97
|
+
|
|
98
|
+
if managed_agents is None:
|
|
99
|
+
self.sub_agent_tools = {}
|
|
100
|
+
else:
|
|
101
|
+
self.sub_agent_tools = {}
|
|
102
|
+
for agent in managed_agents:
|
|
103
|
+
# Pass namespace config from parent's LLM strategy (Solution 1)
|
|
104
|
+
sa = SubAgentTool(
|
|
105
|
+
agent=agent,
|
|
106
|
+
namespace_config=self.llm_strategy.namespace_config,
|
|
107
|
+
)
|
|
108
|
+
self.sub_agent_tools[sa.name] = sa
|
|
109
|
+
|
|
110
|
+
if builtin_tools is None:
|
|
111
|
+
self.builtin_tools = _default_builtin()
|
|
112
|
+
else:
|
|
113
|
+
# Merge: user overrides by name and apply strategy-specific name overrides
|
|
114
|
+
base = {t.name: t for t in _default_builtin()}
|
|
115
|
+
for t in builtin_tools:
|
|
116
|
+
# Apply strategy-specific name override
|
|
117
|
+
t.name = self.llm_strategy.get_tool_name(t.name)
|
|
118
|
+
base[t.name] = t
|
|
119
|
+
self.builtin_tools = list(base.values())
|
|
120
|
+
|
|
121
|
+
self.function_tools = list(function_tools or [])
|
|
122
|
+
|
|
123
|
+
# Actions: ensure final_answer exists; convert list to dict using action.name
|
|
124
|
+
base_actions: Dict[str, Any] = {"final_answer": FinalAnswerAction()}
|
|
125
|
+
|
|
126
|
+
if actions:
|
|
127
|
+
# Convert list of actions to dict using action.name as key
|
|
128
|
+
for action in actions:
|
|
129
|
+
if hasattr(action, "name") and action.name:
|
|
130
|
+
base_actions[action.name] = action
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
f"Action {action.__class__.__name__} must have a valid 'name' attribute"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Validate all actions during agent initialization
|
|
137
|
+
validated_actions = {}
|
|
138
|
+
for name, action in base_actions.items():
|
|
139
|
+
try:
|
|
140
|
+
validate_action(action)
|
|
141
|
+
logger.info("Validated action", action_name=name)
|
|
142
|
+
validated_actions[name] = action
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error("Action validation failed", action_name=name, error=str(e))
|
|
145
|
+
raise ValueError(f"Invalid action '{name}': {e}") from e
|
|
146
|
+
|
|
147
|
+
self.actions = validated_actions
|
|
148
|
+
|
|
149
|
+
async def run(self, task: str) -> "FinalAnswerResult":
|
|
150
|
+
"""Run the agent on a single task and return the final answer.
|
|
151
|
+
|
|
152
|
+
This is a convenience wrapper around run_streaming() that extracts
|
|
153
|
+
just the final result. Creates a new conversation with a generated UUID.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
task: The task description for the agent to accomplish
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
FinalAnswerResult containing answer text and optional metadata (sources, confidence)
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
RuntimeError: If agent is already running or no final answer provided
|
|
163
|
+
ValueError: If task is empty
|
|
164
|
+
"""
|
|
165
|
+
import uuid
|
|
166
|
+
|
|
167
|
+
final_answer: Optional[FinalAnswerResult] = None
|
|
168
|
+
|
|
169
|
+
# Generate new conversation_id for this execution
|
|
170
|
+
conversation_id = str(uuid.uuid4())
|
|
171
|
+
|
|
172
|
+
async for update in self.run_streaming(task, conversation_id):
|
|
173
|
+
if hasattr(update, "type") and update.type == "agent_finished":
|
|
174
|
+
final_answer = getattr(update, "final_answer", None)
|
|
175
|
+
if final_answer is not None:
|
|
176
|
+
return final_answer
|
|
177
|
+
elif hasattr(update, "type") and update.type == "agent_error":
|
|
178
|
+
# Always raise exceptions in the synchronous run() method
|
|
179
|
+
error_msg = getattr(update, "error", "Unknown error")
|
|
180
|
+
raise RuntimeError(f"Agent execution failed: {error_msg}")
|
|
181
|
+
|
|
182
|
+
# If we get here, no final answer was provided
|
|
183
|
+
raise RuntimeError("Agent completed without providing a final answer")
|
|
184
|
+
|
|
185
|
+
async def run_streaming(self, task: str, conversation_id: str):
|
|
186
|
+
"""Run the agent with streaming updates.
|
|
187
|
+
|
|
188
|
+
Creates a fresh context-bound sandbox for each execution.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
task: The task description for the agent to accomplish
|
|
192
|
+
conversation_id: Conversation UUID (required)
|
|
193
|
+
- New conversation: caller generates new UUID
|
|
194
|
+
- Existing conversation: caller provides existing UUID
|
|
195
|
+
|
|
196
|
+
Yields:
|
|
197
|
+
Dict updates about agent state, tool calls, and progress
|
|
198
|
+
"""
|
|
199
|
+
# Add structured logging context
|
|
200
|
+
bound_logger = logger.bind(
|
|
201
|
+
task=task,
|
|
202
|
+
agent_id=id(self),
|
|
203
|
+
llm_strategy=type(self.llm_strategy).__name__,
|
|
204
|
+
memory_strategy=type(self.memory_strategy).__name__,
|
|
205
|
+
conversation_id=conversation_id,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
bound_logger.info("Starting agent execution")
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
# Use new telemetry system for tracing
|
|
212
|
+
from .telemetry import get_tracer
|
|
213
|
+
|
|
214
|
+
tracer = get_tracer()
|
|
215
|
+
|
|
216
|
+
with tracer.span("agent.session") as session_span: # type: ignore
|
|
217
|
+
session_span.set_attribute("agent.task", task)
|
|
218
|
+
session_span.set_attribute(
|
|
219
|
+
"agent.llm_strategy", type(self.llm_strategy).__name__
|
|
220
|
+
)
|
|
221
|
+
session_span.set_attribute(
|
|
222
|
+
"agent.memory_strategy", type(self.memory_strategy).__name__
|
|
223
|
+
)
|
|
224
|
+
session_span.set_attribute("agent.max_turns", self.settings.max_turns)
|
|
225
|
+
|
|
226
|
+
# Create fresh context-bound sandbox for this execution
|
|
227
|
+
sandbox_ctx = await self.sandbox.acreate_context_bound_sandbox()
|
|
228
|
+
|
|
229
|
+
# Create fresh AgentContext for this execution
|
|
230
|
+
context = AgentContext(
|
|
231
|
+
agent_name=self.name,
|
|
232
|
+
task=task,
|
|
233
|
+
llm=self.llm_strategy,
|
|
234
|
+
memory=self.memory_strategy,
|
|
235
|
+
sandbox=sandbox_ctx,
|
|
236
|
+
max_turns=self.settings.max_turns,
|
|
237
|
+
conversation_id=conversation_id,
|
|
238
|
+
sub_agent_tools=self.sub_agent_tools,
|
|
239
|
+
actions=self.actions,
|
|
240
|
+
builtin_tools=self.builtin_tools,
|
|
241
|
+
function_tools=self.function_tools,
|
|
242
|
+
parent_context=None, # Top-level agent
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# RESTORATION LOGIC - Load and apply snapshot if it exists
|
|
246
|
+
snapshot = await context.memory.load_latest_snapshot(conversation_id)
|
|
247
|
+
if snapshot:
|
|
248
|
+
self._restore_context_from_snapshot(context, snapshot)
|
|
249
|
+
bound_logger.info(
|
|
250
|
+
"Restored from snapshot",
|
|
251
|
+
turn=snapshot.turn,
|
|
252
|
+
messages_count=len(snapshot.conversation_history),
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
bound_logger.debug("No snapshot found, starting fresh")
|
|
256
|
+
|
|
257
|
+
# Execute with prepared context
|
|
258
|
+
async for result in self._execute_with_context(
|
|
259
|
+
context, bound_logger, tracer
|
|
260
|
+
):
|
|
261
|
+
yield result
|
|
262
|
+
except Exception as e:
|
|
263
|
+
import traceback
|
|
264
|
+
|
|
265
|
+
# Log with full stack trace
|
|
266
|
+
bound_logger.error(
|
|
267
|
+
"Agent execution failed (outer catch)",
|
|
268
|
+
error=str(e),
|
|
269
|
+
exception=type(e).__name__,
|
|
270
|
+
traceback=traceback.format_exc(),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Also use structured exception logging
|
|
274
|
+
bound_logger.log_exception(e, "agent_execution_outer")
|
|
275
|
+
|
|
276
|
+
yield AgentErrorEvent(
|
|
277
|
+
error=str(e),
|
|
278
|
+
exception_type=type(e).__name__,
|
|
279
|
+
traceback=(
|
|
280
|
+
traceback.format_exc() if self.settings.debug_mode else None
|
|
281
|
+
),
|
|
282
|
+
turn=0,
|
|
283
|
+
)
|
|
284
|
+
if self.settings.debug_mode:
|
|
285
|
+
raise
|
|
286
|
+
|
|
287
|
+
def _restore_context_from_snapshot(
|
|
288
|
+
self, context: AgentContext, snapshot: "AgentSnapshot"
|
|
289
|
+
) -> None:
|
|
290
|
+
"""Restore context from snapshot with smart state handling.
|
|
291
|
+
|
|
292
|
+
- If snapshot.finished = True: Reset to Planning (new task on completed conversation)
|
|
293
|
+
- If snapshot.finished = False: Preserve turn for resumption
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
context: Fresh context to restore into
|
|
297
|
+
snapshot: Snapshot with saved state
|
|
298
|
+
"""
|
|
299
|
+
from openai_harmony import Message
|
|
300
|
+
from .states.planning import PlanningState
|
|
301
|
+
|
|
302
|
+
# Restore conversation history (using from_dict because messages are serialized with to_dict())
|
|
303
|
+
messages = [
|
|
304
|
+
Message.from_dict(msg_data) for msg_data in snapshot.conversation_history
|
|
305
|
+
]
|
|
306
|
+
context.set_conversation_history(messages)
|
|
307
|
+
|
|
308
|
+
# Restore execution state
|
|
309
|
+
context.execution.turn = snapshot.turn
|
|
310
|
+
|
|
311
|
+
# Always reset execution.finished to allow continuation
|
|
312
|
+
context.execution.finished = False
|
|
313
|
+
|
|
314
|
+
# Smart state restoration: reset to Planning if conversation was completed
|
|
315
|
+
if snapshot.finished:
|
|
316
|
+
# Completed conversation: start fresh with new planning
|
|
317
|
+
context.transition_to(PlanningState())
|
|
318
|
+
logger.info(
|
|
319
|
+
"Restored completed conversation, reset to Planning",
|
|
320
|
+
turn=snapshot.turn,
|
|
321
|
+
conversation_id=snapshot.conversation_id,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Restore final answer if present (for context/display)
|
|
325
|
+
# if snapshot.final_answer:
|
|
326
|
+
# from .tools.models import FinalAnswerResult
|
|
327
|
+
|
|
328
|
+
# context._final_answer = FinalAnswerResult(**snapshot.final_answer)
|
|
329
|
+
|
|
330
|
+
logger.debug(
|
|
331
|
+
"Context restored from snapshot",
|
|
332
|
+
turn=snapshot.turn,
|
|
333
|
+
messages_count=len(messages),
|
|
334
|
+
had_final_answer=bool(snapshot.final_answer),
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
async def _execute_with_context(self, context: AgentContext, bound_logger, tracer):
|
|
338
|
+
"""Execute agent with given context.
|
|
339
|
+
|
|
340
|
+
Context is assumed to be ready (fresh or restored).
|
|
341
|
+
NO restoration logic here - that happens in run_streaming().
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
context: Fresh AgentContext with isolated sandbox (possibly restored)
|
|
345
|
+
bound_logger: Logger with bound context
|
|
346
|
+
tracer: Telemetry tracer
|
|
347
|
+
"""
|
|
348
|
+
start_time = time.time()
|
|
349
|
+
try:
|
|
350
|
+
# Initialize remote python executor with actions
|
|
351
|
+
await context.remote_python_executor.send_actions(self.actions)
|
|
352
|
+
|
|
353
|
+
# Initialize conversation history with LLM strategy (only if not restored)
|
|
354
|
+
# if not context.get_conversation_messages():
|
|
355
|
+
initial_messages = self.llm_strategy.prepare_initial_history(context)
|
|
356
|
+
context.initialize_conversation(initial_messages)
|
|
357
|
+
# else:
|
|
358
|
+
# context.initialize_conversation(
|
|
359
|
+
# [Message.from_role_and_content(Role.USER, context.task)]
|
|
360
|
+
# )
|
|
361
|
+
|
|
362
|
+
# Set initial state (only if not restored)
|
|
363
|
+
if context.execution.current_state is None:
|
|
364
|
+
context.transition_to(PlanningState())
|
|
365
|
+
|
|
366
|
+
yield AgentStartedEvent(
|
|
367
|
+
task=context.task,
|
|
368
|
+
turn=context.turn,
|
|
369
|
+
max_turns=self.settings.max_turns,
|
|
370
|
+
agent_name=self.name,
|
|
371
|
+
conversation_id=context.conversation_id,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Execute agent turns until completion
|
|
375
|
+
while not context.is_finished:
|
|
376
|
+
# Create tracing span for individual turn (always - no conditional logic)
|
|
377
|
+
with tracer.span(f"agent.turn.{context.turn}") as turn_span: # type: ignore
|
|
378
|
+
turn_span.set_attribute("agent.turn", context.turn)
|
|
379
|
+
turn_span.set_attribute(
|
|
380
|
+
"agent.state", type(context._state).__name__
|
|
381
|
+
)
|
|
382
|
+
turn_span.set_attribute("agent.task", context.task)
|
|
383
|
+
|
|
384
|
+
turn_result = await context.execute_turn()
|
|
385
|
+
|
|
386
|
+
# Create TurnCompletedEvent with runtime metadata
|
|
387
|
+
turn_event = TurnCompletedEvent(
|
|
388
|
+
turn=context.turn,
|
|
389
|
+
state=turn_result.state,
|
|
390
|
+
is_finished=context.is_finished,
|
|
391
|
+
final_answer=context.final_answer,
|
|
392
|
+
state_metadata=turn_result.state_metadata,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Persist snapshot if conversation_id exists
|
|
396
|
+
if context.conversation_id:
|
|
397
|
+
snapshot = context.create_snapshot()
|
|
398
|
+
snapshot.add_event(
|
|
399
|
+
turn_event
|
|
400
|
+
) # Store event with runtime metadata
|
|
401
|
+
await context.memory.save_snapshot(snapshot)
|
|
402
|
+
|
|
403
|
+
turn_span.set_attribute("turn.completed", True)
|
|
404
|
+
turn_span.set_attribute(
|
|
405
|
+
"turn.final_answer_provided",
|
|
406
|
+
bool(context.final_answer),
|
|
407
|
+
)
|
|
408
|
+
turn_span.set_status("OK")
|
|
409
|
+
|
|
410
|
+
# Yield event for streaming
|
|
411
|
+
yield turn_event
|
|
412
|
+
|
|
413
|
+
if context.turn >= self.settings.max_turns:
|
|
414
|
+
bound_logger.warning(
|
|
415
|
+
"Reached max turns", max_turns=self.settings.max_turns
|
|
416
|
+
)
|
|
417
|
+
break
|
|
418
|
+
|
|
419
|
+
# Log completion with proper FinalAnswerResult handling
|
|
420
|
+
if context.final_answer:
|
|
421
|
+
bound_logger.info(
|
|
422
|
+
"Agent execution completed",
|
|
423
|
+
total_turns=context.turn,
|
|
424
|
+
final_answer_length=len(context.final_answer.answer),
|
|
425
|
+
has_sources=context.final_answer.sources is not None,
|
|
426
|
+
has_confidence=context.final_answer.confidence is not None,
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
bound_logger.info(
|
|
430
|
+
"Agent execution completed",
|
|
431
|
+
total_turns=context.turn,
|
|
432
|
+
final_answer=None,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
yield AgentFinishedEvent(
|
|
436
|
+
final_answer=context.final_answer,
|
|
437
|
+
total_turns=context.turn,
|
|
438
|
+
execution_time=time.time() - start_time,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
except Exception as e:
|
|
442
|
+
import traceback
|
|
443
|
+
|
|
444
|
+
# Log with full stack trace
|
|
445
|
+
bound_logger.error(
|
|
446
|
+
"Agent execution failed",
|
|
447
|
+
error=str(e),
|
|
448
|
+
exception=type(e).__name__,
|
|
449
|
+
traceback=traceback.format_exc(),
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Also log the exception for structured logging
|
|
453
|
+
bound_logger.log_exception(e, "agent_execution")
|
|
454
|
+
yield AgentErrorEvent(
|
|
455
|
+
error=str(e),
|
|
456
|
+
turn=context.turn if context else 0,
|
|
457
|
+
exception_type=type(e).__name__,
|
|
458
|
+
traceback=traceback.format_exc() if self.settings.debug_mode else None,
|
|
459
|
+
)
|
|
460
|
+
if self.settings.debug_mode:
|
|
461
|
+
raise
|
|
462
|
+
|
|
463
|
+
def get_conversation_history(self) -> List[Any]:
|
|
464
|
+
"""Get the current conversation history.
|
|
465
|
+
|
|
466
|
+
Note: This method is deprecated as Agent no longer maintains instance state.
|
|
467
|
+
Context is created fresh for each execution.
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Empty list (no persistent context)
|
|
471
|
+
"""
|
|
472
|
+
return []
|
|
473
|
+
|
|
474
|
+
def get_memory(self) -> List[Dict[str, Any]]:
|
|
475
|
+
"""Get the agent's long-term memory.
|
|
476
|
+
|
|
477
|
+
Note: This method is deprecated as Agent no longer maintains instance state.
|
|
478
|
+
Context is created fresh for each execution.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Empty list (no persistent context)
|
|
482
|
+
"""
|
|
483
|
+
return []
|
|
484
|
+
|
|
485
|
+
def is_running(self) -> bool:
|
|
486
|
+
"""Check if the agent is currently running.
|
|
487
|
+
|
|
488
|
+
Note: This method is deprecated as Agent no longer tracks running state.
|
|
489
|
+
Each execution creates its own context.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
False (no persistent running state)
|
|
493
|
+
"""
|
|
494
|
+
return False
|
|
495
|
+
|
|
496
|
+
def get_current_state(self) -> Optional[str]:
|
|
497
|
+
"""Get the name of the current agent state.
|
|
498
|
+
|
|
499
|
+
Note: This method is deprecated as Agent no longer maintains instance state.
|
|
500
|
+
Context is created fresh for each execution.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
None (no persistent context)
|
|
504
|
+
"""
|
|
505
|
+
return None
|
|
506
|
+
|
|
507
|
+
def get_turn_count(self) -> int:
|
|
508
|
+
"""Get the current turn count.
|
|
509
|
+
|
|
510
|
+
Note: This method is deprecated as Agent no longer maintains instance state.
|
|
511
|
+
Context is created fresh for each execution.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
0 (no persistent context)
|
|
515
|
+
"""
|
|
516
|
+
return 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Agent configuration settings."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Optional
|
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AgentSettings(BaseSettings):
|
|
9
|
+
"""Core agent-specific configuration only.
|
|
10
|
+
|
|
11
|
+
This class focuses solely on agent behaviors rather than strategy configuration.
|
|
12
|
+
Strategies are configured directly when instantiated.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
max_turns: int = Field(
|
|
16
|
+
default=50, description="Maximum number of execution turns before agent stops"
|
|
17
|
+
)
|
|
18
|
+
reflection_turns: int = Field(
|
|
19
|
+
default=5, description="Maximum number of execution turns before agent reflects"
|
|
20
|
+
)
|
|
21
|
+
enable_error_recovery: bool = Field(
|
|
22
|
+
default=True, description="Whether to enable automatic error recovery"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
max_recovery_attempts: int = Field(
|
|
26
|
+
default=3,
|
|
27
|
+
description="Maximum number of recovery attempts for failed operations",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
conversation_history_max_length: Optional[int] = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Maximum number of messages to keep in conversation history",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
debug_mode: bool = Field(
|
|
36
|
+
default=False, description="Enable debug logging and additional validation"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
model_config = SettingsConfigDict(env_prefix="AGENT_", case_sensitive=False)
|
|
File without changes
|