acra-cli 0.1.1__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.
- acra/__init__.py +7 -0
- acra/__main__.py +4 -0
- acra/agents/__init__.py +0 -0
- acra/agents/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/__pycache__/llm.cpython-312.pyc +0 -0
- acra/agents/__pycache__/llm_utils.cpython-312.pyc +0 -0
- acra/agents/coder/__init__.py +0 -0
- acra/agents/coder/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/coder/__pycache__/coder_agent.cpython-312.pyc +0 -0
- acra/agents/coder/__pycache__/coder_chain.cpython-312.pyc +0 -0
- acra/agents/coder/coder_agent.py +235 -0
- acra/agents/coder/coder_chain.py +204 -0
- acra/agents/critic/__init__.py +0 -0
- acra/agents/critic/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/critic/__pycache__/critic_agent.cpython-312.pyc +0 -0
- acra/agents/critic/__pycache__/critic_chain.cpython-312.pyc +0 -0
- acra/agents/critic/critic_agent.py +199 -0
- acra/agents/critic/critic_chain.py +268 -0
- acra/agents/executor/__init__.py +0 -0
- acra/agents/executor/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/docker_runner.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/executor_agent.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/sandbox_runner.cpython-312.pyc +0 -0
- acra/agents/executor/docker_runner.py +114 -0
- acra/agents/executor/executor_agent.py +142 -0
- acra/agents/executor/sandbox_runner.py +144 -0
- acra/agents/human/__init__.py +0 -0
- acra/agents/human/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/human/__pycache__/human_node.cpython-312.pyc +0 -0
- acra/agents/human/human_node.py +37 -0
- acra/agents/llm.py +263 -0
- acra/agents/llm_utils.py +161 -0
- acra/agents/memory/__init__.py +0 -0
- acra/agents/memory/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/memory_agent.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/memory_manager.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/retrieval.cpython-312.pyc +0 -0
- acra/agents/memory/memory_agent.py +239 -0
- acra/agents/memory/memory_manager.py +273 -0
- acra/agents/memory/retrieval.py +355 -0
- acra/agents/planner/__init__.py +0 -0
- acra/agents/planner/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/planner/__pycache__/planner_agent.cpython-312.pyc +0 -0
- acra/agents/planner/__pycache__/planner_chain.cpython-312.pyc +0 -0
- acra/agents/planner/planner_agent.py +97 -0
- acra/agents/planner/planner_chain.py +160 -0
- acra/agents/researcher/__init__.py +1 -0
- acra/agents/researcher/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/researcher/__pycache__/researcher_agent.cpython-312.pyc +0 -0
- acra/agents/researcher/__pycache__/researcher_chain.cpython-312.pyc +0 -0
- acra/agents/researcher/researcher_agent.py +231 -0
- acra/agents/researcher/researcher_chain.py +193 -0
- acra/brain/__init__.py +6 -0
- acra/brain/model_registry.py +19 -0
- acra/brain/provider_manager.py +21 -0
- acra/cli.py +52 -0
- acra/commands/__init__.py +29 -0
- acra/commands/ask.py +49 -0
- acra/commands/brain.py +58 -0
- acra/commands/config.py +25 -0
- acra/commands/context.py +30 -0
- acra/commands/graph.py +18 -0
- acra/commands/keys.py +40 -0
- acra/commands/logs.py +13 -0
- acra/commands/memory.py +27 -0
- acra/commands/plugin.py +18 -0
- acra/commands/research.py +65 -0
- acra/commands/serve.py +13 -0
- acra/commands/session.py +20 -0
- acra/commands/utils.py +12 -0
- acra/config/__init__.py +36 -0
- acra/config/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/config/__pycache__/defaults.cpython-312.pyc +0 -0
- acra/config/__pycache__/profile_manager.cpython-312.pyc +0 -0
- acra/config/defaults.py +88 -0
- acra/config/profile_manager.py +87 -0
- acra/execution/__init__.py +1 -0
- acra/execution/logs/__init__.py +1 -0
- acra/execution/logs/error_logs/__init__.py +1 -0
- acra/execution/logs/execution_logs/__init__.py +1 -0
- acra/execution/sandbox/__init__.py +1 -0
- acra/execution/sandbox/docker_executor.py +209 -0
- acra/execution/sandbox/isolated_runner.py +148 -0
- acra/execution/sandbox/sandbox_manager.py +178 -0
- acra/graph/__init__.py +0 -0
- acra/graph/checkpoint.py +85 -0
- acra/graph/conditional_edges.py +103 -0
- acra/graph/edges.py +85 -0
- acra/graph/graph_visualizer.py +175 -0
- acra/graph/nodes.py +68 -0
- acra/graph/router.py +69 -0
- acra/graph/state.py +93 -0
- acra/graph/workflow.py +117 -0
- acra/memory/__init__.py +1 -0
- acra/memory/checkpoints/__init__.py +1 -0
- acra/memory/checkpoints/data/__init__.py +1 -0
- acra/memory/checkpoints/postgres_checkpoint.py +247 -0
- acra/memory/checkpoints/sqlite_checkpoint.py +309 -0
- acra/memory/chroma_db/__init__.py +1 -0
- acra/memory/conversation_memory/__init__.py +1 -0
- acra/memory/conversation_memory/long_term.py +229 -0
- acra/memory/conversation_memory/short_term.py +158 -0
- acra/memory/storage/__init__.py +1 -0
- acra/memory/vector_store/__init__.py +0 -0
- acra/memory/vector_store/chroma_store.py +260 -0
- acra/memory/vector_store/embeddings.py +43 -0
- acra/observability/__init__.py +1 -0
- acra/observability/langsmith_tracing.py +91 -0
- acra/observability/metrics.py +189 -0
- acra/observability/monitoring.py +162 -0
- acra/observability/token_tracking.py +131 -0
- acra/prompts/__init__.py +1 -0
- acra/schemas/__init__.py +0 -0
- acra/schemas/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/coder_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/critic_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/execution_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/planner_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/researcher_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/shared_schema.cpython-312.pyc +0 -0
- acra/schemas/coder_schema.py +257 -0
- acra/schemas/critic_schema.py +177 -0
- acra/schemas/execution_schema.py +74 -0
- acra/schemas/planner_schema.py +129 -0
- acra/schemas/researcher_schema.py +220 -0
- acra/schemas/shared_schema.py +329 -0
- acra/tools/__init__.py +1 -0
- acra/tools/code_tools/__init__.py +1 -0
- acra/tools/code_tools/file_reader.py +156 -0
- acra/tools/code_tools/file_writer.py +108 -0
- acra/tools/code_tools/python_repl.py +72 -0
- acra/tools/code_tools/terminal_runner.py +98 -0
- acra/tools/github_tools/__init__.py +1 -0
- acra/tools/github_tools/commit_generator.py +91 -0
- acra/tools/github_tools/github_search.py +113 -0
- acra/tools/github_tools/repo_analyzer.py +138 -0
- acra/tools/integration_test_helper.py +370 -0
- acra/tools/validation_tools/__init__.py +1 -0
- acra/tools/validation_tools/dependency_checker.py +101 -0
- acra/tools/validation_tools/environment_config_validator.py +353 -0
- acra/tools/validation_tools/http_validator.py +336 -0
- acra/tools/validation_tools/response_validator.py +407 -0
- acra/tools/validation_tools/security_checker.py +93 -0
- acra/tools/validation_tools/syntax_checker.py +75 -0
- acra/tools/validation_tools/web_framework_validator.py +382 -0
- acra/tools/web_tools/__init__.py +1 -0
- acra/tools/web_tools/arxiv_search.py +110 -0
- acra/tools/web_tools/serpapi_search.py +106 -0
- acra/tools/web_tools/tavily_search.py +96 -0
- acra/ui/__init__.py +8 -0
- acra/ui/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/ui/__pycache__/banner.cpython-312.pyc +0 -0
- acra/ui/__pycache__/components.cpython-312.pyc +0 -0
- acra/ui/__pycache__/shell.cpython-312.pyc +0 -0
- acra/ui/__pycache__/theme.cpython-312.pyc +0 -0
- acra/ui/banner.py +40 -0
- acra/ui/components.py +32 -0
- acra/ui/shell.py +91 -0
- acra/ui/theme.py +28 -0
- acra/utils/__init__.py +21 -0
- acra/utils/context_manager.py +20 -0
- acra/utils/keyring_manager.py +83 -0
- acra/utils/output_formatter.py +18 -0
- acra/utils/session_manager.py +59 -0
- acra_cli-0.1.1.dist-info/METADATA +616 -0
- acra_cli-0.1.1.dist-info/RECORD +169 -0
- acra_cli-0.1.1.dist-info/WHEEL +5 -0
- acra_cli-0.1.1.dist-info/licenses/LICENSE +661 -0
- acra_cli-0.1.1.dist-info/top_level.txt +1 -0
acra/graph/workflow.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from langgraph.graph import (
|
|
2
|
+
StateGraph,
|
|
3
|
+
START,
|
|
4
|
+
END
|
|
5
|
+
)
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from acra.graph.state import AgentState
|
|
9
|
+
|
|
10
|
+
from acra.graph.nodes import (
|
|
11
|
+
NODE_REGISTRY
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from acra.graph.conditional_edges import (
|
|
15
|
+
register_conditional_edges
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from acra.graph.checkpoint import GraphCheckpointManager
|
|
19
|
+
from acra.observability.metrics import metrics_tracker
|
|
20
|
+
from acra.observability.monitoring import system_monitor
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OmniAgentCallbacks:
|
|
24
|
+
"""LangGraph callbacks for metrics and monitoring."""
|
|
25
|
+
|
|
26
|
+
raise_error = False
|
|
27
|
+
ignore_chain = False
|
|
28
|
+
ignore_agent = False
|
|
29
|
+
ignore_llm = False
|
|
30
|
+
ignore_chat_model = False
|
|
31
|
+
|
|
32
|
+
def on_chain_start(self, serialized, inputs, **kwargs):
|
|
33
|
+
try:
|
|
34
|
+
self._start = time.time()
|
|
35
|
+
agent = (serialized or {}).get("name", "unknown") if isinstance(serialized, dict) else "unknown"
|
|
36
|
+
system_monitor.info(agent, f"Agent started: {agent}")
|
|
37
|
+
metrics_tracker.record_agent_execution(agent)
|
|
38
|
+
except Exception:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
def on_chain_end(self, outputs, **kwargs):
|
|
42
|
+
try:
|
|
43
|
+
elapsed = time.time() - getattr(self, "_start", time.time())
|
|
44
|
+
system_monitor.info("workflow", f"Step completed in {elapsed:.2f}s")
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
def on_chain_error(self, error, **kwargs):
|
|
49
|
+
try:
|
|
50
|
+
system_monitor.error("workflow", str(error))
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
def on_chat_model_start(self, serialized, messages, **kwargs):
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def on_llm_end(self, response, **kwargs):
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def on_llm_error(self, error, **kwargs):
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Initialize checkpoint manager
|
|
65
|
+
graph_checkpoint = GraphCheckpointManager()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# create workflow graph
|
|
69
|
+
|
|
70
|
+
def create_workflow():
|
|
71
|
+
"""
|
|
72
|
+
Build OMNIAGENT workflow graph.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
workflow = StateGraph(
|
|
76
|
+
AgentState
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# register nodes
|
|
80
|
+
|
|
81
|
+
for (
|
|
82
|
+
node_name,
|
|
83
|
+
node_function
|
|
84
|
+
) in NODE_REGISTRY.items():
|
|
85
|
+
|
|
86
|
+
workflow.add_node(
|
|
87
|
+
node_name,
|
|
88
|
+
node_function
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# entry point
|
|
92
|
+
|
|
93
|
+
workflow.add_edge(
|
|
94
|
+
START,
|
|
95
|
+
"planner"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# conditional routing
|
|
99
|
+
|
|
100
|
+
workflow = (
|
|
101
|
+
register_conditional_edges(
|
|
102
|
+
workflow
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# compile graph with checkpointer
|
|
107
|
+
|
|
108
|
+
graph = workflow.compile(
|
|
109
|
+
checkpointer=graph_checkpoint.checkpointer
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return graph
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# singleton workflow instance
|
|
116
|
+
|
|
117
|
+
omniagent_graph = create_workflow()
|
acra/memory/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/memory
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/memory/checkpoints
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/memory/checkpoints/data
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import psycopg2
|
|
7
|
+
from psycopg2.extras import RealDictCursor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# postgres config
|
|
11
|
+
|
|
12
|
+
POSTGRES_HOST = os.getenv(
|
|
13
|
+
"POSTGRES_HOST",
|
|
14
|
+
"localhost"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
POSTGRES_PORT = os.getenv(
|
|
18
|
+
"POSTGRES_PORT",
|
|
19
|
+
"5432"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
POSTGRES_DB = os.getenv(
|
|
23
|
+
"POSTGRES_DB",
|
|
24
|
+
"omniagent"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
POSTGRES_USER = os.getenv(
|
|
28
|
+
"POSTGRES_USER",
|
|
29
|
+
"postgres"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
POSTGRES_PASSWORD = os.getenv(
|
|
33
|
+
"POSTGRES_PASSWORD",
|
|
34
|
+
"postgres"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# postgres checkpoint manager
|
|
39
|
+
|
|
40
|
+
class PostgresCheckpointManager:
|
|
41
|
+
"""
|
|
42
|
+
PostgreSQL workflow checkpoint system.
|
|
43
|
+
|
|
44
|
+
Responsibilities:
|
|
45
|
+
- scalable workflow persistence
|
|
46
|
+
- distributed checkpoint storage
|
|
47
|
+
- production-grade recovery
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.connection = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# create connection
|
|
55
|
+
|
|
56
|
+
def _create_connection(self):
|
|
57
|
+
|
|
58
|
+
return psycopg2.connect(
|
|
59
|
+
|
|
60
|
+
host=POSTGRES_HOST,
|
|
61
|
+
|
|
62
|
+
port=POSTGRES_PORT,
|
|
63
|
+
|
|
64
|
+
dbname=POSTGRES_DB,
|
|
65
|
+
|
|
66
|
+
user=POSTGRES_USER,
|
|
67
|
+
|
|
68
|
+
password=POSTGRES_PASSWORD
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# initialize Db
|
|
73
|
+
|
|
74
|
+
def _ensure_connection(self):
|
|
75
|
+
if self.connection is None:
|
|
76
|
+
self.connection = self._create_connection()
|
|
77
|
+
self._initialize_database()
|
|
78
|
+
|
|
79
|
+
return self.connection
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _initialize_database(self) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Create checkpoint table.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
cursor = self.connection.cursor()
|
|
88
|
+
|
|
89
|
+
cursor.execute(
|
|
90
|
+
"""
|
|
91
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
92
|
+
|
|
93
|
+
id SERIAL PRIMARY KEY,
|
|
94
|
+
|
|
95
|
+
checkpoint_id TEXT UNIQUE,
|
|
96
|
+
|
|
97
|
+
session_id TEXT,
|
|
98
|
+
|
|
99
|
+
workflow_status TEXT,
|
|
100
|
+
|
|
101
|
+
active_agent TEXT,
|
|
102
|
+
|
|
103
|
+
retry_count INTEGER,
|
|
104
|
+
|
|
105
|
+
state_data JSONB,
|
|
106
|
+
|
|
107
|
+
created_at TIMESTAMP
|
|
108
|
+
)
|
|
109
|
+
"""
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
self.connection.commit()
|
|
113
|
+
|
|
114
|
+
cursor.close()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# save checkpoint
|
|
118
|
+
|
|
119
|
+
def save_checkpoint(
|
|
120
|
+
self,
|
|
121
|
+
checkpoint_id: str,
|
|
122
|
+
session_id: str,
|
|
123
|
+
workflow_status: str,
|
|
124
|
+
active_agent: str,
|
|
125
|
+
retry_count: int,
|
|
126
|
+
state_data: Dict[str, Any]
|
|
127
|
+
) -> Dict:
|
|
128
|
+
"""
|
|
129
|
+
Persist workflow checkpoint.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
|
|
134
|
+
connection = self._ensure_connection()
|
|
135
|
+
cursor = connection.cursor()
|
|
136
|
+
|
|
137
|
+
cursor.execute(
|
|
138
|
+
"""
|
|
139
|
+
INSERT INTO checkpoints (
|
|
140
|
+
|
|
141
|
+
checkpoint_id,
|
|
142
|
+
session_id,
|
|
143
|
+
workflow_status,
|
|
144
|
+
active_agent,
|
|
145
|
+
retry_count,
|
|
146
|
+
state_data,
|
|
147
|
+
created_at
|
|
148
|
+
|
|
149
|
+
) VALUES (%s, %s, %s, %s, %s, %s, %s)
|
|
150
|
+
|
|
151
|
+
ON CONFLICT (checkpoint_id)
|
|
152
|
+
|
|
153
|
+
DO UPDATE SET
|
|
154
|
+
|
|
155
|
+
workflow_status = EXCLUDED.workflow_status,
|
|
156
|
+
active_agent = EXCLUDED.active_agent,
|
|
157
|
+
retry_count = EXCLUDED.retry_count,
|
|
158
|
+
state_data = EXCLUDED.state_data
|
|
159
|
+
""",
|
|
160
|
+
(
|
|
161
|
+
checkpoint_id,
|
|
162
|
+
session_id,
|
|
163
|
+
workflow_status,
|
|
164
|
+
active_agent,
|
|
165
|
+
retry_count,
|
|
166
|
+
json.dumps(state_data),
|
|
167
|
+
datetime.utcnow()
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self.connection.commit()
|
|
172
|
+
|
|
173
|
+
cursor.close()
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
|
|
177
|
+
"success": True,
|
|
178
|
+
|
|
179
|
+
"checkpoint_id": checkpoint_id
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
|
|
186
|
+
"success": False,
|
|
187
|
+
|
|
188
|
+
"error": str(e)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# load checkpoint
|
|
193
|
+
|
|
194
|
+
def load_checkpoint(
|
|
195
|
+
self,
|
|
196
|
+
checkpoint_id: str
|
|
197
|
+
) -> Dict:
|
|
198
|
+
"""
|
|
199
|
+
Retrieve workflow checkpoint.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
|
|
204
|
+
connection = self._ensure_connection()
|
|
205
|
+
cursor = connection.cursor(
|
|
206
|
+
cursor_factory=RealDictCursor
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
cursor.execute(
|
|
210
|
+
"""
|
|
211
|
+
SELECT *
|
|
212
|
+
FROM checkpoints
|
|
213
|
+
WHERE checkpoint_id = %s
|
|
214
|
+
""",
|
|
215
|
+
(checkpoint_id,)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
result = cursor.fetchone()
|
|
219
|
+
|
|
220
|
+
cursor.close()
|
|
221
|
+
|
|
222
|
+
if not result:
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
|
|
226
|
+
"success": False,
|
|
227
|
+
|
|
228
|
+
"error": (
|
|
229
|
+
"Checkpoint not found."
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
|
|
235
|
+
"success": True,
|
|
236
|
+
|
|
237
|
+
"checkpoint": dict(result)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
|
|
244
|
+
"success": False,
|
|
245
|
+
|
|
246
|
+
"error": str(e)
|
|
247
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sqlite3
|
|
4
|
+
import logging
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
from acra.config import SQLITE_DB_PATH
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# sqlite checkpoint manager
|
|
11
|
+
|
|
12
|
+
class SQLiteCheckpointManager:
|
|
13
|
+
"""
|
|
14
|
+
SQLite-based workflow checkpoint system.
|
|
15
|
+
|
|
16
|
+
Responsibilities:
|
|
17
|
+
- persist workflow state
|
|
18
|
+
- enable workflow recovery
|
|
19
|
+
- support resumable execution
|
|
20
|
+
- track autonomous execution history
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
db_path: str = None
|
|
26
|
+
):
|
|
27
|
+
|
|
28
|
+
if db_path is None:
|
|
29
|
+
db_path = str(SQLITE_DB_PATH)
|
|
30
|
+
self.db_path = db_path
|
|
31
|
+
|
|
32
|
+
self._initialize_database()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# initialize DB
|
|
36
|
+
|
|
37
|
+
def _initialize_database(self) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Create checkpoint table if missing.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
connection = sqlite3.connect(
|
|
43
|
+
self.db_path
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
cursor = connection.cursor()
|
|
47
|
+
|
|
48
|
+
cursor.execute(
|
|
49
|
+
"""
|
|
50
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
51
|
+
|
|
52
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
|
|
54
|
+
checkpoint_id TEXT UNIQUE,
|
|
55
|
+
|
|
56
|
+
session_id TEXT,
|
|
57
|
+
|
|
58
|
+
workflow_status TEXT,
|
|
59
|
+
|
|
60
|
+
active_agent TEXT,
|
|
61
|
+
|
|
62
|
+
retry_count INTEGER,
|
|
63
|
+
|
|
64
|
+
state_data TEXT,
|
|
65
|
+
|
|
66
|
+
created_at TEXT
|
|
67
|
+
)
|
|
68
|
+
"""
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
connection.commit()
|
|
72
|
+
|
|
73
|
+
connection.close()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# save checkpoint
|
|
77
|
+
|
|
78
|
+
def save_checkpoint(
|
|
79
|
+
self,
|
|
80
|
+
checkpoint_id: str,
|
|
81
|
+
session_id: str,
|
|
82
|
+
workflow_status: str,
|
|
83
|
+
active_agent: str,
|
|
84
|
+
retry_count: int,
|
|
85
|
+
state_data: Dict[str, Any]
|
|
86
|
+
) -> Dict:
|
|
87
|
+
"""
|
|
88
|
+
Persist workflow checkpoint.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
|
|
93
|
+
connection = sqlite3.connect(
|
|
94
|
+
self.db_path
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
cursor = connection.cursor()
|
|
98
|
+
|
|
99
|
+
cursor.execute(
|
|
100
|
+
"""
|
|
101
|
+
INSERT OR REPLACE INTO checkpoints (
|
|
102
|
+
|
|
103
|
+
checkpoint_id,
|
|
104
|
+
session_id,
|
|
105
|
+
workflow_status,
|
|
106
|
+
active_agent,
|
|
107
|
+
retry_count,
|
|
108
|
+
state_data,
|
|
109
|
+
created_at
|
|
110
|
+
|
|
111
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
112
|
+
""",
|
|
113
|
+
(
|
|
114
|
+
checkpoint_id,
|
|
115
|
+
session_id,
|
|
116
|
+
workflow_status,
|
|
117
|
+
active_agent,
|
|
118
|
+
retry_count,
|
|
119
|
+
json.dumps(state_data),
|
|
120
|
+
datetime.utcnow().isoformat()
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
connection.commit()
|
|
125
|
+
|
|
126
|
+
connection.close()
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
|
|
130
|
+
"success": True,
|
|
131
|
+
|
|
132
|
+
"checkpoint_id": checkpoint_id
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logging.getLogger(__name__).error("SQLiteCheckpointManager.save_checkpoint failed: %s", e, exc_info=True)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
|
|
140
|
+
"success": False,
|
|
141
|
+
|
|
142
|
+
"error": str(e)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# load checkpoint
|
|
147
|
+
|
|
148
|
+
def load_checkpoint(
|
|
149
|
+
self,
|
|
150
|
+
checkpoint_id: str
|
|
151
|
+
) -> Dict:
|
|
152
|
+
"""
|
|
153
|
+
Retrieve checkpoint by ID.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
|
|
158
|
+
connection = sqlite3.connect(
|
|
159
|
+
self.db_path
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
cursor = connection.cursor()
|
|
163
|
+
|
|
164
|
+
cursor.execute(
|
|
165
|
+
"""
|
|
166
|
+
SELECT *
|
|
167
|
+
FROM checkpoints
|
|
168
|
+
WHERE checkpoint_id = ?
|
|
169
|
+
""",
|
|
170
|
+
(checkpoint_id,)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
row = cursor.fetchone()
|
|
174
|
+
|
|
175
|
+
connection.close()
|
|
176
|
+
|
|
177
|
+
if not row:
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
|
|
181
|
+
"success": False,
|
|
182
|
+
|
|
183
|
+
"error": (
|
|
184
|
+
"Checkpoint not found."
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
|
|
190
|
+
"success": True,
|
|
191
|
+
|
|
192
|
+
"checkpoint": {
|
|
193
|
+
|
|
194
|
+
"checkpoint_id": row[1],
|
|
195
|
+
|
|
196
|
+
"session_id": row[2],
|
|
197
|
+
|
|
198
|
+
"workflow_status": row[3],
|
|
199
|
+
|
|
200
|
+
"active_agent": row[4],
|
|
201
|
+
|
|
202
|
+
"retry_count": row[5],
|
|
203
|
+
|
|
204
|
+
"state_data": json.loads(
|
|
205
|
+
row[6]
|
|
206
|
+
),
|
|
207
|
+
|
|
208
|
+
"created_at": row[7]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logging.getLogger(__name__).error("SQLiteCheckpointManager.load_checkpoint failed: %s", e, exc_info=True)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
|
|
217
|
+
"success": False,
|
|
218
|
+
|
|
219
|
+
"error": str(e)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# list checkpoints
|
|
224
|
+
|
|
225
|
+
def list_checkpoints(
|
|
226
|
+
self,
|
|
227
|
+
session_id: Optional[str] = None
|
|
228
|
+
) -> Dict:
|
|
229
|
+
"""
|
|
230
|
+
Retrieve stored checkpoints.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
|
|
235
|
+
connection = sqlite3.connect(
|
|
236
|
+
self.db_path
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
cursor = connection.cursor()
|
|
240
|
+
|
|
241
|
+
if session_id:
|
|
242
|
+
|
|
243
|
+
cursor.execute(
|
|
244
|
+
"""
|
|
245
|
+
SELECT checkpoint_id,
|
|
246
|
+
workflow_status,
|
|
247
|
+
active_agent,
|
|
248
|
+
created_at
|
|
249
|
+
FROM checkpoints
|
|
250
|
+
WHERE session_id = ?
|
|
251
|
+
ORDER BY created_at DESC
|
|
252
|
+
""",
|
|
253
|
+
(session_id,)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
else:
|
|
257
|
+
|
|
258
|
+
cursor.execute(
|
|
259
|
+
"""
|
|
260
|
+
SELECT checkpoint_id,
|
|
261
|
+
workflow_status,
|
|
262
|
+
active_agent,
|
|
263
|
+
created_at
|
|
264
|
+
FROM checkpoints
|
|
265
|
+
ORDER BY created_at DESC
|
|
266
|
+
"""
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
rows = cursor.fetchall()
|
|
270
|
+
|
|
271
|
+
connection.close()
|
|
272
|
+
|
|
273
|
+
checkpoints = []
|
|
274
|
+
|
|
275
|
+
for row in rows:
|
|
276
|
+
|
|
277
|
+
checkpoints.append({
|
|
278
|
+
|
|
279
|
+
"checkpoint_id": row[0],
|
|
280
|
+
|
|
281
|
+
"workflow_status": row[1],
|
|
282
|
+
|
|
283
|
+
"active_agent": row[2],
|
|
284
|
+
|
|
285
|
+
"created_at": row[3]
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
|
|
290
|
+
"success": True,
|
|
291
|
+
|
|
292
|
+
"checkpoints": checkpoints
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
|
|
299
|
+
"success": False,
|
|
300
|
+
|
|
301
|
+
"error": str(e)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# global checkpoint manager
|
|
306
|
+
|
|
307
|
+
sqlite_checkpoint_manager = (
|
|
308
|
+
SQLiteCheckpointManager()
|
|
309
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/memory/chroma_db
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/memory/conversation_memory
|