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/checkpoint.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from acra import config
|
|
5
|
+
from acra.config import SQLITE_DB_PATH
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GraphCheckpointManager:
|
|
11
|
+
"""
|
|
12
|
+
LangGraph checkpoint abstraction.
|
|
13
|
+
|
|
14
|
+
Responsibilities:
|
|
15
|
+
- provide checkpoint saver for graph persistence
|
|
16
|
+
- support graph resumability
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.backend = os.getenv("CHECKPOINT_BACKEND", config.CHECKPOINT_BACKEND).lower()
|
|
21
|
+
self._backend_context = None
|
|
22
|
+
self.checkpointer = self._initialize_backend()
|
|
23
|
+
logger.info("Checkpoint backend initialized: %s", self.backend)
|
|
24
|
+
|
|
25
|
+
def _initialize_backend(self):
|
|
26
|
+
"""Initialize LangGraph checkpoint saver based on configured backend."""
|
|
27
|
+
if self.backend == "sqlite":
|
|
28
|
+
try:
|
|
29
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
30
|
+
|
|
31
|
+
db_path = str(SQLITE_DB_PATH)
|
|
32
|
+
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
33
|
+
|
|
34
|
+
logger.info("Using SQLite checkpoint: %s", db_path)
|
|
35
|
+
self._backend_context = SqliteSaver.from_conn_string(db_path)
|
|
36
|
+
return self._backend_context.__enter__()
|
|
37
|
+
except ImportError:
|
|
38
|
+
logger.warning(
|
|
39
|
+
"SQLite backend not available. Install: pip install langgraph-checkpoint-sqlite. Falling back to memory saver."
|
|
40
|
+
)
|
|
41
|
+
return self._get_memory_saver()
|
|
42
|
+
except Exception as exc:
|
|
43
|
+
logger.error("Failed to initialize SQLite: %s. Using memory saver.", exc)
|
|
44
|
+
return self._get_memory_saver()
|
|
45
|
+
|
|
46
|
+
if self.backend == "postgres":
|
|
47
|
+
try:
|
|
48
|
+
from langgraph.checkpoint.postgres import PostgresSaver
|
|
49
|
+
|
|
50
|
+
connection_string = os.getenv(
|
|
51
|
+
"POSTGRES_CONNECTION_STRING",
|
|
52
|
+
"postgresql://user:password@localhost/omniagent",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
logger.info("Using PostgreSQL checkpoint")
|
|
56
|
+
self._backend_context = PostgresSaver.from_conn_string(connection_string)
|
|
57
|
+
return self._backend_context.__enter__()
|
|
58
|
+
except ImportError:
|
|
59
|
+
logger.warning(
|
|
60
|
+
"PostgreSQL backend not available. Install: pip install langgraph-checkpoint-postgres. Falling back to memory saver."
|
|
61
|
+
)
|
|
62
|
+
return self._get_memory_saver()
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
logger.error("Failed to initialize PostgreSQL: %s. Using memory saver.", exc)
|
|
65
|
+
return self._get_memory_saver()
|
|
66
|
+
|
|
67
|
+
return self._get_memory_saver()
|
|
68
|
+
|
|
69
|
+
def _get_memory_saver(self):
|
|
70
|
+
"""Get in-memory checkpoint saver (default/fallback)."""
|
|
71
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
72
|
+
|
|
73
|
+
logger.info("Using MemorySaver (in-memory checkpoints)")
|
|
74
|
+
return MemorySaver()
|
|
75
|
+
|
|
76
|
+
def __del__(self):
|
|
77
|
+
if self._backend_context is not None:
|
|
78
|
+
try:
|
|
79
|
+
self._backend_context.__exit__(None, None, None)
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
graph_checkpoint = GraphCheckpointManager()
|
|
85
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from langgraph.graph import END
|
|
2
|
+
|
|
3
|
+
from acra.graph.router import (
|
|
4
|
+
route_workflow
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# conditional edge map
|
|
9
|
+
|
|
10
|
+
CONDITIONAL_EDGE_MAP = {
|
|
11
|
+
|
|
12
|
+
"planner": "planner",
|
|
13
|
+
|
|
14
|
+
"researcher": "researcher",
|
|
15
|
+
|
|
16
|
+
"coder": "coder",
|
|
17
|
+
|
|
18
|
+
"executor": "executor",
|
|
19
|
+
|
|
20
|
+
"critic": "critic",
|
|
21
|
+
|
|
22
|
+
"memory": "memory",
|
|
23
|
+
|
|
24
|
+
"human": "human",
|
|
25
|
+
|
|
26
|
+
END: END
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# register conditional edges
|
|
31
|
+
|
|
32
|
+
def register_conditional_edges(
|
|
33
|
+
workflow
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Register routing logic
|
|
37
|
+
for all workflow nodes.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
workflow.add_conditional_edges(
|
|
41
|
+
|
|
42
|
+
"planner",
|
|
43
|
+
|
|
44
|
+
route_workflow,
|
|
45
|
+
|
|
46
|
+
CONDITIONAL_EDGE_MAP
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
workflow.add_conditional_edges(
|
|
50
|
+
|
|
51
|
+
"researcher",
|
|
52
|
+
|
|
53
|
+
route_workflow,
|
|
54
|
+
|
|
55
|
+
CONDITIONAL_EDGE_MAP
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
workflow.add_conditional_edges(
|
|
59
|
+
|
|
60
|
+
"coder",
|
|
61
|
+
|
|
62
|
+
route_workflow,
|
|
63
|
+
|
|
64
|
+
CONDITIONAL_EDGE_MAP
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
workflow.add_conditional_edges(
|
|
68
|
+
|
|
69
|
+
"executor",
|
|
70
|
+
|
|
71
|
+
route_workflow,
|
|
72
|
+
|
|
73
|
+
CONDITIONAL_EDGE_MAP
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
workflow.add_conditional_edges(
|
|
77
|
+
|
|
78
|
+
"critic",
|
|
79
|
+
|
|
80
|
+
route_workflow,
|
|
81
|
+
|
|
82
|
+
CONDITIONAL_EDGE_MAP
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
workflow.add_conditional_edges(
|
|
86
|
+
|
|
87
|
+
"memory",
|
|
88
|
+
|
|
89
|
+
route_workflow,
|
|
90
|
+
|
|
91
|
+
CONDITIONAL_EDGE_MAP
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
workflow.add_conditional_edges(
|
|
95
|
+
|
|
96
|
+
"human",
|
|
97
|
+
|
|
98
|
+
route_workflow,
|
|
99
|
+
|
|
100
|
+
CONDITIONAL_EDGE_MAP
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return workflow
|
acra/graph/edges.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from langgraph.graph import END
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# static edge
|
|
5
|
+
|
|
6
|
+
GRAPH_EDGES = {
|
|
7
|
+
|
|
8
|
+
"planner": [
|
|
9
|
+
"researcher",
|
|
10
|
+
"coder",
|
|
11
|
+
"human"
|
|
12
|
+
],
|
|
13
|
+
|
|
14
|
+
"researcher": [
|
|
15
|
+
"coder"
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
"coder": [
|
|
19
|
+
"executor"
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
"executor": [
|
|
23
|
+
"critic",
|
|
24
|
+
"coder",
|
|
25
|
+
"human"
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
"critic": [
|
|
29
|
+
"memory",
|
|
30
|
+
"coder",
|
|
31
|
+
"human"
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
"memory": [
|
|
35
|
+
END
|
|
36
|
+
],
|
|
37
|
+
|
|
38
|
+
"human": [
|
|
39
|
+
"coder",
|
|
40
|
+
END
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# validate transition
|
|
46
|
+
|
|
47
|
+
def validate_transition(source: str, destination: str) -> bool:
|
|
48
|
+
"""Returns True if the transition is explicitly defined OR destination is END."""
|
|
49
|
+
if destination == END:
|
|
50
|
+
return True
|
|
51
|
+
return destination in GRAPH_EDGES.get(source, [])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# get next edges
|
|
55
|
+
|
|
56
|
+
def get_next_nodes(
|
|
57
|
+
node_name: str
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Returns possible next nodes.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
return GRAPH_EDGES.get(
|
|
64
|
+
node_name,
|
|
65
|
+
[]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# validate edges
|
|
70
|
+
|
|
71
|
+
def is_valid_transition(
|
|
72
|
+
source: str,
|
|
73
|
+
destination: str
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
Validates graph transition.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
destination
|
|
81
|
+
in GRAPH_EDGES.get(
|
|
82
|
+
source,
|
|
83
|
+
[]
|
|
84
|
+
)
|
|
85
|
+
)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from acra.graph.workflow import (
|
|
5
|
+
omniagent_graph
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# graph visualizer
|
|
10
|
+
|
|
11
|
+
class GraphVisualizer:
|
|
12
|
+
"""
|
|
13
|
+
Responsibilities:
|
|
14
|
+
- visualize LangGraph workflow
|
|
15
|
+
- export Mermaid diagrams
|
|
16
|
+
- save workflow images
|
|
17
|
+
- support debugging/documentation
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
|
|
22
|
+
self.graph = omniagent_graph
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# get mermaid
|
|
26
|
+
|
|
27
|
+
def get_mermaid(self) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Returns Mermaid graph definition.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
self.graph
|
|
36
|
+
.get_graph()
|
|
37
|
+
.draw_mermaid()
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
except Exception as e:
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
f"Failed to generate Mermaid: {e}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# save mermaid
|
|
48
|
+
|
|
49
|
+
def save_mermaid(
|
|
50
|
+
self,
|
|
51
|
+
filepath: str
|
|
52
|
+
) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Save Mermaid graph to file.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
mermaid = self.get_mermaid()
|
|
58
|
+
|
|
59
|
+
Path(filepath).parent.mkdir(
|
|
60
|
+
parents=True,
|
|
61
|
+
exist_ok=True
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
with open(
|
|
65
|
+
filepath,
|
|
66
|
+
"w",
|
|
67
|
+
encoding="utf-8"
|
|
68
|
+
) as file:
|
|
69
|
+
|
|
70
|
+
file.write(mermaid)
|
|
71
|
+
|
|
72
|
+
return filepath
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# save png
|
|
76
|
+
|
|
77
|
+
def save_png(
|
|
78
|
+
self,
|
|
79
|
+
filepath: str
|
|
80
|
+
) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Save workflow graph as PNG.
|
|
83
|
+
|
|
84
|
+
Requires:
|
|
85
|
+
grandalf
|
|
86
|
+
pygraphviz
|
|
87
|
+
OR LangGraph image support
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
|
|
92
|
+
png_data = (
|
|
93
|
+
|
|
94
|
+
self.graph
|
|
95
|
+
.get_graph()
|
|
96
|
+
.draw_mermaid_png()
|
|
97
|
+
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
Path(filepath).parent.mkdir(
|
|
101
|
+
parents=True,
|
|
102
|
+
exist_ok=True
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
with open(
|
|
106
|
+
filepath,
|
|
107
|
+
"wb"
|
|
108
|
+
) as file:
|
|
109
|
+
|
|
110
|
+
file.write(
|
|
111
|
+
png_data
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return filepath
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
|
|
118
|
+
raise RuntimeError(
|
|
119
|
+
f"PNG export failed: {e}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# display mermaid
|
|
124
|
+
|
|
125
|
+
def print_graph(self):
|
|
126
|
+
"""
|
|
127
|
+
Print Mermaid workflow.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
print(
|
|
131
|
+
self.get_mermaid()
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# export documentation
|
|
136
|
+
|
|
137
|
+
def export_documentation(
|
|
138
|
+
self,
|
|
139
|
+
filepath: str
|
|
140
|
+
) -> str:
|
|
141
|
+
"""
|
|
142
|
+
Export graph documentation.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
mermaid = self.get_mermaid()
|
|
146
|
+
|
|
147
|
+
content = f"""
|
|
148
|
+
# OMNIAGENT Workflow
|
|
149
|
+
|
|
150
|
+
```mermaid
|
|
151
|
+
{mermaid}
|
|
152
|
+
```
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
Path(filepath).parent.mkdir(
|
|
156
|
+
parents=True,
|
|
157
|
+
exist_ok=True
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
with open(
|
|
161
|
+
filepath,
|
|
162
|
+
"w",
|
|
163
|
+
encoding="utf-8"
|
|
164
|
+
) as file:
|
|
165
|
+
|
|
166
|
+
file.write(content)
|
|
167
|
+
|
|
168
|
+
return filepath
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# global visualizer
|
|
172
|
+
|
|
173
|
+
graph_visualizer = (
|
|
174
|
+
GraphVisualizer()
|
|
175
|
+
)
|
acra/graph/nodes.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from acra.agents.planner.planner_agent import (
|
|
2
|
+
planner_agent
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
from acra.agents.researcher.researcher_agent import (
|
|
6
|
+
researcher_agent
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from acra.agents.coder.coder_agent import (
|
|
10
|
+
coder_agent
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from acra.agents.executor.executor_agent import (
|
|
14
|
+
executor_agent
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from acra.agents.critic.critic_agent import (
|
|
18
|
+
critic_agent
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from acra.agents.memory.memory_agent import (
|
|
22
|
+
memory_agent
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from acra.agents.human.human_node import (
|
|
26
|
+
human_node
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# node registry
|
|
31
|
+
|
|
32
|
+
NODE_REGISTRY = {
|
|
33
|
+
|
|
34
|
+
"planner": planner_agent,
|
|
35
|
+
|
|
36
|
+
"researcher": researcher_agent,
|
|
37
|
+
|
|
38
|
+
"coder": coder_agent,
|
|
39
|
+
|
|
40
|
+
"executor": executor_agent,
|
|
41
|
+
|
|
42
|
+
"critic": critic_agent,
|
|
43
|
+
|
|
44
|
+
"memory": memory_agent,
|
|
45
|
+
|
|
46
|
+
"human": human_node,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# get nodes
|
|
51
|
+
|
|
52
|
+
def get_node(node_name: str):
|
|
53
|
+
"""
|
|
54
|
+
Returns node function.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
return NODE_REGISTRY.get(node_name)
|
|
58
|
+
|
|
59
|
+
# list nodes
|
|
60
|
+
|
|
61
|
+
def list_nodes():
|
|
62
|
+
"""
|
|
63
|
+
Returns all available nodes.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
return list(
|
|
67
|
+
NODE_REGISTRY.keys()
|
|
68
|
+
)
|
acra/graph/router.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from langgraph.graph import END
|
|
3
|
+
from acra.graph.edges import validate_transition
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# router
|
|
7
|
+
|
|
8
|
+
def route_workflow(state):
|
|
9
|
+
"""
|
|
10
|
+
Central workflow router.
|
|
11
|
+
|
|
12
|
+
Reads state["next_agent"]
|
|
13
|
+
and returns the next graph node.
|
|
14
|
+
Enforces retry limits and validates transitions.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Check retry limit
|
|
18
|
+
retry_count = state.get("retry_count", 0)
|
|
19
|
+
max_retries = state.get("max_retries", 5)
|
|
20
|
+
if retry_count >= max_retries:
|
|
21
|
+
logging.getLogger(__name__).warning(
|
|
22
|
+
"Max retries (%d) reached. Ending workflow.", max_retries
|
|
23
|
+
)
|
|
24
|
+
return END
|
|
25
|
+
|
|
26
|
+
next_agent = state.get(
|
|
27
|
+
"next_agent",
|
|
28
|
+
"end"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if not next_agent:
|
|
32
|
+
|
|
33
|
+
return END
|
|
34
|
+
|
|
35
|
+
if next_agent.lower() == "end":
|
|
36
|
+
|
|
37
|
+
return END
|
|
38
|
+
|
|
39
|
+
# Validate transition
|
|
40
|
+
current_agent = state.get("current_agent", "")
|
|
41
|
+
if not validate_transition(current_agent, next_agent):
|
|
42
|
+
logging.getLogger(__name__).warning(
|
|
43
|
+
"Unexpected transition from '%s' to '%s'", current_agent, next_agent
|
|
44
|
+
)
|
|
45
|
+
return END
|
|
46
|
+
|
|
47
|
+
return next_agent
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# route mapping
|
|
51
|
+
|
|
52
|
+
ROUTE_MAPPING = {
|
|
53
|
+
|
|
54
|
+
"planner": "planner",
|
|
55
|
+
|
|
56
|
+
"researcher": "researcher",
|
|
57
|
+
|
|
58
|
+
"coder": "coder",
|
|
59
|
+
|
|
60
|
+
"executor": "executor",
|
|
61
|
+
|
|
62
|
+
"critic": "critic",
|
|
63
|
+
|
|
64
|
+
"memory": "memory",
|
|
65
|
+
|
|
66
|
+
"human": "human",
|
|
67
|
+
|
|
68
|
+
"end": END
|
|
69
|
+
}
|
acra/graph/state.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import List, Dict, Any
|
|
2
|
+
|
|
3
|
+
from langgraph.graph import MessagesState
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgentState(MessagesState):
|
|
7
|
+
"""Workflow state for OmniAgent.
|
|
8
|
+
|
|
9
|
+
TypedDict does not provide runtime defaults, so callers must read with
|
|
10
|
+
state.get(key, default) or populate fields explicitly when a node first
|
|
11
|
+
introduces them.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# USER INPUT
|
|
15
|
+
|
|
16
|
+
user_request: str
|
|
17
|
+
session_id: str
|
|
18
|
+
|
|
19
|
+
# PLANNING
|
|
20
|
+
|
|
21
|
+
plan: List[str]
|
|
22
|
+
current_step: str
|
|
23
|
+
completed_steps: List[str]
|
|
24
|
+
|
|
25
|
+
# RESEARCH
|
|
26
|
+
|
|
27
|
+
research_data: List[Any]
|
|
28
|
+
research_status: str
|
|
29
|
+
research_summary: str
|
|
30
|
+
research_sources: List[Any]
|
|
31
|
+
references: List[str]
|
|
32
|
+
implementation_recommendations: str
|
|
33
|
+
recommended_technologies: List[str]
|
|
34
|
+
security_considerations: List[str]
|
|
35
|
+
performance_considerations: List[str]
|
|
36
|
+
architecture_suggestions: List[str]
|
|
37
|
+
|
|
38
|
+
# CODE GENERATION
|
|
39
|
+
|
|
40
|
+
project_name: str
|
|
41
|
+
generated_files: Dict[str, str]
|
|
42
|
+
entry_point: str
|
|
43
|
+
coding_status: str
|
|
44
|
+
coding_explanation: str
|
|
45
|
+
|
|
46
|
+
# EXECUTION
|
|
47
|
+
|
|
48
|
+
execution_logs: str
|
|
49
|
+
execution_success: bool
|
|
50
|
+
execution_output: str
|
|
51
|
+
execution_status: str
|
|
52
|
+
execution_time: float
|
|
53
|
+
|
|
54
|
+
# ERROR HANDLING
|
|
55
|
+
|
|
56
|
+
error_message: str
|
|
57
|
+
retry_count: int
|
|
58
|
+
max_retries: int
|
|
59
|
+
|
|
60
|
+
# CRITIC / REVIEW
|
|
61
|
+
|
|
62
|
+
review_status: str
|
|
63
|
+
critic_feedback: str
|
|
64
|
+
critic_summary: str
|
|
65
|
+
quality_score: float
|
|
66
|
+
review_issues: List[Any]
|
|
67
|
+
security_issues: List[str]
|
|
68
|
+
improvement_suggestions: List[str]
|
|
69
|
+
|
|
70
|
+
# HUMAN-IN-THE-LOOP
|
|
71
|
+
|
|
72
|
+
approval_required: bool
|
|
73
|
+
approved: bool
|
|
74
|
+
human_feedback: str
|
|
75
|
+
interactive: bool
|
|
76
|
+
|
|
77
|
+
# MEMORY
|
|
78
|
+
|
|
79
|
+
memory_context: List[str]
|
|
80
|
+
memory_type: str
|
|
81
|
+
memory_stats: Dict[str, Any]
|
|
82
|
+
previous_attempts: List[Dict[str, Any]]
|
|
83
|
+
similar_failures: List[Any]
|
|
84
|
+
|
|
85
|
+
# WORKFLOW CONTROL
|
|
86
|
+
|
|
87
|
+
next_agent: str
|
|
88
|
+
workflow_status: str
|
|
89
|
+
current_agent: str
|
|
90
|
+
|
|
91
|
+
# OBSERVABILITY
|
|
92
|
+
|
|
93
|
+
token_usage: int
|